Private
Public Access
1
0

Fixes to message/conversation handling in tui chat view

This set of changes fixes root/child message cycling and ensures all
database operations happen within a `tea.Cmd`
This commit is contained in:
2024-06-08 21:28:29 +00:00
parent 136c463924
commit 45df957a06
6 changed files with 251 additions and 169 deletions

View File

@@ -1,7 +1,6 @@
package chat
import (
"fmt"
"strings"
"time"
@@ -22,13 +21,13 @@ func (m *Model) HandleResize(width, height int) {
}
}
func (m *Model) waitForReply() tea.Cmd {
func (m *Model) waitForResponse() tea.Cmd {
return func() tea.Msg {
return msgAssistantReply(<-m.replyChan)
return msgResponse(<-m.replyChan)
}
}
func (m *Model) waitForChunk() tea.Cmd {
func (m *Model) waitForResponseChunk() tea.Cmd {
return func() tea.Msg {
return msgResponseChunk(<-m.replyChunkChan)
}
@@ -37,46 +36,59 @@ func (m *Model) waitForChunk() tea.Cmd {
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.HandleResize(msg.Width, msg.Height)
case shared.MsgViewEnter:
// wake up spinners and cursors
cmds = append(cmds, cursor.Blink, m.spinner.Tick)
if m.State.Values.ConvShortname != "" && m.conversation.ShortName.String != m.State.Values.ConvShortname {
if m.State.Values.ConvShortname != "" {
// (re)load conversation contents
cmds = append(cmds, m.loadConversation(m.State.Values.ConvShortname))
if m.conversation.ShortName.String != m.State.Values.ConvShortname {
// clear existing messages if we're loading a new conversation
m.messages = []models.Message{}
m.selectedMessage = 0
}
}
m.rebuildMessageCache()
m.updateContent()
case tea.WindowSizeMsg:
m.HandleResize(msg.Width, msg.Height)
case tuiutil.MsgTempfileEditorClosed:
contents := string(msg)
switch m.editorTarget {
case input:
m.input.SetValue(contents)
case selectedMessage:
m.setMessageContents(m.selectedMessage, contents)
if m.persistence && m.messages[m.selectedMessage].ID > 0 {
// update persisted message
err := m.State.Ctx.Store.UpdateMessage(&m.messages[m.selectedMessage])
if err != nil {
cmds = append(cmds, shared.WrapError(fmt.Errorf("Could not save edited message: %v", err)))
toEdit := m.messages[m.selectedMessage]
if toEdit.Content != contents {
toEdit.Content = contents
m.setMessage(m.selectedMessage, toEdit)
if m.persistence && toEdit.ID > 0 {
// create clone of message with its new contents
cmds = append(cmds, m.cloneMessage(toEdit, true))
}
}
m.updateContent()
}
case msgConversationLoaded:
m.conversation = (*models.Conversation)(msg)
m.rootMessages, _ = m.State.Ctx.Store.RootMessages(m.conversation.ID)
cmds = append(cmds, m.loadMessages(m.conversation))
m.conversation = msg.conversation
m.rootMessages = msg.rootMessages
m.selectedMessage = -1
if len(m.rootMessages) > 0 {
cmds = append(cmds, m.loadConversationMessages())
}
case msgMessagesLoaded:
m.selectedMessage = len(msg) - 1
m.messages = msg
if m.selectedMessage == -1 {
m.selectedMessage = len(msg) - 1
} else {
m.selectedMessage = min(m.selectedMessage, len(m.messages))
}
m.rebuildMessageCache()
m.updateContent()
m.content.GotoBottom()
case msgResponseChunk:
cmds = append(cmds, m.waitForChunk()) // wait for the next chunk
cmds = append(cmds, m.waitForResponseChunk()) // wait for the next chunk
chunk := string(msg)
if chunk == "" {
@@ -102,8 +114,8 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
m.tokenCount++
m.elapsed = time.Now().Sub(m.startTime)
case msgAssistantReply:
cmds = append(cmds, m.waitForReply()) // wait for the next reply
case msgResponse:
cmds = append(cmds, m.waitForResponse()) // wait for the next response
reply := models.Message(msg)
reply.Content = strings.TrimSpace(reply.Content)
@@ -121,10 +133,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
}
if m.persistence {
err := m.persistConversation()
if err != nil {
cmds = append(cmds, shared.WrapError(err))
}
cmds = append(cmds, m.persistConversation())
}
if m.conversation.Title == "" {
@@ -146,20 +155,30 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
m.status = "Press ctrl+s to send"
m.State.Err = error(msg)
m.updateContent()
case msgConversationTitleChanged:
case msgConversationTitleGenerated:
title := string(msg)
m.conversation.Title = title
if m.persistence {
err := m.State.Ctx.Store.UpdateConversation(m.conversation)
if err != nil {
cmds = append(cmds, shared.WrapError(err))
}
cmds = append(cmds, m.updateConversationTitle(m.conversation))
}
case cursor.BlinkMsg:
if m.waitingForReply {
// ensure we show updated "wait for response" cursor blink state
// ensure we show the updated "wait for response" cursor blink state
m.updateContent()
}
case msgConversationPersisted:
m.conversation = msg.conversation
m.messages = msg.messages
m.rebuildMessageCache()
m.updateContent()
case msgMessageCloned:
if msg.Parent == nil {
m.conversation = &msg.Conversation
m.rootMessages = append(m.rootMessages, *msg)
}
cmds = append(cmds, m.loadConversationMessages())
case msgSelectedRootCycled, msgSelectedReplyCycled, msgMessageUpdated:
cmds = append(cmds, m.loadConversationMessages())
}
var cmd tea.Cmd
@@ -218,12 +237,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
// move cursor up until content reaches the bottom of the viewport
m.input.CursorUp()
}
m.input, cmd = m.input.Update(nil)
m.input, _ = m.input.Update(nil)
for i := 0; i < dist; i++ {
// move cursor back down to its previous position
m.input.CursorDown()
}
m.input, cmd = m.input.Update(nil)
m.input, _ = m.input.Update(nil)
}
}