Feat: toggle system; edit msg; switch focus

This commit is contained in:
Grail Finder
2024-11-17 18:55:59 +03:00
parent b2c8698926
commit d3cc8774b1
3 changed files with 78 additions and 31 deletions

View File

@@ -5,8 +5,15 @@
- show msg id next to the msg; + - show msg id next to the msg; +
- regen last message; + - regen last message; +
- delete last message; + - delete last message; +
- edit message? (including from bot); - edit message? (including from bot); +
- use chatml template (but do not show it to the user);
- ability to copy message; - ability to copy message;
- aility to copy selected text; - aility to copy selected text; (I can do it though vim mode of the terminal, so +)
- menu with old chats (chat files); + - menu with old chats (chat files); +
- fullscreen textarea option (for long prompt);
- tab to switch selection between textview and textarea (input and chat); +
- basic tools: memorize and recall;
- stop stream from the bot;
### FIX:
- bot responding (or haninging) blocks everything; +
- programm requires history folder, but it is .gitignore;

39
bot.go
View File

@@ -310,28 +310,27 @@ func chatToText(showSys bool) string {
return strings.Join(s, "") return strings.Join(s, "")
} }
func textToChat(chat []string) []models.MessagesStory { func textToMsg(rawMsg string) models.MessagesStory {
msg := models.MessagesStory{}
// system and tool?
if strings.HasPrefix(rawMsg, assistantIcon) {
msg.Role = assistantRole
msg.Content = strings.TrimPrefix(rawMsg, assistantIcon)
return msg
}
if strings.HasPrefix(rawMsg, userIcon) {
msg.Role = userRole
msg.Content = strings.TrimPrefix(rawMsg, userIcon)
return msg
}
return msg
}
func textSliceToChat(chat []string) []models.MessagesStory {
resp := make([]models.MessagesStory, len(chat)) resp := make([]models.MessagesStory, len(chat))
for i, rawMsg := range chat { for i, rawMsg := range chat {
// trim icon msg := textToMsg(rawMsg)
var ( resp[i] = msg
role string
msg string
)
// system and tool?
if strings.HasPrefix(rawMsg, assistantIcon) {
role = assistantRole
msg = strings.TrimPrefix(rawMsg, assistantIcon)
goto messagebuild
}
if strings.HasPrefix(rawMsg, userIcon) {
role = assistantRole
msg = strings.TrimPrefix(rawMsg, userIcon)
goto messagebuild
}
messagebuild:
resp[i].Role = role
resp[i].Content = msg
} }
return resp return resp
} }

57
main.go
View File

@@ -14,9 +14,11 @@ import (
var ( var (
normalMode = false normalMode = false
botRespMode = false botRespMode = false
editMode = false
botMsg = "no" botMsg = "no"
selectedIndex = int(-1) selectedIndex = int(-1)
indexLine = "manage chats: F1; regen last: F2; delete msg menu: F3; Row: [yellow]%d[white], Column: [yellow]%d; normal mode: %v" indexLine = "Esc: send msg; Tab: switch focus; F1: manage chats; F2: regen last; F3:delete msg menu; F4: edit msg; F5: toggle system; Row: [yellow]%d[white], Column: [yellow]%d; normal mode: %v"
focusSwitcher = map[tview.Primitive]tview.Primitive{}
) )
func isASCII(s string) bool { func isASCII(s string) bool {
@@ -41,6 +43,8 @@ func main() {
app.Draw() app.Draw()
}) })
textView.SetBorder(true).SetTitle("chat") textView.SetBorder(true).SetTitle("chat")
focusSwitcher[textArea] = textView
focusSwitcher[textView] = textArea
position := tview.NewTextView(). position := tview.NewTextView().
SetDynamicColors(true). SetDynamicColors(true).
SetTextAlign(tview.AlignCenter) SetTextAlign(tview.AlignCenter)
@@ -53,7 +57,7 @@ func main() {
if fromRow == toRow && fromColumn == toColumn { if fromRow == toRow && fromColumn == toColumn {
position.SetText(fmt.Sprintf(indexLine, fromRow, fromColumn, normalMode)) position.SetText(fmt.Sprintf(indexLine, fromRow, fromColumn, normalMode))
} else { } else {
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)) position.SetText(fmt.Sprintf("Esc: send msg; Tab: switch focus; F1: manage chats; F2: regen last; F3:delete msg menu; F4: edit msg; F5: toggle system; 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"} chatOpts := []string{"cancel", "new"}
@@ -95,6 +99,24 @@ func main() {
return return
} }
}) })
editArea := tview.NewTextArea().
SetPlaceholder("Replace msg...")
editArea.SetBorder(true).SetTitle("input")
editArea.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == tcell.KeyEscape && editMode {
editedMsg := editArea.GetText()
// TODO: trim msg number and icon
chatBody.Messages[selectedIndex].Content = editedMsg
// change textarea
textView.SetText(chatToText(showSystemMsgs))
pages.RemovePage("editArea")
editMode = false
// panic("do we get here?")
// pages.ShowPage("main")
return nil
}
return event
})
indexPickWindow := tview.NewInputField(). indexPickWindow := tview.NewInputField().
SetLabel("Enter a msg index: "). SetLabel("Enter a msg index: ").
SetFieldWidth(4). SetFieldWidth(4).
@@ -110,6 +132,16 @@ func main() {
if err != nil { if err != nil {
logger.Error("failed to convert provided index", "error", err, "si", si) logger.Error("failed to convert provided index", "error", err, "si", si)
} }
if len(chatBody.Messages) <= selectedIndex && selectedIndex < 0 {
logger.Warn("chosen index is out of bounds", "index", selectedIndex)
return nil
}
pages.AddPage("editArea", editArea, true, true)
m := chatBody.Messages[selectedIndex]
// editArea.SetText(m.ToText(selectedIndex), true)
editArea.SetText(m.Content, true)
editMode = true
// editArea.SetText(si, true)
} }
return event return event
}) })
@@ -119,10 +151,6 @@ func main() {
textView.SetText(chatToText(showSystemMsgs)) textView.SetText(chatToText(showSystemMsgs))
textView.ScrollToEnd() textView.ScrollToEnd()
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if botRespMode {
// do nothing while bot typing
return nil
}
if event.Key() == tcell.KeyF1 { if event.Key() == tcell.KeyF1 {
fList, err := listHistoryFiles(historyDir) fList, err := listHistoryFiles(historyDir)
if err != nil { if err != nil {
@@ -144,13 +172,22 @@ func main() {
// modal window with input field // modal window with input field
chatBody.Messages = chatBody.Messages[:len(chatBody.Messages)-1] chatBody.Messages = chatBody.Messages[:len(chatBody.Messages)-1]
textView.SetText(chatToText(showSystemMsgs)) textView.SetText(chatToText(showSystemMsgs))
botRespMode = false // hmmm; is that correct?
return nil return nil
} }
if event.Key() == tcell.KeyF4 { if event.Key() == tcell.KeyF4 {
// edit msg // edit msg
pages.AddPage("getIndex", indexPickWindow, true, true) pages.AddPage("getIndex", indexPickWindow, true, true)
editMode = true
return nil
} }
if event.Key() == tcell.KeyEscape { if event.Key() == tcell.KeyF5 {
// switch showSystemMsgs
showSystemMsgs = !showSystemMsgs
textView.SetText(chatToText(showSystemMsgs))
}
// cannot send msg in editMode or botRespMode
if event.Key() == tcell.KeyEscape && !editMode && !botRespMode {
fromRow, fromColumn, _, _ := textArea.GetCursor() fromRow, fromColumn, _, _ := textArea.GetCursor()
position.SetText(fmt.Sprintf(indexLine, fromRow, fromColumn, normalMode)) position.SetText(fmt.Sprintf(indexLine, fromRow, fromColumn, normalMode))
// read all text into buffer // read all text into buffer
@@ -164,7 +201,11 @@ func main() {
go chatRound(msgText, userRole, textView) go chatRound(msgText, userRole, textView)
return nil return nil
} }
if isASCII(string(event.Rune())) { if event.Key() == tcell.KeyTab {
currentF := app.GetFocus()
app.SetFocus(focusSwitcher[currentF])
}
if isASCII(string(event.Rune())) && !botRespMode {
// normalMode = false // normalMode = false
// fromRow, fromColumn, _, _ := textArea.GetCursor() // fromRow, fromColumn, _, _ := textArea.GetCursor()
// position.SetText(fmt.Sprintf(indexLine, fromRow, fromColumn, normalMode)) // position.SetText(fmt.Sprintf(indexLine, fromRow, fromColumn, normalMode))