From eee5e83d329fa9a19cf04ba290102ea650aca580 Mon Sep 17 00:00:00 2001 From: Grail Finder Date: Wed, 20 Aug 2025 22:45:41 +0300 Subject: Feat: bot to write for any char in chat (completion only) --- .gitignore | 2 +- bot.go | 5 +++- config/config.go | 23 +++++++++--------- llm.go | 18 +++++++++++--- main.go | 11 +++++---- tui.go | 73 ++++++++++++++++++++++++++++++++++++++++++++------------ 6 files changed, 96 insertions(+), 36 deletions(-) diff --git a/.gitignore b/.gitignore index 77a54f3..8377edb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ *.txt *.json testlog -elefant history/ *.db config.toml @@ -11,4 +10,5 @@ history_bak/ .aider* tags gf-lt +gflt chat_exports/*.json diff --git a/bot.go b/bot.go index 35ea0fa..8373edf 100644 --- a/bot.go +++ b/bot.go @@ -203,7 +203,7 @@ func sendMsgToLLM(body io.Reader) { line, err := reader.ReadBytes('\n') if err != nil { logger.Error("error reading response body", "error", err, "line", string(line), - "reqbody", string(bodyBytes), "user_role", cfg.UserRole, "parser", chunkParser, "link", cfg.CurrentAPI) + "user_role", cfg.UserRole, "parser", chunkParser, "link", cfg.CurrentAPI) // if err.Error() != "EOF" { streamDone <- true break @@ -355,6 +355,9 @@ func chatRound(userMsg, role string, tv *tview.TextView, regen, resume bool) { logger.Error("empty reader from msgs", "role", role, "error", err) return } + if cfg.SkipLLMResp { + return + } go sendMsgToLLM(reader) logger.Debug("looking at vars in chatRound", "msg", userMsg, "regen", regen, "resume", resume) if !resume { diff --git a/config/config.go b/config/config.go index 76d7519..cc6e0de 100644 --- a/config/config.go +++ b/config/config.go @@ -15,17 +15,18 @@ type Config struct { CurrentProvider string APIMap map[string]string // - ShowSys bool `toml:"ShowSys"` - LogFile string `toml:"LogFile"` - UserRole string `toml:"UserRole"` - ToolRole string `toml:"ToolRole"` - ToolUse bool `toml:"ToolUse"` - ThinkUse bool `toml:"ThinkUse"` - AssistantRole string `toml:"AssistantRole"` - SysDir string `toml:"SysDir"` - ChunkLimit uint32 `toml:"ChunkLimit"` - WriteNextMsgAs string - SkipLLMResp bool + ShowSys bool `toml:"ShowSys"` + LogFile string `toml:"LogFile"` + UserRole string `toml:"UserRole"` + ToolRole string `toml:"ToolRole"` + ToolUse bool `toml:"ToolUse"` + ThinkUse bool `toml:"ThinkUse"` + AssistantRole string `toml:"AssistantRole"` + SysDir string `toml:"SysDir"` + ChunkLimit uint32 `toml:"ChunkLimit"` + WriteNextMsgAs string + WriteNextMsgAsCompletionAgent string + SkipLLMResp bool // embeddings RAGEnabled bool `toml:"RAGEnabled"` EmbedURL string `toml:"EmbedURL"` diff --git a/llm.go b/llm.go index 05874a1..4cef914 100644 --- a/llm.go +++ b/llm.go @@ -92,7 +92,11 @@ func (lcp LlamaCPPeer) FormMsg(msg, role string, resume bool) (io.Reader, error) prompt := strings.Join(messages, "\n") // strings builder? if !resume { - botMsgStart := "\n" + cfg.AssistantRole + ":\n" + botPersona := cfg.AssistantRole + if cfg.WriteNextMsgAsCompletionAgent != "" { + botPersona = cfg.WriteNextMsgAsCompletionAgent + } + botMsgStart := "\n" + botPersona + ":\n" prompt += botMsgStart } if cfg.ThinkUse && !cfg.ToolUse { @@ -234,7 +238,11 @@ func (ds DeepSeekerCompletion) FormMsg(msg, role string, resume bool) (io.Reader prompt := strings.Join(messages, "\n") // strings builder? if !resume { - botMsgStart := "\n" + cfg.AssistantRole + ":\n" + botPersona := cfg.AssistantRole + if cfg.WriteNextMsgAsCompletionAgent != "" { + botPersona = cfg.WriteNextMsgAsCompletionAgent + } + botMsgStart := "\n" + botPersona + ":\n" prompt += botMsgStart } if cfg.ThinkUse && !cfg.ToolUse { @@ -376,7 +384,11 @@ func (or OpenRouterCompletion) FormMsg(msg, role string, resume bool) (io.Reader prompt := strings.Join(messages, "\n") // strings builder? if !resume { - botMsgStart := "\n" + cfg.AssistantRole + ":\n" + botPersona := cfg.AssistantRole + if cfg.WriteNextMsgAsCompletionAgent != "" { + botPersona = cfg.WriteNextMsgAsCompletionAgent + } + botMsgStart := "\n" + botPersona + ":\n" prompt += botMsgStart } if cfg.ThinkUse && !cfg.ToolUse { diff --git a/main.go b/main.go index b332ac0..82027c6 100644 --- a/main.go +++ b/main.go @@ -9,11 +9,12 @@ import ( ) 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) | toolUseAdviced: [orange:-:b]%v[-:-:-] (ctrl+k) | model: [orange:-:b]%s[-:-:-] (ctrl+l) | skip LLM resp: [orange:-:b]%v[-:-:-] (F10)\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) | Writing as: [orange:-:b]%s[-:-:-] (ctrl+q)" - focusSwitcher = map[tview.Primitive]tview.Primitive{} + botRespMode = false + editMode = false + selectedIndex = int(-1) + indexLine = "F12 to show keys help | bot resp mode: [orange:-:b]%v[-:-:-] (F6) | card's char: [orange:-:b]%s[-:-:-] (ctrl+s) | chat: [orange:-:b]%s[-:-:-] (F1) | toolUseAdviced: [orange:-:b]%v[-:-:-] (ctrl+k) | model: [orange:-:b]%s[-:-:-] (ctrl+l) | skip LLM resp: [orange:-:b]%v[-:-:-] (F10)\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) | Writing as: [orange:-:b]%s[-:-:-] (ctrl+q)" + indexLineCompletion = "F12 to show keys help | bot resp mode: [orange:-:b]%v[-:-:-] (F6) | card's char: [orange:-:b]%s[-:-:-] (ctrl+s) | chat: [orange:-:b]%s[-:-:-] (F1) | toolUseAdviced: [orange:-:b]%v[-:-:-] (ctrl+k) | model: [orange:-:b]%s[-:-:-] (ctrl+l) | skip LLM resp: [orange:-:b]%v[-:-:-] (F10)\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) | Writing as: [orange:-:b]%s[-:-:-] (ctrl+q) | Bot will write as [orange:-:b]%s[-:-:-] (ctrl+x)" + focusSwitcher = map[tview.Primitive]tview.Primitive{} ) func isASCII(s string) bool { diff --git a/tui.go b/tui.go index ee0e5e6..5f6ceb1 100644 --- a/tui.go +++ b/tui.go @@ -98,6 +98,15 @@ func loadImage() { imgView.SetImage(img) } +func strInSlice(s string, sl []string) bool { + for _, el := range sl { + if strings.EqualFold(s, el) { + return true + } + } + return false +} + func colorText() { text := textView.GetText(false) // Step 1: Extract code blocks and replace them with unique placeholders @@ -151,8 +160,17 @@ func updateStatusLine() { if cfg.WriteNextMsgAs != "" { persona = cfg.WriteNextMsgAs } - position.SetText(fmt.Sprintf(indexLine, botRespMode, cfg.AssistantRole, activeChatName, cfg.ToolUse, chatBody.Model, - cfg.SkipLLMResp, cfg.CurrentAPI, cfg.ThinkUse, logLevel.Level(), isRecording, persona)) + if strings.Contains(cfg.CurrentAPI, "chat") { + position.SetText(fmt.Sprintf(indexLine, botRespMode, cfg.AssistantRole, activeChatName, cfg.ToolUse, chatBody.Model, + cfg.SkipLLMResp, cfg.CurrentAPI, cfg.ThinkUse, logLevel.Level(), isRecording, persona)) + return + } + botPersona := cfg.AssistantRole + if cfg.WriteNextMsgAsCompletionAgent != "" { + botPersona = cfg.WriteNextMsgAsCompletionAgent + } + position.SetText(fmt.Sprintf(indexLineCompletion, botRespMode, cfg.AssistantRole, activeChatName, cfg.ToolUse, chatBody.Model, + cfg.SkipLLMResp, cfg.CurrentAPI, cfg.ThinkUse, logLevel.Level(), isRecording, persona, botPersona)) } func initSysCards() ([]string, error) { @@ -354,7 +372,6 @@ func init() { editArea.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { // if event.Key() == tcell.KeyEscape && editMode { if event.Key() == tcell.KeyEscape { - logger.Warn("edit debug; esc is pressed") defer colorText() editedMsg := editArea.GetText() if editedMsg == "" { @@ -424,10 +441,7 @@ func init() { if err := copyToClipboard(m.Content); err != nil { logger.Error("failed to copy to clipboard", "error", err) } - previewLen := 30 - if len(m.Content) < 30 { - previewLen = len(m.Content) - } + previewLen := min(30, len(m.Content)) notification := fmt.Sprintf("msg '%s' was copied to the clipboard", m.Content[:previewLen]) if err := notifyUser("copied", notification); err != nil { logger.Error("failed to send notification", "error", err) @@ -573,10 +587,7 @@ func init() { if err := copyToClipboard(m.Content); err != nil { logger.Error("failed to copy to clipboard", "error", err) } - previewLen := 30 - if len(m.Content) < 30 { - previewLen = len(m.Content) - } + previewLen := min(30, len(m.Content)) notification := fmt.Sprintf("msg '%s' was copied to the clipboard", m.Content[:previewLen]) if err := notifyUser("copied", notification); err != nil { logger.Error("failed to send notification", "error", err) @@ -798,7 +809,12 @@ func init() { roles := chatBody.ListRoles() if len(roles) == 0 { logger.Warn("empty roles in chat") + return nil + } + if !strInSlice(cfg.UserRole, roles) { + roles = append(roles, cfg.UserRole) } + logger.Info("list roles", "roles", roles) for i, role := range roles { if strings.EqualFold(role, persona) { if i == len(roles)-1 { @@ -806,6 +822,33 @@ func init() { break } cfg.WriteNextMsgAs = roles[i+1] // get next role + logger.Info("picked role", "roles", roles, "index", i+1) + break + } + } + updateStatusLine() + return nil + } + if event.Key() == tcell.KeyCtrlX { + persona := cfg.AssistantRole + if cfg.WriteNextMsgAsCompletionAgent != "" { + persona = cfg.WriteNextMsgAsCompletionAgent + } + roles := chatBody.ListRoles() + if len(roles) == 0 { + logger.Warn("empty roles in chat") + } + if !strInSlice(cfg.AssistantRole, roles) { + roles = append(roles, cfg.AssistantRole) + } + for i, role := range roles { + if strings.EqualFold(role, persona) { + if i == len(roles)-1 { + cfg.WriteNextMsgAsCompletionAgent = roles[0] // reached last, get first + break + } + cfg.WriteNextMsgAsCompletionAgent = roles[i+1] // get next role + logger.Info("picked role", "roles", roles, "index", i+1) break } } @@ -836,10 +879,10 @@ func init() { textView.ScrollToEnd() colorText() } - if !cfg.SkipLLMResp { - // update statue line - go chatRound(msgText, persona, textView, false, false) - } + go chatRound(msgText, persona, textView, false, false) + // if !cfg.SkipLLMResp { + // // update statue line + // } return nil } if event.Key() == tcell.KeyPgUp || event.Key() == tcell.KeyPgDn { -- cgit v1.2.3