diff options
| -rw-r--r-- | README.md | 6 | ||||
| -rw-r--r-- | tables.go | 139 |
2 files changed, 41 insertions, 104 deletions
@@ -7,9 +7,9 @@ made with use of [tview](https://github.com/rivo/tview) - llama.cpp api, deepseek, openrouter (other ones were not tested); - showing images (not really, for now only if your char card is png it could show it); - tts/stt (if whisper.cpp server / fastapi tts server are provided); +- image input; #### does not have/support -- images; (ctrl+j will show an image of the card you use, but that is about it); - RAG; (RAG was implemented, but I found it unusable and then sql extention broke, so no RAG); - MCP; (agentic is implemented, but as a raw and predefined functions for llm to use. see [tools.go](https://github.com/GrailFinder/gf-lt/blob/master/tools.go)); @@ -44,8 +44,9 @@ F12: show this help page Ctrl+w: resume generation on the last msg Ctrl+s: load new char/agent Ctrl+e: export chat to json file -Ctrl+n: start a new chat Ctrl+c: close programm +Ctrl+n: start a new chat +Ctrl+o: open file picker for img input Ctrl+p: props edit form (min-p, dry, etc.) Ctrl+v: switch between /completion and /chat api (if provided in config) Ctrl+r: start/stop recording from your microphone (needs stt server) @@ -55,6 +56,7 @@ Ctrl+k: switch tool use (recommend tool use to llm after user msg) Ctrl+j: if chat agent is char.png will show the image; then any key to return Ctrl+a: interrupt tts (needs tts server) Ctrl+q: cycle through mentioned chars in chat, to pick persona to send next msg as +Ctrl+x: cycle through mentioned chars in chat, to pick persona to send next msg as (for llm) ``` #### setting up config @@ -600,74 +600,29 @@ func makeFilePicker() *tview.Flex { listView := tview.NewList() listView.SetBorder(true).SetTitle("Files & Directories").SetTitleAlign(tview.AlignLeft) - // Path input field - pathInput := tview.NewInputField(). - SetLabel("Path: "). - SetText(startDir). - SetFieldWidth(50) - pathInput.SetBorder(true).SetTitle("Enter Path").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) - buttonBar := tview.NewFlex() - - // Button functions - loadButton := tview.NewButton("Load") - loadButton.SetSelectedFunc(func() { - if selectedFile != "" { - // Check if the selected file is an image - if isImageFile(selectedFile) { - // For image files, set it as an attachment for the next LLM message - SetImageAttachment(selectedFile) - statusView.SetText("Image attached: " + selectedFile + " (will be sent with next message)") - // Close the file picker but don't change the text area - pages.RemovePage(filePickerPage) - } else { - // For non-image files, update the text area with file path - textArea.SetText(selectedFile, true) - app.SetFocus(textArea) - pages.RemovePage(filePickerPage) - } - } else { - // If no file is selected, just close the picker - pages.RemovePage(filePickerPage) - } - }) - - cancelButton := tview.NewButton("Cancel") - cancelButton.SetSelectedFunc(func() { - pages.RemovePage(filePickerPage) - }) - - // Path input button - will be updated after refreshList is defined - goButton := tview.NewButton("Go") - - buttonBar.AddItem(tview.NewBox().SetBackgroundColor(tcell.ColorDefault), 0, 1, false) - buttonBar.AddItem(goButton, 5, 1, true) - buttonBar.AddItem(tview.NewBox(), 1, 1, false) - buttonBar.AddItem(loadButton, 8, 1, true) - buttonBar.AddItem(tview.NewBox(), 1, 1, false) - buttonBar.AddItem(cancelButton, 8, 1, true) - buttonBar.AddItem(tview.NewBox().SetBackgroundColor(tcell.ColorDefault), 0, 1, false) - - // Layout - add path input field at the top + // Layout - only include list view and status view flex := tview.NewFlex().SetDirection(tview.FlexRow) - flex.AddItem(pathInput, 3, 0, false) flex.AddItem(listView, 0, 3, true) flex.AddItem(statusView, 3, 0, false) - flex.AddItem(buttonBar, 3, 0, false) // Refresh the file list var refreshList func(string) refreshList = func(dir string) { listView.Clear() - // Update the path input field and current display directory - pathInput.SetText(dir) + // 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) @@ -676,7 +631,7 @@ func makeFilePicker() *tview.Flex { if parentDir == dir && dir == "/" { // We're at the root ("/") and trying to go up, just don't add the parent item } else { - listView.AddItem("../", "(Parent Directory)", 'p', func() { + listView.AddItem("../ [gray](Parent Directory)[-]", "", 'p', func() { refreshList(parentDir) dirStack = append(dirStack, parentDir) currentStackPos = len(dirStack) - 1 @@ -703,7 +658,7 @@ func makeFilePicker() *tview.Flex { if file.IsDir() { // Capture the directory name for the closure to avoid loop variable issues dirName := name - listView.AddItem(dirName+"/", "(Directory)", 0, func() { + listView.AddItem(dirName+"/ [gray](Directory)[-]", "", 0, func() { newDir := path.Join(dir, dirName) refreshList(newDir) dirStack = append(dirStack, newDir) @@ -716,14 +671,14 @@ func makeFilePicker() *tview.Flex { // Capture the file name for the closure to avoid loop variable issues fileName := name fullFilePath := path.Join(dir, fileName) - listView.AddItem(fileName, "(File)", 0, func() { + 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 - statusView.SetText("Selected image: " + selectedFile + " (Press Load to attach)") + statusView.SetText("Selected image: " + selectedFile) } else { // For non-image files, display as before statusView.SetText("Selected: " + selectedFile) @@ -739,40 +694,6 @@ func makeFilePicker() *tview.Flex { // Initialize the file list refreshList(startDir) - // Set up keyboard navigation for the path input - pathInput.SetDoneFunc(func(key tcell.Key) { - if key == tcell.KeyEnter { - // Trigger the Go functionality when Enter is pressed in the path input - newPath := pathInput.GetText() - if newPath != "" { - // Check if path exists and is a directory - if info, err := os.Stat(newPath); err == nil && info.IsDir() { - refreshList(newPath) - dirStack = append(dirStack, newPath) - currentStackPos = len(dirStack) - 1 - statusView.SetText("Current: " + newPath) - } else { - statusView.SetText("Invalid directory: " + newPath) - } - } - } - }) - - // Now that refreshList is defined, set the Go button's functionality - goButton.SetSelectedFunc(func() { - newPath := pathInput.GetText() - if newPath != "" { - // Check if path exists and is a directory - if info, err := os.Stat(newPath); err == nil && info.IsDir() { - refreshList(newPath) - dirStack = append(dirStack, newPath) - currentStackPos = len(dirStack) - 1 - statusView.SetText("Current: " + newPath) - } else { - statusView.SetText("Invalid directory: " + newPath) - } - } - }) // Set up keyboard navigation flex.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { @@ -799,12 +720,26 @@ func makeFilePicker() *tview.Flex { itemText, _ := listView.GetItemText(itemIndex) logger.Info("choosing dir", "itemText", itemText) - // Check if it's a directory (typically ends with /) - if strings.HasSuffix(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 // Since the item text ends with "/" and represents a directory var targetDir string - if strings.HasPrefix(itemText, "../") { + if strings.HasPrefix(actualItemName, "../") { // Parent directory - need to go up from current directory targetDir = path.Dir(currentDisplayDir) // Avoid going above root - if parent is same as current and it's system root @@ -815,7 +750,7 @@ func makeFilePicker() *tview.Flex { } } else { // Regular subdirectory - dirName := strings.TrimSuffix(itemText, "/") + dirName := strings.TrimSuffix(actualItemName, "/") targetDir = path.Join(currentDisplayDir, dirName) } @@ -827,23 +762,23 @@ func makeFilePicker() *tview.Flex { statusView.SetText("Current: " + targetDir) return nil } else { - // It's a file - construct the full path from current directory and the item name + // It's a file - construct the full path from current directory and the actual item name // We can't rely only on the selectedFile variable since Enter key might be pressed // without having clicked the file first - filePath := path.Join(currentDisplayDir, itemText) + filePath := path.Join(currentDisplayDir, actualItemName) // Verify it's actually a file (not just lacking a directory suffix) if info, err := os.Stat(filePath); err == nil && !info.IsDir() { // Check if the file is an image - if isImageFile(itemText) { + if isImageFile(actualItemName) { // For image files, set it as an attachment for the next LLM message // Use the version without UI updates to avoid hangs in event handlers - logger.Info("setting image", "file", itemText) + logger.Info("setting image", "file", actualItemName) SetImageAttachment(filePath) - logger.Info("after setting image", "file", itemText) + logger.Info("after setting image", "file", actualItemName) statusView.SetText("Image attached: " + filePath + " (will be sent with next message)") - logger.Info("after setting text", "file", itemText) + logger.Info("after setting text", "file", actualItemName) pages.RemovePage(filePickerPage) - logger.Info("after update drawn", "file", itemText) + logger.Info("after update drawn", "file", actualItemName) } else { // For non-image files, update the text area with file path textArea.SetText(filePath, true) |
