summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrail Finder <wohilas@gmail.com>2025-11-21 12:50:47 +0300
committerGrail Finder <wohilas@gmail.com>2025-11-21 12:50:47 +0300
commitee31f8ccf47b969ce50a49abdcd492cd902f761d (patch)
tree34198cdcf4056609853d3e4fff3043829dfbdfac
parentc2ecef69b4fde4ae310c5db1ddbb85855522457b (diff)
Enha: ctrl+g exit option
-rw-r--r--config.example.toml1
-rw-r--r--tables.go35
-rw-r--r--tui.go20
3 files changed, 6 insertions, 50 deletions
diff --git a/config.example.toml b/config.example.toml
index 98a7c71..850047b 100644
--- a/config.example.toml
+++ b/config.example.toml
@@ -14,6 +14,7 @@ ChunkLimit = 100000
RAGBatchSize = 100
RAGWordLimit = 80
RAGWorkers = 5
+RAGDir = "ragimport"
# extra tts
TTS_ENABLED = false
TTS_URL = "http://localhost:8880/v1/audio/speech"
diff --git a/tables.go b/tables.go
index 59c1ec8..7e7ade3 100644
--- a/tables.go
+++ b/tables.go
@@ -171,7 +171,7 @@ func makeChatTable(chatMap map[string]models.Chat) *tview.Table {
// nolint:unused
func makeRAGTable(fileList []string) *tview.Flex {
- actions := []string{"load", "delete"}
+ actions := []string{"load", "delete", "exit"}
rows, cols := len(fileList), len(actions)+1
fileTable := tview.NewTable().
SetBorders(true)
@@ -227,7 +227,7 @@ func makeRAGTable(fileList []string) *tview.Flex {
}
}()
fileTable.Select(0, 0).SetFixed(1, 1).SetDoneFunc(func(key tcell.Key) {
- if key == tcell.KeyEsc || key == tcell.KeyF1 {
+ if key == tcell.KeyEsc || key == tcell.KeyF1 || key == tcell.Key('x') || key == tcell.KeyCtrlX {
pages.RemovePage(RAGPage)
return
}
@@ -265,6 +265,7 @@ func makeRAGTable(fileList []string) *tview.Flex {
}
return
default:
+ pages.RemovePage(RAGPage)
return
}
})
@@ -555,7 +556,6 @@ func makeFilePicker() *tview.Flex {
if startDir == "" {
startDir = "."
}
-
// If startDir is ".", resolve it to the actual current working directory
if startDir == "." {
wd, err := os.Getwd()
@@ -563,29 +563,22 @@ func makeFilePicker() *tview.Flex {
startDir = wd
}
}
-
// Track navigation history
dirStack := []string{startDir}
currentStackPos := 0
-
// Track selected file
var selectedFile string
-
// Track currently displayed directory (changes as user navigates)
currentDisplayDir := startDir
-
// Helper function to check if a file has an allowed extension from config
hasAllowedExtension := func(filename string) bool {
// If no allowed extensions are specified in config, allow all files
if cfg.FilePickerExts == "" {
return true
}
-
// Split the allowed extensions from the config string
allowedExts := strings.Split(cfg.FilePickerExts, ",")
-
lowerFilename := strings.ToLower(strings.TrimSpace(filename))
-
for _, ext := range allowedExts {
ext = strings.TrimSpace(ext) // Remove any whitespace around the extension
if ext != "" && strings.HasSuffix(lowerFilename, "."+ext) {
@@ -594,7 +587,6 @@ func makeFilePicker() *tview.Flex {
}
return false
}
-
// Helper function to check if a file is an image
isImageFile := func(filename string) bool {
imageExtensions := []string{".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".tiff", ".svg"}
@@ -606,34 +598,27 @@ func makeFilePicker() *tview.Flex {
}
return false
}
-
// Create UI elements
listView := tview.NewList()
listView.SetBorder(true).SetTitle("Files & Directories").SetTitleAlign(tview.AlignLeft)
-
// Status view for selected file information
statusView := tview.NewTextView()
statusView.SetBorder(true).SetTitle("Selected File").SetTitleAlign(tview.AlignLeft)
statusView.SetTextColor(tcell.ColorYellow)
-
// Layout - only include list view and status view
flex := tview.NewFlex().SetDirection(tview.FlexRow)
flex.AddItem(listView, 0, 3, true)
flex.AddItem(statusView, 3, 0, false)
-
// Refresh the file list
var refreshList func(string)
refreshList = func(dir string) {
listView.Clear()
-
// Update the current display directory
currentDisplayDir = dir // Update the current display directory
-
// Add exit option at the top
listView.AddItem("Exit file picker [gray](Close without selecting)[-]", "", 'x', func() {
pages.RemovePage(filePickerPage)
})
-
// Add parent directory (..) if not at root
if dir != "/" {
parentDir := path.Dir(dir)
@@ -649,23 +634,19 @@ func makeFilePicker() *tview.Flex {
})
}
}
-
// Read directory contents
files, err := os.ReadDir(dir)
if err != nil {
statusView.SetText("Error reading directory: " + err.Error())
return
}
-
// Add directories and files to the list
for _, file := range files {
name := file.Name()
-
// Skip hidden files and directories (those starting with a dot)
if strings.HasPrefix(name, ".") {
continue
}
-
if file.IsDir() {
// Capture the directory name for the closure to avoid loop variable issues
dirName := name
@@ -685,7 +666,6 @@ func makeFilePicker() *tview.Flex {
listView.AddItem(fileName+" [gray](File)[-]", "", 0, func() {
selectedFile = fullFilePath
statusView.SetText("Selected: " + selectedFile)
-
// Check if the file is an image
if isImageFile(fileName) {
// For image files, offer to attach to the next LLM message
@@ -698,13 +678,10 @@ func makeFilePicker() *tview.Flex {
}
}
}
-
statusView.SetText("Current: " + dir)
}
-
// Initialize the file list
refreshList(startDir)
-
// Set up keyboard navigation
flex.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
@@ -728,22 +705,18 @@ func makeFilePicker() *tview.Flex {
// Since we can't directly get the item text, we'll keep track of items differently
// Let's improve the approach by tracking the currently selected item
itemText, _ := listView.GetItemText(itemIndex)
-
logger.Info("choosing dir", "itemText", itemText)
-
// Check for the exit option first (should be the first item)
if strings.HasPrefix(itemText, "Exit file picker") {
pages.RemovePage(filePickerPage)
return nil
}
-
// Extract the actual filename/directory name by removing the type info in brackets
// Format is "name [gray](type)[-]"
actualItemName := itemText
if bracketPos := strings.Index(itemText, " ["); bracketPos != -1 {
actualItemName = itemText[:bracketPos]
}
-
// Check if it's a directory (ends with /)
if strings.HasSuffix(actualItemName, "/") {
// This is a directory, we need to get the full path
@@ -763,7 +736,6 @@ func makeFilePicker() *tview.Flex {
dirName := strings.TrimSuffix(actualItemName, "/")
targetDir = path.Join(currentDisplayDir, dirName)
}
-
// Navigate to the selected directory
logger.Info("going to the dir", "dir", targetDir)
refreshList(targetDir)
@@ -803,6 +775,5 @@ func makeFilePicker() *tview.Flex {
}
return event
})
-
return flex
}
diff --git a/tui.go b/tui.go
index 32cc550..ced27b1 100644
--- a/tui.go
+++ b/tui.go
@@ -74,8 +74,10 @@ var (
[yellow]Ctrl+k[white]: switch tool use (recommend tool use to llm after user msg)
[yellow]Ctrl+j[white]: if chat agent is char.png will show the image; then any key to return
[yellow]Ctrl+a[white]: interrupt tts (needs tts server)
+[yellow]Ctrl+g[white]: open RAG file manager (load files for context retrieval)
[yellow]Ctrl+q[white]: cycle through mentioned chars in chat, to pick persona to send next msg as
[yellow]Ctrl+x[white]: cycle through mentioned chars in chat, to pick persona to send next msg as (for llm)
+RAG Window: [yellow]x[white]: close window | [yellow]Enter[white]: select action
%s
@@ -684,26 +686,8 @@ func init() {
}
table := makeCodeBlockTable(cb)
pages.AddPage(codeBlockPage, table, true, true)
- // updateStatusLine()
return nil
}
- // if event.Key() == tcell.KeyF10 {
- // // list rag loaded in db
- // loadedFiles, err := ragger.ListLoaded()
- // if err != nil {
- // logger.Error("failed to list regfiles in db", "error", err)
- // return nil
- // }
- // if len(loadedFiles) == 0 {
- // if err := notifyUser("loaded RAG", "no files in db"); err != nil {
- // logger.Error("failed to send notification", "error", err)
- // }
- // return nil
- // }
- // dbRAGTable := makeLoadedRAGTable(loadedFiles)
- // pages.AddPage(RAGPage, dbRAGTable, true, true)
- // return nil
- // }
if event.Key() == tcell.KeyF10 {
cfg.SkipLLMResp = !cfg.SkipLLMResp
updateStatusLine()