Feat: rename user through props
This commit is contained in:
2
bot.go
2
bot.go
@@ -437,6 +437,7 @@ func removeThinking(chatBody *models.ChatBody) {
|
|||||||
|
|
||||||
func applyCharCard(cc *models.CharCard) {
|
func applyCharCard(cc *models.CharCard) {
|
||||||
cfg.AssistantRole = cc.Role
|
cfg.AssistantRole = cc.Role
|
||||||
|
// FIXME: remove
|
||||||
// Initialize Cluedo if enabled and matching role
|
// Initialize Cluedo if enabled and matching role
|
||||||
if cfg.EnableCluedo && cc.Role == "CluedoPlayer" {
|
if cfg.EnableCluedo && cc.Role == "CluedoPlayer" {
|
||||||
playerOrder = []string{cfg.UserRole, cfg.AssistantRole, cfg.CluedoRole2}
|
playerOrder = []string{cfg.UserRole, cfg.AssistantRole, cfg.CluedoRole2}
|
||||||
@@ -444,6 +445,7 @@ func applyCharCard(cc *models.CharCard) {
|
|||||||
}
|
}
|
||||||
history, err := loadAgentsLastChat(cfg.AssistantRole)
|
history, err := loadAgentsLastChat(cfg.AssistantRole)
|
||||||
if err != nil {
|
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)
|
logger.Warn("failed to load last agent chat;", "agent", cc.Role, "err", err)
|
||||||
history = []models.RoleMsg{
|
history = []models.RoleMsg{
|
||||||
{Role: "system", Content: cc.SysPrompt},
|
{Role: "system", Content: cc.SysPrompt},
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -20,10 +19,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
TTSTextChan = make(chan string, 10000)
|
TTSTextChan = make(chan string, 10000)
|
||||||
TTSFlushChan = make(chan bool, 1)
|
TTSFlushChan = make(chan bool, 1)
|
||||||
TTSDoneChan = make(chan bool, 1)
|
TTSDoneChan = make(chan bool, 1)
|
||||||
endsWithPunctuation = regexp.MustCompile(`[;.!?]$`)
|
// endsWithPunctuation = regexp.MustCompile(`[;.!?]$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Orator interface {
|
type Orator interface {
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -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) | 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{}
|
focusSwitcher = map[tview.Primitive]tview.Primitive{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gf-lt/config"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"gf-lt/config"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -76,6 +76,27 @@ type ChatBody struct {
|
|||||||
Messages []RoleMsg `json:"messages"`
|
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 {
|
type ChatToolsBody struct {
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
Messages []RoleMsg `json:"messages"`
|
Messages []RoleMsg `json:"messages"`
|
||||||
|
|||||||
106
tables.go
106
tables.go
@@ -267,59 +267,59 @@ func makeRAGTable(fileList []string) *tview.Flex {
|
|||||||
return ragflex
|
return ragflex
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeLoadedRAGTable(fileList []string) *tview.Table {
|
// func makeLoadedRAGTable(fileList []string) *tview.Table {
|
||||||
actions := []string{"delete"}
|
// actions := []string{"delete"}
|
||||||
rows, cols := len(fileList), len(actions)+1
|
// rows, cols := len(fileList), len(actions)+1
|
||||||
fileTable := tview.NewTable().
|
// fileTable := tview.NewTable().
|
||||||
SetBorders(true)
|
// SetBorders(true)
|
||||||
for r := 0; r < rows; r++ {
|
// for r := 0; r < rows; r++ {
|
||||||
for c := 0; c < cols; c++ {
|
// for c := 0; c < cols; c++ {
|
||||||
color := tcell.ColorWhite
|
// color := tcell.ColorWhite
|
||||||
if c < 1 {
|
// if c < 1 {
|
||||||
fileTable.SetCell(r, c,
|
// fileTable.SetCell(r, c,
|
||||||
tview.NewTableCell(fileList[r]).
|
// tview.NewTableCell(fileList[r]).
|
||||||
SetTextColor(color).
|
// SetTextColor(color).
|
||||||
SetAlign(tview.AlignCenter))
|
// SetAlign(tview.AlignCenter))
|
||||||
} else {
|
// } else {
|
||||||
fileTable.SetCell(r, c,
|
// fileTable.SetCell(r, c,
|
||||||
tview.NewTableCell(actions[c-1]).
|
// tview.NewTableCell(actions[c-1]).
|
||||||
SetTextColor(color).
|
// SetTextColor(color).
|
||||||
SetAlign(tview.AlignCenter))
|
// SetAlign(tview.AlignCenter))
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
fileTable.Select(0, 0).SetFixed(1, 1).SetDoneFunc(func(key tcell.Key) {
|
// fileTable.Select(0, 0).SetFixed(1, 1).SetDoneFunc(func(key tcell.Key) {
|
||||||
if key == tcell.KeyEsc || key == tcell.KeyF1 {
|
// if key == tcell.KeyEsc || key == tcell.KeyF1 {
|
||||||
pages.RemovePage(RAGPage)
|
// pages.RemovePage(RAGPage)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
if key == tcell.KeyEnter {
|
// if key == tcell.KeyEnter {
|
||||||
fileTable.SetSelectable(true, true)
|
// fileTable.SetSelectable(true, true)
|
||||||
}
|
// }
|
||||||
}).SetSelectedFunc(func(row int, column int) {
|
// }).SetSelectedFunc(func(row int, column int) {
|
||||||
defer pages.RemovePage(RAGPage)
|
// defer pages.RemovePage(RAGPage)
|
||||||
tc := fileTable.GetCell(row, column)
|
// tc := fileTable.GetCell(row, column)
|
||||||
tc.SetTextColor(tcell.ColorRed)
|
// tc.SetTextColor(tcell.ColorRed)
|
||||||
fileTable.SetSelectable(false, false)
|
// fileTable.SetSelectable(false, false)
|
||||||
fpath := fileList[row]
|
// fpath := fileList[row]
|
||||||
// notification := fmt.Sprintf("chat: %s; action: %s", fpath, tc.Text)
|
// // notification := fmt.Sprintf("chat: %s; action: %s", fpath, tc.Text)
|
||||||
switch tc.Text {
|
// switch tc.Text {
|
||||||
case "delete":
|
// case "delete":
|
||||||
if err := ragger.RemoveFile(fpath); err != nil {
|
// if err := ragger.RemoveFile(fpath); err != nil {
|
||||||
logger.Error("failed to delete file", "filename", fpath, "error", err)
|
// logger.Error("failed to delete file", "filename", fpath, "error", err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
if err := notifyUser("chat deleted", fpath+" was deleted"); err != nil {
|
// if err := notifyUser("chat deleted", fpath+" was deleted"); err != nil {
|
||||||
logger.Error("failed to send notification", "error", err)
|
// logger.Error("failed to send notification", "error", err)
|
||||||
}
|
// }
|
||||||
return
|
// return
|
||||||
default:
|
// default:
|
||||||
// pages.RemovePage(RAGPage)
|
// // pages.RemovePage(RAGPage)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
return fileTable
|
// return fileTable
|
||||||
}
|
// }
|
||||||
|
|
||||||
func makeAgentTable(agentList []string) *tview.Table {
|
func makeAgentTable(agentList []string) *tview.Table {
|
||||||
actions := []string{"load"}
|
actions := []string{"load"}
|
||||||
|
|||||||
78
tui.go
78
tui.go
@@ -43,6 +43,7 @@ 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
|
||||||
@@ -55,7 +56,6 @@ 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]: manage loaded rag files (that already in vector db)
|
|
||||||
[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
|
||||||
@@ -65,11 +65,12 @@ var (
|
|||||||
[yellow]Ctrl+c[white]: close programm
|
[yellow]Ctrl+c[white]: close programm
|
||||||
[yellow]Ctrl+p[white]: props edit form (min-p, dry, etc.)
|
[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+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+t[white]: remove thinking (<think>) and tool messages from context (delete from chat)
|
||||||
[yellow]Ctrl+l[white]: update connected model name (llamacpp)
|
[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+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)
|
||||||
|
|
||||||
Press Enter to go back
|
Press Enter to go back
|
||||||
`
|
`
|
||||||
@@ -144,7 +145,7 @@ func updateStatusLine() {
|
|||||||
if asr != nil {
|
if asr != nil {
|
||||||
isRecording = asr.IsRecording()
|
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) {
|
func initSysCards() ([]string, error) {
|
||||||
@@ -166,6 +167,36 @@ func initSysCards() ([]string, error) {
|
|||||||
return labels, nil
|
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() {
|
func startNewChat() {
|
||||||
id, err := store.ChatGetMaxID()
|
id, err := store.ChatGetMaxID()
|
||||||
if err != nil {
|
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,
|
}).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
|
||||||
}).
|
}).AddInputField("username: ", cfg.UserRole, 32, tview.InputFieldMaxLength(32), func(text string) {
|
||||||
|
if text != "" {
|
||||||
|
renameUser(cfg.UserRole, text)
|
||||||
|
cfg.UserRole = text
|
||||||
|
}
|
||||||
|
}).
|
||||||
AddButton("Quit", func() {
|
AddButton("Quit", func() {
|
||||||
pages.RemovePage(propsPage)
|
pages.RemovePage(propsPage)
|
||||||
})
|
})
|
||||||
@@ -545,23 +581,23 @@ func init() {
|
|||||||
// updateStatusLine()
|
// updateStatusLine()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if event.Key() == tcell.KeyF10 {
|
// if event.Key() == tcell.KeyF10 {
|
||||||
// list rag loaded in db
|
// // list rag loaded in db
|
||||||
loadedFiles, err := ragger.ListLoaded()
|
// loadedFiles, err := ragger.ListLoaded()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
logger.Error("failed to list regfiles in db", "error", err)
|
// logger.Error("failed to list regfiles in db", "error", err)
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
if len(loadedFiles) == 0 {
|
// if len(loadedFiles) == 0 {
|
||||||
if err := notifyUser("loaded RAG", "no files in db"); err != nil {
|
// if err := notifyUser("loaded RAG", "no files in db"); err != nil {
|
||||||
logger.Error("failed to send notification", "error", err)
|
// logger.Error("failed to send notification", "error", err)
|
||||||
}
|
// }
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
dbRAGTable := makeLoadedRAGTable(loadedFiles)
|
// dbRAGTable := makeLoadedRAGTable(loadedFiles)
|
||||||
pages.AddPage(RAGPage, dbRAGTable, true, true)
|
// pages.AddPage(RAGPage, dbRAGTable, true, true)
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user