Feat: bot to write for any char in chat (completion only)

This commit is contained in:
Grail Finder
2025-08-20 22:45:41 +03:00
parent 6955a5bc3a
commit eee5e83d32
6 changed files with 96 additions and 36 deletions

2
.gitignore vendored
View File

@@ -1,7 +1,6 @@
*.txt *.txt
*.json *.json
testlog testlog
elefant
history/ history/
*.db *.db
config.toml config.toml
@@ -11,4 +10,5 @@ history_bak/
.aider* .aider*
tags tags
gf-lt gf-lt
gflt
chat_exports/*.json chat_exports/*.json

5
bot.go
View File

@@ -203,7 +203,7 @@ func sendMsgToLLM(body io.Reader) {
line, err := reader.ReadBytes('\n') line, err := reader.ReadBytes('\n')
if err != nil { if err != nil {
logger.Error("error reading response body", "error", err, "line", string(line), 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" { // if err.Error() != "EOF" {
streamDone <- true streamDone <- true
break 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) logger.Error("empty reader from msgs", "role", role, "error", err)
return return
} }
if cfg.SkipLLMResp {
return
}
go sendMsgToLLM(reader) go sendMsgToLLM(reader)
logger.Debug("looking at vars in chatRound", "msg", userMsg, "regen", regen, "resume", resume) logger.Debug("looking at vars in chatRound", "msg", userMsg, "regen", regen, "resume", resume)
if !resume { if !resume {

View File

@@ -25,6 +25,7 @@ type Config struct {
SysDir string `toml:"SysDir"` SysDir string `toml:"SysDir"`
ChunkLimit uint32 `toml:"ChunkLimit"` ChunkLimit uint32 `toml:"ChunkLimit"`
WriteNextMsgAs string WriteNextMsgAs string
WriteNextMsgAsCompletionAgent string
SkipLLMResp bool SkipLLMResp bool
// embeddings // embeddings
RAGEnabled bool `toml:"RAGEnabled"` RAGEnabled bool `toml:"RAGEnabled"`

18
llm.go
View File

@@ -92,7 +92,11 @@ func (lcp LlamaCPPeer) FormMsg(msg, role string, resume bool) (io.Reader, error)
prompt := strings.Join(messages, "\n") prompt := strings.Join(messages, "\n")
// strings builder? // strings builder?
if !resume { if !resume {
botMsgStart := "\n" + cfg.AssistantRole + ":\n" botPersona := cfg.AssistantRole
if cfg.WriteNextMsgAsCompletionAgent != "" {
botPersona = cfg.WriteNextMsgAsCompletionAgent
}
botMsgStart := "\n" + botPersona + ":\n"
prompt += botMsgStart prompt += botMsgStart
} }
if cfg.ThinkUse && !cfg.ToolUse { 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") prompt := strings.Join(messages, "\n")
// strings builder? // strings builder?
if !resume { if !resume {
botMsgStart := "\n" + cfg.AssistantRole + ":\n" botPersona := cfg.AssistantRole
if cfg.WriteNextMsgAsCompletionAgent != "" {
botPersona = cfg.WriteNextMsgAsCompletionAgent
}
botMsgStart := "\n" + botPersona + ":\n"
prompt += botMsgStart prompt += botMsgStart
} }
if cfg.ThinkUse && !cfg.ToolUse { 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") prompt := strings.Join(messages, "\n")
// strings builder? // strings builder?
if !resume { if !resume {
botMsgStart := "\n" + cfg.AssistantRole + ":\n" botPersona := cfg.AssistantRole
if cfg.WriteNextMsgAsCompletionAgent != "" {
botPersona = cfg.WriteNextMsgAsCompletionAgent
}
botMsgStart := "\n" + botPersona + ":\n"
prompt += botMsgStart prompt += botMsgStart
} }
if cfg.ThinkUse && !cfg.ToolUse { if cfg.ThinkUse && !cfg.ToolUse {

View File

@@ -12,7 +12,8 @@ var (
botRespMode = false botRespMode = false
editMode = false editMode = false
selectedIndex = int(-1) 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)" 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{} focusSwitcher = map[tview.Primitive]tview.Primitive{}
) )

67
tui.go
View File

@@ -98,6 +98,15 @@ func loadImage() {
imgView.SetImage(img) 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() { func colorText() {
text := textView.GetText(false) text := textView.GetText(false)
// Step 1: Extract code blocks and replace them with unique placeholders // Step 1: Extract code blocks and replace them with unique placeholders
@@ -151,8 +160,17 @@ func updateStatusLine() {
if cfg.WriteNextMsgAs != "" { if cfg.WriteNextMsgAs != "" {
persona = cfg.WriteNextMsgAs persona = cfg.WriteNextMsgAs
} }
if strings.Contains(cfg.CurrentAPI, "chat") {
position.SetText(fmt.Sprintf(indexLine, botRespMode, cfg.AssistantRole, activeChatName, cfg.ToolUse, chatBody.Model, position.SetText(fmt.Sprintf(indexLine, botRespMode, cfg.AssistantRole, activeChatName, cfg.ToolUse, chatBody.Model,
cfg.SkipLLMResp, cfg.CurrentAPI, cfg.ThinkUse, logLevel.Level(), isRecording, persona)) 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) { func initSysCards() ([]string, error) {
@@ -354,7 +372,6 @@ func init() {
editArea.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { editArea.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
// if event.Key() == tcell.KeyEscape && editMode { // if event.Key() == tcell.KeyEscape && editMode {
if event.Key() == tcell.KeyEscape { if event.Key() == tcell.KeyEscape {
logger.Warn("edit debug; esc is pressed")
defer colorText() defer colorText()
editedMsg := editArea.GetText() editedMsg := editArea.GetText()
if editedMsg == "" { if editedMsg == "" {
@@ -424,10 +441,7 @@ func init() {
if err := copyToClipboard(m.Content); err != nil { if err := copyToClipboard(m.Content); err != nil {
logger.Error("failed to copy to clipboard", "error", err) logger.Error("failed to copy to clipboard", "error", err)
} }
previewLen := 30 previewLen := min(30, len(m.Content))
if len(m.Content) < 30 {
previewLen = len(m.Content)
}
notification := fmt.Sprintf("msg '%s' was copied to the clipboard", m.Content[:previewLen]) notification := fmt.Sprintf("msg '%s' was copied to the clipboard", m.Content[:previewLen])
if err := notifyUser("copied", notification); err != nil { if err := notifyUser("copied", notification); err != nil {
logger.Error("failed to send notification", "error", err) logger.Error("failed to send notification", "error", err)
@@ -573,10 +587,7 @@ func init() {
if err := copyToClipboard(m.Content); err != nil { if err := copyToClipboard(m.Content); err != nil {
logger.Error("failed to copy to clipboard", "error", err) logger.Error("failed to copy to clipboard", "error", err)
} }
previewLen := 30 previewLen := min(30, len(m.Content))
if len(m.Content) < 30 {
previewLen = len(m.Content)
}
notification := fmt.Sprintf("msg '%s' was copied to the clipboard", m.Content[:previewLen]) notification := fmt.Sprintf("msg '%s' was copied to the clipboard", m.Content[:previewLen])
if err := notifyUser("copied", notification); err != nil { if err := notifyUser("copied", notification); err != nil {
logger.Error("failed to send notification", "error", err) logger.Error("failed to send notification", "error", err)
@@ -798,7 +809,12 @@ func init() {
roles := chatBody.ListRoles() roles := chatBody.ListRoles()
if len(roles) == 0 { if len(roles) == 0 {
logger.Warn("empty roles in chat") 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 { for i, role := range roles {
if strings.EqualFold(role, persona) { if strings.EqualFold(role, persona) {
if i == len(roles)-1 { if i == len(roles)-1 {
@@ -806,6 +822,33 @@ func init() {
break break
} }
cfg.WriteNextMsgAs = roles[i+1] // get next role 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 break
} }
} }
@@ -836,10 +879,10 @@ func init() {
textView.ScrollToEnd() textView.ScrollToEnd()
colorText() 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 return nil
} }
if event.Key() == tcell.KeyPgUp || event.Key() == tcell.KeyPgDn { if event.Key() == tcell.KeyPgUp || event.Key() == tcell.KeyPgDn {