Skip to content

Commit 8f53b06

Browse files
authored
Merge pull request #1427 from dgageot/more-tui-cleanup
More tui cleanup
2 parents a232d08 + fcecc20 commit 8f53b06

File tree

16 files changed

+162
-183
lines changed

16 files changed

+162
-183
lines changed

pkg/app/app.go

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ import (
1313
"github.com/modelcontextprotocol/go-sdk/mcp"
1414

1515
"github.com/docker/cagent/pkg/app/export"
16+
"github.com/docker/cagent/pkg/app/transcript"
1617
"github.com/docker/cagent/pkg/chat"
18+
"github.com/docker/cagent/pkg/cli"
1719
"github.com/docker/cagent/pkg/config/types"
1820
"github.com/docker/cagent/pkg/runtime"
1921
"github.com/docker/cagent/pkg/session"
2022
"github.com/docker/cagent/pkg/tools"
2123
mcptools "github.com/docker/cagent/pkg/tools/mcp"
24+
"github.com/docker/cagent/pkg/tui/messages"
2225
)
2326

2427
type App struct {
@@ -87,18 +90,26 @@ func New(ctx context.Context, rt runtime.Runtime, sess *session.Session, opts ..
8790
return app
8891
}
8992

90-
func (a *App) FirstMessage() *string {
91-
return a.firstMessage
92-
}
93+
func (a *App) SendFirstMessage() tea.Cmd {
94+
if a.firstMessage == nil {
95+
return nil
96+
}
9397

94-
// FirstMessageAttachment returns the attachment path for the first message.
95-
func (a *App) FirstMessageAttachment() string {
96-
return a.firstMessageAttach
97-
}
98+
return func() tea.Msg {
99+
// Use the shared PrepareUserMessage function for consistent attachment handling
100+
userMsg := cli.PrepareUserMessage(context.Background(), a.runtime, *a.firstMessage, a.firstMessageAttach)
98101

99-
// Runtime returns the runtime for this app.
100-
func (a *App) Runtime() runtime.Runtime {
101-
return a.runtime
102+
// If the message has multi-content (attachments), we need to handle it specially
103+
if len(userMsg.Message.MultiContent) > 0 {
104+
return messages.SendAttachmentMsg{
105+
Content: userMsg,
106+
}
107+
}
108+
109+
return messages.SendMsg{
110+
Content: userMsg.Message.Content,
111+
}
112+
}
102113
}
103114

104115
// CurrentAgentCommands returns the commands for the active agent
@@ -475,7 +486,7 @@ func (a *App) CompactSession(additionalPrompt string) {
475486
}
476487

477488
func (a *App) PlainTextTranscript() string {
478-
return transcript(a.session)
489+
return transcript.PlainText(a.session)
479490
}
480491

481492
// SessionStore returns the session store for browsing/loading sessions.
File renamed without changes.

pkg/app/testdata/assistant_message_with_reasoning.golden renamed to pkg/app/transcript/testdata/assistant_message_with_reasoning.golden

File renamed without changes.
File renamed without changes.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package app
1+
package transcript
22

33
import (
44
"encoding/json"
@@ -9,7 +9,7 @@ import (
99
"github.com/docker/cagent/pkg/session"
1010
)
1111

12-
func transcript(sess *session.Session) string {
12+
func PlainText(sess *session.Session) string {
1313
var builder strings.Builder
1414

1515
messages := sess.GetAllMessages()
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package app
1+
package transcript
22

33
import (
44
"testing"
@@ -12,7 +12,7 @@ import (
1212

1313
func TestSimple(t *testing.T) {
1414
sess := session.New(session.WithUserMessage("Hello"))
15-
content := transcript(sess)
15+
content := PlainText(sess)
1616
golden.Assert(t, content, "simple.golden")
1717
}
1818

@@ -27,7 +27,7 @@ func TestAssistantMessage(t *testing.T) {
2727
Content: "Hello to you too",
2828
},
2929
})
30-
content := transcript(sess)
30+
content := PlainText(sess)
3131
golden.Assert(t, content, "assistant_message.golden")
3232
}
3333

@@ -43,7 +43,7 @@ func TestAssistantMessageWithReasoning(t *testing.T) {
4343
ReasoningContent: "Hm....",
4444
},
4545
})
46-
content := transcript(sess)
46+
content := PlainText(sess)
4747
golden.Assert(t, content, "assistant_message_with_reasoning.golden")
4848
}
4949

@@ -71,7 +71,7 @@ func TestToolCalls(t *testing.T) {
7171
Content: ".\n..",
7272
},
7373
})
74-
content := transcript(sess)
74+
content := PlainText(sess)
7575

7676
golden.Assert(t, content, "tool_calls.golden")
7777
}

pkg/tui/components/editor/editor.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/docker/cagent/pkg/tui/components/editor/completions"
2626
"github.com/docker/cagent/pkg/tui/core"
2727
"github.com/docker/cagent/pkg/tui/core/layout"
28+
"github.com/docker/cagent/pkg/tui/messages"
2829
"github.com/docker/cagent/pkg/tui/styles"
2930
)
3031

@@ -55,12 +56,6 @@ type AttachmentPreview struct {
5556
Content string
5657
}
5758

58-
// SendMsg represents a message to send
59-
type SendMsg struct {
60-
Content string // Full content sent to the agent (with file contents expanded)
61-
Attachments map[string]string // Map of filename to content for attachments
62-
}
63-
6459
// Editor represents an input editor component
6560
type Editor interface {
6661
layout.Model
@@ -509,7 +504,7 @@ func (e *editor) resetAndSend(content string) tea.Cmd {
509504
e.textarea.Reset()
510505
e.userTyped = false
511506
e.clearSuggestion()
512-
return core.CmdHandler(SendMsg{Content: content, Attachments: attachments})
507+
return core.CmdHandler(messages.SendMsg{Content: content, Attachments: attachments})
513508
}
514509

515510
// configureNewlineKeybinding sets up the appropriate newline keybinding

pkg/tui/components/messages/messages.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,12 @@ import (
2424
"github.com/docker/cagent/pkg/tui/components/tool/editfile"
2525
"github.com/docker/cagent/pkg/tui/core"
2626
"github.com/docker/cagent/pkg/tui/core/layout"
27+
"github.com/docker/cagent/pkg/tui/messages"
2728
"github.com/docker/cagent/pkg/tui/service"
2829
"github.com/docker/cagent/pkg/tui/styles"
2930
"github.com/docker/cagent/pkg/tui/types"
3031
)
3132

32-
// StreamCancelledMsg notifies components that the stream has been cancelled
33-
type StreamCancelledMsg struct {
34-
ShowMessage bool // Whether to show a cancellation message after cleanup
35-
}
36-
3733
// ToggleHideToolResultsMsg triggers hiding/showing tool results
3834
type ToggleHideToolResultsMsg struct{}
3935

@@ -146,7 +142,7 @@ func (m *model) Update(msg tea.Msg) (layout.Model, tea.Cmd) {
146142
var cmds []tea.Cmd
147143

148144
switch msg := msg.(type) {
149-
case StreamCancelledMsg:
145+
case messages.StreamCancelledMsg:
150146
m.removeSpinner()
151147
m.removePendingToolCallMessages()
152148
return m, nil
@@ -1185,7 +1181,7 @@ func (m *model) removeSpinner() {
11851181
}
11861182

11871183
func (m *model) removePendingToolCallMessages() {
1188-
messages := make([]*types.Message, 0, len(m.messages))
1184+
toolCallMessages := make([]*types.Message, 0, len(m.messages))
11891185
views := make([]layout.Model, 0, len(m.views))
11901186

11911187
for i, msg := range m.messages {
@@ -1194,14 +1190,14 @@ func (m *model) removePendingToolCallMessages() {
11941190
continue
11951191
}
11961192

1197-
messages = append(messages, msg)
1193+
toolCallMessages = append(toolCallMessages, msg)
11981194
if i < len(m.views) {
11991195
views = append(views, m.views[i])
12001196
}
12011197
}
12021198

1203-
if len(messages) != len(m.messages) {
1204-
m.messages = messages
1199+
if len(toolCallMessages) != len(m.messages) {
1200+
m.messages = toolCallMessages
12051201
m.views = views
12061202
m.invalidateAllItems()
12071203
}

pkg/tui/components/sidebar/queue_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func TestQueueSection_SingleMessage(t *testing.T) {
1616
sessionState := &service.SessionState{}
1717
m := New(sessionState).(*model)
1818

19-
m.SetQueuedMessages([]string{"Hello world"})
19+
m.SetQueuedMessages("Hello world")
2020

2121
result := m.queueSection(40)
2222

@@ -39,7 +39,7 @@ func TestQueueSection_MultipleMessages(t *testing.T) {
3939
sessionState := &service.SessionState{}
4040
m := New(sessionState).(*model)
4141

42-
m.SetQueuedMessages([]string{"First", "Second", "Third"})
42+
m.SetQueuedMessages("First", "Second", "Third")
4343

4444
result := m.queueSection(40)
4545

@@ -67,7 +67,7 @@ func TestQueueSection_LongMessageTruncation(t *testing.T) {
6767

6868
// Create a very long message
6969
longMessage := strings.Repeat("x", 100)
70-
m.SetQueuedMessages([]string{longMessage})
70+
m.SetQueuedMessages(longMessage)
7171

7272
result := m.queueSection(30) // Narrow width to force truncation
7373

@@ -91,7 +91,7 @@ func TestQueueSection_InRenderSections(t *testing.T) {
9191
assert.NotContains(t, outputWithoutQueue, "Queue")
9292

9393
// With queued messages, queue section should appear
94-
m.SetQueuedMessages([]string{"Pending task"})
94+
m.SetQueuedMessages("Pending task")
9595
linesWithQueue := m.renderSections(35)
9696
outputWithQueue := strings.Join(linesWithQueue, "\n")
9797
assert.Contains(t, outputWithQueue, "Queue (1)")

0 commit comments

Comments
 (0)