tui: Error handling tweak
Moved errors to bottom of screen, fix infinite loop by typing errors properly
This commit is contained in:
parent
172bfc57e1
commit
3ec2675632
@ -8,7 +8,6 @@ import (
|
|||||||
"git.mlow.ca/mlow/lmcli/pkg/api"
|
"git.mlow.ca/mlow/lmcli/pkg/api"
|
||||||
cmdutil "git.mlow.ca/mlow/lmcli/pkg/cmd/util"
|
cmdutil "git.mlow.ca/mlow/lmcli/pkg/cmd/util"
|
||||||
"git.mlow.ca/mlow/lmcli/pkg/lmcli"
|
"git.mlow.ca/mlow/lmcli/pkg/lmcli"
|
||||||
"git.mlow.ca/mlow/lmcli/pkg/tui/shared"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type LoadedConversation struct {
|
type LoadedConversation struct {
|
||||||
@ -59,7 +58,7 @@ func (m *AppModel) NewConversation() {
|
|||||||
func (m *AppModel) LoadConversations() (error, []LoadedConversation) {
|
func (m *AppModel) LoadConversations() (error, []LoadedConversation) {
|
||||||
messages, err := m.Ctx.Store.LatestConversationMessages()
|
messages, err := m.Ctx.Store.LatestConversationMessages()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return shared.MsgError(fmt.Errorf("Could not load conversations: %v", err)), nil
|
return fmt.Errorf("Could not load conversations: %v", err), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
conversations := make([]LoadedConversation, len(messages))
|
conversations := make([]LoadedConversation, len(messages))
|
||||||
|
@ -32,7 +32,7 @@ type (
|
|||||||
// sent to a state when it is entered
|
// sent to a state when it is entered
|
||||||
MsgViewEnter struct{}
|
MsgViewEnter struct{}
|
||||||
// sent when a recoverable error occurs (displayed to user)
|
// sent when a recoverable error occurs (displayed to user)
|
||||||
MsgError error
|
MsgError struct { Err error }
|
||||||
// sent when the view has handled a key input
|
// sent when the view has handled a key input
|
||||||
MsgKeyHandled tea.KeyMsg
|
MsgKeyHandled tea.KeyMsg
|
||||||
)
|
)
|
||||||
@ -57,6 +57,10 @@ func KeyHandled(key tea.KeyMsg) tea.Cmd {
|
|||||||
|
|
||||||
func WrapError(err error) tea.Cmd {
|
func WrapError(err error) tea.Cmd {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
return MsgError(err)
|
return MsgError{ Err: err }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AsMsgError(err error) MsgError {
|
||||||
|
return MsgError{ Err: err }
|
||||||
|
}
|
||||||
|
@ -91,7 +91,8 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.activeView = shared.View(msg)
|
m.activeView = shared.View(msg)
|
||||||
return m, tea.Batch(tea.WindowSize(), shared.ViewEnter())
|
return m, tea.Batch(tea.WindowSize(), shared.ViewEnter())
|
||||||
case shared.MsgError:
|
case shared.MsgError:
|
||||||
m.errs = append(m.errs, msg)
|
m.errs = append(m.errs, msg.Err)
|
||||||
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
view, cmd := m.views[m.activeView].Update(msg)
|
view, cmd := m.views[m.activeView].Update(msg)
|
||||||
@ -124,12 +125,12 @@ func (m *Model) View() string {
|
|||||||
if content != "" {
|
if content != "" {
|
||||||
sections = append(sections, content)
|
sections = append(sections, content)
|
||||||
}
|
}
|
||||||
if len(errBanners) > 0 {
|
|
||||||
sections = append(sections, lipgloss.JoinVertical(lipgloss.Left, errBanners...))
|
|
||||||
}
|
|
||||||
if footer != "" {
|
if footer != "" {
|
||||||
sections = append(sections, footer)
|
sections = append(sections, footer)
|
||||||
}
|
}
|
||||||
|
for _, errBanner := range errBanners {
|
||||||
|
sections = append(sections, errBanner)
|
||||||
|
}
|
||||||
return lipgloss.JoinVertical(lipgloss.Left, sections...)
|
return lipgloss.JoinVertical(lipgloss.Left, sections...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,11 +25,13 @@ type (
|
|||||||
}
|
}
|
||||||
// sent when a conversation's messages are laoded
|
// sent when a conversation's messages are laoded
|
||||||
msgConversationMessagesLoaded struct {
|
msgConversationMessagesLoaded struct {
|
||||||
messages []api.Message
|
messages []api.Message
|
||||||
rootMessages []api.Message
|
rootMessages []api.Message
|
||||||
}
|
}
|
||||||
// a special case of common.MsgError that stops the response waiting animation
|
// a special case of common.MsgError that stops the response waiting animation
|
||||||
msgChatResponseError error
|
msgChatResponseError struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
// sent on each chunk received from LLM
|
// sent on each chunk received from LLM
|
||||||
msgChatResponseChunk api.Chunk
|
msgChatResponseChunk api.Chunk
|
||||||
// sent on each completed reply
|
// sent on each completed reply
|
||||||
@ -72,9 +74,9 @@ const (
|
|||||||
|
|
||||||
type Model struct {
|
type Model struct {
|
||||||
// App state
|
// App state
|
||||||
App *model.AppModel
|
App *model.AppModel
|
||||||
Height int
|
Height int
|
||||||
Width int
|
Width int
|
||||||
|
|
||||||
// Chat view state
|
// Chat view state
|
||||||
state state // current overall status of the view
|
state state // current overall status of the view
|
||||||
@ -108,8 +110,8 @@ func Chat(app *model.AppModel) *Model {
|
|||||||
m := Model{
|
m := Model{
|
||||||
App: app,
|
App: app,
|
||||||
|
|
||||||
state: idle,
|
state: idle,
|
||||||
persistence: true,
|
persistence: true,
|
||||||
|
|
||||||
stopSignal: make(chan struct{}),
|
stopSignal: make(chan struct{}),
|
||||||
replyChan: make(chan api.Message),
|
replyChan: make(chan api.Message),
|
||||||
|
@ -19,11 +19,11 @@ func (m *Model) loadConversationMessages() tea.Cmd {
|
|||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
messages, err := m.App.LoadConversationMessages()
|
messages, err := m.App.LoadConversationMessages()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return shared.MsgError(err)
|
return shared.AsMsgError(err)
|
||||||
}
|
}
|
||||||
rootMessages, err := m.App.LoadConversationRootMessages()
|
rootMessages, err := m.App.LoadConversationRootMessages()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return shared.MsgError(err)
|
return shared.AsMsgError(err)
|
||||||
}
|
}
|
||||||
return msgConversationMessagesLoaded{
|
return msgConversationMessagesLoaded{
|
||||||
messages, rootMessages,
|
messages, rootMessages,
|
||||||
@ -35,7 +35,7 @@ func (m *Model) generateConversationTitle() tea.Cmd {
|
|||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
title, err := m.App.GenerateConversationTitle(m.App.Messages)
|
title, err := m.App.GenerateConversationTitle(m.App.Messages)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return shared.MsgError(err)
|
return shared.AsMsgError(err)
|
||||||
}
|
}
|
||||||
return msgConversationTitleGenerated(title)
|
return msgConversationTitleGenerated(title)
|
||||||
}
|
}
|
||||||
@ -104,7 +104,7 @@ func (m *Model) persistConversation() tea.Cmd {
|
|||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
conversation, messages, err := m.App.PersistConversation(m.App.Conversation, m.App.Messages)
|
conversation, messages, err := m.App.PersistConversation(m.App.Conversation, m.App.Messages)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return shared.MsgError(err)
|
return shared.AsMsgError(err)
|
||||||
}
|
}
|
||||||
return msgConversationPersisted{conversation.ID == 0, conversation, messages}
|
return msgConversationPersisted{conversation.ID == 0, conversation, messages}
|
||||||
}
|
}
|
||||||
@ -114,7 +114,7 @@ func (m *Model) executeToolCalls(toolCalls []api.ToolCall) tea.Cmd {
|
|||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
results, err := m.App.ExecuteToolCalls(toolCalls)
|
results, err := m.App.ExecuteToolCalls(toolCalls)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return shared.MsgError(err)
|
return shared.AsMsgError(err)
|
||||||
}
|
}
|
||||||
return msgToolResults(results)
|
return msgToolResults(results)
|
||||||
}
|
}
|
||||||
@ -131,7 +131,7 @@ func (m *Model) promptLLM() tea.Cmd {
|
|||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
resp, err := m.App.PromptLLM(m.App.Messages, m.chatReplyChunks, m.stopSignal)
|
resp, err := m.App.PromptLLM(m.App.Messages, m.chatReplyChunks, m.stopSignal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return msgChatResponseError(err)
|
return msgChatResponseError{ Err: err }
|
||||||
}
|
}
|
||||||
return msgChatResponse(resp)
|
return msgChatResponse(resp)
|
||||||
}
|
}
|
||||||
|
@ -170,7 +170,7 @@ func (m *Model) Update(msg tea.Msg) (shared.ViewModel, tea.Cmd) {
|
|||||||
case msgChatResponseError:
|
case msgChatResponseError:
|
||||||
m.state = idle
|
m.state = idle
|
||||||
m.updateContent()
|
m.updateContent()
|
||||||
return m, shared.WrapError(msg)
|
return m, shared.WrapError(msg.Err)
|
||||||
case msgToolResults:
|
case msgToolResults:
|
||||||
last := len(m.App.Messages) - 1
|
last := len(m.App.Messages) - 1
|
||||||
if last < 0 {
|
if last < 0 {
|
||||||
|
@ -182,7 +182,7 @@ func (m *Model) loadConversations() tea.Cmd {
|
|||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
err, conversations := m.App.LoadConversations()
|
err, conversations := m.App.LoadConversations()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return shared.MsgError(fmt.Errorf("Could not load conversations: %v", err))
|
return shared.AsMsgError(fmt.Errorf("Could not load conversations: %v", err))
|
||||||
}
|
}
|
||||||
return msgConversationsLoaded(conversations)
|
return msgConversationsLoaded(conversations)
|
||||||
}
|
}
|
||||||
@ -192,7 +192,7 @@ func (m *Model) deleteConversation(conv api.Conversation) tea.Cmd {
|
|||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
err := m.App.Ctx.Store.DeleteConversation(&conv)
|
err := m.App.Ctx.Store.DeleteConversation(&conv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return shared.MsgError(fmt.Errorf("Could not delete conversation: %v", err))
|
return shared.AsMsgError(fmt.Errorf("Could not delete conversation: %v", err))
|
||||||
}
|
}
|
||||||
return msgConversationDeleted{}
|
return msgConversationDeleted{}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user