summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bot.go2
-rw-r--r--extra/tts.go9
-rw-r--r--main.go2
-rw-r--r--models/models.go23
-rw-r--r--tables.go106
-rw-r--r--tui.go78
6 files changed, 139 insertions, 81 deletions
diff --git a/bot.go b/bot.go
index 08920cc..0503548 100644
--- a/bot.go
+++ b/bot.go
@@ -437,6 +437,7 @@ func removeThinking(chatBody *models.ChatBody) {
func applyCharCard(cc *models.CharCard) {
cfg.AssistantRole = cc.Role
+ // FIXME: remove
// Initialize Cluedo if enabled and matching role
if cfg.EnableCluedo && cc.Role == "CluedoPlayer" {
playerOrder = []string{cfg.UserRole, cfg.AssistantRole, cfg.CluedoRole2}
@@ -444,6 +445,7 @@ func applyCharCard(cc *models.CharCard) {
}
history, err := loadAgentsLastChat(cfg.AssistantRole)
if err != nil {
+ // TODO: too much action for err != nil; loadAgentsLastChat needs to be split up
logger.Warn("failed to load last agent chat;", "agent", cc.Role, "err", err)
history = []models.RoleMsg{
{Role: "system", Content: cc.SysPrompt},
diff --git a/extra/tts.go b/extra/tts.go
index 6cdecd4..31e6887 100644
--- a/extra/tts.go
+++ b/extra/tts.go
@@ -9,7 +9,6 @@ import (
"io"
"log/slog"
"net/http"
- "regexp"
"strings"
"time"
@@ -20,10 +19,10 @@ import (
)
var (
- TTSTextChan = make(chan string, 10000)
- TTSFlushChan = make(chan bool, 1)
- TTSDoneChan = make(chan bool, 1)
- endsWithPunctuation = regexp.MustCompile(`[;.!?]$`)
+ TTSTextChan = make(chan string, 10000)
+ TTSFlushChan = make(chan bool, 1)
+ TTSDoneChan = make(chan bool, 1)
+ // endsWithPunctuation = regexp.MustCompile(`[;.!?]$`)
)
type Orator interface {
diff --git a/main.go b/main.go
index c1f7f87..c73cf3c 100644
--- a/main.go
+++ b/main.go
@@ -12,7 +12,7 @@ var (
botRespMode = false
editMode = false
selectedIndex = int(-1)
- indexLine = "F12 to show keys help | bot resp mode: [orange:-:b]%v[-:-:-] (F6) | char: [orange:-:b]%s[-:-:-] (ctrl+s) | chat: [orange:-:b]%s[-:-:-] (F1) | RAGEnabled: [orange:-:b]%v[-:-:-] (F11) | toolUseAdviced: [orange:-:b]%v[-:-:-] (ctrl+k) | model: [orange:-:b]%s[-:-:-] (ctrl+l)\nAPI_URL: [orange:-:b]%s[-:-:-] (ctrl+v) | ThinkUse: [orange:-:b]%v[-:-:-] (ctrl+p) | Log Level: [orange:-:b]%v[-:-:-] (ctrl+p) | Recording: [orange:-:b]%v[-:-:-] (ctrl+r)"
+ indexLine = "F12 to show keys help | bot resp mode: [orange:-:b]%v[-:-:-] (F6) | char: [orange:-:b]%s[-:-:-] (ctrl+s) | chat: [orange:-:b]%s[-:-:-] (F1) | toolUseAdviced: [orange:-:b]%v[-:-:-] (ctrl+k) | model: [orange:-:b]%s[-:-:-] (ctrl+l)\nAPI_URL: [orange:-:b]%s[-:-:-] (ctrl+v) | ThinkUse: [orange:-:b]%v[-:-:-] (ctrl+p) | Log Level: [orange:-:b]%v[-:-:-] (ctrl+p) | Recording: [orange:-:b]%v[-:-:-] (ctrl+r)"
focusSwitcher = map[tview.Primitive]tview.Primitive{}
)
diff --git a/models/models.go b/models/models.go
index 2e2ef34..918e35e 100644
--- a/models/models.go
+++ b/models/models.go
@@ -1,8 +1,8 @@
package models
import (
- "gf-lt/config"
"fmt"
+ "gf-lt/config"
"strings"
)
@@ -76,6 +76,27 @@ type ChatBody struct {
Messages []RoleMsg `json:"messages"`
}
+func (cb *ChatBody) Rename(oldname, newname string) {
+ for i, m := range cb.Messages {
+ cb.Messages[i].Content = strings.ReplaceAll(m.Content, oldname, newname)
+ cb.Messages[i].Role = strings.ReplaceAll(m.Role, oldname, newname)
+ }
+}
+
+func (cb *ChatBody) ListRoles() []string {
+ namesMap := make(map[string]struct{})
+ for _, m := range cb.Messages {
+ namesMap[m.Role] = struct{}{}
+ }
+ resp := make([]string, len(namesMap))
+ i := 0
+ for k := range namesMap {
+ resp[i] = k
+ i++
+ }
+ return resp
+}
+
type ChatToolsBody struct {
Model string `json:"model"`
Messages []RoleMsg `json:"messages"`
diff --git a/tables.go b/tables.go
index 0f22321..c4c97b9 100644
--- a/tables.go
+++ b/tables.go
@@ -267,59 +267,59 @@ func makeRAGTable(fileList []string) *tview.Flex {
return ragflex
}
-func makeLoadedRAGTable(fileList []string) *tview.Table {
- actions := []string{"delete"}
- rows, cols := len(fileList), len(actions)+1
- fileTable := tview.NewTable().
- SetBorders(true)
- for r := 0; r < rows; r++ {
- for c := 0; c < cols; c++ {
- color := tcell.ColorWhite
- if c < 1 {
- fileTable.SetCell(r, c,
- tview.NewTableCell(fileList[r]).
- SetTextColor(color).
- SetAlign(tview.AlignCenter))
- } else {
- fileTable.SetCell(r, c,
- tview.NewTableCell(actions[c-1]).
- SetTextColor(color).
- SetAlign(tview.AlignCenter))
- }
- }
- }
- fileTable.Select(0, 0).SetFixed(1, 1).SetDoneFunc(func(key tcell.Key) {
- if key == tcell.KeyEsc || key == tcell.KeyF1 {
- pages.RemovePage(RAGPage)
- return
- }
- if key == tcell.KeyEnter {
- fileTable.SetSelectable(true, true)
- }
- }).SetSelectedFunc(func(row int, column int) {
- defer pages.RemovePage(RAGPage)
- tc := fileTable.GetCell(row, column)
- tc.SetTextColor(tcell.ColorRed)
- fileTable.SetSelectable(false, false)
- fpath := fileList[row]
- // notification := fmt.Sprintf("chat: %s; action: %s", fpath, tc.Text)
- switch tc.Text {
- case "delete":
- if err := ragger.RemoveFile(fpath); err != nil {
- logger.Error("failed to delete file", "filename", fpath, "error", err)
- return
- }
- if err := notifyUser("chat deleted", fpath+" was deleted"); err != nil {
- logger.Error("failed to send notification", "error", err)
- }
- return
- default:
- // pages.RemovePage(RAGPage)
- return
- }
- })
- return fileTable
-}
+// func makeLoadedRAGTable(fileList []string) *tview.Table {
+// actions := []string{"delete"}
+// rows, cols := len(fileList), len(actions)+1
+// fileTable := tview.NewTable().
+// SetBorders(true)
+// for r := 0; r < rows; r++ {
+// for c := 0; c < cols; c++ {
+// color := tcell.ColorWhite
+// if c < 1 {
+// fileTable.SetCell(r, c,
+// tview.NewTableCell(fileList[r]).
+// SetTextColor(color).
+// SetAlign(tview.AlignCenter))
+// } else {
+// fileTable.SetCell(r, c,
+// tview.NewTableCell(actions[c-1]).
+// SetTextColor(color).
+// SetAlign(tview.AlignCenter))
+// }
+// }
+// }
+// fileTable.Select(0, 0).SetFixed(1, 1).SetDoneFunc(func(key tcell.Key) {
+// if key == tcell.KeyEsc || key == tcell.KeyF1 {
+// pages.RemovePage(RAGPage)
+// return
+// }
+// if key == tcell.KeyEnter {
+// fileTable.SetSelectable(true, true)
+// }
+// }).SetSelectedFunc(func(row int, column int) {
+// defer pages.RemovePage(RAGPage)
+// tc := fileTable.GetCell(row, column)
+// tc.SetTextColor(tcell.ColorRed)
+// fileTable.SetSelectable(false, false)
+// fpath := fileList[row]
+// // notification := fmt.Sprintf("chat: %s; action: %s", fpath, tc.Text)
+// switch tc.Text {
+// case "delete":
+// if err := ragger.RemoveFile(fpath); err != nil {
+// logger.Error("failed to delete file", "filename", fpath, "error", err)
+// return
+// }
+// if err := notifyUser("chat deleted", fpath+" was deleted"); err != nil {
+// logger.Error("failed to send notification", "error", err)
+// }
+// return
+// default:
+// // pages.RemovePage(RAGPage)
+// return
+// }
+// })
+// return fileTable
+// }
func makeAgentTable(agentList []string) *tview.Table {
actions := []string{"load"}
diff --git a/tui.go b/tui.go
index df300bb..85f65c6 100644
--- a/tui.go
+++ b/tui.go
@@ -43,6 +43,7 @@ var (
codeBlockPage = "codeBlockPage"
imgPage = "imgPage"
// help text
+ // [yellow]F10[white]: manage loaded rag files (that already in vector db)
helpText = `
[yellow]Esc[white]: send msg
[yellow]PgUp/Down[white]: switch focus between input and chat widgets
@@ -55,7 +56,6 @@ var (
[yellow]F7[white]: copy last msg to clipboard (linux xclip)
[yellow]F8[white]: copy n msg to clipboard (linux xclip)
[yellow]F9[white]: table to copy from; with all code blocks
-[yellow]F10[white]: manage loaded rag files (that already in vector db)
[yellow]F11[white]: import chat file
[yellow]F12[white]: show this help page
[yellow]Ctrl+w[white]: resume generation on the last msg
@@ -65,11 +65,12 @@ var (
[yellow]Ctrl+c[white]: close programm
[yellow]Ctrl+p[white]: props edit form (min-p, dry, etc.)
[yellow]Ctrl+v[white]: switch between /completion and /chat api (if provided in config)
-[yellow]Ctrl+r[white]: menu of files that can be loaded in vector db (RAG)
+[yellow]Ctrl+r[white]: start/stop recording from your microphone (needs stt server)
[yellow]Ctrl+t[white]: remove thinking (<think>) and tool messages from context (delete from chat)
[yellow]Ctrl+l[white]: update connected model name (llamacpp)
[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)
Press Enter to go back
`
@@ -144,7 +145,7 @@ func updateStatusLine() {
if asr != nil {
isRecording = asr.IsRecording()
}
- position.SetText(fmt.Sprintf(indexLine, botRespMode, cfg.AssistantRole, activeChatName, cfg.RAGEnabled, cfg.ToolUse, chatBody.Model, cfg.CurrentAPI, cfg.ThinkUse, logLevel.Level(), isRecording))
+ position.SetText(fmt.Sprintf(indexLine, botRespMode, cfg.AssistantRole, activeChatName, cfg.ToolUse, chatBody.Model, cfg.CurrentAPI, cfg.ThinkUse, logLevel.Level(), isRecording))
}
func initSysCards() ([]string, error) {
@@ -166,6 +167,36 @@ func initSysCards() ([]string, error) {
return labels, nil
}
+func renameUser(oldname, newname string) {
+ if oldname == "" {
+ // not provided; deduce who user is
+ // INFO: if user not yet spoke, it is hard to replace mentions in sysprompt and first message about thme
+ roles := chatBody.ListRoles()
+ for _, role := range roles {
+ if role == cfg.AssistantRole {
+ continue
+ }
+ if role == cfg.ToolRole {
+ continue
+ }
+ if role == "system" {
+ continue
+ }
+ oldname = role
+ break
+ }
+ if oldname == "" {
+ // still
+ logger.Warn("fn: renameUser; failed to find old name", "newname", newname)
+ return
+ }
+ }
+ viewText := textView.GetText(false)
+ viewText = strings.ReplaceAll(viewText, oldname, newname)
+ chatBody.Rename(oldname, newname)
+ textView.SetText(viewText)
+}
+
func startNewChat() {
id, err := store.ChatGetMaxID()
if err != nil {
@@ -218,7 +249,12 @@ func makePropsForm(props map[string]float32) *tview.Form {
}).AddDropDown("Select a model: ", []string{chatBody.Model, "deepseek-chat", "deepseek-reasoner"}, 0,
func(option string, optionIndex int) {
chatBody.Model = option
- }).
+ }).AddInputField("username: ", cfg.UserRole, 32, tview.InputFieldMaxLength(32), func(text string) {
+ if text != "" {
+ renameUser(cfg.UserRole, text)
+ cfg.UserRole = text
+ }
+ }).
AddButton("Quit", func() {
pages.RemovePage(propsPage)
})
@@ -545,23 +581,23 @@ func init() {
// 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 {
+ // // 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.KeyF11 {
// read files in chat_exports
dirname := "chat_exports"