Skip to content
This repository was archived by the owner on Sep 18, 2025. It is now read-only.

Commit 25e9651

Browse files
committed
add anim to the messages
1 parent abbbc05 commit 25e9651

8 files changed

Lines changed: 257 additions & 70 deletions

File tree

internal/tui/components/anim/anim.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -216,16 +216,18 @@ func (a anim) View() string {
216216
b.WriteRune(c.currentValue)
217217
}
218218

219-
textStyle := styles.BaseStyle().
220-
Foreground(t.Text())
221-
222-
for _, c := range a.labelChars {
223-
b.WriteString(
224-
textStyle.Render(string(c.currentValue)),
225-
)
219+
if len(a.label) > 1 {
220+
textStyle := styles.BaseStyle().
221+
Foreground(t.Text())
222+
for _, c := range a.labelChars {
223+
b.WriteString(
224+
textStyle.Render(string(c.currentValue)),
225+
)
226+
}
227+
return b.String() + textStyle.Render(a.ellipsis.View())
226228
}
227229

228-
return b.String() + textStyle.Render(a.ellipsis.View())
230+
return b.String()
229231
}
230232

231233
func makeGradientRamp(length int) []color.Color {

internal/tui/components/chat/chat.go

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"sort"
66

77
"github.com/charmbracelet/lipgloss/v2"
8-
"github.com/charmbracelet/x/ansi"
98
"github.com/opencode-ai/opencode/internal/config"
109
"github.com/opencode-ai/opencode/internal/message"
1110
"github.com/opencode-ai/opencode/internal/session"
@@ -25,26 +24,24 @@ type SessionClearedMsg struct{}
2524

2625
type EditorFocusMsg bool
2726

28-
func header(width int) string {
27+
func header() string {
2928
return lipgloss.JoinVertical(
3029
lipgloss.Top,
31-
logo(width),
32-
repo(width),
30+
logo(),
31+
repo(),
3332
"",
34-
cwd(width),
33+
cwd(),
3534
)
3635
}
3736

38-
func lspsConfigured(width int) string {
37+
func lspsConfigured() string {
3938
cfg := config.Get()
4039
title := "LSP Configuration"
41-
title = ansi.Truncate(title, width, "…")
4240

4341
t := theme.CurrentTheme()
4442
baseStyle := styles.BaseStyle()
4543

4644
lsps := baseStyle.
47-
Width(width).
4845
Foreground(t.Primary()).
4946
Bold(true).
5047
Render(title)
@@ -64,15 +61,13 @@ func lspsConfigured(width int) string {
6461
Render(fmt.Sprintf("• %s", name))
6562

6663
cmd := lsp.Command
67-
cmd = ansi.Truncate(cmd, width-lipgloss.Width(lspName)-3, "…")
6864

6965
lspPath := baseStyle.
7066
Foreground(t.TextMuted()).
7167
Render(fmt.Sprintf(" (%s)", cmd))
7268

7369
lspViews = append(lspViews,
7470
baseStyle.
75-
Width(width).
7671
Render(
7772
lipgloss.JoinHorizontal(
7873
lipgloss.Left,
@@ -84,7 +79,6 @@ func lspsConfigured(width int) string {
8479
}
8580

8681
return baseStyle.
87-
Width(width).
8882
Render(
8983
lipgloss.JoinVertical(
9084
lipgloss.Left,
@@ -97,7 +91,7 @@ func lspsConfigured(width int) string {
9791
)
9892
}
9993

100-
func logo(width int) string {
94+
func logo() string {
10195
logo := fmt.Sprintf("%s %s", styles.OpenCodeIcon, "OpenCode")
10296
t := theme.CurrentTheme()
10397
baseStyle := styles.BaseStyle()
@@ -108,7 +102,6 @@ func logo(width int) string {
108102

109103
return baseStyle.
110104
Bold(true).
111-
Width(width).
112105
Render(
113106
lipgloss.JoinHorizontal(
114107
lipgloss.Left,
@@ -119,22 +112,33 @@ func logo(width int) string {
119112
)
120113
}
121114

122-
func repo(width int) string {
115+
func repo() string {
123116
repo := "https://github.com/opencode-ai/opencode"
124117
t := theme.CurrentTheme()
125118

126119
return styles.BaseStyle().
127120
Foreground(t.TextMuted()).
128-
Width(width).
129121
Render(repo)
130122
}
131123

132-
func cwd(width int) string {
124+
func cwd() string {
133125
cwd := fmt.Sprintf("cwd: %s", config.WorkingDirectory())
134126
t := theme.CurrentTheme()
135127

136128
return styles.BaseStyle().
137129
Foreground(t.TextMuted()).
138-
Width(width).
139130
Render(cwd)
140131
}
132+
133+
func initialScreen() string {
134+
baseStyle := styles.BaseStyle()
135+
136+
return baseStyle.Render(
137+
lipgloss.JoinVertical(
138+
lipgloss.Top,
139+
header(),
140+
"",
141+
lspsConfigured(),
142+
),
143+
)
144+
}

internal/tui/components/chat/list.go

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ func (m *messagesCmp) View() string {
282282
Width(m.width).
283283
Height(m.height - 1).
284284
Render(
285-
m.initialScreen(),
285+
initialScreen(),
286286
)
287287

288288
return baseStyle.
@@ -400,19 +400,6 @@ func (m *messagesCmp) help() string {
400400
Render(text)
401401
}
402402

403-
func (m *messagesCmp) initialScreen() string {
404-
baseStyle := styles.BaseStyle()
405-
406-
return baseStyle.Width(m.width).Render(
407-
lipgloss.JoinVertical(
408-
lipgloss.Top,
409-
header(m.width),
410-
"",
411-
lspsConfigured(m.width),
412-
),
413-
)
414-
}
415-
416403
func (m *messagesCmp) rerender() {
417404
for _, msg := range m.messages {
418405
delete(m.cachedContent, msg.ID)

internal/tui/components/chat/list_v2.go

Lines changed: 114 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/charmbracelet/lipgloss/v2"
99
"github.com/opencode-ai/opencode/internal/app"
1010
"github.com/opencode-ai/opencode/internal/message"
11+
"github.com/opencode-ai/opencode/internal/pubsub"
1112
"github.com/opencode-ai/opencode/internal/session"
1213
"github.com/opencode-ai/opencode/internal/tui/components/chat/messages"
1314
"github.com/opencode-ai/opencode/internal/tui/components/core/list"
@@ -25,8 +26,9 @@ type messageListCmp struct {
2526
app *app.App
2627
width, height int
2728
session session.Session
28-
messages []util.Model
2929
listCmp list.ListModel
30+
31+
lastUserMessageTime int64
3032
}
3133

3234
func NewMessagesListCmp(app *app.App) MessageListCmp {
@@ -54,6 +56,12 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
5456
return m, cmd
5557
}
5658
return m, nil
59+
case SessionClearedMsg:
60+
m.session = session.Session{}
61+
return m, m.listCmp.SetItems([]util.Model{})
62+
63+
case pubsub.Event[message.Message]:
64+
return m, m.handleMessageEvent(msg)
5765
default:
5866
var cmds []tea.Cmd
5967
u, cmd := m.listCmp.Update(msg)
@@ -64,19 +72,91 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
6472
}
6573

6674
func (m *messageListCmp) View() string {
75+
if len(m.listCmp.Items()) == 0 {
76+
return initialScreen()
77+
}
6778
return lipgloss.JoinVertical(lipgloss.Left, m.listCmp.View())
6879
}
6980

70-
// GetSize implements MessageListCmp.
71-
func (m *messageListCmp) GetSize() (int, int) {
72-
return m.width, m.height
81+
func (m *messageListCmp) handleChildSession(event pubsub.Event[message.Message]) {
82+
// TODO: update the agent tool message with the changes
7383
}
7484

75-
// SetSize implements MessageListCmp.
76-
func (m *messageListCmp) SetSize(width int, height int) tea.Cmd {
77-
m.width = width
78-
m.height = height - 1
79-
return m.listCmp.SetSize(width, height-1)
85+
func (m *messageListCmp) handleMessageEvent(event pubsub.Event[message.Message]) tea.Cmd {
86+
switch event.Type {
87+
case pubsub.CreatedEvent:
88+
if event.Payload.SessionID != m.session.ID {
89+
m.handleChildSession(event)
90+
}
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 {
97+
messageExists = true
98+
break
99+
}
100+
}
101+
if messageExists {
102+
return nil
103+
}
104+
switch event.Payload.Role {
105+
case message.User:
106+
return m.handleNewUserMessage(event.Payload)
107+
case message.Assistant:
108+
return m.handleNewAssistantMessage(event.Payload)
109+
}
110+
// TODO: handle tools
111+
case pubsub.UpdatedEvent:
112+
return m.handleUpdateAssistantMessage(event.Payload)
113+
}
114+
return nil
115+
}
116+
117+
func (m *messageListCmp) handleNewUserMessage(msg message.Message) tea.Cmd {
118+
m.lastUserMessageTime = msg.CreatedAt
119+
return m.listCmp.AppendItem(messages.NewMessageCmp(msg))
120+
}
121+
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 {
128+
return nil
129+
}
130+
// for now just updet the last message
131+
if len(msg.ToolCalls()) == 0 || msg.Content().Text != "" || msg.IsThinking() {
132+
m.listCmp.UpdateItem(
133+
len(items)-1,
134+
messages.NewMessageCmp(
135+
msg,
136+
messages.WithLastUserMessageTime(time.Unix(m.lastUserMessageTime, 0)),
137+
),
138+
)
139+
}
140+
return nil
141+
}
142+
143+
func (m *messageListCmp) handleNewAssistantMessage(msg message.Message) tea.Cmd {
144+
var cmds []tea.Cmd
145+
// Only add assistant messages if they don't have tool calls or there is some content
146+
if len(msg.ToolCalls()) == 0 || msg.Content().Text != "" || msg.IsThinking() {
147+
cmd := m.listCmp.AppendItem(
148+
messages.NewMessageCmp(
149+
msg,
150+
messages.WithLastUserMessageTime(time.Unix(m.lastUserMessageTime, 0)),
151+
),
152+
)
153+
cmds = append(cmds, cmd)
154+
}
155+
for _, tc := range msg.ToolCalls() {
156+
cmd := m.listCmp.AppendItem(messages.NewToolCallCmp(tc))
157+
cmds = append(cmds, cmd)
158+
}
159+
return tea.Batch(cmds...)
80160
}
81161

82162
func (m *messageListCmp) SetSession(session session.Session) tea.Cmd {
@@ -88,8 +168,8 @@ func (m *messageListCmp) SetSession(session session.Session) tea.Cmd {
88168
if err != nil {
89169
return util.ReportError(err)
90170
}
91-
m.messages = make([]util.Model, 0)
92-
lastUserMessageTime := sessionMessages[0].CreatedAt
171+
uiMessages := make([]util.Model, 0)
172+
m.lastUserMessageTime = sessionMessages[0].CreatedAt
93173
toolResultMap := make(map[string]message.ToolResult)
94174
// first pass to get all tool results
95175
for _, msg := range sessionMessages {
@@ -100,12 +180,18 @@ func (m *messageListCmp) SetSession(session session.Session) tea.Cmd {
100180
for _, msg := range sessionMessages {
101181
switch msg.Role {
102182
case message.User:
103-
lastUserMessageTime = msg.CreatedAt
104-
m.messages = append(m.messages, messages.NewMessageCmp(msg))
183+
m.lastUserMessageTime = msg.CreatedAt
184+
uiMessages = append(uiMessages, messages.NewMessageCmp(msg))
105185
case message.Assistant:
106186
// Only add assistant messages if they don't have tool calls or there is some content
107187
if len(msg.ToolCalls()) == 0 || msg.Content().Text != "" || msg.IsThinking() {
108-
m.messages = append(m.messages, messages.NewMessageCmp(msg, messages.WithLastUserMessageTime(time.Unix(lastUserMessageTime, 0))))
188+
uiMessages = append(
189+
uiMessages,
190+
messages.NewMessageCmp(
191+
msg,
192+
messages.WithLastUserMessageTime(time.Unix(m.lastUserMessageTime, 0)),
193+
),
194+
)
109195
}
110196
for _, tc := range msg.ToolCalls() {
111197
options := []messages.ToolCallOption{}
@@ -115,10 +201,21 @@ func (m *messageListCmp) SetSession(session session.Session) tea.Cmd {
115201
if msg.FinishPart().Reason == message.FinishReasonCanceled {
116202
options = append(options, messages.WithToolCallCancelled())
117203
}
118-
m.messages = append(m.messages, messages.NewToolCallCmp(tc, options...))
204+
uiMessages = append(uiMessages, messages.NewToolCallCmp(tc, options...))
119205
}
120206
}
121207
}
122-
m.listCmp.SetItems(m.messages)
123-
return nil
208+
return m.listCmp.SetItems(uiMessages)
209+
}
210+
211+
// GetSize implements MessageListCmp.
212+
func (m *messageListCmp) GetSize() (int, int) {
213+
return m.width, m.height
214+
}
215+
216+
// SetSize implements MessageListCmp.
217+
func (m *messageListCmp) SetSize(width int, height int) tea.Cmd {
218+
m.width = width
219+
m.height = height - 1
220+
return m.listCmp.SetSize(width, height-1)
124221
}

0 commit comments

Comments
 (0)