Compare commits
9 Commits
feat/tab-c
...
1675af98d4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1675af98d4 | ||
|
|
61a0ddfdfd | ||
|
|
26ab5c59e3 | ||
|
|
35cc8c068f | ||
|
|
27fdec1361 | ||
|
|
76827a71cc | ||
|
|
3a9a7dbe99 | ||
|
|
d3361c13c5 | ||
|
|
7c1a8b0122 |
4
bot.go
4
bot.go
@@ -456,6 +456,7 @@ func monitorModelLoad(modelID string) {
|
|||||||
if err := notifyUser("model loaded", "Model "+modelID+" is now loaded and ready."); err != nil {
|
if err := notifyUser("model loaded", "Model "+modelID+" is now loaded and ready."); err != nil {
|
||||||
logger.Debug("failed to notify user", "error", err)
|
logger.Debug("failed to notify user", "error", err)
|
||||||
}
|
}
|
||||||
|
refreshChatDisplay()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -763,6 +764,7 @@ func chatWatcher(ctx context.Context) {
|
|||||||
|
|
||||||
func chatRound(r *models.ChatRoundReq) error {
|
func chatRound(r *models.ChatRoundReq) error {
|
||||||
botRespMode = true
|
botRespMode = true
|
||||||
|
updateStatusLine()
|
||||||
botPersona := cfg.AssistantRole
|
botPersona := cfg.AssistantRole
|
||||||
if cfg.WriteNextMsgAsCompletionAgent != "" {
|
if cfg.WriteNextMsgAsCompletionAgent != "" {
|
||||||
botPersona = cfg.WriteNextMsgAsCompletionAgent
|
botPersona = cfg.WriteNextMsgAsCompletionAgent
|
||||||
@@ -1070,7 +1072,7 @@ func findCall(msg, toolCall string) bool {
|
|||||||
}
|
}
|
||||||
resp := callToolWithAgent(fc.Name, fc.Args)
|
resp := callToolWithAgent(fc.Name, fc.Args)
|
||||||
toolMsg := string(resp) // Remove the "tool response: " prefix and %+v formatting
|
toolMsg := string(resp) // Remove the "tool response: " prefix and %+v formatting
|
||||||
logger.Info("llm used tool call", "tool_resp", toolMsg, "tool_attrs", fc)
|
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",
|
fmt.Fprintf(textView, "%s[-:-:b](%d) <%s>: [-:-:-]\n%s\n",
|
||||||
"\n\n", len(chatBody.Messages), cfg.ToolRole, toolMsg)
|
"\n\n", len(chatBody.Messages), cfg.ToolRole, toolMsg)
|
||||||
// Create tool response message with the proper tool_call_id
|
// Create tool response message with the proper tool_call_id
|
||||||
|
|||||||
63
colors.go
Normal file
63
colors.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/rivo/tview"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
colorschemes = map[string]tview.Theme{
|
||||||
|
"default": tview.Theme{
|
||||||
|
PrimitiveBackgroundColor: tcell.ColorDefault,
|
||||||
|
ContrastBackgroundColor: tcell.ColorGray,
|
||||||
|
MoreContrastBackgroundColor: tcell.ColorSteelBlue,
|
||||||
|
BorderColor: tcell.ColorGray,
|
||||||
|
TitleColor: tcell.ColorRed,
|
||||||
|
GraphicsColor: tcell.ColorBlue,
|
||||||
|
PrimaryTextColor: tcell.ColorLightGray,
|
||||||
|
SecondaryTextColor: tcell.ColorYellow,
|
||||||
|
TertiaryTextColor: tcell.ColorOrange,
|
||||||
|
InverseTextColor: tcell.ColorPurple,
|
||||||
|
ContrastSecondaryTextColor: tcell.ColorLime,
|
||||||
|
},
|
||||||
|
"gruvbox": tview.Theme{
|
||||||
|
PrimitiveBackgroundColor: tcell.NewHexColor(0x282828), // Background: #282828 (dark gray)
|
||||||
|
ContrastBackgroundColor: tcell.ColorDarkGoldenrod, // Selected option: warm yellow (#b57614)
|
||||||
|
MoreContrastBackgroundColor: tcell.ColorDarkSlateGray, // Non-selected options: dark grayish-blue (#32302f)
|
||||||
|
BorderColor: tcell.ColorLightGray, // Light gray (#a89984)
|
||||||
|
TitleColor: tcell.ColorRed, // Red (#fb4934)
|
||||||
|
GraphicsColor: tcell.ColorDarkCyan, // Cyan (#689d6a)
|
||||||
|
PrimaryTextColor: tcell.ColorLightGray, // Light gray (#d5c4a1)
|
||||||
|
SecondaryTextColor: tcell.ColorYellow, // Yellow (#fabd2f)
|
||||||
|
TertiaryTextColor: tcell.ColorOrange, // Orange (#fe8019)
|
||||||
|
InverseTextColor: tcell.ColorWhite, // White (#f9f5d7) for selected text
|
||||||
|
ContrastSecondaryTextColor: tcell.ColorLightGreen, // Light green (#b8bb26)
|
||||||
|
},
|
||||||
|
"solarized": tview.Theme{
|
||||||
|
PrimitiveBackgroundColor: tcell.NewHexColor(0x002b36), // Background: #002b36 (base03)
|
||||||
|
ContrastBackgroundColor: tcell.ColorDarkCyan, // Selected option: cyan (#2aa198)
|
||||||
|
MoreContrastBackgroundColor: tcell.ColorDarkSlateGray, // Non-selected options: dark blue (#073642)
|
||||||
|
BorderColor: tcell.ColorLightBlue, // Light blue (#839496)
|
||||||
|
TitleColor: tcell.ColorRed, // Red (#dc322f)
|
||||||
|
GraphicsColor: tcell.ColorBlue, // Blue (#268bd2)
|
||||||
|
PrimaryTextColor: tcell.ColorWhite, // White (#fdf6e3)
|
||||||
|
SecondaryTextColor: tcell.ColorYellow, // Yellow (#b58900)
|
||||||
|
TertiaryTextColor: tcell.ColorOrange, // Orange (#cb4b16)
|
||||||
|
InverseTextColor: tcell.ColorWhite, // White (#eee8d5) for selected text
|
||||||
|
ContrastSecondaryTextColor: tcell.ColorLightCyan, // Light cyan (#93a1a1)
|
||||||
|
},
|
||||||
|
"dracula": tview.Theme{
|
||||||
|
PrimitiveBackgroundColor: tcell.NewHexColor(0x282a36), // Background: #282a36
|
||||||
|
ContrastBackgroundColor: tcell.ColorDarkMagenta, // Selected option: magenta (#bd93f9)
|
||||||
|
MoreContrastBackgroundColor: tcell.ColorDarkGray, // Non-selected options: dark gray (#44475a)
|
||||||
|
BorderColor: tcell.ColorLightGray, // Light gray (#f8f8f2)
|
||||||
|
TitleColor: tcell.ColorRed, // Red (#ff5555)
|
||||||
|
GraphicsColor: tcell.ColorDarkCyan, // Cyan (#8be9fd)
|
||||||
|
PrimaryTextColor: tcell.ColorWhite, // White (#f8f8f2)
|
||||||
|
SecondaryTextColor: tcell.ColorYellow, // Yellow (#f1fa8c)
|
||||||
|
TertiaryTextColor: tcell.ColorOrange, // Orange (#ffb86c)
|
||||||
|
InverseTextColor: tcell.ColorWhite, // White (#f8f8f2) for selected text
|
||||||
|
ContrastSecondaryTextColor: tcell.ColorLightGreen, // Light green (#50fa7b)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
114
helpfuncs.go
114
helpfuncs.go
@@ -9,8 +9,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
@@ -18,6 +20,46 @@ import (
|
|||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Cached model color - updated by background goroutine
|
||||||
|
var cachedModelColor string = "orange"
|
||||||
|
|
||||||
|
// startModelColorUpdater starts a background goroutine that periodically updates
|
||||||
|
// the cached model color. Only runs HTTP requests for local llama.cpp APIs.
|
||||||
|
func startModelColorUpdater() {
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(5 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
// Initial check
|
||||||
|
updateCachedModelColor()
|
||||||
|
|
||||||
|
for range ticker.C {
|
||||||
|
updateCachedModelColor()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateCachedModelColor updates the global cachedModelColor variable
|
||||||
|
func updateCachedModelColor() {
|
||||||
|
if !isLocalLlamacpp() {
|
||||||
|
cachedModelColor = "orange"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if model is loaded
|
||||||
|
loaded, err := isModelLoaded(chatBody.Model)
|
||||||
|
if err != nil {
|
||||||
|
// On error, assume not loaded (red)
|
||||||
|
cachedModelColor = "red"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if loaded {
|
||||||
|
cachedModelColor = "green"
|
||||||
|
} else {
|
||||||
|
cachedModelColor = "red"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func isASCII(s string) bool {
|
func isASCII(s string) bool {
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
if s[i] > unicode.MaxASCII {
|
if s[i] > unicode.MaxASCII {
|
||||||
@@ -60,6 +102,7 @@ func refreshChatDisplay() {
|
|||||||
displayText := chatToText(filteredMessages, cfg.ShowSys)
|
displayText := chatToText(filteredMessages, cfg.ShowSys)
|
||||||
textView.SetText(displayText)
|
textView.SetText(displayText)
|
||||||
colorText()
|
colorText()
|
||||||
|
updateStatusLine()
|
||||||
if scrollToEndEnabled {
|
if scrollToEndEnabled {
|
||||||
textView.ScrollToEnd()
|
textView.ScrollToEnd()
|
||||||
}
|
}
|
||||||
@@ -130,8 +173,8 @@ func colorText() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateStatusLine() {
|
func updateStatusLine() {
|
||||||
statusLineWidget.SetText(makeStatusLine())
|
status := makeStatusLine()
|
||||||
helpView.SetText(fmt.Sprintf(helpText, makeStatusLine()))
|
statusLineWidget.SetText(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initSysCards() ([]string, error) {
|
func initSysCards() ([]string, error) {
|
||||||
@@ -273,22 +316,11 @@ func isLocalLlamacpp() bool {
|
|||||||
return host == "localhost" || host == "127.0.0.1" || host == "::1"
|
return host == "localhost" || host == "127.0.0.1" || host == "::1"
|
||||||
}
|
}
|
||||||
|
|
||||||
// getModelColor returns the color tag for the model name based on its load status.
|
// getModelColor returns the cached color tag for the model name.
|
||||||
|
// The cached value is updated by a background goroutine every 5 seconds.
|
||||||
// For non-local models, returns orange. For local llama.cpp models, returns green if loaded, red if not.
|
// For non-local models, returns orange. For local llama.cpp models, returns green if loaded, red if not.
|
||||||
func getModelColor() string {
|
func getModelColor() string {
|
||||||
if !isLocalLlamacpp() {
|
return cachedModelColor
|
||||||
return "orange"
|
|
||||||
}
|
|
||||||
// Check if model is loaded
|
|
||||||
loaded, err := isModelLoaded(chatBody.Model)
|
|
||||||
if err != nil {
|
|
||||||
// On error, assume not loaded (red)
|
|
||||||
return "red"
|
|
||||||
}
|
|
||||||
if loaded {
|
|
||||||
return "green"
|
|
||||||
}
|
|
||||||
return "red"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeStatusLine() string {
|
func makeStatusLine() string {
|
||||||
@@ -706,23 +738,51 @@ func searchPrev() {
|
|||||||
// == tab completion ==
|
// == tab completion ==
|
||||||
|
|
||||||
func scanFiles(dir, filter string) []string {
|
func scanFiles(dir, filter string) []string {
|
||||||
|
const maxDepth = 3
|
||||||
|
const maxFiles = 50
|
||||||
var files []string
|
var files []string
|
||||||
entries, err := os.ReadDir(dir)
|
|
||||||
if err != nil {
|
var scanRecursive func(currentDir string, currentDepth int, relPath string)
|
||||||
return files
|
scanRecursive = func(currentDir string, currentDepth int, relPath string) {
|
||||||
}
|
if len(files) >= maxFiles {
|
||||||
for _, entry := range entries {
|
return
|
||||||
name := entry.Name()
|
|
||||||
if strings.HasPrefix(name, ".") {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if filter == "" || strings.HasPrefix(strings.ToLower(name), strings.ToLower(filter)) {
|
if currentDepth > maxDepth {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := os.ReadDir(currentDir)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
if len(files) >= maxFiles {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := entry.Name()
|
||||||
|
if strings.HasPrefix(name, ".") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath := name
|
||||||
|
if relPath != "" {
|
||||||
|
fullPath = relPath + "/" + name
|
||||||
|
}
|
||||||
|
|
||||||
if entry.IsDir() {
|
if entry.IsDir() {
|
||||||
files = append(files, name+"/")
|
// Recursively scan subdirectories
|
||||||
|
scanRecursive(filepath.Join(currentDir, name), currentDepth+1, fullPath)
|
||||||
} else {
|
} else {
|
||||||
files = append(files, name)
|
// Check if file matches filter
|
||||||
|
if filter == "" || strings.HasPrefix(strings.ToLower(fullPath), strings.ToLower(filter)) {
|
||||||
|
files = append(files, fullPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scanRecursive(dir, 0, "")
|
||||||
return files
|
return files
|
||||||
}
|
}
|
||||||
|
|||||||
120
popups.go
120
popups.go
@@ -387,3 +387,123 @@ func showFileCompletionPopup(filter string) {
|
|||||||
pages.AddPage("fileCompletionPopup", modal(widget, 80, 20), true, true)
|
pages.AddPage("fileCompletionPopup", modal(widget, 80, 20), true, true)
|
||||||
app.SetFocus(widget)
|
app.SetFocus(widget)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateWidgetColors(theme tview.Theme) {
|
||||||
|
bgColor := theme.PrimitiveBackgroundColor
|
||||||
|
fgColor := theme.PrimaryTextColor
|
||||||
|
borderColor := theme.BorderColor
|
||||||
|
titleColor := theme.TitleColor
|
||||||
|
|
||||||
|
textView.SetBackgroundColor(bgColor)
|
||||||
|
textView.SetTextColor(fgColor)
|
||||||
|
textView.SetBorderColor(borderColor)
|
||||||
|
textView.SetTitleColor(titleColor)
|
||||||
|
|
||||||
|
textArea.SetBackgroundColor(bgColor)
|
||||||
|
textArea.SetBorderColor(borderColor)
|
||||||
|
textArea.SetTitleColor(titleColor)
|
||||||
|
textArea.SetTextStyle(tcell.StyleDefault.Background(bgColor).Foreground(fgColor))
|
||||||
|
textArea.SetPlaceholderStyle(tcell.StyleDefault.Background(bgColor).Foreground(fgColor))
|
||||||
|
// Force textarea refresh by restoring text (SetTextStyle doesn't trigger redraw)
|
||||||
|
textArea.SetText(textArea.GetText(), true)
|
||||||
|
|
||||||
|
editArea.SetBackgroundColor(bgColor)
|
||||||
|
editArea.SetBorderColor(borderColor)
|
||||||
|
editArea.SetTitleColor(titleColor)
|
||||||
|
editArea.SetTextStyle(tcell.StyleDefault.Background(bgColor).Foreground(fgColor))
|
||||||
|
editArea.SetPlaceholderStyle(tcell.StyleDefault.Background(bgColor).Foreground(fgColor))
|
||||||
|
// Force textarea refresh by restoring text (SetTextStyle doesn't trigger redraw)
|
||||||
|
editArea.SetText(editArea.GetText(), true)
|
||||||
|
|
||||||
|
statusLineWidget.SetBackgroundColor(bgColor)
|
||||||
|
statusLineWidget.SetTextColor(fgColor)
|
||||||
|
statusLineWidget.SetBorderColor(borderColor)
|
||||||
|
statusLineWidget.SetTitleColor(titleColor)
|
||||||
|
|
||||||
|
helpView.SetBackgroundColor(bgColor)
|
||||||
|
helpView.SetTextColor(fgColor)
|
||||||
|
helpView.SetBorderColor(borderColor)
|
||||||
|
helpView.SetTitleColor(titleColor)
|
||||||
|
|
||||||
|
searchField.SetBackgroundColor(bgColor)
|
||||||
|
searchField.SetBorderColor(borderColor)
|
||||||
|
searchField.SetTitleColor(titleColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// showColorschemeSelectionPopup creates a modal popup to select a colorscheme
|
||||||
|
func showColorschemeSelectionPopup() {
|
||||||
|
// Get the list of available colorschemes
|
||||||
|
schemeNames := make([]string, 0, len(colorschemes))
|
||||||
|
for name := range colorschemes {
|
||||||
|
schemeNames = append(schemeNames, name)
|
||||||
|
}
|
||||||
|
slices.Sort(schemeNames)
|
||||||
|
// Check for empty options list
|
||||||
|
if len(schemeNames) == 0 {
|
||||||
|
logger.Warn("no colorschemes available for selection")
|
||||||
|
message := "No colorschemes available."
|
||||||
|
if err := notifyUser("Empty list", message); err != nil {
|
||||||
|
logger.Error("failed to send notification", "error", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Create a list primitive
|
||||||
|
schemeListWidget := tview.NewList().ShowSecondaryText(false).
|
||||||
|
SetSelectedBackgroundColor(tcell.ColorGray)
|
||||||
|
schemeListWidget.SetTitle("Select Colorscheme").SetBorder(true)
|
||||||
|
|
||||||
|
currentScheme := "default"
|
||||||
|
for name := range colorschemes {
|
||||||
|
if tview.Styles == colorschemes[name] {
|
||||||
|
currentScheme = name
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentSchemeIndex := -1
|
||||||
|
for i, scheme := range schemeNames {
|
||||||
|
if scheme == currentScheme {
|
||||||
|
currentSchemeIndex = i
|
||||||
|
}
|
||||||
|
schemeListWidget.AddItem(scheme, "", 0, nil)
|
||||||
|
}
|
||||||
|
// Set the current selection if found
|
||||||
|
if currentSchemeIndex != -1 {
|
||||||
|
schemeListWidget.SetCurrentItem(currentSchemeIndex)
|
||||||
|
}
|
||||||
|
schemeListWidget.SetSelectedFunc(func(index int, mainText string, secondaryText string, shortcut rune) {
|
||||||
|
// Update the colorscheme
|
||||||
|
if theme, ok := colorschemes[mainText]; ok {
|
||||||
|
tview.Styles = theme
|
||||||
|
go func() {
|
||||||
|
app.QueueUpdateDraw(func() {
|
||||||
|
updateWidgetColors(theme)
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
// Remove the popup page
|
||||||
|
pages.RemovePage("colorschemeSelectionPopup")
|
||||||
|
})
|
||||||
|
schemeListWidget.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||||
|
if event.Key() == tcell.KeyEscape {
|
||||||
|
pages.RemovePage("colorschemeSelectionPopup")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if event.Key() == tcell.KeyRune && event.Rune() == 'x' {
|
||||||
|
pages.RemovePage("colorschemeSelectionPopup")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return event
|
||||||
|
})
|
||||||
|
modal := func(p tview.Primitive, width, height int) tview.Primitive {
|
||||||
|
return tview.NewFlex().
|
||||||
|
AddItem(nil, 0, 1, false).
|
||||||
|
AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
|
||||||
|
AddItem(nil, 0, 1, false).
|
||||||
|
AddItem(p, height, 1, true).
|
||||||
|
AddItem(nil, 0, 1, false), width, 1, true).
|
||||||
|
AddItem(nil, 0, 1, false)
|
||||||
|
}
|
||||||
|
// Add modal page and make it visible
|
||||||
|
pages.AddPage("colorschemeSelectionPopup", modal(schemeListWidget, 40, len(schemeNames)+2), true, true)
|
||||||
|
app.SetFocus(schemeListWidget)
|
||||||
|
}
|
||||||
|
|||||||
86
tui.go
86
tui.go
@@ -10,14 +10,11 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = sync.RWMutex{}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
app *tview.Application
|
app *tview.Application
|
||||||
pages *tview.Pages
|
pages *tview.Pages
|
||||||
@@ -96,6 +93,7 @@ var (
|
|||||||
[yellow]Alt+8[white]: show char img or last picked img
|
[yellow]Alt+8[white]: show char img or last picked img
|
||||||
[yellow]Alt+9[white]: warm up (load) selected llama.cpp model
|
[yellow]Alt+9[white]: warm up (load) selected llama.cpp model
|
||||||
[yellow]Alt+t[white]: toggle thinking blocks visibility (collapse/expand <think> blocks)
|
[yellow]Alt+t[white]: toggle thinking blocks visibility (collapse/expand <think> blocks)
|
||||||
|
[yellow]Alt+i[white]: show colorscheme selection popup
|
||||||
|
|
||||||
=== scrolling chat window (some keys similar to vim) ===
|
=== scrolling chat window (some keys similar to vim) ===
|
||||||
[yellow]arrows up/down and j/k[white]: scroll up and down
|
[yellow]arrows up/down and j/k[white]: scroll up and down
|
||||||
@@ -112,70 +110,18 @@ var (
|
|||||||
[yellow]x[white]: to exit
|
[yellow]x[white]: to exit
|
||||||
|
|
||||||
=== shell mode ===
|
=== shell mode ===
|
||||||
[yellow]@match->Tab[white]: file completion (type @ in input to get file suggestions)
|
[yellow]@match->Tab[white]: file completion with relative paths (recursive, depth 3, max 50 files)
|
||||||
|
|
||||||
=== status line ===
|
=== status line ===
|
||||||
%s
|
%s
|
||||||
|
|
||||||
Press <Enter> or 'x' to return
|
Press <Enter> or 'x' to return
|
||||||
`
|
`
|
||||||
colorschemes = map[string]tview.Theme{
|
|
||||||
"default": tview.Theme{
|
|
||||||
PrimitiveBackgroundColor: tcell.ColorDefault,
|
|
||||||
ContrastBackgroundColor: tcell.ColorGray,
|
|
||||||
MoreContrastBackgroundColor: tcell.ColorSteelBlue,
|
|
||||||
BorderColor: tcell.ColorGray,
|
|
||||||
TitleColor: tcell.ColorRed,
|
|
||||||
GraphicsColor: tcell.ColorBlue,
|
|
||||||
PrimaryTextColor: tcell.ColorLightGray,
|
|
||||||
SecondaryTextColor: tcell.ColorYellow,
|
|
||||||
TertiaryTextColor: tcell.ColorOrange,
|
|
||||||
InverseTextColor: tcell.ColorPurple,
|
|
||||||
ContrastSecondaryTextColor: tcell.ColorLime,
|
|
||||||
},
|
|
||||||
"gruvbox": tview.Theme{
|
|
||||||
PrimitiveBackgroundColor: tcell.ColorBlack, // Matches #1e1e2e
|
|
||||||
ContrastBackgroundColor: tcell.ColorDarkGoldenrod, // Selected option: warm yellow (#b57614)
|
|
||||||
MoreContrastBackgroundColor: tcell.ColorDarkSlateGray, // Non-selected options: dark grayish-blue (#32302f)
|
|
||||||
BorderColor: tcell.ColorLightGray, // Light gray (#a89984)
|
|
||||||
TitleColor: tcell.ColorRed, // Red (#fb4934)
|
|
||||||
GraphicsColor: tcell.ColorDarkCyan, // Cyan (#689d6a)
|
|
||||||
PrimaryTextColor: tcell.ColorLightGray, // Light gray (#d5c4a1)
|
|
||||||
SecondaryTextColor: tcell.ColorYellow, // Yellow (#fabd2f)
|
|
||||||
TertiaryTextColor: tcell.ColorOrange, // Orange (#fe8019)
|
|
||||||
InverseTextColor: tcell.ColorWhite, // White (#f9f5d7) for selected text
|
|
||||||
ContrastSecondaryTextColor: tcell.ColorLightGreen, // Light green (#b8bb26)
|
|
||||||
},
|
|
||||||
"solarized": tview.Theme{
|
|
||||||
PrimitiveBackgroundColor: tcell.NewHexColor(0x1e1e2e), // #1e1e2e for main dropdown box
|
|
||||||
ContrastBackgroundColor: tcell.ColorDarkCyan, // Selected option: cyan (#2aa198)
|
|
||||||
MoreContrastBackgroundColor: tcell.ColorDarkSlateGray, // Non-selected options: dark blue (#073642)
|
|
||||||
BorderColor: tcell.ColorLightBlue, // Light blue (#839496)
|
|
||||||
TitleColor: tcell.ColorRed, // Red (#dc322f)
|
|
||||||
GraphicsColor: tcell.ColorBlue, // Blue (#268bd2)
|
|
||||||
PrimaryTextColor: tcell.ColorWhite, // White (#fdf6e3)
|
|
||||||
SecondaryTextColor: tcell.ColorYellow, // Yellow (#b58900)
|
|
||||||
TertiaryTextColor: tcell.ColorOrange, // Orange (#cb4b16)
|
|
||||||
InverseTextColor: tcell.ColorWhite, // White (#eee8d5) for selected text
|
|
||||||
ContrastSecondaryTextColor: tcell.ColorLightCyan, // Light cyan (#93a1a1)
|
|
||||||
},
|
|
||||||
"dracula": tview.Theme{
|
|
||||||
PrimitiveBackgroundColor: tcell.NewHexColor(0x1e1e2e), // #1e1e2e for main dropdown box
|
|
||||||
ContrastBackgroundColor: tcell.ColorDarkMagenta, // Selected option: magenta (#bd93f9)
|
|
||||||
MoreContrastBackgroundColor: tcell.ColorDarkGray, // Non-selected options: dark gray (#44475a)
|
|
||||||
BorderColor: tcell.ColorLightGray, // Light gray (#f8f8f2)
|
|
||||||
TitleColor: tcell.ColorRed, // Red (#ff5555)
|
|
||||||
GraphicsColor: tcell.ColorDarkCyan, // Cyan (#8be9fd)
|
|
||||||
PrimaryTextColor: tcell.ColorWhite, // White (#f8f8f2)
|
|
||||||
SecondaryTextColor: tcell.ColorYellow, // Yellow (#f1fa8c)
|
|
||||||
TertiaryTextColor: tcell.ColorOrange, // Orange (#ffb86c)
|
|
||||||
InverseTextColor: tcell.ColorWhite, // White (#f8f8f2) for selected text
|
|
||||||
ContrastSecondaryTextColor: tcell.ColorLightGreen, // Light green (#50fa7b)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
// Start background goroutine to update model color cache
|
||||||
|
startModelColorUpdater()
|
||||||
tview.Styles = colorschemes["default"]
|
tview.Styles = colorschemes["default"]
|
||||||
app = tview.NewApplication()
|
app = tview.NewApplication()
|
||||||
pages = tview.NewPages()
|
pages = tview.NewPages()
|
||||||
@@ -286,6 +232,11 @@ func init() {
|
|||||||
statusLineWidget = tview.NewTextView().
|
statusLineWidget = tview.NewTextView().
|
||||||
SetDynamicColors(true).
|
SetDynamicColors(true).
|
||||||
SetTextAlign(tview.AlignCenter)
|
SetTextAlign(tview.AlignCenter)
|
||||||
|
// // vertical text center alignment
|
||||||
|
// statusLineWidget.SetDrawFunc(func(screen tcell.Screen, x, y, w, h int) (int, int, int, int) {
|
||||||
|
// y += h / 2
|
||||||
|
// return x, y, w, h
|
||||||
|
// })
|
||||||
// Initially set up flex without search bar
|
// Initially set up flex without search bar
|
||||||
flex = tview.NewFlex().SetDirection(tview.FlexRow).
|
flex = tview.NewFlex().SetDirection(tview.FlexRow).
|
||||||
AddItem(textView, 0, 40, false).
|
AddItem(textView, 0, 40, false).
|
||||||
@@ -482,6 +433,19 @@ func init() {
|
|||||||
pages.RemovePage(helpPage)
|
pages.RemovePage(helpPage)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// Allow scrolling keys to pass through to the TextView
|
||||||
|
switch event.Key() {
|
||||||
|
case tcell.KeyUp, tcell.KeyDown,
|
||||||
|
tcell.KeyPgUp, tcell.KeyPgDn,
|
||||||
|
tcell.KeyHome, tcell.KeyEnd:
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
if event.Key() == tcell.KeyRune {
|
||||||
|
switch event.Rune() {
|
||||||
|
case 'j', 'k', 'g', 'G':
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
//
|
//
|
||||||
@@ -560,6 +524,10 @@ func init() {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if event.Key() == tcell.KeyRune && event.Rune() == 'i' && event.Modifiers()&tcell.ModAlt != 0 {
|
||||||
|
showColorschemeSelectionPopup()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if event.Key() == tcell.KeyF1 {
|
if event.Key() == tcell.KeyF1 {
|
||||||
// chatList, err := loadHistoryChats()
|
// chatList, err := loadHistoryChats()
|
||||||
chatList, err := store.GetChatByChar(cfg.AssistantRole)
|
chatList, err := store.GetChatByChar(cfg.AssistantRole)
|
||||||
@@ -731,6 +699,8 @@ func init() {
|
|||||||
}
|
}
|
||||||
if event.Key() == tcell.KeyF12 {
|
if event.Key() == tcell.KeyF12 {
|
||||||
// help window cheatsheet
|
// help window cheatsheet
|
||||||
|
// Update help text with current status before showing
|
||||||
|
helpView.SetText(fmt.Sprintf(helpText, makeStatusLine()))
|
||||||
pages.AddPage(helpPage, helpView, true, true)
|
pages.AddPage(helpPage, helpView, true, true)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user