Feat: user can send messages under different personas

This commit is contained in:
Grail Finder
2025-07-27 11:35:13 +03:00
parent cd606a774e
commit a4b7503e79
3 changed files with 65 additions and 16 deletions

View File

@@ -24,6 +24,8 @@ type Config struct {
AssistantRole string `toml:"AssistantRole"` AssistantRole string `toml:"AssistantRole"`
SysDir string `toml:"SysDir"` SysDir string `toml:"SysDir"`
ChunkLimit uint32 `toml:"ChunkLimit"` ChunkLimit uint32 `toml:"ChunkLimit"`
WriteNextMsgAs string
SkipLLMResp bool
// embeddings // embeddings
RAGEnabled bool `toml:"RAGEnabled"` RAGEnabled bool `toml:"RAGEnabled"`
EmbedURL string `toml:"EmbedURL"` EmbedURL string `toml:"EmbedURL"`

View File

@@ -12,7 +12,7 @@ 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)\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) | 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{} focusSwitcher = map[tview.Primitive]tview.Primitive{}
) )

57
tui.go
View File

@@ -43,7 +43,6 @@ var (
codeBlockPage = "codeBlockPage" codeBlockPage = "codeBlockPage"
imgPage = "imgPage" imgPage = "imgPage"
// help text // help text
// [yellow]F10[white]: manage loaded rag files (that already in vector db)
helpText = ` helpText = `
[yellow]Esc[white]: send msg [yellow]Esc[white]: send msg
[yellow]PgUp/Down[white]: switch focus between input and chat widgets [yellow]PgUp/Down[white]: switch focus between input and chat widgets
@@ -56,6 +55,7 @@ var (
[yellow]F7[white]: copy last msg to clipboard (linux xclip) [yellow]F7[white]: copy last msg to clipboard (linux xclip)
[yellow]F8[white]: copy n 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]F9[white]: table to copy from; with all code blocks
[yellow]F10[white]: switch if LLM will respond on this message (for user to write multiple messages in a row)
[yellow]F11[white]: import chat file [yellow]F11[white]: import chat file
[yellow]F12[white]: show this help page [yellow]F12[white]: show this help page
[yellow]Ctrl+w[white]: resume generation on the last msg [yellow]Ctrl+w[white]: resume generation on the last msg
@@ -71,6 +71,7 @@ var (
[yellow]Ctrl+k[white]: switch tool use (recommend tool use to llm after user msg) [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+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) [yellow]Ctrl+a[white]: interrupt tts (needs tts server)
[yellow]Ctrl+q[white]: cycle through mentioned chars in chat, to pick persona to send next msg as
Press Enter to go back Press Enter to go back
` `
@@ -145,7 +146,12 @@ func updateStatusLine() {
if asr != nil { if asr != nil {
isRecording = asr.IsRecording() isRecording = asr.IsRecording()
} }
position.SetText(fmt.Sprintf(indexLine, botRespMode, cfg.AssistantRole, activeChatName, cfg.ToolUse, chatBody.Model, cfg.CurrentAPI, cfg.ThinkUse, logLevel.Level(), isRecording)) persona := cfg.UserRole
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))
} }
func initSysCards() ([]string, error) { func initSysCards() ([]string, error) {
@@ -249,6 +255,14 @@ func makePropsForm(props map[string]float32) *tview.Form {
}).AddDropDown("Select a model: ", []string{chatBody.Model, "deepseek-chat", "deepseek-reasoner"}, 0, }).AddDropDown("Select a model: ", []string{chatBody.Model, "deepseek-chat", "deepseek-reasoner"}, 0,
func(option string, optionIndex int) { func(option string, optionIndex int) {
chatBody.Model = option chatBody.Model = option
}).AddDropDown("Write next message as: ", chatBody.ListRoles(), 0,
func(option string, optionIndex int) {
cfg.WriteNextMsgAs = option
}).AddInputField("new char to write msg as: ", "", 32, tview.InputFieldMaxLength(32),
func(text string) {
if text != "" {
cfg.WriteNextMsgAs = text
}
}).AddInputField("username: ", cfg.UserRole, 32, tview.InputFieldMaxLength(32), func(text string) { }).AddInputField("username: ", cfg.UserRole, 32, tview.InputFieldMaxLength(32), func(text string) {
if text != "" { if text != "" {
renameUser(cfg.UserRole, text) renameUser(cfg.UserRole, text)
@@ -598,6 +612,10 @@ func init() {
// pages.AddPage(RAGPage, dbRAGTable, true, true) // pages.AddPage(RAGPage, dbRAGTable, true, true)
// return nil // return nil
// } // }
if event.Key() == tcell.KeyF10 {
cfg.SkipLLMResp = !cfg.SkipLLMResp
updateStatusLine()
}
if event.Key() == tcell.KeyF11 { if event.Key() == tcell.KeyF11 {
// read files in chat_exports // read files in chat_exports
dirname := "chat_exports" dirname := "chat_exports"
@@ -751,7 +769,7 @@ func init() {
} }
// I need keybind for tts to shut up // I need keybind for tts to shut up
if event.Key() == tcell.KeyCtrlA { if event.Key() == tcell.KeyCtrlA {
textArea.SetText("pressed ctrl+A", true) // textArea.SetText("pressed ctrl+A", true)
if cfg.TTS_ENABLED { if cfg.TTS_ENABLED {
// audioStream.TextChan <- chunk // audioStream.TextChan <- chunk
extra.TTSDoneChan <- true extra.TTSDoneChan <- true
@@ -764,27 +782,56 @@ func init() {
go chatRound("", lastRole, textView, false, true) go chatRound("", lastRole, textView, false, true)
return nil return nil
} }
if event.Key() == tcell.KeyCtrlQ {
persona := cfg.UserRole
if cfg.WriteNextMsgAs != "" {
persona = cfg.WriteNextMsgAs
}
roles := chatBody.ListRoles()
if len(roles) == 0 {
logger.Warn("empty roles in chat")
}
for i, role := range roles {
if strings.EqualFold(role, persona) {
if i == len(roles)-1 {
cfg.WriteNextMsgAs = roles[0] // reached last, get first
break
}
cfg.WriteNextMsgAs = roles[i+1] // get next role
break
}
}
updateStatusLine()
return nil
}
// cannot send msg in editMode or botRespMode // cannot send msg in editMode or botRespMode
if event.Key() == tcell.KeyEscape && !editMode && !botRespMode { if event.Key() == tcell.KeyEscape && !editMode && !botRespMode {
// read all text into buffer // read all text into buffer
msgText := textArea.GetText() msgText := textArea.GetText()
nl := "\n" nl := "\n"
prevText := textView.GetText(true) prevText := textView.GetText(true)
persona := cfg.UserRole
// strings.LastIndex() // strings.LastIndex()
// newline is not needed is prev msg ends with one // newline is not needed is prev msg ends with one
if strings.HasSuffix(prevText, nl) { if strings.HasSuffix(prevText, nl) {
nl = "" nl = ""
} }
if msgText != "" { if msgText != "" {
// as what char user sends msg?
if cfg.WriteNextMsgAs != "" {
persona = cfg.WriteNextMsgAs
}
// add user icon before user msg // add user icon before user msg
fmt.Fprintf(textView, "%s[-:-:b](%d) <%s>: [-:-:-]\n%s\n", fmt.Fprintf(textView, "%s[-:-:b](%d) <%s>: [-:-:-]\n%s\n",
nl, len(chatBody.Messages), cfg.UserRole, msgText) nl, len(chatBody.Messages), persona, msgText)
textArea.SetText("", true) textArea.SetText("", true)
textView.ScrollToEnd() textView.ScrollToEnd()
colorText() colorText()
} }
if !cfg.SkipLLMResp {
// update statue line // update statue line
go chatRound(msgText, cfg.UserRole, textView, false, false) go chatRound(msgText, persona, textView, false, false)
}
return nil return nil
} }
if event.Key() == tcell.KeyPgUp || event.Key() == tcell.KeyPgDn { if event.Key() == tcell.KeyPgUp || event.Key() == tcell.KeyPgDn {