Feat: rename chat
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
- consider adding use /completion of llamacpp, since openai endpoint clearly has template|format issues;
|
- consider adding use /completion of llamacpp, since openai endpoint clearly has template|format issues;
|
||||||
- change temp, min-p and other params from tui;
|
- change temp, min-p and other params from tui;
|
||||||
- help page with all key bindings;
|
- help page with all key bindings;
|
||||||
|
- rename current chat;
|
||||||
|
|
||||||
### FIX:
|
### FIX:
|
||||||
- bot responding (or haninging) blocks everything; +
|
- bot responding (or haninging) blocks everything; +
|
||||||
@@ -30,4 +31,4 @@
|
|||||||
- empty input to continue bot msg gens new msg index and bot icon;
|
- 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; +
|
- sometimes bots put additional info around the tool call, have a regexp to match tool call; +
|
||||||
- remove all panics from code; +
|
- remove all panics from code; +
|
||||||
- new chat is not saved in db;
|
- new chat replaces old ones in db;
|
||||||
|
|||||||
249
main.go
249
main.go
@@ -1,13 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"elefant/models"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,7 +11,7 @@ var (
|
|||||||
editMode = false
|
editMode = false
|
||||||
botMsg = "no"
|
botMsg = "no"
|
||||||
selectedIndex = int(-1)
|
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{}
|
focusSwitcher = map[tview.Primitive]tview.Primitive{}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,248 +25,6 @@ func isASCII(s string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
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
|
|
||||||
})
|
|
||||||
//
|
|
||||||
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
|
|
||||||
}
|
|
||||||
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
|
|
||||||
})
|
|
||||||
pages.AddPage("main", flex, true, true)
|
pages.AddPage("main", flex, true, true)
|
||||||
if err := app.SetRoot(pages,
|
if err := app.SetRoot(pages,
|
||||||
true).EnableMouse(true).Run(); err != nil {
|
true).EnableMouse(true).Run(); err != nil {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ func updateStorageChat(name string, msgs []models.MessagesStory) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
chat.UpdatedAt = time.Now()
|
chat.UpdatedAt = time.Now()
|
||||||
|
// if new chat will create id
|
||||||
_, err = store.UpsertChat(chat)
|
_, err = store.UpsertChat(chat)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 *;"
|
query := "INSERT INTO memories (agent, topic, mind) VALUES (:agent, :topic, :mind) RETURNING *;"
|
||||||
stmt, err := p.db.PrepareNamed(query)
|
stmt, err := p.db.PrepareNamed(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
p.logger.Error("failed to prepare stmt", "query", query, "error", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer stmt.Close()
|
defer stmt.Close()
|
||||||
var memory models.Memory
|
var memory models.Memory
|
||||||
err = stmt.Get(&memory, m)
|
err = stmt.Get(&memory, m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
p.logger.Error("failed to insert memory", "query", query, "error", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &memory, nil
|
return &memory, nil
|
||||||
@@ -28,6 +30,7 @@ func (p ProviderSQL) Recall(agent, topic string) (string, error) {
|
|||||||
var mind string
|
var mind string
|
||||||
err := p.db.Get(&mind, query, agent, topic)
|
err := p.db.Get(&mind, query, agent, topic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
p.logger.Error("failed to get memory", "query", query, "error", err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return mind, nil
|
return mind, nil
|
||||||
@@ -38,6 +41,7 @@ func (p ProviderSQL) RecallTopics(agent string) ([]string, error) {
|
|||||||
var topics []string
|
var topics []string
|
||||||
err := p.db.Select(&topics, query, agent)
|
err := p.db.Select(&topics, query, agent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
p.logger.Error("failed to get topics", "query", query, "error", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return topics, nil
|
return topics, nil
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type ChatHistory interface {
|
|||||||
GetLastChat() (*models.Chat, error)
|
GetLastChat() (*models.Chat, error)
|
||||||
UpsertChat(chat *models.Chat) (*models.Chat, error)
|
UpsertChat(chat *models.Chat) (*models.Chat, error)
|
||||||
RemoveChat(id uint32) error
|
RemoveChat(id uint32) error
|
||||||
|
ChatGetMaxID() (uint32, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderSQL struct {
|
type ProviderSQL struct {
|
||||||
@@ -66,6 +67,13 @@ func (p ProviderSQL) RemoveChat(id uint32) error {
|
|||||||
return err
|
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 {
|
func NewProviderSQL(dbPath string, logger *slog.Logger) FullRepo {
|
||||||
db, err := sqlx.Open("sqlite", dbPath)
|
db, err := sqlx.Open("sqlite", dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
311
tui.go
Normal file
311
tui.go
Normal file
@@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user