Private
Public Access
1
0

TUI refactor

- Clean up, improved startup logic, initial conversation load
- Moved converation/message business logic (mostly) into `model/tui`
This commit is contained in:
2024-09-16 00:48:45 +00:00
parent 1570988b98
commit 443c8096d3
11 changed files with 431 additions and 350 deletions

View File

@@ -15,7 +15,7 @@ func (m *Model) HandleResize(width, height int) {
m.Width, m.Height = width, height
m.content.Width = width
m.input.SetWidth(width - m.input.FocusedStyle.Base.GetHorizontalFrameSize())
if len(m.messages) > 0 {
if len(m.App.Messages) > 0 {
m.rebuildMessageCache()
m.updateContent()
}
@@ -36,26 +36,21 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
// wake up spinners and cursors
cmds = append(cmds, cursor.Blink, m.spinner.Tick)
if m.Shared.Values.ConvShortname != "" {
// (re)load conversation contents
cmds = append(cmds, m.loadConversation(m.Shared.Values.ConvShortname))
if m.conversation.ShortName.String != m.Shared.Values.ConvShortname {
// clear existing messages if we're loading a new conversation
m.messages = []api.Message{}
m.selectedMessage = 0
}
}
// Refresh view
m.rebuildMessageCache()
m.updateContent()
if m.App.Conversation != nil && m.App.Conversation.ID > 0 {
// (re)load conversation contents
cmds = append(cmds, m.loadConversationMessages())
}
case tuiutil.MsgTempfileEditorClosed:
contents := string(msg)
switch m.editorTarget {
case input:
m.input.SetValue(contents)
case selectedMessage:
toEdit := m.messages[m.selectedMessage]
toEdit := m.App.Messages[m.selectedMessage]
if toEdit.Content != contents {
toEdit.Content = contents
m.setMessage(m.selectedMessage, toEdit)
@@ -66,18 +61,18 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
}
}
case msgConversationLoaded:
m.conversation = msg.conversation
m.rootMessages = msg.rootMessages
m.App.Conversation = msg.conversation
m.App.RootMessages = msg.rootMessages
m.selectedMessage = -1
if len(m.rootMessages) > 0 {
if len(m.App.RootMessages) > 0 {
cmds = append(cmds, m.loadConversationMessages())
}
case msgMessagesLoaded:
m.messages = msg
m.App.Messages = msg
if m.selectedMessage == -1 {
m.selectedMessage = len(msg) - 1
} else {
m.selectedMessage = min(m.selectedMessage, len(m.messages))
m.selectedMessage = min(m.selectedMessage, len(m.App.Messages))
}
m.rebuildMessageCache()
m.updateContent()
@@ -88,10 +83,10 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
break
}
last := len(m.messages) - 1
if last >= 0 && m.messages[last].Role.IsAssistant() {
last := len(m.App.Messages) - 1
if last >= 0 && m.App.Messages[last].Role.IsAssistant() {
// append chunk to existing message
m.setMessageContents(last, m.messages[last].Content+msg.Content)
m.setMessageContents(last, m.App.Messages[last].Content+msg.Content)
} else {
// use chunk in a new message
m.addMessage(api.Message{
@@ -113,12 +108,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
reply := (*api.Message)(msg)
reply.Content = strings.TrimSpace(reply.Content)
last := len(m.messages) - 1
last := len(m.App.Messages) - 1
if last < 0 {
panic("Unexpected empty messages handling msgAssistantReply")
}
if m.messages[last].Role.IsAssistant() {
if m.App.Messages[last].Role.IsAssistant() {
// TODO: handle continuations gracefully - some models support them well, others fail horribly.
m.setMessage(last, *reply)
} else {
@@ -136,7 +131,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
cmds = append(cmds, m.persistConversation())
}
if m.conversation.Title == "" {
if m.App.Conversation.Title == "" {
cmds = append(cmds, m.generateConversationTitle())
}
@@ -149,12 +144,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
m.Shared.Err = error(msg)
m.updateContent()
case msgToolResults:
last := len(m.messages) - 1
last := len(m.App.Messages) - 1
if last < 0 {
panic("Unexpected empty messages handling msgAssistantReply")
}
if m.messages[last].Role != api.MessageRoleToolCall {
if m.App.Messages[last].Role != api.MessageRoleToolCall {
panic("Previous message not a tool call, unexpected")
}
@@ -170,29 +165,29 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
m.updateContent()
case msgConversationTitleGenerated:
title := string(msg)
m.conversation.Title = title
m.App.Conversation.Title = title
if m.persistence {
cmds = append(cmds, m.updateConversationTitle(m.conversation))
cmds = append(cmds, m.updateConversationTitle(m.App.Conversation))
}
case cursor.BlinkMsg:
if m.state == pendingResponse {
// ensure we show the updated "wait for response" cursor blink state
last := len(m.messages)-1
last := len(m.App.Messages) - 1
m.messageCache[last] = m.renderMessage(last)
m.updateContent()
}
case msgConversationPersisted:
m.conversation = msg.conversation
m.messages = msg.messages
m.App.Conversation = msg.conversation
m.App.Messages = msg.messages
if msg.isNew {
m.rootMessages = []api.Message{m.messages[0]}
m.App.RootMessages = []api.Message{m.App.Messages[0]}
}
m.rebuildMessageCache()
m.updateContent()
case msgMessageCloned:
if msg.Parent == nil {
m.conversation = msg.Conversation
m.rootMessages = append(m.rootMessages, *msg)
m.App.Conversation = msg.Conversation
m.App.RootMessages = append(m.App.RootMessages, *msg)
}
cmds = append(cmds, m.loadConversationMessages())
case msgSelectedRootCycled, msgSelectedReplyCycled, msgMessageUpdated: