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

View File

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

View File

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

View File

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

View File

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