Enha: change textview chat history based on current user persona

This commit is contained in:
Grail Finder
2026-01-17 11:42:35 +03:00
parent 12be603690
commit 8b162ef34f
7 changed files with 41 additions and 31 deletions

15
bot.go
View File

@@ -153,7 +153,8 @@ func filterMessagesForCharacter(messages []models.RoleMsg, character string) []m
return messages return messages
} }
filtered := make([]models.RoleMsg, 0, len(messages)) filtered := make([]models.RoleMsg, 0, len(messages))
for _, msg := range messages { for i, msg := range messages {
logger.Info("filtering messages", "character", character, "index", i, "known_to", msg.KnownTo)
// If KnownTo is nil or empty, message is visible to all // If KnownTo is nil or empty, message is visible to all
if len(msg.KnownTo) == 0 { if len(msg.KnownTo) == 0 {
filtered = append(filtered, msg) filtered = append(filtered, msg)
@@ -1003,9 +1004,9 @@ func findCall(msg, toolCall string, tv *tview.TextView) {
chatRound("", cfg.AssistantRole, tv, false, false) chatRound("", cfg.AssistantRole, tv, false, false)
} }
func chatToTextSlice(showSys bool) []string { func chatToTextSlice(messages []models.RoleMsg, showSys bool) []string {
resp := make([]string, len(chatBody.Messages)) resp := make([]string, len(messages))
for i, msg := range chatBody.Messages { for i, msg := range messages {
// INFO: skips system msg and tool msg // INFO: skips system msg and tool msg
if !showSys && (msg.Role == cfg.ToolRole || msg.Role == "system") { if !showSys && (msg.Role == cfg.ToolRole || msg.Role == "system") {
continue continue
@@ -1015,8 +1016,8 @@ func chatToTextSlice(showSys bool) []string {
return resp return resp
} }
func chatToText(showSys bool) string { func chatToText(messages []models.RoleMsg, showSys bool) string {
s := chatToTextSlice(showSys) s := chatToTextSlice(messages, showSys)
return strings.Join(s, "\n") return strings.Join(s, "\n")
} }
@@ -1140,7 +1141,7 @@ func summarizeAndStartNewChat() {
} }
chatBody.Messages = append(chatBody.Messages, toolMsg) chatBody.Messages = append(chatBody.Messages, toolMsg)
// Update UI // Update UI
textView.SetText(chatToText(cfg.ShowSys)) textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
colorText() colorText()
// Update storage // Update storage
if err := updateStorageChat(activeChatName, chatBody.Messages); err != nil { if err := updateStorageChat(activeChatName, chatBody.Messages); err != nil {

View File

@@ -34,3 +34,6 @@ Again, this is not going to work with openais /v1/chat endpoint since it convert
alternative approach to the tag string would be to have a judge agent to determine after each message what characters should hae access to it. but it means to make an additional call to llm after each msg. alternative approach to the tag string would be to have a judge agent to determine after each message what characters should hae access to it. but it means to make an additional call to llm after each msg.
need to update character card loader to support multiple characters

View File

@@ -109,7 +109,7 @@ func startNewChat() {
} }
// set chat body // set chat body
chatBody.Messages = chatBody.Messages[:2] chatBody.Messages = chatBody.Messages[:2]
textView.SetText(chatToText(cfg.ShowSys)) textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
newChat := &models.Chat{ newChat := &models.Chat{
ID: id + 1, ID: id + 1,
Name: fmt.Sprintf("%d_%s", id+1, cfg.AssistantRole), Name: fmt.Sprintf("%d_%s", id+1, cfg.AssistantRole),

1
llm.go
View File

@@ -180,7 +180,6 @@ func (lcp LCPCompletion) FormMsg(msg, role string, resume bool) (io.Reader, erro
} }
prompt = sb.String() prompt = sb.String()
} }
logger.Debug("checking prompt for /completion", "tool_use", cfg.ToolUse, logger.Debug("checking prompt for /completion", "tool_use", cfg.ToolUse,
"msg", msg, "resume", resume, "prompt", prompt, "multimodal_data_count", len(multimodalData)) "msg", msg, "resume", resume, "prompt", prompt, "multimodal_data_count", len(multimodalData))
payload := models.NewLCPReq(prompt, chatBody.Model, multimodalData, defaultLCPProps, chatBody.MakeStopSlice()) payload := models.NewLCPReq(prompt, chatBody.Model, multimodalData, defaultLCPProps, chatBody.MakeStopSlice())

View File

@@ -119,7 +119,7 @@ func makeChatTable(chatMap map[string]models.Chat) *tview.Table {
return return
} }
chatBody.Messages = history chatBody.Messages = history
textView.SetText(chatToText(cfg.ShowSys)) textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
activeChatName = selectedChat activeChatName = selectedChat
pages.RemovePage(historyPage) pages.RemovePage(historyPage)
return return
@@ -142,7 +142,7 @@ func makeChatTable(chatMap map[string]models.Chat) *tview.Table {
} }
// load last chat // load last chat
chatBody.Messages = loadOldChatOrGetNew() chatBody.Messages = loadOldChatOrGetNew()
textView.SetText(chatToText(cfg.ShowSys)) textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
pages.RemovePage(historyPage) pages.RemovePage(historyPage)
return return
case "update card": case "update card":
@@ -175,7 +175,7 @@ func makeChatTable(chatMap map[string]models.Chat) *tview.Table {
case "move sysprompt onto 1st msg": case "move sysprompt onto 1st msg":
chatBody.Messages[1].Content = chatBody.Messages[0].Content + chatBody.Messages[1].Content chatBody.Messages[1].Content = chatBody.Messages[0].Content + chatBody.Messages[1].Content
chatBody.Messages[0].Content = rpDefenitionSysMsg chatBody.Messages[0].Content = rpDefenitionSysMsg
textView.SetText(chatToText(cfg.ShowSys)) textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
activeChatName = selectedChat activeChatName = selectedChat
pages.RemovePage(historyPage) pages.RemovePage(historyPage)
return return
@@ -546,7 +546,7 @@ func makeAgentTable(agentList []string) *tview.Table {
return return
} }
// replace textview // replace textview
textView.SetText(chatToText(cfg.ShowSys)) textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
colorText() colorText()
updateStatusLine() updateStatusLine()
// sysModal.ClearButtons() // sysModal.ClearButtons()
@@ -715,7 +715,7 @@ func makeImportChatTable(filenames []string) *tview.Table {
colorText() colorText()
updateStatusLine() updateStatusLine()
// redraw the text in text area // redraw the text in text area
textView.SetText(chatToText(cfg.ShowSys)) textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
pages.RemovePage(historyPage) pages.RemovePage(historyPage)
app.SetFocus(textArea) app.SetFocus(textArea)
return return

View File

@@ -24,7 +24,7 @@ var (
starRE = regexp.MustCompile(`(\*.*?\*)`) starRE = regexp.MustCompile(`(\*.*?\*)`)
thinkRE = regexp.MustCompile(`(<think>\s*([\s\S]*?)</think>)`) thinkRE = regexp.MustCompile(`(<think>\s*([\s\S]*?)</think>)`)
codeBlockRE = regexp.MustCompile(`(?s)\x60{3}(?:.*?)\n(.*?)\n\s*\x60{3}\s*`) codeBlockRE = regexp.MustCompile(`(?s)\x60{3}(?:.*?)\n(.*?)\n\s*\x60{3}\s*`)
singleBacktickRE = regexp.MustCompile(`\x60([^\x60]*)\x60`) singleBacktickRE = regexp.MustCompile(`\x60([^\x60]*)\x60`)
roleRE = regexp.MustCompile(`^(\w+):`) roleRE = regexp.MustCompile(`^(\w+):`)
rpDefenitionSysMsg = ` rpDefenitionSysMsg = `
For this roleplay immersion is at most importance. For this roleplay immersion is at most importance.
@@ -945,7 +945,7 @@ func summarizeChat(args map[string]string) []byte {
return []byte("No chat history to summarize.") return []byte("No chat history to summarize.")
} }
// Format chat history for the agent // Format chat history for the agent
chatText := chatToText(true) // include system and tool messages chatText := chatToText(chatBody.Messages, true) // include system and tool messages
return []byte(chatText) return []byte(chatText)
} }

37
tui.go
View File

@@ -310,7 +310,7 @@ func performSearch(term string) {
searchResultLengths = nil searchResultLengths = nil
originalTextForSearch = "" originalTextForSearch = ""
// Re-render text without highlights // Re-render text without highlights
textView.SetText(chatToText(cfg.ShowSys)) textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
colorText() colorText()
return return
} }
@@ -517,8 +517,8 @@ func init() {
searchResults = nil // Clear search results searchResults = nil // Clear search results
searchResultLengths = nil // Clear search result lengths searchResultLengths = nil // Clear search result lengths
originalTextForSearch = "" originalTextForSearch = ""
textView.SetText(chatToText(cfg.ShowSys)) // Reset text without search regions textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys)) // Reset text without search regions
colorText() // Apply normal chat coloring colorText() // Apply normal chat coloring
} else { } else {
// Original logic if no search is active // Original logic if no search is active
currentSelection := textView.GetHighlights() currentSelection := textView.GetHighlights()
@@ -594,7 +594,7 @@ func init() {
} }
chatBody.Messages[selectedIndex].Content = editedMsg chatBody.Messages[selectedIndex].Content = editedMsg
// change textarea // change textarea
textView.SetText(chatToText(cfg.ShowSys)) textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
pages.RemovePage(editMsgPage) pages.RemovePage(editMsgPage)
editMode = false editMode = false
return nil return nil
@@ -627,7 +627,7 @@ func init() {
} }
if selectedIndex >= 0 && selectedIndex < len(chatBody.Messages) { if selectedIndex >= 0 && selectedIndex < len(chatBody.Messages) {
chatBody.Messages[selectedIndex].Role = newRole chatBody.Messages[selectedIndex].Role = newRole
textView.SetText(chatToText(cfg.ShowSys)) textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
colorText() colorText()
pages.RemovePage(roleEditPage) pages.RemovePage(roleEditPage)
} }
@@ -739,7 +739,7 @@ func init() {
searchResults = nil searchResults = nil
searchResultLengths = nil searchResultLengths = nil
originalTextForSearch = "" originalTextForSearch = ""
textView.SetText(chatToText(cfg.ShowSys)) textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
colorText() colorText()
return return
} else { } else {
@@ -787,7 +787,7 @@ func init() {
// //
textArea.SetMovedFunc(updateStatusLine) textArea.SetMovedFunc(updateStatusLine)
updateStatusLine() updateStatusLine()
textView.SetText(chatToText(cfg.ShowSys)) textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
colorText() colorText()
if scrollToEndEnabled { if scrollToEndEnabled {
textView.ScrollToEnd() textView.ScrollToEnd()
@@ -801,7 +801,7 @@ func init() {
if event.Key() == tcell.KeyRune && event.Rune() == '5' && event.Modifiers()&tcell.ModAlt != 0 { if event.Key() == tcell.KeyRune && event.Rune() == '5' && event.Modifiers()&tcell.ModAlt != 0 {
// switch cfg.ShowSys // switch cfg.ShowSys
cfg.ShowSys = !cfg.ShowSys cfg.ShowSys = !cfg.ShowSys
textView.SetText(chatToText(cfg.ShowSys)) textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
colorText() colorText()
} }
if event.Key() == tcell.KeyRune && event.Rune() == '3' && event.Modifiers()&tcell.ModAlt != 0 { if event.Key() == tcell.KeyRune && event.Rune() == '3' && event.Modifiers()&tcell.ModAlt != 0 {
@@ -866,7 +866,7 @@ func init() {
chatBody.Messages = chatBody.Messages[:len(chatBody.Messages)-1] chatBody.Messages = chatBody.Messages[:len(chatBody.Messages)-1]
// there is no case where user msg is regenerated // there is no case where user msg is regenerated
// lastRole := chatBody.Messages[len(chatBody.Messages)-1].Role // lastRole := chatBody.Messages[len(chatBody.Messages)-1].Role
textView.SetText(chatToText(cfg.ShowSys)) textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
go chatRound("", cfg.UserRole, textView, true, false) go chatRound("", cfg.UserRole, textView, true, false)
return nil return nil
} }
@@ -888,7 +888,7 @@ func init() {
return nil return nil
} }
chatBody.Messages = chatBody.Messages[:len(chatBody.Messages)-1] chatBody.Messages = chatBody.Messages[:len(chatBody.Messages)-1]
textView.SetText(chatToText(cfg.ShowSys)) textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
colorText() colorText()
return nil return nil
} }
@@ -1052,7 +1052,7 @@ func init() {
// clear context // clear context
// remove tools and thinking // remove tools and thinking
removeThinking(chatBody) removeThinking(chatBody)
textView.SetText(chatToText(cfg.ShowSys)) textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
colorText() colorText()
return nil return nil
} }
@@ -1184,20 +1184,26 @@ func init() {
if strings.EqualFold(role, persona) { if strings.EqualFold(role, persona) {
if i == len(roles)-1 { if i == len(roles)-1 {
cfg.WriteNextMsgAs = roles[0] // reached last, get first cfg.WriteNextMsgAs = roles[0] // reached last, get first
persona = cfg.WriteNextMsgAs
break break
} }
cfg.WriteNextMsgAs = roles[i+1] // get next role cfg.WriteNextMsgAs = roles[i+1] // get next role
persona = cfg.WriteNextMsgAs
logger.Info("picked role", "roles", roles, "index", i+1) logger.Info("picked role", "roles", roles, "index", i+1)
break break
} }
} }
// role got switch, update textview with character specific context for user
filtered := filterMessagesForCharacter(chatBody.Messages, persona)
textView.SetText(chatToText(filtered, cfg.ShowSys))
updateStatusLine() updateStatusLine()
colorText()
return nil return nil
} }
if event.Key() == tcell.KeyCtrlX { if event.Key() == tcell.KeyCtrlX {
persona := cfg.AssistantRole botPersona := cfg.AssistantRole
if cfg.WriteNextMsgAsCompletionAgent != "" { if cfg.WriteNextMsgAsCompletionAgent != "" {
persona = cfg.WriteNextMsgAsCompletionAgent botPersona = cfg.WriteNextMsgAsCompletionAgent
} }
roles := chatBody.ListRoles() roles := chatBody.ListRoles()
if len(roles) == 0 { if len(roles) == 0 {
@@ -1207,12 +1213,14 @@ func init() {
roles = append(roles, cfg.AssistantRole) roles = append(roles, cfg.AssistantRole)
} }
for i, role := range roles { for i, role := range roles {
if strings.EqualFold(role, persona) { if strings.EqualFold(role, botPersona) {
if i == len(roles)-1 { if i == len(roles)-1 {
cfg.WriteNextMsgAsCompletionAgent = roles[0] // reached last, get first cfg.WriteNextMsgAsCompletionAgent = roles[0] // reached last, get first
botPersona = cfg.WriteNextMsgAsCompletionAgent
break break
} }
cfg.WriteNextMsgAsCompletionAgent = roles[i+1] // get next role cfg.WriteNextMsgAsCompletionAgent = roles[i+1] // get next role
botPersona = cfg.WriteNextMsgAsCompletionAgent
logger.Info("picked role", "roles", roles, "index", i+1) logger.Info("picked role", "roles", roles, "index", i+1)
break break
} }
@@ -1295,7 +1303,6 @@ func init() {
// 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 {
msgText := textArea.GetText() msgText := textArea.GetText()
// TODO: add shellmode command -> output to the chat history, or at least have an option
if shellMode && msgText != "" { if shellMode && msgText != "" {
// In shell mode, execute command instead of sending to LLM // In shell mode, execute command instead of sending to LLM
executeCommandAndDisplay(msgText) executeCommandAndDisplay(msgText)