Chat view cleanup

Replace `waitingForReply` and the `status` string with the `state`
variable.
This commit is contained in:
Matt Low 2024-06-08 21:58:39 +00:00
parent 45df957a06
commit c9e92e186e
5 changed files with 48 additions and 43 deletions

View File

@ -13,20 +13,6 @@ import (
"github.com/charmbracelet/lipgloss"
)
type focusState int
const (
focusInput focusState = iota
focusMessages
)
type editorTarget int
const (
input editorTarget = iota
selectedMessage
)
// custom tea.Msg types
type (
// sent on each chunk received from LLM
@ -61,16 +47,38 @@ type (
msgMessageCloned *models.Message
)
type focusState int
const (
focusInput focusState = iota
focusMessages
)
type editorTarget int
const (
input editorTarget = iota
selectedMessage
)
type state int
const (
idle state = iota
loading
pendingResponse
)
type Model struct {
shared.State
shared.Sections
// app state
state state // current overall status of the view
conversation *models.Conversation
rootMessages []models.Message
messages []models.Message
selectedMessage int
waitingForReply bool
editorTarget editorTarget
stopSignal chan struct{}
replyChan chan models.Message
@ -80,7 +88,6 @@ type Model struct {
// ui state
focus focusState
wrap bool // whether message content is wrapped to viewport width
status string // a general status message
showToolResults bool // whether tool calls and results are shown
messageCache []string // cache of syntax highlighted and wrapped message content
messageOffsets []int
@ -101,6 +108,7 @@ func Chat(state shared.State) Model {
m := Model{
State: state,
state: idle,
conversation: &models.Conversation{},
persistence: true,
@ -150,8 +158,6 @@ func Chat(state shared.State) Model {
m.input.FocusedStyle.Base = inputFocusedStyle
m.input.BlurredStyle.Base = inputBlurredStyle
m.waitingForReply = false
m.status = "Press ctrl+s to send"
return m
}

View File

@ -244,9 +244,8 @@ func (m *Model) persistConversation() tea.Cmd {
}
func (m *Model) promptLLM() tea.Cmd {
m.waitingForReply = true
m.state = pendingResponse
m.replyCursor.Blink = false
m.status = "Press ctrl+c to cancel"
m.tokenCount = 0
m.startTime = time.Now()

View File

@ -33,7 +33,7 @@ func (m *Model) HandleInput(msg tea.KeyMsg) (bool, tea.Cmd) {
switch msg.String() {
case "esc":
if m.waitingForReply {
if m.state == pendingResponse {
m.stopSignal <- struct{}{}
return true, nil
}
@ -41,7 +41,7 @@ func (m *Model) HandleInput(msg tea.KeyMsg) (bool, tea.Cmd) {
return shared.MsgViewChange(shared.StateConversations)
}
case "ctrl+c":
if m.waitingForReply {
if m.state == pendingResponse {
m.stopSignal <- struct{}{}
return true, nil
}
@ -112,15 +112,14 @@ func (m *Model) handleMessagesKey(msg tea.KeyMsg) (bool, tea.Cmd) {
return cmd != nil, cmd
case "ctrl+r":
// resubmit the conversation with all messages up until and including the selected message
if m.waitingForReply || len(m.messages) == 0 {
return true, nil
if m.state == idle && m.selectedMessage < len(m.messages) {
m.messages = m.messages[:m.selectedMessage+1]
m.messageCache = m.messageCache[:m.selectedMessage+1]
cmd := m.promptLLM()
m.updateContent()
m.content.GotoBottom()
return true, cmd
}
m.messages = m.messages[:m.selectedMessage+1]
m.messageCache = m.messageCache[:m.selectedMessage+1]
cmd := m.promptLLM()
m.updateContent()
m.content.GotoBottom()
return true, cmd
}
return false, nil
}
@ -141,8 +140,8 @@ func (m *Model) handleInputKey(msg tea.KeyMsg) (bool, tea.Cmd) {
m.input.Blur()
return true, nil
case "ctrl+s":
// TODO: call a "handleSend" function with returns a tea.Cmd
if m.waitingForReply {
// TODO: call a "handleSend" function which returns a tea.Cmd
if m.state != idle {
return false, nil
}

View File

@ -142,17 +142,15 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
m.updateContent()
case msgResponseEnd:
m.waitingForReply = false
m.state = idle
last := len(m.messages) - 1
if last < 0 {
panic("Unexpected empty messages handling msgResponseEnd")
}
m.setMessageContents(last, strings.TrimSpace(m.messages[last].Content))
m.updateContent()
m.status = "Press ctrl+s to send"
case msgResponseError:
m.waitingForReply = false
m.status = "Press ctrl+s to send"
m.state = idle
m.State.Err = error(msg)
m.updateContent()
case msgConversationTitleGenerated:
@ -162,7 +160,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
cmds = append(cmds, m.updateConversationTitle(m.conversation))
}
case cursor.BlinkMsg:
if m.waitingForReply {
if m.state == pendingResponse {
// ensure we show the updated "wait for response" cursor blink state
m.updateContent()
}

View File

@ -139,7 +139,7 @@ func (m *Model) renderMessage(i int) string {
}
// Show the assistant's cursor
if m.waitingForReply && i == len(m.messages)-1 && msg.Role == models.MessageRoleAssistant {
if m.state == pendingResponse && i == len(m.messages)-1 && msg.Role == models.MessageRoleAssistant {
sb.WriteString(m.replyCursor.View())
}
@ -237,7 +237,7 @@ func (m *Model) conversationMessagesView() string {
lineCnt += lipgloss.Height(heading)
var rendered string
if m.waitingForReply && i == len(m.messages)-1 {
if m.state == pendingResponse && i == len(m.messages)-1 {
// do a direct render of final (assistant) message to handle the
// assistant cursor blink
rendered = m.renderMessage(i)
@ -251,7 +251,7 @@ func (m *Model) conversationMessagesView() string {
}
// Render a placeholder for the incoming assistant reply
if m.waitingForReply && (len(m.messages) == 0 || m.messages[len(m.messages)-1].Role != models.MessageRoleAssistant) {
if m.state == pendingResponse && (len(m.messages) == 0 || m.messages[len(m.messages)-1].Role != models.MessageRoleAssistant) {
heading := m.renderMessageHeading(-1, &models.Message{
Role: models.MessageRoleAssistant,
})
@ -289,9 +289,12 @@ func (m *Model) footerView() string {
saving = savingStyle.Foreground(lipgloss.Color("1")).Render("❌💾")
}
status := m.status
if m.waitingForReply {
status += m.spinner.View()
var status string
switch m.state {
case pendingResponse:
status = "Press ctrl+c to cancel" + m.spinner.View()
default:
status = "Press ctrl+s to send"
}
leftSegments := []string{