diff options
-rw-r--r-- | README.md | 14 | ||||
-rw-r--r-- | bot.go | 5 | ||||
-rw-r--r-- | main.go | 57 | ||||
-rw-r--r-- | models/models.go | 10 |
4 files changed, 64 insertions, 22 deletions
@@ -1,12 +1,12 @@ ### TODO: -- scrolling chat history; (somewhat works out of box); -- log errors to file; -- give serial id to each msg in chat to track it; -- regen last message; -- delete last message +- scrolling chat history; (somewhat works out of box); + +- log errors to file; + +- give serial id to each msg in chat to track it; (use slice index) + +- show msg id next to the msg; + +- regen last message; + +- delete last message; + - edit message? (including from bot); - use chatml template (but do not show it to the user); -- use mistral template; - ability to copy message; - aility to copy selected text; -- menu with old chats (chat files); +- menu with old chats (chat files); + @@ -162,6 +162,7 @@ func chatRound(userMsg, role string, tv *tview.TextView) { botRespMode = true reader := formMsg(chatBody, userMsg, role) go sendMsgToLLM(reader) + fmt.Fprintf(tv, fmt.Sprintf("(%d) ", len(chatBody.Messages))) fmt.Fprintf(tv, assistantIcon) respText := strings.Builder{} out: @@ -171,6 +172,7 @@ out: // fmt.Printf(chunk) fmt.Fprintf(tv, chunk) respText.WriteString(chunk) + tv.ScrollToEnd() case <-streamDone: break out } @@ -179,7 +181,6 @@ out: chatBody.Messages = append(chatBody.Messages, models.MessagesStory{ Role: assistantRole, Content: respText.String(), }) - // TODO: // bot msg is done; // now check it for func call logChat(chatFileLoaded, chatBody.Messages) @@ -299,7 +300,7 @@ func chatToTextSlice(showSys bool) []string { if !showSys && (msg.Role != assistantRole && msg.Role != userRole) { continue } - resp[i] = msg.ToText() + resp[i] = msg.ToText(i) } return resp } @@ -3,6 +3,7 @@ package main import ( "fmt" "path" + "strconv" "time" "unicode" @@ -11,10 +12,11 @@ import ( ) var ( - normalMode = false - botRespMode = false - botMsg = "no" - indexLine = "Row: [yellow]%d[white], Column: [yellow]%d; normal mode: %v" + normalMode = false + botRespMode = false + botMsg = "no" + selectedIndex = int(-1) + indexLine = "manage chats: F1; regen last: F2; delete msg menu: F3; Row: [yellow]%d[white], Column: [yellow]%d; normal mode: %v" ) func isASCII(s string) bool { @@ -51,7 +53,7 @@ func main() { if fromRow == toRow && fromColumn == toColumn { position.SetText(fmt.Sprintf(indexLine, fromRow, fromColumn, normalMode)) } else { - position.SetText(fmt.Sprintf("[red]From[white] Row: [yellow]%d[white], Column: [yellow]%d[white] - [red]To[white] Row: [yellow]%d[white], To Column: [yellow]%d; normal mode: %v", fromRow, fromColumn, toRow, toColumn, normalMode)) + position.SetText(fmt.Sprintf("manage chats: F1; regen last: F2; delete msg menu: F3; [red]From[white] Row: [yellow]%d[white], Column: [yellow]%d[white] - [red]To[white] Row: [yellow]%d[white], To Column: [yellow]%d; normal mode: %v", fromRow, fromColumn, toRow, toColumn, normalMode)) } } chatOpts := []string{"cancel", "new"} @@ -60,7 +62,7 @@ func main() { panic(err) } chatOpts = append(chatOpts, fList...) - modal := tview.NewModal(). + chatActModal := tview.NewModal(). SetText("Chat actions:"). AddButtons(chatOpts). SetDoneFunc(func(buttonIndex int, buttonLabel string) { @@ -93,9 +95,29 @@ func main() { return } }) + indexPickWindow := tview.NewInputField(). + SetLabel("Enter a msg index: "). + SetFieldWidth(4). + SetAcceptanceFunc(tview.InputFieldInteger). + SetDoneFunc(func(key tcell.Key) { + pages.RemovePage("getIndex") + return + }) + indexPickWindow.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyEnter { + si := indexPickWindow.GetText() + selectedIndex, err = strconv.Atoi(si) + if err != nil { + logger.Error("failed to convert provided index", "error", err, "si", si) + } + } + return event + }) + // textArea.SetMovedFunc(updateStatusLine) updateStatusLine() textView.SetText(chatToText(showSystemMsgs)) + textView.ScrollToEnd() app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if botRespMode { // do nothing while bot typing @@ -107,17 +129,36 @@ func main() { panic(err) } chatOpts = append(chatOpts, fList...) - pages.AddPage("history", modal, true, true) + pages.AddPage("history", chatActModal, true, true) return nil } + if event.Key() == tcell.KeyF2 { + // regen last msg + chatBody.Messages = chatBody.Messages[:len(chatBody.Messages)-1] + textView.SetText(chatToText(showSystemMsgs)) + go chatRound("", userRole, textView) + return nil + } + if event.Key() == tcell.KeyF3 { + // TODO: delete last n messages + // modal window with input field + chatBody.Messages = chatBody.Messages[:len(chatBody.Messages)-1] + textView.SetText(chatToText(showSystemMsgs)) + return nil + } + if event.Key() == tcell.KeyF4 { + // edit msg + pages.AddPage("getIndex", indexPickWindow, true, true) + } if event.Key() == tcell.KeyEscape { fromRow, fromColumn, _, _ := textArea.GetCursor() position.SetText(fmt.Sprintf(indexLine, fromRow, fromColumn, normalMode)) // read all text into buffer msgText := textArea.GetText() if msgText != "" { - fmt.Fprintf(textView, "\n<user>: %s\n", msgText) + fmt.Fprintf(textView, "\n(%d) <user>: %s\n", len(chatBody.Messages), msgText) textArea.SetText("", true) + textView.ScrollToEnd() } // update statue line go chatRound(msgText, userRole, textView) diff --git a/models/models.go b/models/models.go index 30ba548..880779f 100644 --- a/models/models.go +++ b/models/models.go @@ -61,17 +61,17 @@ type MessagesStory struct { Content string `json:"content"` } -func (m MessagesStory) ToText() string { +func (m MessagesStory) ToText(i int) string { icon := "" switch m.Role { case "assistant": - icon = "<🤖>: " + icon = fmt.Sprintf("(%d) <🤖>: ", i) case "user": - icon = "<user>: " + icon = fmt.Sprintf("(%d) <user>: ", i) case "system": - icon = "<system>: " + icon = fmt.Sprintf("(%d) <system>: ", i) case "tool": - icon = "<tool>: " + icon = fmt.Sprintf("(%d) <tool>: ", i) } textMsg := fmt.Sprintf("%s%s\n", icon, m.Content) return strings.ReplaceAll(textMsg, "\n\n", "\n") |