2 Commits

Author SHA1 Message Date
Grail Finder
fa846225ee Enha: remove updatequeue, since it waits for another main action 2026-02-17 10:29:28 +03:00
Grail Finder
7b2fa04391 Fix (img prompt): botname: after <__media__> for /completion 2026-02-17 08:23:08 +03:00
2 changed files with 64 additions and 112 deletions

16
llm.go
View File

@@ -190,14 +190,6 @@ func (lcp LCPCompletion) FormMsg(msg, role string, resume bool) (io.Reader, erro
messages[i] = m.ToPrompt() messages[i] = m.ToPrompt()
} }
prompt := strings.Join(messages, "\n") prompt := strings.Join(messages, "\n")
// strings builder?
if !resume {
botMsgStart := "\n" + botPersona + ":\n"
prompt += botMsgStart
}
if cfg.ThinkUse && !cfg.ToolUse {
prompt += "<think>"
}
// Add multimodal media markers to the prompt text when multimodal data is present // Add multimodal media markers to the prompt text when multimodal data is present
// This is required by llama.cpp multimodal models so they know where to insert media // This is required by llama.cpp multimodal models so they know where to insert media
if len(multimodalData) > 0 { if len(multimodalData) > 0 {
@@ -209,6 +201,14 @@ func (lcp LCPCompletion) FormMsg(msg, role string, resume bool) (io.Reader, erro
} }
prompt = sb.String() prompt = sb.String()
} }
// needs to be after <__media__> if there are images
if !resume {
botMsgStart := "\n" + botPersona + ":\n"
prompt += botMsgStart
}
if cfg.ThinkUse && !cfg.ToolUse {
prompt += "<think>"
}
logger.Debug("checking prompt for /completion", "tool_use", cfg.ToolUse, logger.Debug("checking prompt for /completion", "tool_use", cfg.ToolUse,
"msg", msg, "resume", resume, "prompt", prompt, "multimodal_data_count", len(multimodalData)) "msg", msg, "resume", resume, "prompt", prompt, "multimodal_data_count", len(multimodalData))
payload := models.NewLCPReq(prompt, chatBody.Model, multimodalData, payload := models.NewLCPReq(prompt, chatBody.Model, multimodalData,

160
tables.go
View File

@@ -789,18 +789,17 @@ func makeFilePicker() *tview.Flex {
var selectedFile string var selectedFile string
// Track currently displayed directory (changes as user navigates) // Track currently displayed directory (changes as user navigates)
currentDisplayDir := startDir currentDisplayDir := startDir
// --- NEW: search state ---
searching := false
searchQuery := ""
// Helper function to check if a file has an allowed extension from config // Helper function to check if a file has an allowed extension from config
hasAllowedExtension := func(filename string) bool { hasAllowedExtension := func(filename string) bool {
// If no allowed extensions are specified in config, allow all files
if cfg.FilePickerExts == "" { if cfg.FilePickerExts == "" {
return true return true
} }
// Split the allowed extensions from the config string
allowedExts := strings.Split(cfg.FilePickerExts, ",") allowedExts := strings.Split(cfg.FilePickerExts, ",")
lowerFilename := strings.ToLower(strings.TrimSpace(filename)) lowerFilename := strings.ToLower(strings.TrimSpace(filename))
for _, ext := range allowedExts { for _, ext := range allowedExts {
ext = strings.TrimSpace(ext) ext = strings.TrimSpace(ext) // Remove any whitespace around the extension
if ext != "" && strings.HasSuffix(lowerFilename, "."+ext) { if ext != "" && strings.HasSuffix(lowerFilename, "."+ext) {
return true return true
} }
@@ -845,12 +844,12 @@ func makeFilePicker() *tview.Flex {
flex := tview.NewFlex().SetDirection(tview.FlexRow) flex := tview.NewFlex().SetDirection(tview.FlexRow)
flex.AddItem(hFlex, 0, 3, true) flex.AddItem(hFlex, 0, 3, true)
flex.AddItem(statusView, 3, 0, false) flex.AddItem(statusView, 3, 0, false)
// Refresh the file list now accepts a filter string // Refresh the file list
var refreshList func(string, string) var refreshList func(string)
refreshList = func(dir string, filter string) { refreshList = func(dir string) {
listView.Clear() listView.Clear()
// Update the current display directory // Update the current display directory
currentDisplayDir = dir currentDisplayDir = dir // Update the current display directory
// Add exit option at the top // Add exit option at the top
listView.AddItem("Exit file picker [gray](Close without selecting)[-]", "", 'x', func() { listView.AddItem("Exit file picker [gray](Close without selecting)[-]", "", 'x', func() {
pages.RemovePage(filePickerPage) pages.RemovePage(filePickerPage)
@@ -858,16 +857,14 @@ func makeFilePicker() *tview.Flex {
// Add parent directory (..) if not at root // Add parent directory (..) if not at root
if dir != "/" { if dir != "/" {
parentDir := path.Dir(dir) parentDir := path.Dir(dir)
// For Unix-like systems, avoid infinite loop when at root // Special handling for edge cases - only return if we're truly at a system root
if parentDir != dir { // For Unix-like systems, path.Dir("/") returns "/" which would cause parentDir == dir
if parentDir == dir && dir == "/" {
// We're at the root ("/") and trying to go up, just don't add the parent item
} else {
listView.AddItem("../ [gray](Parent Directory)[-]", "", 'p', func() { listView.AddItem("../ [gray](Parent Directory)[-]", "", 'p', func() {
// Clear search on navigation imgPreview.SetImage(nil)
searching = false refreshList(parentDir)
searchQuery = ""
if cfg.ImagePreview {
imgPreview.SetImage(nil)
}
refreshList(parentDir, "")
dirStack = append(dirStack, parentDir) dirStack = append(dirStack, parentDir)
currentStackPos = len(dirStack) - 1 currentStackPos = len(dirStack) - 1
}) })
@@ -879,66 +876,48 @@ func makeFilePicker() *tview.Flex {
statusView.SetText("Error reading directory: " + err.Error()) statusView.SetText("Error reading directory: " + err.Error())
return return
} }
// Helper to check if an item passes the filter // Add directories and files to the list
matchesFilter := func(name string) bool {
if filter == "" {
return true
}
return strings.Contains(strings.ToLower(name), strings.ToLower(filter))
}
// Add directories
for _, file := range files { for _, file := range files {
name := file.Name() name := file.Name()
// Skip hidden files and directories (those starting with a dot)
if strings.HasPrefix(name, ".") { if strings.HasPrefix(name, ".") {
continue continue
} }
if file.IsDir() && matchesFilter(name) { if file.IsDir() {
// Capture the directory name for the closure to avoid loop variable issues
dirName := name dirName := name
listView.AddItem(dirName+"/ [gray](Directory)[-]", "", 0, func() { listView.AddItem(dirName+"/ [gray](Directory)[-]", "", 0, func() {
// Clear search on navigation imgPreview.SetImage(nil)
searching = false
searchQuery = ""
if cfg.ImagePreview {
imgPreview.SetImage(nil)
}
newDir := path.Join(dir, dirName) newDir := path.Join(dir, dirName)
refreshList(newDir, "") refreshList(newDir)
dirStack = append(dirStack, newDir) dirStack = append(dirStack, newDir)
currentStackPos = len(dirStack) - 1 currentStackPos = len(dirStack) - 1
statusView.SetText("Current: " + newDir) statusView.SetText("Current: " + newDir)
}) })
} } else if hasAllowedExtension(name) {
} // Only show files that have allowed extensions (from config)
// Add files with allowed extensions // Capture the file name for the closure to avoid loop variable issues
for _, file := range files {
name := file.Name()
if strings.HasPrefix(name, ".") || file.IsDir() {
continue
}
if hasAllowedExtension(name) && matchesFilter(name) {
fileName := name fileName := name
fullFilePath := path.Join(dir, fileName) fullFilePath := path.Join(dir, fileName)
listView.AddItem(fileName+" [gray](File)[-]", "", 0, func() { listView.AddItem(fileName+" [gray](File)[-]", "", 0, func() {
selectedFile = fullFilePath selectedFile = fullFilePath
statusView.SetText("Selected: " + selectedFile) statusView.SetText("Selected: " + selectedFile)
// Check if the file is an image
if isImageFile(fileName) { if isImageFile(fileName) {
// For image files, offer to attach to the next LLM message
statusView.SetText("Selected image: " + selectedFile) statusView.SetText("Selected image: " + selectedFile)
} else {
// For non-image files, display as before
statusView.SetText("Selected: " + selectedFile)
} }
}) })
} }
} }
// Update status line based on search state statusView.SetText("Current: " + dir)
if searching {
statusView.SetText("Search: " + searchQuery + "_")
} else if searchQuery != "" {
statusView.SetText("Current: " + dir + " (filter: " + searchQuery + ")")
} else {
statusView.SetText("Current: " + dir)
}
} }
// Initialize the file list // Initialize the file list
refreshList(startDir, "") refreshList(startDir)
// Update image preview when selection changes (unchanged) // Update image preview when selection changes
if cfg.ImagePreview && imgPreview != nil { if cfg.ImagePreview && imgPreview != nil {
listView.SetChangedFunc(func(index int, mainText, secondaryText string, rune rune) { listView.SetChangedFunc(func(index int, mainText, secondaryText string, rune rune) {
itemText, _ := listView.GetItemText(index) itemText, _ := listView.GetItemText(index)
@@ -975,35 +954,6 @@ func makeFilePicker() *tview.Flex {
} }
// Set up keyboard navigation // Set up keyboard navigation
flex.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { flex.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
// --- Handle search mode ---
if searching {
switch event.Key() {
case tcell.KeyEsc:
// Exit search, clear filter
searching = false
searchQuery = ""
refreshList(currentDisplayDir, "")
return nil
case tcell.KeyBackspace, tcell.KeyBackspace2:
if len(searchQuery) > 0 {
searchQuery = searchQuery[:len(searchQuery)-1]
refreshList(currentDisplayDir, searchQuery)
}
return nil
case tcell.KeyRune:
r := event.Rune()
if r != 0 {
searchQuery += string(r)
refreshList(currentDisplayDir, searchQuery)
}
return nil
default:
// Pass all other keys (arrows, Enter, etc.) to normal processing
// This allows selecting items while still in search mode
return event
}
}
// --- Not searching ---
switch event.Key() { switch event.Key() {
case tcell.KeyEsc: case tcell.KeyEsc:
pages.RemovePage(filePickerPage) pages.RemovePage(filePickerPage)
@@ -1015,46 +965,43 @@ func makeFilePicker() *tview.Flex {
if currentStackPos > 0 { if currentStackPos > 0 {
currentStackPos-- currentStackPos--
prevDir := dirStack[currentStackPos] prevDir := dirStack[currentStackPos]
// Clear search when navigating with backspace refreshList(prevDir)
searching = false // Trim the stack to current position to avoid deep history
searchQuery = ""
refreshList(prevDir, "")
// Trim the stack to current position
dirStack = dirStack[:currentStackPos+1] dirStack = dirStack[:currentStackPos+1]
} }
return nil return nil
case tcell.KeyRune:
if event.Rune() == '/' {
// Enter search mode
searching = true
searchQuery = ""
refreshList(currentDisplayDir, "")
return nil
}
case tcell.KeyEnter: case tcell.KeyEnter:
// Get the currently highlighted item in the list // Get the currently highlighted item in the list
itemIndex := listView.GetCurrentItem() itemIndex := listView.GetCurrentItem()
if itemIndex >= 0 && itemIndex < listView.GetItemCount() { if itemIndex >= 0 && itemIndex < listView.GetItemCount() {
// We need to get the text of the currently selected item to determine if it's a directory
// 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) itemText, _ := listView.GetItemText(itemIndex)
logger.Info("choosing dir", "itemText", itemText) logger.Info("choosing dir", "itemText", itemText)
// Check for the exit option first // Check for the exit option first (should be the first item)
if strings.HasPrefix(itemText, "Exit file picker") { if strings.HasPrefix(itemText, "Exit file picker") {
pages.RemovePage(filePickerPage) pages.RemovePage(filePickerPage)
return nil return nil
} }
// Extract the actual filename/directory name by removing the type info // Extract the actual filename/directory name by removing the type info in brackets
// Format is "name [gray](type)[-]"
actualItemName := itemText actualItemName := itemText
if bracketPos := strings.Index(itemText, " ["); bracketPos != -1 { if bracketPos := strings.Index(itemText, " ["); bracketPos != -1 {
actualItemName = itemText[:bracketPos] actualItemName = itemText[:bracketPos]
} }
// Check if it's a directory (ends with /) // Check if it's a directory (ends with /)
if strings.HasSuffix(actualItemName, "/") { 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 var targetDir string
if strings.HasPrefix(actualItemName, "../") { if strings.HasPrefix(actualItemName, "../") {
// Parent directory // Parent directory - need to go up from current directory
targetDir = path.Dir(currentDisplayDir) targetDir = path.Dir(currentDisplayDir)
// Avoid going above root - if parent is same as current and it's system root
if targetDir == currentDisplayDir && currentDisplayDir == "/" { if targetDir == currentDisplayDir && currentDisplayDir == "/" {
logger.Warn("at root, cannot go up") // We're at root, don't navigate
logger.Warn("went to root", "dir", targetDir)
return nil return nil
} }
} else { } else {
@@ -1062,23 +1009,27 @@ func makeFilePicker() *tview.Flex {
dirName := strings.TrimSuffix(actualItemName, "/") dirName := strings.TrimSuffix(actualItemName, "/")
targetDir = path.Join(currentDisplayDir, dirName) targetDir = path.Join(currentDisplayDir, dirName)
} }
// Navigate clear search // Navigate to the selected directory
logger.Info("going to dir", "dir", targetDir) logger.Info("going to the dir", "dir", targetDir)
if cfg.ImagePreview && imgPreview != nil { if cfg.ImagePreview && imgPreview != nil {
imgPreview.SetImage(nil) imgPreview.SetImage(nil)
} }
searching = false refreshList(targetDir)
searchQuery = ""
refreshList(targetDir, "")
dirStack = append(dirStack, targetDir) dirStack = append(dirStack, targetDir)
currentStackPos = len(dirStack) - 1 currentStackPos = len(dirStack) - 1
statusView.SetText("Current: " + targetDir) statusView.SetText("Current: " + targetDir)
return nil return nil
} else { } else {
// It's a file // 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, actualItemName) 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() { if info, err := os.Stat(filePath); err == nil && !info.IsDir() {
// Check if the file is an image
if isImageFile(actualItemName) { 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", actualItemName) logger.Info("setting image", "file", actualItemName)
SetImageAttachment(filePath) SetImageAttachment(filePath)
logger.Info("after setting image", "file", actualItemName) logger.Info("after setting image", "file", actualItemName)
@@ -1087,6 +1038,7 @@ func makeFilePicker() *tview.Flex {
pages.RemovePage(filePickerPage) pages.RemovePage(filePickerPage)
logger.Info("after update drawn", "file", actualItemName) logger.Info("after update drawn", "file", actualItemName)
} else { } else {
// For non-image files, update the text area with file path
textArea.SetText(filePath, true) textArea.SetText(filePath, true)
app.SetFocus(textArea) app.SetFocus(textArea)
pages.RemovePage(filePickerPage) pages.RemovePage(filePickerPage)