summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md8
-rw-r--r--bot.go11
-rw-r--r--main.go223
-rw-r--r--session.go1
-rw-r--r--storage/memory.go4
-rw-r--r--storage/storage.go8
-rw-r--r--tools.go29
-rw-r--r--tui.go311
8 files changed, 344 insertions, 251 deletions
diff --git a/README.md b/README.md
index 94f2ca6..cfbe3a5 100644
--- a/README.md
+++ b/README.md
@@ -13,12 +13,14 @@
- stop stream from the bot; +
- sqlitedb instead of chatfiles; +
- define tools and sys prompt for them to be used; +
+- add system prompt without tools (for mistral); +
+- option to switch between predefined sys prompts; +
- sqlite for the bot memory;
- fullscreen textarea option (bothersome to implement);
-- add system prompt without tools (for mistral);
-- option to switch between predefined sys prompts;
- consider adding use /completion of llamacpp, since openai endpoint clearly has template|format issues;
- change temp, min-p and other params from tui;
+- help page with all key bindings;
+- rename current chat;
### FIX:
- bot responding (or haninging) blocks everything; +
@@ -29,4 +31,4 @@
- empty input to continue bot msg gens new msg index and bot icon;
- sometimes bots put additional info around the tool call, have a regexp to match tool call; +
- remove all panics from code; +
-- new chat is not saved in db;
+- new chat replaces old ones in db;
diff --git a/bot.go b/bot.go
index b71ed90..d4c86f0 100644
--- a/bot.go
+++ b/bot.go
@@ -23,13 +23,13 @@ var httpClient = http.Client{
var (
logger *slog.Logger
- APIURL = "http://localhost:8080/v1/chat/completions"
userRole = "user"
assistantRole = "assistant"
toolRole = "tool"
assistantIcon = "<🤖>: "
userIcon = "<user>: "
// TODO: pass as an cli arg or have config
+ APIURL = "http://localhost:8080/v1/chat/completions"
logFileName = "log.txt"
showSystemMsgs bool
chunkLimit = 1000
@@ -43,8 +43,8 @@ var (
{Role: "system", Content: systemMsg},
{Role: assistantRole, Content: defaultFirstMsg},
}
- defaultStarterBytes, _ = json.Marshal(chatBody.Messages)
- interruptResp = false
+ defaultStarterBytes = []byte{}
+ interruptResp = false
)
// ====
@@ -234,6 +234,11 @@ func init() {
logger.Error("failed to open log file", "error", err, "filename", logFileName)
return
}
+ defaultStarterBytes, err = json.Marshal(defaultStarter)
+ if err != nil {
+ logger.Error("failed to marshal defaultStarter", "error", err)
+ return
+ }
logger = slog.New(slog.NewTextHandler(file, nil))
store = storage.NewProviderSQL("test.db", logger)
// https://github.com/coreydaley/ggerganov-llama.cpp/blob/master/examples/server/README.md
diff --git a/main.go b/main.go
index c7528f5..46cb42c 100644
--- a/main.go
+++ b/main.go
@@ -1,13 +1,8 @@
package main
import (
- "elefant/models"
- "fmt"
- "strconv"
- "time"
"unicode"
- "github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
@@ -16,7 +11,7 @@ var (
editMode = false
botMsg = "no"
selectedIndex = int(-1)
- indexLine = "Esc: send msg; PgUp/Down: switch focus; F1: manage chats; F2: regen last; F3:delete last msg; F4: edit msg; F5: toggle system; F6: interrupt bot resp; Row: [yellow]%d[white], Column: [yellow]%d; bot resp mode: %v"
+ indexLine = "Esc: send msg; PgUp/Down: switch focus; F1: manage chats; F2: regen last; F3:delete last msg; F4: edit msg; F5: toggle system; F6: interrupt bot resp; bot resp mode: %v; current chat: %s"
focusSwitcher = map[tview.Primitive]tview.Primitive{}
)
@@ -30,222 +25,6 @@ func isASCII(s string) bool {
}
func main() {
- app := tview.NewApplication()
- pages := tview.NewPages()
- textArea := tview.NewTextArea().
- SetPlaceholder("Type your prompt...")
- textArea.SetBorder(true).SetTitle("input")
- textView := tview.NewTextView().
- SetDynamicColors(true).
- SetRegions(true).
- SetChangedFunc(func() {
- app.Draw()
- })
- textView.SetBorder(true).SetTitle("chat")
- focusSwitcher[textArea] = textView
- focusSwitcher[textView] = textArea
- position := tview.NewTextView().
- SetDynamicColors(true).
- SetTextAlign(tview.AlignCenter)
- flex := tview.NewFlex().SetDirection(tview.FlexRow).
- AddItem(textView, 0, 40, false).
- AddItem(textArea, 0, 10, true).
- AddItem(position, 0, 1, false)
- updateStatusLine := func() {
- fromRow, fromColumn, toRow, toColumn := textArea.GetCursor()
- if fromRow == toRow && fromColumn == toColumn {
- position.SetText(fmt.Sprintf(indexLine, fromRow, fromColumn, botRespMode))
- } else {
- position.SetText(fmt.Sprintf("Esc: send msg; PgUp/Down: switch focus; F1: manage chats; F2: regen last; F3:delete last msg; F4: edit msg; F5: toggle system; F6: interrupt bot resp; Row: [yellow]%d[white], Column: [yellow]%d[white] - [red]To[white] Row: [yellow]%d[white], To Column: [yellow]%d; bot resp mode: %v", fromRow, fromColumn, toRow, toColumn, botRespMode))
- }
- }
- chatOpts := []string{"cancel", "new"}
- fList, err := loadHistoryChats()
- if err != nil {
- logger.Error("failed to load chat history", "error", err)
- }
- chatOpts = append(chatOpts, fList...)
- chatActModal := tview.NewModal().
- SetText("Chat actions:").
- AddButtons(chatOpts).
- SetDoneFunc(func(buttonIndex int, buttonLabel string) {
- switch buttonLabel {
- case "new":
- // set chat body
- chatBody.Messages = defaultStarter
- textView.SetText(chatToText(showSystemMsgs))
- newChat := &models.Chat{
- Name: fmt.Sprintf("%v_%v", "new", time.Now().Unix()),
- Msgs: string(defaultStarterBytes),
- }
- // activeChatName = path.Join(historyDir, fmt.Sprintf("%d_chat.json", time.Now().Unix()))
- activeChatName = newChat.Name
- chatMap[newChat.Name] = newChat
- pages.RemovePage("history")
- return
- // set text
- case "cancel":
- pages.RemovePage("history")
- return
- default:
- fn := buttonLabel
- history, err := loadHistoryChat(fn)
- if err != nil {
- logger.Error("failed to read history file", "filename", fn)
- pages.RemovePage("history")
- return
- }
- chatBody.Messages = history
- textView.SetText(chatToText(showSystemMsgs))
- activeChatName = fn
- pages.RemovePage("history")
- 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()
- if editedMsg == "" {
- notifyUser("edit", "no edit provided")
- pages.RemovePage("editArea")
- editMode = false
- return nil
- }
- chatBody.Messages[selectedIndex].Content = editedMsg
- // change textarea
- textView.SetText(chatToText(showSystemMsgs))
- pages.RemovePage("editArea")
- editMode = false
- return nil
- }
- return event
- })
- 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 {
- si := indexPickWindow.GetText()
- selectedIndex, err = strconv.Atoi(si)
- if err != nil {
- 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
- }
- m := chatBody.Messages[selectedIndex]
- if editMode && event.Key() == tcell.KeyEnter {
- pages.AddPage("editArea", editArea, true, true)
- editArea.SetText(m.Content, true)
- }
- if !editMode && event.Key() == tcell.KeyEnter {
- copyToClipboard(m.Content)
- notification := fmt.Sprintf("msg '%s' was copied to the clipboard", m.Content[:30])
- notifyUser("copied", notification)
- }
- return event
- })
- //
- textArea.SetMovedFunc(updateStatusLine)
- updateStatusLine()
- textView.SetText(chatToText(showSystemMsgs))
- textView.ScrollToEnd()
- app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
- if event.Key() == tcell.KeyF1 {
- fList, err := loadHistoryChats()
- if err != nil {
- logger.Error("failed to load chat history", "error", err)
- return nil
- }
- chatOpts = append(chatOpts, fList...)
- 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 {
- // delete last msg
- chatBody.Messages = chatBody.Messages[:len(chatBody.Messages)-1]
- textView.SetText(chatToText(showSystemMsgs))
- botRespMode = false // hmmm; is that correct?
- return nil
- }
- if event.Key() == tcell.KeyF4 {
- // edit msg
- editMode = true
- pages.AddPage("getIndex", indexPickWindow, true, true)
- return nil
- }
- if event.Key() == tcell.KeyF5 {
- // switch showSystemMsgs
- showSystemMsgs = !showSystemMsgs
- textView.SetText(chatToText(showSystemMsgs))
- }
- if event.Key() == tcell.KeyF6 {
- interruptResp = true
- botRespMode = false
- return nil
- }
- if event.Key() == tcell.KeyF7 {
- // copy msg to clipboard
- editMode = false
- m := chatBody.Messages[len(chatBody.Messages)-1]
- copyToClipboard(m.Content)
- notification := fmt.Sprintf("msg '%s' was copied to the clipboard", m.Content[:30])
- notifyUser("copied", notification)
- return nil
- }
- if event.Key() == tcell.KeyF8 {
- // copy msg to clipboard
- editMode = false
- pages.AddPage("getIndex", indexPickWindow, true, true)
- return nil
- }
- if event.Key() == tcell.KeyCtrlE {
- textArea.SetText("pressed ctrl+e", true)
- return nil
- }
- // cannot send msg in editMode or botRespMode
- if event.Key() == tcell.KeyEscape && !editMode && !botRespMode {
- fromRow, fromColumn, _, _ := textArea.GetCursor()
- position.SetText(fmt.Sprintf(indexLine, fromRow, fromColumn, botRespMode))
- // read all text into buffer
- msgText := textArea.GetText()
- if 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)
- return nil
- }
- if event.Key() == tcell.KeyPgUp || event.Key() == tcell.KeyPgDn {
- currentF := app.GetFocus()
- app.SetFocus(focusSwitcher[currentF])
- return nil
- }
- if isASCII(string(event.Rune())) && !botRespMode {
- // botRespMode = false
- // fromRow, fromColumn, _, _ := textArea.GetCursor()
- // position.SetText(fmt.Sprintf(indexLine, fromRow, fromColumn, botRespMode))
- return event
- }
- return event
- })
pages.AddPage("main", flex, true, true)
if err := app.SetRoot(pages,
true).EnableMouse(true).Run(); err != nil {
diff --git a/session.go b/session.go
index 23d725d..7c035fa 100644
--- a/session.go
+++ b/session.go
@@ -37,6 +37,7 @@ func updateStorageChat(name string, msgs []models.MessagesStory) error {
return err
}
chat.UpdatedAt = time.Now()
+ // if new chat will create id
_, err = store.UpsertChat(chat)
return err
}
diff --git a/storage/memory.go b/storage/memory.go
index a7bf8cc..088ce1c 100644
--- a/storage/memory.go
+++ b/storage/memory.go
@@ -12,12 +12,14 @@ func (p ProviderSQL) Memorise(m *models.Memory) (*models.Memory, error) {
query := "INSERT INTO memories (agent, topic, mind) VALUES (:agent, :topic, :mind) RETURNING *;"
stmt, err := p.db.PrepareNamed(query)
if err != nil {
+ p.logger.Error("failed to prepare stmt", "query", query, "error", err)
return nil, err
}
defer stmt.Close()
var memory models.Memory
err = stmt.Get(&memory, m)
if err != nil {
+ p.logger.Error("failed to insert memory", "query", query, "error", err)
return nil, err
}
return &memory, nil
@@ -28,6 +30,7 @@ func (p ProviderSQL) Recall(agent, topic string) (string, error) {
var mind string
err := p.db.Get(&mind, query, agent, topic)
if err != nil {
+ p.logger.Error("failed to get memory", "query", query, "error", err)
return "", err
}
return mind, nil
@@ -38,6 +41,7 @@ func (p ProviderSQL) RecallTopics(agent string) ([]string, error) {
var topics []string
err := p.db.Select(&topics, query, agent)
if err != nil {
+ p.logger.Error("failed to get topics", "query", query, "error", err)
return nil, err
}
return topics, nil
diff --git a/storage/storage.go b/storage/storage.go
index 08cd81a..c863799 100644
--- a/storage/storage.go
+++ b/storage/storage.go
@@ -19,6 +19,7 @@ type ChatHistory interface {
GetLastChat() (*models.Chat, error)
UpsertChat(chat *models.Chat) (*models.Chat, error)
RemoveChat(id uint32) error
+ ChatGetMaxID() (uint32, error)
}
type ProviderSQL struct {
@@ -66,6 +67,13 @@ func (p ProviderSQL) RemoveChat(id uint32) error {
return err
}
+func (p ProviderSQL) ChatGetMaxID() (uint32, error) {
+ query := "SELECT MAX(id) FROM chats;"
+ var id uint32
+ err := p.db.Get(&id, query)
+ return id, err
+}
+
func NewProviderSQL(dbPath string, logger *slog.Logger) FullRepo {
db, err := sqlx.Open("sqlite", dbPath)
if err != nil {
diff --git a/tools.go b/tools.go
index b42cb62..b752790 100644
--- a/tools.go
+++ b/tools.go
@@ -9,29 +9,9 @@ import (
var (
// TODO: form that message based on existing funcs
- // systemMsg = `You're a helpful assistant.
- // # Tools
- // You can do functions call if needed.
- // Your current tools:
- // <tools>
- // {
- // "name":"get_id",
- // "args": "username"
- // }
- // </tools>
- // To make a function call return a json object within __tool_call__ tags;
- // Example:
- // __tool_call__
- // {
- // "name":"get_id",
- // "args": "Adam"
- // }
- // __tool_call__
- // When making function call avoid typing anything else. 'tool' user will respond with the results of the call.
- // After that you are free to respond to the user.
- // `
- toolCallRE = regexp.MustCompile(`__tool_call__\s*([\s\S]*?)__tool_call__`)
- systemMsg = `You're a helpful assistant.
+ basicSysMsg = `Large Language Model that helps user with any of his requests.`
+ toolCallRE = regexp.MustCompile(`__tool_call__\s*([\s\S]*?)__tool_call__`)
+ toolSysMsg = `You're a helpful assistant.
# Tools
You can do functions call if needed.
Your current tools:
@@ -65,6 +45,9 @@ __tool_call__
When done right, tool call will be delivered to the 'tool' agent. 'tool' agent will respond with the results of the call.
After that you are free to respond to the user.
`
+ systemMsg = toolSysMsg
+ sysMap = map[string]string{"basic_sys": basicSysMsg, "tool_sys": toolSysMsg}
+ sysLabels = []string{"cancel", "basic_sys", "tool_sys"}
)
/*
diff --git a/tui.go b/tui.go
new file mode 100644
index 0000000..ba12e6d
--- /dev/null
+++ b/tui.go
@@ -0,0 +1,311 @@
+package main
+
+import (
+ "elefant/models"
+ "fmt"
+ "strconv"
+ "time"
+
+ "github.com/gdamore/tcell/v2"
+ "github.com/rivo/tview"
+)
+
+var (
+ app *tview.Application
+ pages *tview.Pages
+ textArea *tview.TextArea
+ editArea *tview.TextArea
+ textView *tview.TextView
+ position *tview.TextView
+ flex *tview.Flex
+ chatActModal *tview.Modal
+ sysModal *tview.Modal
+ indexPickWindow *tview.InputField
+ renameWindow *tview.InputField
+)
+
+func init() {
+ app = tview.NewApplication()
+ pages = tview.NewPages()
+ textArea = tview.NewTextArea().
+ SetPlaceholder("Type your prompt...")
+ textArea.SetBorder(true).SetTitle("input")
+ textView = tview.NewTextView().
+ SetDynamicColors(true).
+ SetRegions(true).
+ SetChangedFunc(func() {
+ app.Draw()
+ })
+ textView.SetBorder(true).SetTitle("chat")
+ focusSwitcher[textArea] = textView
+ focusSwitcher[textView] = textArea
+ position = tview.NewTextView().
+ SetDynamicColors(true).
+ SetTextAlign(tview.AlignCenter)
+ flex = tview.NewFlex().SetDirection(tview.FlexRow).
+ AddItem(textView, 0, 40, false).
+ AddItem(textArea, 0, 10, true).
+ AddItem(position, 0, 1, false)
+ updateStatusLine := func() {
+ fromRow, fromColumn, toRow, toColumn := textArea.GetCursor()
+ if fromRow == toRow && fromColumn == toColumn {
+ position.SetText(fmt.Sprintf(indexLine, botRespMode, activeChatName))
+ } else {
+ position.SetText(fmt.Sprintf("Esc: send msg; PgUp/Down: switch focus; F1: manage chats; F2: regen last; F3:delete last msg; F4: edit msg; F5: toggle system; F6: interrupt bot resp; Row: [yellow]%d[white], Column: [yellow]%d[white] - [red]To[white] Row: [yellow]%d[white], To Column: [yellow]%d; bot resp mode: %v", fromRow, fromColumn, toRow, toColumn, botRespMode))
+ }
+ }
+ chatOpts := []string{"cancel", "new", "rename current"}
+ chatList, err := loadHistoryChats()
+ if err != nil {
+ logger.Error("failed to load chat history", "error", err)
+ chatList = []string{}
+ }
+ chatActModal := tview.NewModal().
+ SetText("Chat actions:").
+ AddButtons(append(chatOpts, chatList...)).
+ SetDoneFunc(func(buttonIndex int, buttonLabel string) {
+ switch buttonLabel {
+ case "new":
+ id, err := store.ChatGetMaxID()
+ if err != nil {
+ logger.Error("failed to get chat id", "error", err)
+ }
+ // set chat body
+ chatBody.Messages = defaultStarter
+ textView.SetText(chatToText(showSystemMsgs))
+ newChat := &models.Chat{
+ ID: id,
+ Name: fmt.Sprintf("%v_%v", "new", time.Now().Unix()),
+ Msgs: string(defaultStarterBytes),
+ }
+ // activeChatName = path.Join(historyDir, fmt.Sprintf("%d_chat.json", time.Now().Unix()))
+ activeChatName = newChat.Name
+ chatMap[newChat.Name] = newChat
+ pages.RemovePage("history")
+ return
+ // set text
+ case "cancel":
+ pages.RemovePage("history")
+ return
+ case "rename current":
+ // add input field
+ pages.RemovePage("history")
+ pages.AddPage("renameW", renameWindow, true, true)
+ return
+ default:
+ fn := buttonLabel
+ history, err := loadHistoryChat(fn)
+ if err != nil {
+ logger.Error("failed to read history file", "chat", fn)
+ pages.RemovePage("history")
+ return
+ }
+ chatBody.Messages = history
+ textView.SetText(chatToText(showSystemMsgs))
+ activeChatName = fn
+ pages.RemovePage("history")
+ return
+ }
+ })
+ sysModal = tview.NewModal().
+ SetText("Switch sys msg:").
+ AddButtons(sysLabels).
+ SetDoneFunc(func(buttonIndex int, buttonLabel string) {
+ switch buttonLabel {
+ case "cancel":
+ pages.RemovePage("sys")
+ return
+ default:
+ sysMsg, ok := sysMap[buttonLabel]
+ if !ok {
+ logger.Warn("no such sys msg", "name", buttonLabel)
+ pages.RemovePage("sys")
+ return
+ }
+ chatBody.Messages[0].Content = sysMsg
+ // replace textview
+ textView.SetText(chatToText(showSystemMsgs))
+ pages.RemovePage("sys")
+ }
+ })
+ 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()
+ if editedMsg == "" {
+ notifyUser("edit", "no edit provided")
+ pages.RemovePage("editArea")
+ editMode = false
+ return nil
+ }
+ chatBody.Messages[selectedIndex].Content = editedMsg
+ // change textarea
+ textView.SetText(chatToText(showSystemMsgs))
+ pages.RemovePage("editArea")
+ editMode = false
+ return nil
+ }
+ return event
+ })
+ 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 {
+ si := indexPickWindow.GetText()
+ selectedIndex, err = strconv.Atoi(si)
+ if err != nil {
+ 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
+ }
+ m := chatBody.Messages[selectedIndex]
+ if editMode && event.Key() == tcell.KeyEnter {
+ pages.AddPage("editArea", editArea, true, true)
+ editArea.SetText(m.Content, true)
+ }
+ if !editMode && event.Key() == tcell.KeyEnter {
+ copyToClipboard(m.Content)
+ notification := fmt.Sprintf("msg '%s' was copied to the clipboard", m.Content[:30])
+ notifyUser("copied", notification)
+ }
+ return event
+ })
+ //
+ renameWindow = tview.NewInputField().
+ SetLabel("Enter a msg index: ").
+ SetFieldWidth(20).
+ SetAcceptanceFunc(tview.InputFieldMaxLength(100)).
+ SetDoneFunc(func(key tcell.Key) {
+ pages.RemovePage("renameW")
+ return
+ })
+ renameWindow.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
+ if event.Key() == tcell.KeyEnter {
+ nname := renameWindow.GetText()
+ if nname == "" {
+ return event
+ }
+ currentChat := chatMap[activeChatName]
+ delete(chatMap, activeChatName)
+ currentChat.Name = nname
+ activeChatName = nname
+ chatMap[activeChatName] = currentChat
+ _, err := store.UpsertChat(currentChat)
+ if err != nil {
+ logger.Error("failed to upsert chat", "error", err, "chat", currentChat)
+ }
+ notification := fmt.Sprintf("renamed chat to '%s'", activeChatName)
+ notifyUser("renamed", notification)
+ }
+ return event
+ })
+ //
+ textArea.SetMovedFunc(updateStatusLine)
+ updateStatusLine()
+ textView.SetText(chatToText(showSystemMsgs))
+ textView.ScrollToEnd()
+ app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
+ if event.Key() == tcell.KeyF1 {
+ chatList, err := loadHistoryChats()
+ if err != nil {
+ logger.Error("failed to load chat history", "error", err)
+ return nil
+ }
+ chatOpts := append(chatOpts, chatList...)
+ chatActModal.ClearButtons()
+ chatActModal.AddButtons(chatOpts)
+ 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 {
+ // delete last msg
+ chatBody.Messages = chatBody.Messages[:len(chatBody.Messages)-1]
+ textView.SetText(chatToText(showSystemMsgs))
+ botRespMode = false // hmmm; is that correct?
+ return nil
+ }
+ if event.Key() == tcell.KeyF4 {
+ // edit msg
+ editMode = true
+ pages.AddPage("getIndex", indexPickWindow, true, true)
+ return nil
+ }
+ if event.Key() == tcell.KeyF5 {
+ // switch showSystemMsgs
+ showSystemMsgs = !showSystemMsgs
+ textView.SetText(chatToText(showSystemMsgs))
+ }
+ if event.Key() == tcell.KeyF6 {
+ interruptResp = true
+ botRespMode = false
+ return nil
+ }
+ if event.Key() == tcell.KeyF7 {
+ // copy msg to clipboard
+ editMode = false
+ m := chatBody.Messages[len(chatBody.Messages)-1]
+ copyToClipboard(m.Content)
+ notification := fmt.Sprintf("msg '%s' was copied to the clipboard", m.Content[:30])
+ notifyUser("copied", notification)
+ return nil
+ }
+ if event.Key() == tcell.KeyF8 {
+ // copy msg to clipboard
+ editMode = false
+ pages.AddPage("getIndex", indexPickWindow, true, true)
+ return nil
+ }
+ if event.Key() == tcell.KeyCtrlE {
+ textArea.SetText("pressed ctrl+e", true)
+ return nil
+ }
+ if event.Key() == tcell.KeyCtrlS {
+ // switch sys prompt
+ pages.AddPage("sys", sysModal, true, true)
+ return nil
+ }
+ // cannot send msg in editMode or botRespMode
+ if event.Key() == tcell.KeyEscape && !editMode && !botRespMode {
+ fromRow, fromColumn, _, _ := textArea.GetCursor()
+ position.SetText(fmt.Sprintf(indexLine, fromRow, fromColumn, botRespMode))
+ // read all text into buffer
+ msgText := textArea.GetText()
+ if 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)
+ return nil
+ }
+ if event.Key() == tcell.KeyPgUp || event.Key() == tcell.KeyPgDn {
+ currentF := app.GetFocus()
+ app.SetFocus(focusSwitcher[currentF])
+ return nil
+ }
+ if isASCII(string(event.Rune())) && !botRespMode {
+ // botRespMode = false
+ // fromRow, fromColumn, _, _ := textArea.GetCursor()
+ // position.SetText(fmt.Sprintf(indexLine, fromRow, fromColumn, botRespMode))
+ return event
+ }
+ return event
+ })
+}