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"
|
"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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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{
|
||||||
|
Loading…
Reference in New Issue
Block a user