Compare commits
5 Commits
6c03a1a277
...
feat/ragto
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b386c1181f | ||
|
|
b8e7649e69 | ||
|
|
6664c1a0fc | ||
|
|
e0c3fe554f | ||
|
|
40943ff4d3 |
16
bot.go
16
bot.go
@@ -830,11 +830,21 @@ func chatWatcher(ctx context.Context) {
|
||||
func showSpinner() {
|
||||
spinners := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
|
||||
var i int
|
||||
for botRespMode {
|
||||
botPersona := cfg.AssistantRole
|
||||
if cfg.WriteNextMsgAsCompletionAgent != "" {
|
||||
botPersona = cfg.WriteNextMsgAsCompletionAgent
|
||||
}
|
||||
for botRespMode || toolRunningMode {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
spin := i % len(spinners)
|
||||
app.QueueUpdateDraw(func() {
|
||||
textArea.SetTitle(spinners[spin] + " input")
|
||||
if toolRunningMode {
|
||||
textArea.SetTitle(spinners[spin] + " tool")
|
||||
} else if botRespMode {
|
||||
textArea.SetTitle(spinners[spin] + " " + botPersona)
|
||||
} else {
|
||||
textArea.SetTitle(spinners[spin] + " input")
|
||||
}
|
||||
})
|
||||
i++
|
||||
}
|
||||
@@ -1219,7 +1229,9 @@ func findCall(msg, toolCall string) bool {
|
||||
}
|
||||
// Show tool call progress indicator before execution
|
||||
fmt.Fprintf(textView, "\n[yellow::i][tool: %s...][-:-:-]", fc.Name)
|
||||
toolRunningMode = true
|
||||
resp := callToolWithAgent(fc.Name, fc.Args)
|
||||
toolRunningMode = false
|
||||
toolMsg := string(resp) // Remove the "tool response: " prefix and %+v formatting
|
||||
logger.Info("llm used a tool call", "tool_name", fc.Name, "too_args", fc.Args, "id", fc.ID, "tool_resp", toolMsg)
|
||||
fmt.Fprintf(textView, "%s[-:-:b](%d) <%s>: [-:-:-]\n%s\n",
|
||||
|
||||
4
go.mod
4
go.mod
@@ -6,17 +6,19 @@ require (
|
||||
github.com/BurntSushi/toml v1.5.0
|
||||
github.com/GrailFinder/google-translate-tts v0.1.3
|
||||
github.com/GrailFinder/searchagent v0.2.0
|
||||
github.com/PuerkitoBio/goquery v1.11.0
|
||||
github.com/gdamore/tcell/v2 v2.13.2
|
||||
github.com/glebarez/go-sqlite v1.22.0
|
||||
github.com/gopxl/beep/v2 v2.1.1
|
||||
github.com/gordonklaus/portaudio v0.0.0-20250206071425-98a94950218b
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/ledongthuc/pdf v0.0.0-20250511090121-5959a4027728
|
||||
github.com/neurosnap/sentences v1.1.2
|
||||
github.com/rivo/tview v0.42.0
|
||||
github.com/yuin/goldmark v1.4.13
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.11.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/ebitengine/oto/v3 v3.4.0 // indirect
|
||||
|
||||
3
go.sum
3
go.sum
@@ -43,6 +43,8 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||
github.com/ledongthuc/pdf v0.0.0-20250511090121-5959a4027728 h1:QwWKgMY28TAXaDl+ExRDqGQltzXqN/xypdKP86niVn8=
|
||||
github.com/ledongthuc/pdf v0.0.0-20250511090121-5959a4027728/go.mod h1:1fEHWurg7pvf5SG6XNE5Q8UZmOwex51Mkx3SLhrW5B4=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||
@@ -67,6 +69,7 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
|
||||
1
main.go
1
main.go
@@ -7,6 +7,7 @@ import (
|
||||
var (
|
||||
boolColors = map[bool]string{true: "green", false: "red"}
|
||||
botRespMode = false
|
||||
toolRunningMode = false
|
||||
editMode = false
|
||||
roleEditMode = false
|
||||
injectRole = true
|
||||
|
||||
184
rag/extractors.go
Normal file
184
rag/extractors.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package rag
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/ledongthuc/pdf"
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/renderer/html"
|
||||
)
|
||||
|
||||
func ExtractText(fpath string) (string, error) {
|
||||
ext := strings.ToLower(path.Ext(fpath))
|
||||
switch ext {
|
||||
case ".txt":
|
||||
return extractTextFromFile(fpath)
|
||||
case ".md", ".markdown":
|
||||
return extractTextFromMarkdown(fpath)
|
||||
case ".html", ".htm":
|
||||
return extractTextFromHtmlFile(fpath)
|
||||
case ".epub":
|
||||
return extractTextFromEpub(fpath)
|
||||
case ".pdf":
|
||||
return extractTextFromPdf(fpath)
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported file format: %s", ext)
|
||||
}
|
||||
}
|
||||
|
||||
func extractTextFromFile(fpath string) (string, error) {
|
||||
data, err := os.ReadFile(fpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func extractTextFromHtmlFile(fpath string) (string, error) {
|
||||
data, err := os.ReadFile(fpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return extractTextFromHtmlContent(data)
|
||||
}
|
||||
|
||||
// non utf-8 encoding?
|
||||
func extractTextFromHtmlContent(data []byte) (string, error) {
|
||||
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Remove script and style tags
|
||||
doc.Find("script, style, noscript").Each(func(i int, s *goquery.Selection) {
|
||||
s.Remove()
|
||||
})
|
||||
// Get text and clean it
|
||||
text := doc.Text()
|
||||
// Collapse all whitespace (newlines, tabs, multiple spaces) into single spaces
|
||||
cleaned := strings.Join(strings.Fields(text), " ")
|
||||
return cleaned, nil
|
||||
}
|
||||
|
||||
func extractTextFromMarkdown(fpath string) (string, error) {
|
||||
data, err := os.ReadFile(fpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Convert markdown to HTML
|
||||
md := goldmark.New(
|
||||
goldmark.WithExtensions(extension.GFM),
|
||||
goldmark.WithParserOptions(parser.WithAutoHeadingID()),
|
||||
goldmark.WithRendererOptions(html.WithUnsafe()), // allow raw HTML if needed
|
||||
)
|
||||
var buf bytes.Buffer
|
||||
if err := md.Convert(data, &buf); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Now extract text from the resulting HTML (using goquery or similar)
|
||||
return extractTextFromHtmlContent(buf.Bytes())
|
||||
}
|
||||
|
||||
func extractTextFromEpub(fpath string) (string, error) {
|
||||
r, err := zip.OpenReader(fpath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to open epub: %w", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
var sb strings.Builder
|
||||
|
||||
for _, f := range r.File {
|
||||
ext := strings.ToLower(path.Ext(f.Name))
|
||||
if ext != ".xhtml" && ext != ".html" && ext != ".htm" && ext != ".xml" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip manifest, toc, ncx files - they don't contain book content
|
||||
nameLower := strings.ToLower(f.Name)
|
||||
if strings.Contains(nameLower, "toc") || strings.Contains(nameLower, "nav") ||
|
||||
strings.Contains(nameLower, "manifest") || strings.Contains(nameLower, ".opf") ||
|
||||
strings.HasSuffix(nameLower, ".ncx") {
|
||||
continue
|
||||
}
|
||||
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if sb.Len() > 0 {
|
||||
sb.WriteString("\n\n")
|
||||
}
|
||||
sb.WriteString(f.Name)
|
||||
sb.WriteString("\n")
|
||||
|
||||
buf, readErr := io.ReadAll(rc)
|
||||
rc.Close()
|
||||
if readErr == nil {
|
||||
sb.WriteString(stripHTML(string(buf)))
|
||||
}
|
||||
}
|
||||
|
||||
if sb.Len() == 0 {
|
||||
return "", errors.New("no content extracted from epub")
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
func stripHTML(html string) string {
|
||||
var sb strings.Builder
|
||||
inTag := false
|
||||
for _, r := range html {
|
||||
switch r {
|
||||
case '<':
|
||||
inTag = true
|
||||
case '>':
|
||||
inTag = false
|
||||
default:
|
||||
if !inTag {
|
||||
sb.WriteRune(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func extractTextFromPdf(fpath string) (string, error) {
|
||||
_, err := exec.LookPath("pdftotext")
|
||||
if err == nil {
|
||||
out, err := exec.Command("pdftotext", "-layout", fpath, "-").Output()
|
||||
if err == nil && len(out) > 0 {
|
||||
return string(out), nil
|
||||
}
|
||||
}
|
||||
return extractTextFromPdfPureGo(fpath)
|
||||
}
|
||||
|
||||
func extractTextFromPdfPureGo(fpath string) (string, error) {
|
||||
df, r, err := pdf.Open(fpath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to open pdf: %w", err)
|
||||
}
|
||||
defer df.Close()
|
||||
textReader, err := r.GetPlainText()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to extract text from pdf: %w", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
_, err = io.Copy(&buf, textReader)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read pdf text: %w", err)
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"gf-lt/models"
|
||||
"gf-lt/storage"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
@@ -58,7 +57,7 @@ func wordCounter(sentence string) int {
|
||||
func (r *RAG) LoadRAG(fpath string) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
data, err := os.ReadFile(fpath)
|
||||
fileText, err := ExtractText(fpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -68,7 +67,6 @@ func (r *RAG) LoadRAG(fpath string) error {
|
||||
default:
|
||||
r.logger.Warn("LongJobStatusCh channel is full or closed, dropping status message", "message", LoadedFileRAGStatus)
|
||||
}
|
||||
fileText := string(data)
|
||||
tokenizer, err := english.NewSentenceTokenizer(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
125
tables.go
125
tables.go
@@ -247,9 +247,49 @@ func formatSize(size int64) string {
|
||||
return fmt.Sprintf("%.1f%s", s, units[i])
|
||||
}
|
||||
|
||||
func makeRAGTable(fileList []string) *tview.Flex {
|
||||
actions := []string{"load", "delete"}
|
||||
rows, cols := len(fileList), len(actions)+2
|
||||
type ragFileInfo struct {
|
||||
name string
|
||||
inRAGDir bool
|
||||
isLoaded bool
|
||||
fullPath string
|
||||
}
|
||||
|
||||
func makeRAGTable(fileList []string, loadedFiles []string) *tview.Flex {
|
||||
// Build set of loaded files for quick lookup
|
||||
loadedSet := make(map[string]bool)
|
||||
for _, f := range loadedFiles {
|
||||
loadedSet[f] = true
|
||||
}
|
||||
|
||||
// Build merged list: files from ragdir + orphaned files from DB
|
||||
ragFiles := make([]ragFileInfo, 0, len(fileList)+len(loadedFiles))
|
||||
seen := make(map[string]bool)
|
||||
|
||||
// Add files from ragdir
|
||||
for _, f := range fileList {
|
||||
ragFiles = append(ragFiles, ragFileInfo{
|
||||
name: f,
|
||||
inRAGDir: true,
|
||||
isLoaded: loadedSet[f],
|
||||
fullPath: path.Join(cfg.RAGDir, f),
|
||||
})
|
||||
seen[f] = true
|
||||
}
|
||||
|
||||
// Add orphaned files (in DB but not in ragdir)
|
||||
for _, f := range loadedFiles {
|
||||
if !seen[f] {
|
||||
ragFiles = append(ragFiles, ragFileInfo{
|
||||
name: f,
|
||||
inRAGDir: false,
|
||||
isLoaded: true,
|
||||
fullPath: "",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
rows := len(ragFiles)
|
||||
cols := 4 // File Name | Preview | Action | Delete
|
||||
fileTable := tview.NewTable().
|
||||
SetBorders(true)
|
||||
longStatusView := tview.NewTextView()
|
||||
@@ -273,7 +313,7 @@ func makeRAGTable(fileList []string) *tview.Flex {
|
||||
SetAlign(tview.AlignCenter).
|
||||
SetSelectable(false))
|
||||
fileTable.SetCell(0, 2,
|
||||
tview.NewTableCell("Load").
|
||||
tview.NewTableCell("Load/Unload").
|
||||
SetTextColor(tcell.ColorWhite).
|
||||
SetAlign(tview.AlignCenter).
|
||||
SetSelectable(false))
|
||||
@@ -284,18 +324,29 @@ func makeRAGTable(fileList []string) *tview.Flex {
|
||||
SetSelectable(false))
|
||||
// Add the file rows starting from row 1
|
||||
for r := 0; r < rows; r++ {
|
||||
f := ragFiles[r]
|
||||
for c := 0; c < cols; c++ {
|
||||
color := tcell.ColorWhite
|
||||
switch {
|
||||
case c == 0:
|
||||
displayName := f.name
|
||||
if !f.inRAGDir {
|
||||
displayName = f.name + " (orphaned)"
|
||||
}
|
||||
fileTable.SetCell(r+1, c,
|
||||
tview.NewTableCell(fileList[r]).
|
||||
tview.NewTableCell(displayName).
|
||||
SetTextColor(color).
|
||||
SetAlign(tview.AlignCenter).
|
||||
SetSelectable(false))
|
||||
case c == 1:
|
||||
fpath := path.Join(cfg.RAGDir, fileList[r])
|
||||
if fi, err := os.Stat(fpath); err == nil {
|
||||
if !f.inRAGDir {
|
||||
// Orphaned file - no preview available
|
||||
fileTable.SetCell(r+1, c,
|
||||
tview.NewTableCell("not in ragdir").
|
||||
SetTextColor(tcell.ColorYellow).
|
||||
SetAlign(tview.AlignCenter).
|
||||
SetSelectable(false))
|
||||
} else if fi, err := os.Stat(f.fullPath); err == nil {
|
||||
size := fi.Size()
|
||||
modTime := fi.ModTime()
|
||||
preview := fmt.Sprintf("%s | %s", formatSize(size), modTime.Format("2006-01-02 15:04"))
|
||||
@@ -312,15 +363,32 @@ func makeRAGTable(fileList []string) *tview.Flex {
|
||||
SetSelectable(false))
|
||||
}
|
||||
case c == 2:
|
||||
actionText := "load"
|
||||
if f.isLoaded {
|
||||
actionText = "unload"
|
||||
}
|
||||
if !f.inRAGDir {
|
||||
// Orphaned file - can only unload
|
||||
actionText = "unload"
|
||||
}
|
||||
fileTable.SetCell(r+1, c,
|
||||
tview.NewTableCell("load").
|
||||
SetTextColor(color).
|
||||
SetAlign(tview.AlignCenter))
|
||||
default:
|
||||
fileTable.SetCell(r+1, c,
|
||||
tview.NewTableCell("delete").
|
||||
tview.NewTableCell(actionText).
|
||||
SetTextColor(color).
|
||||
SetAlign(tview.AlignCenter))
|
||||
case c == 3:
|
||||
if !f.inRAGDir {
|
||||
// Orphaned file - cannot delete from ragdir (not there)
|
||||
fileTable.SetCell(r+1, c,
|
||||
tview.NewTableCell("-").
|
||||
SetTextColor(tcell.ColorDarkGray).
|
||||
SetAlign(tview.AlignCenter).
|
||||
SetSelectable(false))
|
||||
} else {
|
||||
fileTable.SetCell(r+1, c,
|
||||
tview.NewTableCell("delete").
|
||||
SetTextColor(color).
|
||||
SetAlign(tview.AlignCenter))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -376,12 +444,16 @@ func makeRAGTable(fileList []string) *tview.Flex {
|
||||
pages.RemovePage(RAGPage)
|
||||
return
|
||||
}
|
||||
// For file rows, get the filename (row index - 1 because of the exit row at index 0)
|
||||
fpath := fileList[row-1] // -1 to account for the exit row at index 0
|
||||
// notification := fmt.Sprintf("chat: %s; action: %s", fpath, tc.Text)
|
||||
// For file rows, get the file info (row index - 1 because of the exit row at index 0)
|
||||
f := ragFiles[row-1]
|
||||
// Handle "-" case (orphaned file with no delete option)
|
||||
if tc.Text == "-" {
|
||||
pages.RemovePage(RAGPage)
|
||||
return
|
||||
}
|
||||
switch tc.Text {
|
||||
case "load":
|
||||
fpath = path.Join(cfg.RAGDir, fpath)
|
||||
fpath := path.Join(cfg.RAGDir, f.name)
|
||||
longStatusView.SetText("clicked load")
|
||||
go func() {
|
||||
if err := ragger.LoadRAG(fpath); err != nil {
|
||||
@@ -398,8 +470,25 @@ func makeRAGTable(fileList []string) *tview.Flex {
|
||||
})
|
||||
}()
|
||||
return
|
||||
case "unload":
|
||||
longStatusView.SetText("clicked unload")
|
||||
go func() {
|
||||
if err := ragger.RemoveFile(f.name); err != nil {
|
||||
logger.Error("failed to unload file from RAG", "filename", f.name, "error", err)
|
||||
_ = notifyUser("RAG", "failed to unload file; error: "+err.Error())
|
||||
app.QueueUpdate(func() {
|
||||
pages.RemovePage(RAGPage)
|
||||
})
|
||||
return
|
||||
}
|
||||
_ = notifyUser("RAG", "file unloaded successfully")
|
||||
app.QueueUpdate(func() {
|
||||
pages.RemovePage(RAGPage)
|
||||
})
|
||||
}()
|
||||
return
|
||||
case "delete":
|
||||
fpath = path.Join(cfg.RAGDir, fpath)
|
||||
fpath := path.Join(cfg.RAGDir, f.name)
|
||||
if err := os.Remove(fpath); err != nil {
|
||||
logger.Error("failed to delete file", "filename", fpath, "error", err)
|
||||
return
|
||||
|
||||
19
tui.go
19
tui.go
@@ -913,6 +913,7 @@ func init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Get files from ragdir
|
||||
fileList := []string{}
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
@@ -920,22 +921,14 @@ func init() {
|
||||
}
|
||||
fileList = append(fileList, f.Name())
|
||||
}
|
||||
chatRAGTable := makeRAGTable(fileList)
|
||||
pages.AddPage(RAGPage, chatRAGTable, true, true)
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyCtrlY { // Use Ctrl+Y to list loaded RAG files
|
||||
// List files already loaded into the RAG system
|
||||
fileList, err := ragger.ListLoaded()
|
||||
// Get loaded files from vector DB
|
||||
loadedFiles, err := ragger.ListLoaded()
|
||||
if err != nil {
|
||||
logger.Error("failed to list loaded RAG files", "error", err)
|
||||
if notifyerr := notifyUser("failed to list RAG files", err.Error()); notifyerr != nil {
|
||||
logger.Error("failed to send notification", "error", notifyerr)
|
||||
}
|
||||
return nil
|
||||
loadedFiles = []string{} // Continue with empty list on error
|
||||
}
|
||||
chatLoadedRAGTable := makeLoadedRAGTable(fileList)
|
||||
pages.AddPage(RAGLoadedPage, chatLoadedRAGTable, true, true)
|
||||
chatRAGTable := makeRAGTable(fileList, loadedFiles)
|
||||
pages.AddPage(RAGPage, chatRAGTable, true, true)
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyRune && event.Modifiers() == tcell.ModAlt && event.Rune() == '1' {
|
||||
|
||||
Reference in New Issue
Block a user