Chat view cleanup
Replace `waitingForReply` and the `status` string with the `state` variable.
This commit is contained in:
parent
45df957a06
commit
c9e92e186e
@ -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
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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{
|
||||
|
Loading…
Reference in New Issue
Block a user