wip: refactoring tui

This commit is contained in:
adamdottv 2025-05-29 13:12:39 -05:00
parent fce9e79d38
commit 26606ccbf7
No known key found for this signature in database
GPG key ID: 9CB48779AF150E75
3 changed files with 198 additions and 267 deletions

View file

@ -11,7 +11,6 @@ import (
"github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/ansi"
"github.com/sst/opencode/internal/config" "github.com/sst/opencode/internal/config"
"github.com/sst/opencode/internal/diff" "github.com/sst/opencode/internal/diff"
"github.com/sst/opencode/internal/llm/models"
"github.com/sst/opencode/internal/llm/tools" "github.com/sst/opencode/internal/llm/tools"
"github.com/sst/opencode/internal/message" "github.com/sst/opencode/internal/message"
"github.com/sst/opencode/internal/tui/styles" "github.com/sst/opencode/internal/tui/styles"
@ -22,64 +21,25 @@ import (
type uiMessageType int type uiMessageType int
const ( const (
userMessageType uiMessageType = iota
assistantMessageType
toolMessageType
maxResultHeight = 10 maxResultHeight = 10
) )
type uiMessage struct { func toMarkdown(content string, width int) string {
ID string
messageType uiMessageType
content string
}
func toMarkdown(content string, focused bool, width int) string {
r := styles.GetMarkdownRenderer(width) r := styles.GetMarkdownRenderer(width)
rendered, _ := r.Render(content) rendered, _ := r.Render(content)
return rendered return strings.TrimSuffix(rendered, "\n")
} }
func renderMessage(msg string, isUser bool, isFocused bool, width int, info ...string) string { func renderUserMessage(msg client.MessageInfo, width int) string {
t := theme.CurrentTheme() t := theme.CurrentTheme()
style := styles.BaseStyle(). style := styles.BaseStyle().
// Width(width - 1).
BorderLeft(true). BorderLeft(true).
Foreground(t.TextMuted()). Foreground(t.TextMuted()).
BorderForeground(t.Primary()). BorderForeground(t.Secondary()).
BorderStyle(lipgloss.ThickBorder()) BorderStyle(lipgloss.ThickBorder())
if isUser {
style = style.BorderForeground(t.Secondary())
}
// Apply markdown formatting and handle background color
parts := []string{
styles.ForceReplaceBackgroundWithLipgloss(toMarkdown(msg, isFocused, width), t.Background()),
}
// Remove newline at the end
parts[0] = strings.TrimSuffix(parts[0], "\n")
if len(info) > 0 {
parts = append(parts, info...)
}
rendered := style.Render(
lipgloss.JoinVertical(
lipgloss.Left,
parts...,
),
)
return rendered
}
func renderUserMessage(msg client.MessageInfo, isFocused bool, width int) uiMessage {
// var styledAttachments []string
t := theme.CurrentTheme()
baseStyle := styles.BaseStyle() baseStyle := styles.BaseStyle()
// var styledAttachments []string
// attachmentStyles := baseStyle. // attachmentStyles := baseStyle.
// MarginLeft(1). // MarginLeft(1).
// Background(t.TextMuted()). // Background(t.TextMuted()).
@ -95,16 +55,12 @@ func renderUserMessage(msg client.MessageInfo, isFocused bool, width int) uiMess
// styledAttachments = append(styledAttachments, attachmentStyles.Render(filename)) // styledAttachments = append(styledAttachments, attachmentStyles.Render(filename))
// } // }
info := []string{}
// Add timestamp info // Add timestamp info
timestamp := time.UnixMilli(int64(msg.Metadata.Time.Created)).Local().Format("02 Jan 2006 03:04 PM") timestamp := time.UnixMilli(int64(msg.Metadata.Time.Created)).Local().Format("02 Jan 2006 03:04 PM")
username, _ := config.GetUsername() username, _ := config.GetUsername()
info = append(info, baseStyle. info := baseStyle.
Width(width-1).
Foreground(t.TextMuted()). Foreground(t.TextMuted()).
Render(fmt.Sprintf(" %s (%s)", username, timestamp)), Render(fmt.Sprintf(" %s (%s)", username, timestamp))
)
content := "" content := ""
// if len(styledAttachments) > 0 { // if len(styledAttachments) > 0 {
@ -120,125 +76,175 @@ func renderUserMessage(msg client.MessageInfo, isFocused bool, width int) uiMess
switch part.(type) { switch part.(type) {
case client.MessagePartText: case client.MessagePartText:
textPart := part.(client.MessagePartText) textPart := part.(client.MessagePartText)
content = renderMessage(textPart.Text, true, isFocused, width, info...) text := toMarkdown(textPart.Text, width)
content = style.Render(lipgloss.JoinVertical(lipgloss.Left, text, info))
} }
} }
// content = renderMessage(msg.Parts, true, isFocused, width, info...)
userMsg := uiMessage{ return content
ID: msg.Id, }
messageType: userMessageType,
content: content, func convertToMap(input *any) (map[string]any, bool) {
} if input == nil {
return userMsg return nil, false // Handle nil pointer
}
value := *input // Dereference the pointer to get the interface value
m, ok := value.(map[string]any) // Type assertion
return m, ok
} }
// Returns multiple uiMessages because of the tool calls
func renderAssistantMessage( func renderAssistantMessage(
msg message.Message, msg client.MessageInfo,
msgIndex int,
allMessages []message.Message, // we need this to get tool results and the user message
messagesService message.Service, // We need this to get the task tool messages
focusedUIMessageId string,
width int, width int,
position int,
showToolMessages bool, showToolMessages bool,
) []uiMessage { ) string {
messages := []uiMessage{}
content := strings.TrimSpace(msg.Content().String())
thinking := msg.IsThinking()
thinkingContent := msg.ReasoningContent().Thinking
finished := msg.IsFinished()
finishData := msg.FinishPart()
info := []string{}
t := theme.CurrentTheme() t := theme.CurrentTheme()
baseStyle := styles.BaseStyle() style := styles.BaseStyle().
BorderLeft(true).
// Always add timestamp info
timestamp := msg.CreatedAt.Local().Format("02 Jan 2006 03:04 PM")
modelName := "Assistant"
if msg.Model != "" {
modelName = models.SupportedModels[msg.Model].Name
}
info = append(info, baseStyle.
Width(width-1).
Foreground(t.TextMuted()). Foreground(t.TextMuted()).
Render(fmt.Sprintf(" %s (%s)", modelName, timestamp)), BorderForeground(t.Primary()).
) BorderStyle(lipgloss.ThickBorder())
toolStyle := styles.BaseStyle().
BorderLeft(true).
Foreground(t.TextMuted()).
BorderForeground(t.TextMuted()).
BorderStyle(lipgloss.ThickBorder())
if finished { baseStyle := styles.BaseStyle()
// Add finish info if available messages := []string{}
switch finishData.Reason {
case message.FinishReasonCanceled: // content := strings.TrimSpace(msg.Content().String())
info = append(info, baseStyle. // thinking := msg.IsThinking()
Width(width-1). // thinkingContent := msg.ReasoningContent().Thinking
Foreground(t.Warning()). // finished := msg.IsFinished()
Render("(canceled)"), // finishData := msg.FinishPart()
)
case message.FinishReasonError: // Add timestamp info
info = append(info, baseStyle. timestamp := time.UnixMilli(int64(msg.Metadata.Time.Created)).Local().Format("02 Jan 2006 03:04 PM")
Width(width-1). modelName := msg.Metadata.Assistant.ModelID
Foreground(t.Error()). info := baseStyle.
Render("(error)"), Foreground(t.TextMuted()).
) Render(fmt.Sprintf(" %s (%s)", modelName, timestamp))
case message.FinishReasonPermissionDenied:
info = append(info, baseStyle. for _, p := range msg.Parts {
Width(width-1). part, err := p.ValueByDiscriminator()
Foreground(t.Info()). if err != nil {
Render("(permission denied)"), continue //TODO: handle error?
) }
switch part.(type) {
case client.MessagePartText:
textPart := part.(client.MessagePartText)
text := toMarkdown(textPart.Text, width)
content := style.Render(lipgloss.JoinVertical(lipgloss.Left, text, info))
messages = append(messages, content)
case client.MessagePartToolInvocation:
if !showToolMessages {
continue
}
toolInvocationPart := part.(client.MessagePartToolInvocation)
toolInvocation, _ := toolInvocationPart.ToolInvocation.ValueByDiscriminator()
switch toolInvocation.(type) {
case client.MessageToolInvocationToolCall:
toolCall := toolInvocation.(client.MessageToolInvocationToolCall)
toolName := toolName(toolCall.ToolName)
var toolArgs []string
toolMap, _ := convertToMap(toolCall.Args)
for _, arg := range toolMap {
toolArgs = append(toolArgs, fmt.Sprintf("%v", arg))
}
params := renderParams(width-lipgloss.Width(toolName)-1, toolArgs...)
title := styles.Padded().Render(fmt.Sprintf("%s: %s", toolName, params))
content := toolStyle.Render(lipgloss.JoinVertical(lipgloss.Left,
title,
" In progress...",
))
messages = append(messages, content)
case client.MessageToolInvocationToolResult:
toolInvocationResult := toolInvocation.(client.MessageToolInvocationToolResult)
toolName := toolName(toolInvocationResult.ToolName)
var toolArgs []string
toolMap, _ := convertToMap(toolInvocationResult.Args)
for _, arg := range toolMap {
toolArgs = append(toolArgs, fmt.Sprintf("%v", arg))
}
result := truncateHeight(strings.TrimSpace(toolInvocationResult.Result), 10)
params := renderParams(width-lipgloss.Width(toolName)-1, toolArgs...)
title := styles.Padded().Render(fmt.Sprintf("%s: %s", toolName, params))
markdown := toMarkdown(result, width)
content := toolStyle.Render(lipgloss.JoinVertical(lipgloss.Left,
title,
markdown,
))
messages = append(messages, content)
}
} }
} }
if content != "" || (finished && finishData.Reason == message.FinishReasonEndTurn) { // if finished {
if content == "" { // // Add finish info if available
content = "*Finished without output*" // switch finishData.Reason {
} // case message.FinishReasonCanceled:
// info = append(info, baseStyle.
// Width(width-1).
// Foreground(t.Warning()).
// Render("(canceled)"),
// )
// case message.FinishReasonError:
// info = append(info, baseStyle.
// Width(width-1).
// Foreground(t.Error()).
// Render("(error)"),
// )
// case message.FinishReasonPermissionDenied:
// info = append(info, baseStyle.
// Width(width-1).
// Foreground(t.Info()).
// Render("(permission denied)"),
// )
// }
// }
content = renderMessage(content, false, true, width, info...) // if content != "" || (finished && finishData.Reason == message.FinishReasonEndTurn) {
messages = append(messages, uiMessage{ // if content == "" {
ID: msg.ID, // content = "*Finished without output*"
messageType: assistantMessageType, // }
// position: position, //
// height: lipgloss.Height(content), // content = renderMessage(content, false, width, info...)
content: content, // messages = append(messages, content)
}) // // position += messages[0].height
// position += messages[0].height // position++ // for the space
position++ // for the space // } else if thinking && thinkingContent != "" {
} else if thinking && thinkingContent != "" { // // Render the thinking content with timestamp
// Render the thinking content with timestamp // content = renderMessage(thinkingContent, false, width, info...)
content = renderMessage(thinkingContent, false, msg.ID == focusedUIMessageId, width, info...) // messages = append(messages, content)
messages = append(messages, uiMessage{ // position += lipgloss.Height(content)
ID: msg.ID, // position++ // for the space
messageType: assistantMessageType, // }
// position: position,
// height: lipgloss.Height(content),
content: content,
})
position += lipgloss.Height(content)
position++ // for the space
}
// Only render tool messages if they should be shown // Only render tool messages if they should be shown
if showToolMessages { if showToolMessages {
for i, toolCall := range msg.ToolCalls() { // for i, toolCall := range msg.ToolCalls() {
toolCallContent := renderToolMessage( // toolCallContent := renderToolMessage(
toolCall, // toolCall,
allMessages, // allMessages,
messagesService, // messagesService,
focusedUIMessageId, // focusedUIMessageId,
false, // false,
width, // width,
i+1, // i+1,
) // )
messages = append(messages, toolCallContent) // messages = append(messages, toolCallContent)
// position += toolCallContent.height // }
position++ // for the space
}
} }
return messages
return strings.Join(messages, "\n\n")
} }
func findToolResponse(toolCallID string, futureMessages []message.Message) *message.ToolResult { func findToolResponse(toolCallID string, futureMessages []message.Message) *message.ToolResult {
@ -497,7 +503,7 @@ func renderToolResponse(toolCall message.ToolCall, response message.ToolResult,
case tools.BashToolName: case tools.BashToolName:
resultContent = fmt.Sprintf("```bash\n%s\n```", resultContent) resultContent = fmt.Sprintf("```bash\n%s\n```", resultContent)
return styles.ForceReplaceBackgroundWithLipgloss( return styles.ForceReplaceBackgroundWithLipgloss(
toMarkdown(resultContent, true, width), toMarkdown(resultContent, width),
t.Background(), t.Background(),
) )
case tools.EditToolName: case tools.EditToolName:
@ -517,7 +523,7 @@ func renderToolResponse(toolCall message.ToolCall, response message.ToolResult,
} }
resultContent = fmt.Sprintf("```%s\n%s\n```", mdFormat, resultContent) resultContent = fmt.Sprintf("```%s\n%s\n```", mdFormat, resultContent)
return styles.ForceReplaceBackgroundWithLipgloss( return styles.ForceReplaceBackgroundWithLipgloss(
toMarkdown(resultContent, true, width), toMarkdown(resultContent, width),
t.Background(), t.Background(),
) )
case tools.GlobToolName: case tools.GlobToolName:
@ -537,7 +543,7 @@ func renderToolResponse(toolCall message.ToolCall, response message.ToolResult,
} }
resultContent = fmt.Sprintf("```%s\n%s\n```", ext, truncateHeight(metadata.Content, maxResultHeight)) resultContent = fmt.Sprintf("```%s\n%s\n```", ext, truncateHeight(metadata.Content, maxResultHeight))
return styles.ForceReplaceBackgroundWithLipgloss( return styles.ForceReplaceBackgroundWithLipgloss(
toMarkdown(resultContent, true, width), toMarkdown(resultContent, width),
t.Background(), t.Background(),
) )
case tools.WriteToolName: case tools.WriteToolName:
@ -553,7 +559,7 @@ func renderToolResponse(toolCall message.ToolCall, response message.ToolResult,
} }
resultContent = fmt.Sprintf("```%s\n%s\n```", ext, truncateHeight(params.Content, maxResultHeight)) resultContent = fmt.Sprintf("```%s\n%s\n```", ext, truncateHeight(params.Content, maxResultHeight))
return styles.ForceReplaceBackgroundWithLipgloss( return styles.ForceReplaceBackgroundWithLipgloss(
toMarkdown(resultContent, true, width), toMarkdown(resultContent, width),
t.Background(), t.Background(),
) )
case tools.BatchToolName: case tools.BatchToolName:
@ -591,7 +597,7 @@ func renderToolResponse(toolCall message.ToolCall, response message.ToolResult,
default: default:
resultContent = fmt.Sprintf("```text\n%s\n```", resultContent) resultContent = fmt.Sprintf("```text\n%s\n```", resultContent)
return styles.ForceReplaceBackgroundWithLipgloss( return styles.ForceReplaceBackgroundWithLipgloss(
toMarkdown(resultContent, true, width), toMarkdown(resultContent, width),
t.Background(), t.Background(),
) )
} }
@ -605,7 +611,7 @@ func renderToolMessage(
nested bool, nested bool,
width int, width int,
position int, position int,
) uiMessage { ) string {
if nested { if nested {
width = width - 3 width = width - 3
} }
@ -634,13 +640,7 @@ func renderToolMessage(
Render(fmt.Sprintf("%s", toolAction)) Render(fmt.Sprintf("%s", toolAction))
content := style.Render(lipgloss.JoinHorizontal(lipgloss.Left, toolNameText, progressText)) content := style.Render(lipgloss.JoinHorizontal(lipgloss.Left, toolNameText, progressText))
toolMsg := uiMessage{ return content
messageType: toolMessageType,
// position: position,
// height: lipgloss.Height(content),
content: content,
}
return toolMsg
} }
params := renderToolParams(width-1-lipgloss.Width(toolNameText), toolCall) params := renderToolParams(width-1-lipgloss.Width(toolNameText), toolCall)
@ -702,11 +702,5 @@ func renderToolMessage(
parts..., parts...,
) )
} }
toolMsg := uiMessage{ return content
messageType: toolMessageType,
// position: position,
// height: lipgloss.Height(content),
content: content,
}
return toolMsg
} }

View file

@ -1,8 +1,6 @@
package chat package chat
import ( import (
"fmt"
"math"
"time" "time"
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
@ -20,18 +18,11 @@ import (
"github.com/sst/opencode/pkg/client" "github.com/sst/opencode/pkg/client"
) )
type cacheItem struct {
width int
content []uiMessage
}
type messagesCmp struct { type messagesCmp struct {
app *app.App app *app.App
width, height int width, height int
viewport viewport.Model viewport viewport.Model
uiMessages []uiMessage
currentMsgID string currentMsgID string
cachedContent map[string]cacheItem
spinner spinner.Model spinner spinner.Model
rendering bool rendering bool
attachments viewport.Model attachments viewport.Model
@ -71,17 +62,17 @@ func (m *messagesCmp) Init() tea.Cmd {
} }
func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.renderView() // m.renderView()
var cmds []tea.Cmd var cmds []tea.Cmd
switch msg := msg.(type) { switch msg := msg.(type) {
case dialog.ThemeChangedMsg: case dialog.ThemeChangedMsg:
m.rerender() m.renderView()
return m, nil return m, nil
case ToggleToolMessagesMsg: case ToggleToolMessagesMsg:
m.showToolMessages = !m.showToolMessages m.showToolMessages = !m.showToolMessages
// Clear the cache to force re-rendering of all messages // Clear the cache to force re-rendering of all messages
m.cachedContent = make(map[string]cacheItem) // m.cachedContent = make(map[string]cacheItem)
m.renderView() m.renderView()
return m, nil return m, nil
case state.SessionSelectedMsg: case state.SessionSelectedMsg:
@ -171,93 +162,43 @@ func (m *messagesCmp) IsAgentWorking() bool {
return m.app.PrimaryAgentOLD.IsSessionBusy(m.app.CurrentSessionOLD.ID) return m.app.PrimaryAgentOLD.IsSessionBusy(m.app.CurrentSessionOLD.ID)
} }
func formatTimeDifference(unixTime1, unixTime2 int64) string {
diffSeconds := float64(math.Abs(float64(unixTime2 - unixTime1)))
if diffSeconds < 60 {
return fmt.Sprintf("%.1fs", diffSeconds)
}
minutes := int(diffSeconds / 60)
seconds := int(diffSeconds) % 60
return fmt.Sprintf("%dm%ds", minutes, seconds)
}
func (m *messagesCmp) renderView() { func (m *messagesCmp) renderView() {
m.uiMessages = make([]uiMessage, 0)
baseStyle := styles.BaseStyle()
if m.width == 0 { if m.width == 0 {
return return
} }
t := theme.CurrentTheme()
messages := make([]string, 0)
for _, msg := range m.app.Messages { for _, msg := range m.app.Messages {
switch msg.Role { switch msg.Role {
case client.User: case client.User:
if cache, ok := m.cachedContent[msg.Id]; ok && cache.width == m.width { content := renderUserMessage(
m.uiMessages = append(m.uiMessages, cache.content...)
continue
}
userMsg := renderUserMessage(
msg, msg,
msg.Id == m.currentMsgID,
m.width, m.width,
) )
m.uiMessages = append(m.uiMessages, userMsg) messages = append(messages, content+"\n")
m.cachedContent[msg.Id] = cacheItem{
width: m.width,
content: []uiMessage{userMsg},
}
// pos += userMsg.height + 1 // + 1 for spacing
case client.Assistant: case client.Assistant:
if cache, ok := m.cachedContent[msg.Id]; ok && cache.width == m.width { content := renderAssistantMessage(
m.uiMessages = append(m.uiMessages, cache.content...) msg,
continue m.width,
} m.showToolMessages,
// assistantMessages := renderAssistantMessage( )
// msg, messages = append(messages, content+"\n")
// inx,
// m.app.Messages,
// m.app.MessagesOLD,
// m.currentMsgID,
// m.width,
// pos,
// m.showToolMessages,
// )
// for _, msg := range assistantMessages {
// m.uiMessages = append(m.uiMessages, msg)
// // pos += msg.height + 1 // + 1 for spacing
// }
// m.cachedContent[msg.Id] = cacheItem{
// width: m.width,
// content: assistantMessages,
// }
} }
} }
messages := make([]string, 0) m.viewport.SetContent(
for _, v := range m.uiMessages { styles.ForceReplaceBackgroundWithLipgloss(
messages = append(messages, lipgloss.JoinVertical(lipgloss.Left, v.content), styles.BaseStyle().
baseStyle.
Width(m.width). Width(m.width).
Render( Render(
"", lipgloss.JoinVertical(
lipgloss.Top,
messages...,
),
), ),
) t.Background(),
} ),
// temp, _ := json.MarshalIndent(m.app.State, "", " ")
m.viewport.SetContent(
baseStyle.
Width(m.width).
Render(
// string(temp),
lipgloss.JoinVertical(
lipgloss.Top,
messages...,
),
),
) )
} }
@ -416,13 +357,6 @@ func (m *messagesCmp) initialScreen() string {
) )
} }
func (m *messagesCmp) rerender() {
for _, msg := range m.app.Messages {
delete(m.cachedContent, msg.Id)
}
m.renderView()
}
func (m *messagesCmp) SetSize(width, height int) tea.Cmd { func (m *messagesCmp) SetSize(width, height int) tea.Cmd {
if m.width == width && m.height == height { if m.width == width && m.height == height {
return nil return nil
@ -433,7 +367,7 @@ func (m *messagesCmp) SetSize(width, height int) tea.Cmd {
m.viewport.Height = height - 2 m.viewport.Height = height - 2
m.attachments.Width = width + 40 m.attachments.Width = width + 40
m.attachments.Height = 3 m.attachments.Height = 3
m.rerender() m.renderView()
return nil return nil
} }
@ -453,7 +387,7 @@ func (m *messagesCmp) Reload(session *session.Session) tea.Cmd {
if len(m.app.Messages) > 0 { if len(m.app.Messages) > 0 {
m.currentMsgID = m.app.Messages[len(m.app.Messages)-1].Id m.currentMsgID = m.app.Messages[len(m.app.Messages)-1].Id
} }
delete(m.cachedContent, m.currentMsgID) // delete(m.cachedContent, m.currentMsgID)
m.rendering = true m.rendering = true
return func() tea.Msg { return func() tea.Msg {
m.renderView() m.renderView()
@ -476,18 +410,19 @@ func NewMessagesCmp(app *app.App) tea.Model {
FPS: time.Second / 3, FPS: time.Second / 3,
} }
s := spinner.New(spinner.WithSpinner(customSpinner)) s := spinner.New(spinner.WithSpinner(customSpinner))
vp := viewport.New(0, 0) vp := viewport.New(0, 0)
attachmets := viewport.New(0, 0) attachments := viewport.New(0, 0)
vp.KeyMap.PageUp = messageKeys.PageUp vp.KeyMap.PageUp = messageKeys.PageUp
vp.KeyMap.PageDown = messageKeys.PageDown vp.KeyMap.PageDown = messageKeys.PageDown
vp.KeyMap.HalfPageUp = messageKeys.HalfPageUp vp.KeyMap.HalfPageUp = messageKeys.HalfPageUp
vp.KeyMap.HalfPageDown = messageKeys.HalfPageDown vp.KeyMap.HalfPageDown = messageKeys.HalfPageDown
return &messagesCmp{ return &messagesCmp{
app: app, app: app,
cachedContent: make(map[string]cacheItem),
viewport: vp, viewport: vp,
spinner: s, spinner: s,
attachments: attachmets, attachments: attachments,
showToolMessages: true, showToolMessages: true,
} }
} }

View file

@ -285,7 +285,8 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
a.app.Session = &sessionInfo a.app.Session = &sessionInfo
} }
return a, nil
return a.updateAllPages(state.StateUpdatedMsg{State: a.app.State})
} }
if parts[0] == "session" && parts[1] == "message" { if parts[0] == "session" && parts[1] == "message" {
@ -303,7 +304,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.Id == messageId { if m.Id == messageId {
a.app.Messages[i] = message a.app.Messages[i] = message
slog.Debug("Updated message", "message", message) slog.Debug("Updated message", "message", message)
return a, nil return a.updateAllPages(state.StateUpdatedMsg{State: a.app.State})
} }
} }
@ -316,7 +317,8 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// a.app.CurrentSession.Cost += message.Cost // a.app.CurrentSession.Cost += message.Cost
// a.app.CurrentSession.UpdatedAt = message.CreatedAt // a.app.CurrentSession.UpdatedAt = message.CreatedAt
} }
return a, nil
return a.updateAllPages(state.StateUpdatedMsg{State: a.app.State})
} }
// log key and content // log key and content