@@ -8,6 +8,7 @@ import (
8 "github.com/charmbracelet/lipgloss/v2"
8 "github.com/charmbracelet/lipgloss/v2"
9 "github.com/opencode-ai/opencode/internal/app"
9 "github.com/opencode-ai/opencode/internal/app"
10 "github.com/opencode-ai/opencode/internal/message"
10 "github.com/opencode-ai/opencode/internal/message"
11+ "github.com/opencode-ai/opencode/internal/pubsub"
11 "github.com/opencode-ai/opencode/internal/session"
12 "github.com/opencode-ai/opencode/internal/session"
12 "github.com/opencode-ai/opencode/internal/tui/components/chat/messages"
13 "github.com/opencode-ai/opencode/internal/tui/components/chat/messages"
13 "github.com/opencode-ai/opencode/internal/tui/components/core/list"
14 "github.com/opencode-ai/opencode/internal/tui/components/core/list"
@@ -25,8 +26,9 @@ type messageListCmp struct {
25 app *app.App
26 app *app.App
26 width, height int
27 width, height int
27 session session.Session
28 session session.Session
28− messages []util.Model
29 listCmp list.ListModel
29 listCmp list.ListModel
31+ lastUserMessageTime int64
32func NewMessagesListCmp(app *app.App) MessageListCmp {
34func NewMessagesListCmp(app *app.App) MessageListCmp {
@@ -54,6 +56,12 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
54 return m, cmd
56 return m, cmd
56 return m, nil
58 return m, nil
59+ case SessionClearedMsg:
60+ m.session = session.Session{}
61+ return m, m.listCmp.SetItems([]util.Model{})
63+ case pubsub.Event[message.Message]:
64+ return m, m.handleMessageEvent(msg)
58 var cmds []tea.Cmd
66 var cmds []tea.Cmd
59 u, cmd := m.listCmp.Update(msg)
67 u, cmd := m.listCmp.Update(msg)
@@ -64,19 +72,91 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
66func (m *messageListCmp) View() string {
74func (m *messageListCmp) View() string {
75+ if len(m.listCmp.Items()) == 0 {
76+ return initialScreen()
67 return lipgloss.JoinVertical(lipgloss.Left, m.listCmp.View())
78 return lipgloss.JoinVertical(lipgloss.Left, m.listCmp.View())
70−// GetSize implements MessageListCmp.
81+func (m *messageListCmp) handleChildSession(event pubsub.Event[message.Message]) {
71−func (m *messageListCmp) GetSize() (int, int) {
82+ // TODO: update the agent tool message with the changes
72− return m.width, m.height
75−// SetSize implements MessageListCmp.
85+func (m *messageListCmp) handleMessageEvent(event pubsub.Event[message.Message]) tea.Cmd {
87+ case pubsub.CreatedEvent:
88+ if event.Payload.SessionID != m.session.ID {
89+ m.handleChildSession(event)
91+ messageExists := false
92+ // more likely to be at the end of the list
93+ items := m.listCmp.Items()
94+ for i := len(items) - 1; i >= 0; i-- {
95+ msg := items[i].(messages.MessageCmp)
96+ if msg.GetMessage().ID == event.Payload.ID {
104+ switch event.Payload.Role {
106+ return m.handleNewUserMessage(event.Payload)
107+ case message.Assistant:
108+ return m.handleNewAssistantMessage(event.Payload)
110+ // TODO: handle tools
111+ case pubsub.UpdatedEvent:
112+ return m.handleUpdateAssistantMessage(event.Payload)
117+func (m *messageListCmp) handleNewUserMessage(msg message.Message) tea.Cmd {
118+ m.lastUserMessageTime = msg.CreatedAt
119+ return m.listCmp.AppendItem(messages.NewMessageCmp(msg))
122+func (m *messageListCmp) handleUpdateAssistantMessage(msg message.Message) tea.Cmd {
123+ // Simple update the content
124+ items := m.listCmp.Items()
125+ lastItem := items[len(items)-1].(messages.MessageCmp)
126+ // TODO:handle tool calls
127+ if lastItem.GetMessage().ID != msg.ID {
130+ // for now just updet the last message
131+ if len(msg.ToolCalls()) == 0 || msg.Content().Text != "" || msg.IsThinking() {
132+ m.listCmp.UpdateItem(
134+ messages.NewMessageCmp(
136+ messages.WithLastUserMessageTime(time.Unix(m.lastUserMessageTime, 0)),
76−func (m *messageListCmp) SetSize(width int, height int) tea.Cmd {
143+func (m *messageListCmp) handleNewAssistantMessage(msg message.Message) tea.Cmd {
77− m.width = width
144+ var cmds []tea.Cmd
78− m.height = height - 1
145+ // Only add assistant messages if they don't have tool calls or there is some content
79− return m.listCmp.SetSize(width, height-1)
146+ if len(msg.ToolCalls()) == 0 || msg.Content().Text != "" || msg.IsThinking() {
147+ cmd := m.listCmp.AppendItem(
148+ messages.NewMessageCmp(
150+ messages.WithLastUserMessageTime(time.Unix(m.lastUserMessageTime, 0)),
153+ cmds = append(cmds, cmd)
155+ for _, tc := range msg.ToolCalls() {
156+ cmd := m.listCmp.AppendItem(messages.NewToolCallCmp(tc))
157+ cmds = append(cmds, cmd)
159+ return tea.Batch(cmds...)
82func (m *messageListCmp) SetSession(session session.Session) tea.Cmd {
162func (m *messageListCmp) SetSession(session session.Session) tea.Cmd {
@@ -88,8 +168,8 @@ func (m *messageListCmp) SetSession(session session.Session) tea.Cmd {
88 if err != nil {
168 if err != nil {
89 return util.ReportError(err)
169 return util.ReportError(err)
91− m.messages = make([]util.Model, 0)
171+ uiMessages := make([]util.Model, 0)
92− lastUserMessageTime := sessionMessages[0].CreatedAt
172+ m.lastUserMessageTime = sessionMessages[0].CreatedAt
93 toolResultMap := make(map[string]message.ToolResult)
173 toolResultMap := make(map[string]message.ToolResult)
94 // first pass to get all tool results
174 // first pass to get all tool results
95 for _, msg := range sessionMessages {
175 for _, msg := range sessionMessages {
@@ -100,12 +180,18 @@ func (m *messageListCmp) SetSession(session session.Session) tea.Cmd {
100 for _, msg := range sessionMessages {
180 for _, msg := range sessionMessages {
101 switch msg.Role {
181 switch msg.Role {
102 case message.User:
182 case message.User:
103− lastUserMessageTime = msg.CreatedAt
183+ m.lastUserMessageTime = msg.CreatedAt
104− m.messages = append(m.messages, messages.NewMessageCmp(msg))
184+ uiMessages = append(uiMessages, messages.NewMessageCmp(msg))
105 case message.Assistant:
185 case message.Assistant:
106 // Only add assistant messages if they don't have tool calls or there is some content
186 // Only add assistant messages if they don't have tool calls or there is some content
107 if len(msg.ToolCalls()) == 0 || msg.Content().Text != "" || msg.IsThinking() {
187 if len(msg.ToolCalls()) == 0 || msg.Content().Text != "" || msg.IsThinking() {
188+ uiMessages = append(
190+ messages.NewMessageCmp(
108− m.messages = append(m.messages, messages.NewMessageCmp(msg, messages.WithLastUserMessageTime(time.Unix(lastUserMessageTime, 0))))
192+ messages.WithLastUserMessageTime(time.Unix(m.lastUserMessageTime, 0)),
110 for _, tc := range msg.ToolCalls() {
196 for _, tc := range msg.ToolCalls() {
111 options := []messages.ToolCallOption{}
197 options := []messages.ToolCallOption{}
@@ -115,10 +201,21 @@ func (m *messageListCmp) SetSession(session session.Session) tea.Cmd {
115 if msg.FinishPart().Reason == message.FinishReasonCanceled {
201 if msg.FinishPart().Reason == message.FinishReasonCanceled {
116 options = append(options, messages.WithToolCallCancelled())
202 options = append(options, messages.WithToolCallCancelled())
118− m.messages = append(m.messages, messages.NewToolCallCmp(tc, options...))
204+ uiMessages = append(uiMessages, messages.NewToolCallCmp(tc, options...))
122− m.listCmp.SetItems(m.messages)
208+ return m.listCmp.SetItems(uiMessages)
211+// GetSize implements MessageListCmp.
212+func (m *messageListCmp) GetSize() (int, int) {
213+ return m.width, m.height
216+// SetSize implements MessageListCmp.
217+func (m *messageListCmp) SetSize(width int, height int) tea.Cmd {
219+ m.height = height - 1
220+ return m.listCmp.SetSize(width, height-1)