Skip to content

Commit cda41d5

Browse files
authored
Merge pull request #755 from rumpl/tool-rendering
Create a component for each builtin tool that we support
2 parents b4a8b71 + 6aa76a8 commit cda41d5

File tree

33 files changed

+1346
-755
lines changed

33 files changed

+1346
-755
lines changed

pkg/tools/builtin/cmd_unix.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ func platformSpecificSysProcAttr() *syscall.SysProcAttr {
1717
}
1818
}
1919

20-
func createProcessGroup(proc *os.Process) (*processGroup, error) {
20+
func createProcessGroup(_ *os.Process) (*processGroup, error) {
2121
return &processGroup{}, nil
2222
}
2323

24-
func kill(proc *os.Process, pg *processGroup) error {
24+
func kill(proc *os.Process, _ *processGroup) error {
2525
return syscall.Kill(-proc.Pid, syscall.SIGTERM)
2626
}

pkg/tui/components/message/message.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,7 @@ func (mv *messageModel) Render(width int) string {
7878
case types.MessageTypeSpinner:
7979
return mv.spinner.View()
8080
case types.MessageTypeUser:
81-
if rendered, err := markdown.NewRenderer(width - len(styles.UserMessageBorderStyle.Render(""))).Render(msg.Content); err == nil {
82-
return styles.UserMessageBorderStyle.Render(strings.TrimRight(rendered, "\n\r\t "))
83-
}
84-
85-
return msg.Content
81+
return styles.UserMessageBorderStyle.Width(width - 1).Render(msg.Content)
8682
case types.MessageTypeAssistant:
8783
if msg.Content == "" {
8884
return mv.spinner.View()
@@ -113,8 +109,6 @@ func (mv *messageModel) Render(width int) string {
113109
return strings.TrimRight(rendered, "\n\r\t ")
114110
}
115111
return msg.Content
116-
case types.MessageTypeSeparator:
117-
return styles.MutedStyle.Render("•" + strings.Repeat("─", mv.width-3) + "•")
118112
case types.MessageTypeCancelled:
119113
return styles.WarningStyle.Render("⚠ stream cancelled ⚠")
120114
case types.MessageTypeWelcome:

pkg/tui/components/messages/messages.go

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ import (
1919
"github.com/docker/cagent/pkg/tui/components/message"
2020
"github.com/docker/cagent/pkg/tui/components/notification"
2121
"github.com/docker/cagent/pkg/tui/components/tool"
22+
"github.com/docker/cagent/pkg/tui/components/tool/editfile"
2223
"github.com/docker/cagent/pkg/tui/core"
2324
"github.com/docker/cagent/pkg/tui/core/layout"
25+
"github.com/docker/cagent/pkg/tui/service"
2426
"github.com/docker/cagent/pkg/tui/styles"
2527
"github.com/docker/cagent/pkg/tui/types"
2628
)
@@ -46,7 +48,6 @@ type Model interface {
4648
AddUserMessage(content string) tea.Cmd
4749
AddErrorMessage(content string) tea.Cmd
4850
AddAssistantMessage() tea.Cmd
49-
AddSeparatorMessage() tea.Cmd
5051
AddCancelledMessage() tea.Cmd
5152
AddWelcomeMessage(content string) tea.Cmd
5253
AddOrUpdateToolCall(agentName string, toolCall tools.ToolCall, toolDef tools.Tool, status types.ToolStatus) tea.Cmd
@@ -55,7 +56,6 @@ type Model interface {
5556
AddShellOutputMessage(content string) tea.Cmd
5657

5758
ScrollToBottom() tea.Cmd
58-
IsAtBottom() bool
5959
}
6060

6161
// renderedItem represents a cached rendered message with position information
@@ -112,19 +112,30 @@ type model struct {
112112

113113
selection selectionState
114114

115-
splitDiffView bool
115+
sessionState *service.SessionState
116116

117117
xPos, yPos int
118118
}
119119

120120
// New creates a new message list component
121-
func New(a *app.App) Model {
121+
func New(a *app.App, sessionState *service.SessionState) Model {
122122
return &model{
123123
width: 120,
124124
height: 24,
125125
app: a,
126126
renderedItems: make(map[int]renderedItem),
127-
splitDiffView: true,
127+
sessionState: sessionState,
128+
}
129+
}
130+
131+
// NewScrollableView creates a simple scrollable view for displaying messages in dialogs
132+
// This is a lightweight version that doesn't require app or session state management
133+
func NewScrollableView(width, height int, sessionState *service.SessionState) Model {
134+
return &model{
135+
width: width,
136+
height: height,
137+
renderedItems: make(map[int]renderedItem),
138+
sessionState: sessionState,
128139
}
129140
}
130141

@@ -220,19 +231,11 @@ func (m *model) Update(msg tea.Msg) (layout.Model, tea.Cmd) {
220231
}
221232
return m, nil
222233

223-
case tool.ToggleDiffViewMsg:
224-
m.splitDiffView = !m.splitDiffView
225-
226-
var cmds []tea.Cmd
227-
for i, view := range m.views {
228-
updatedView, cmd := view.Update(tool.ToggleDiffViewMsg{})
229-
m.views[i] = updatedView
230-
cmds = append(cmds, cmd)
231-
}
234+
case editfile.ToggleDiffViewMsg:
235+
m.sessionState.ToggleSplitDiffView()
232236

233237
m.invalidateAllItems()
234-
235-
return m, tea.Batch(cmds...)
238+
return m, nil
236239

237240
case tea.KeyPressMsg:
238241
switch msg.String() {
@@ -403,14 +406,16 @@ func (m *model) shouldCacheMessage(index int) bool {
403406

404407
switch msg.Type {
405408
case types.MessageTypeToolCall:
406-
return msg.ToolStatus == types.ToolStatusCompleted || msg.ToolStatus == types.ToolStatusError
409+
return msg.ToolStatus == types.ToolStatusCompleted ||
410+
msg.ToolStatus == types.ToolStatusError ||
411+
msg.ToolStatus == types.ToolStatusConfirmation
407412
case types.MessageTypeToolResult:
408413
return true
409414
case types.MessageTypeAssistant, types.MessageTypeAssistantReasoning:
410415
// Only cache assistant messages that have content (completed streaming)
411416
// Empty assistant messages have spinners and need constant re-rendering
412417
return strings.Trim(msg.Content, "\r\n\t ") != ""
413-
case types.MessageTypeUser, types.MessageTypeSeparator:
418+
case types.MessageTypeUser:
414419
// Always cache static content
415420
return true
416421
default:
@@ -493,8 +498,8 @@ func (m *model) invalidateAllItems() {
493498
m.totalHeight = 0
494499
}
495500

496-
// IsAtBottom returns true if the viewport is at the bottom
497-
func (m *model) IsAtBottom() bool {
501+
// isAtBottom returns true if the viewport is at the bottom
502+
func (m *model) isAtBottom() bool {
498503
if len(m.messages) == 0 {
499504
return true
500505
}
@@ -504,11 +509,6 @@ func (m *model) IsAtBottom() bool {
504509
return m.scrollOffset >= maxScrollOffset
505510
}
506511

507-
// isAtBottom is kept as a private method for internal use
508-
func (m *model) isAtBottom() bool {
509-
return m.IsAtBottom()
510-
}
511-
512512
// AddUserMessage adds a user message to the chat
513513
func (m *model) AddUserMessage(content string) tea.Cmd {
514514
return m.addMessage(&types.Message{
@@ -563,20 +563,6 @@ func (m *model) addMessage(msg *types.Message) tea.Cmd {
563563
return tea.Batch(cmds...)
564564
}
565565

566-
// AddSeparatorMessage adds a separator message to the chat
567-
func (m *model) AddSeparatorMessage() tea.Cmd {
568-
m.removeSpinner()
569-
msg := types.Message{
570-
Type: types.MessageTypeSeparator,
571-
}
572-
m.messages = append(m.messages, msg)
573-
574-
view := m.createMessageView(&msg)
575-
m.views = append(m.views, view)
576-
577-
return view.Init()
578-
}
579-
580566
// AddCancelledMessage adds a cancellation indicator to the chat
581567
func (m *model) AddCancelledMessage() tea.Cmd {
582568
msg := types.Message{
@@ -707,7 +693,7 @@ func (m *model) ScrollToBottom() tea.Cmd {
707693
}
708694

709695
func (m *model) createToolCallView(msg *types.Message) layout.Model {
710-
view := tool.New(msg, m.app, markdown.NewRenderer(m.width), m.splitDiffView)
696+
view := tool.New(msg, markdown.NewRenderer(m.width), m.sessionState)
711697
view.SetSize(m.width, 0)
712698
return view
713699
}

pkg/tui/components/sidebar/sidebar.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import (
1111

1212
"github.com/docker/cagent/pkg/runtime"
1313
"github.com/docker/cagent/pkg/tools"
14-
"github.com/docker/cagent/pkg/tui/components/todo"
14+
"github.com/docker/cagent/pkg/tui/components/tool/todotool"
1515
"github.com/docker/cagent/pkg/tui/core/layout"
16+
"github.com/docker/cagent/pkg/tui/service"
1617
"github.com/docker/cagent/pkg/tui/styles"
1718
)
1819

@@ -40,20 +41,20 @@ type model struct {
4041
width int
4142
height int
4243
usage *runtime.Usage
43-
todoComp *todo.Component
44+
todoComp *todotool.SidebarComponent
4445
working bool
4546
mcpInit bool
4647
spinner spinner.Model
4748
mode Mode
4849
sessionTitle string
4950
}
5051

51-
func New() Model {
52+
func New(manager *service.TodoManager) Model {
5253
return &model{
5354
width: 20,
5455
height: 24,
5556
usage: &runtime.Usage{},
56-
todoComp: todo.NewComponent(),
57+
todoComp: todotool.NewSidebarComponent(manager),
5758
spinner: spinner.New(spinner.WithSpinner(spinner.Dot)),
5859
sessionTitle: "New session",
5960
}
@@ -142,13 +143,17 @@ func (m *model) View() string {
142143

143144
func (m *model) horizontalView() string {
144145
pwd := getCurrentWorkingDirectory()
146+
147+
wi := m.workingIndicator()
148+
titleGapWidth := m.width - lipgloss.Width(m.sessionTitle) - lipgloss.Width(wi) - 2
149+
title := fmt.Sprintf("%s%*s%s", m.sessionTitle, titleGapWidth, "", m.workingIndicator())
150+
145151
gapWidth := m.width - lipgloss.Width(pwd) - lipgloss.Width(m.tokenUsage()) - 2
146-
title := m.sessionTitle + " " + m.workingIndicator()
147152
return lipgloss.JoinVertical(lipgloss.Top, title, fmt.Sprintf("%s%*s%s", styles.MutedStyle.Render(pwd), gapWidth, "", m.tokenUsage()))
148153
}
149154

150155
func (m *model) verticalView() string {
151-
topContent := m.sessionTitle
156+
topContent := m.sessionTitle + "\n"
152157

153158
if pwd := getCurrentWorkingDirectory(); pwd != "" {
154159
topContent += styles.MutedStyle.Render(pwd) + "\n\n"

pkg/tui/components/todo/todo.go

Lines changed: 0 additions & 136 deletions
This file was deleted.

0 commit comments

Comments
 (0)