Skip to content

Commit cc4a6aa

Browse files
committed
chore: small ux improvement
1 parent 63acd20 commit cc4a6aa

File tree

3 files changed

+49
-84
lines changed

3 files changed

+49
-84
lines changed

internal/agent/fetch_tool.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func (c *coordinator) fetchTool(_ context.Context, client *http.Client) (fantasy
8282
return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to fetch URL: %s", err)), nil
8383
}
8484

85-
tmpDir, err := os.MkdirTemp("", "crush-fetch-*")
85+
tmpDir, err := os.MkdirTemp(c.cfg.Options.DataDirectory, "crush-fetch-*")
8686
if err != nil {
8787
return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to create temporary directory: %s", err)), nil
8888
}

internal/tui/components/chat/chat.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/charmbracelet/bubbles/v2/key"
1010
tea "github.com/charmbracelet/bubbletea/v2"
1111
"github.com/charmbracelet/crush/internal/agent"
12+
"github.com/charmbracelet/crush/internal/agent/tools"
1213
"github.com/charmbracelet/crush/internal/app"
1314
"github.com/charmbracelet/crush/internal/message"
1415
"github.com/charmbracelet/crush/internal/permission"
@@ -636,7 +637,7 @@ func (m *messageListCmp) convertAssistantMessage(msg message.Message, toolResult
636637
options := m.buildToolCallOptions(tc, msg, toolResultMap)
637638
uiMessages = append(uiMessages, messages.NewToolCallCmp(msg.ID, tc, m.app.Permissions, options...))
638639
// If this tool call is the agent tool, fetch nested tool calls
639-
if tc.Name == agent.AgentToolName {
640+
if tc.Name == agent.AgentToolName || tc.Name == tools.FetchToolName {
640641
agentToolSessionID := m.app.Sessions.CreateAgentToolSessionID(msg.ID, tc.ID)
641642
nestedMessages, _ := m.app.Messages.List(context.Background(), agentToolSessionID)
642643
nestedToolResultMap := m.buildToolResultMap(nestedMessages)

internal/tui/components/chat/messages/renderer.go

Lines changed: 46 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -406,64 +406,44 @@ type fetchRenderer struct {
406406
func (fr fetchRenderer) Render(v *toolCallCmp) string {
407407
t := styles.CurrentTheme()
408408
var params tools.FetchParams
409-
fr.unmarshalParams(v.call.Input, &params)
409+
var args []string
410+
if err := fr.unmarshalParams(v.call.Input, &params); err == nil {
411+
args = newParamBuilder().
412+
addMain(params.URL).
413+
build()
414+
}
410415

411416
prompt := params.Prompt
412417
prompt = strings.ReplaceAll(prompt, "\n", " ")
413418

414-
header := fr.makeHeader(v, "Fetch", v.textWidth())
415-
416-
// Check for error or cancelled states
417-
if v.result.IsError {
418-
message := v.renderToolError()
419-
message = t.S().Base.PaddingLeft(2).Render(message)
420-
return lipgloss.JoinVertical(lipgloss.Left, header, "", message)
421-
}
422-
if v.cancelled {
423-
message := t.S().Base.Foreground(t.FgSubtle).Render("Canceled.")
424-
message = t.S().Base.PaddingLeft(2).Render(message)
425-
return lipgloss.JoinVertical(lipgloss.Left, header, "", message)
426-
}
427-
428-
if v.result.ToolCallID == "" && v.permissionRequested && !v.permissionGranted {
429-
message := t.S().Base.Foreground(t.FgSubtle).Render("Requesting for permission...")
430-
message = t.S().Base.PaddingLeft(2).Render(message)
431-
return lipgloss.JoinVertical(lipgloss.Left, header, "", message)
432-
}
433-
434-
// Show URL and prompt like agent tool shows task
435-
urlTag := t.S().Base.Padding(0, 1).MarginLeft(1).Background(t.BlueLight).Foreground(t.White).Render("URL")
436-
promptTag := t.S().Base.Padding(0, 1).MarginLeft(1).Background(t.Green).Foreground(t.White).Render("Prompt")
437-
438-
// Calculate left gutter width (icon + spacing)
439-
leftGutterWidth := lipgloss.Width(urlTag) + 2 // +2 for " " spacing
440-
441-
// Cap at 120 cols minus left gutter
442-
maxTextWidth := 120 - leftGutterWidth
443-
if v.textWidth()-leftGutterWidth < maxTextWidth {
444-
maxTextWidth = v.textWidth() - leftGutterWidth
419+
header := fr.makeHeader(v, "Fetch", v.textWidth(), args...)
420+
if res, done := earlyState(header, v); v.cancelled && done {
421+
return res
445422
}
446423

447-
urlText := t.S().Muted.Width(maxTextWidth).Render(params.URL)
448-
promptText := t.S().Muted.Width(maxTextWidth).Render(prompt)
449-
424+
taskTag := t.S().Base.Padding(0, 1).MarginLeft(2).Background(t.GreenLight).Foreground(t.White).Render("Prompt")
425+
remainingWidth := v.textWidth() - (lipgloss.Width(taskTag) + 1)
426+
remainingWidth = min(remainingWidth, 120-(lipgloss.Width(taskTag)+1))
427+
prompt = t.S().Muted.Width(remainingWidth).Render(prompt)
450428
header = lipgloss.JoinVertical(
451429
lipgloss.Left,
452430
header,
453431
"",
454-
lipgloss.JoinHorizontal(lipgloss.Left, urlTag, " ", urlText),
455-
"",
456-
lipgloss.JoinHorizontal(lipgloss.Left, promptTag, " ", promptText),
432+
lipgloss.JoinHorizontal(
433+
lipgloss.Left,
434+
taskTag,
435+
" ",
436+
prompt,
437+
),
457438
)
458-
459-
// Show nested tool calls (from sub-agent) in a tree
460439
childTools := tree.Root(header)
440+
461441
for _, call := range v.nestedToolCalls {
442+
call.SetSize(remainingWidth, 1)
462443
childTools.Child(call.View())
463444
}
464-
465445
parts := []string{
466-
childTools.Enumerator(RoundedEnumerator).String(),
446+
childTools.Enumerator(RoundedEnumeratorWithWidth(lipgloss.Width(taskTag) - 2)).String(),
467447
}
468448

469449
if v.result.ToolCallID == "" {
@@ -481,7 +461,6 @@ func (fr fetchRenderer) Render(v *toolCallCmp) string {
481461
if v.result.ToolCallID == "" {
482462
return header
483463
}
484-
485464
body := renderMarkdownContent(v, v.result.Content)
486465
return joinHeaderBody(header, body)
487466
}
@@ -505,39 +484,17 @@ type webFetchRenderer struct {
505484

506485
// Render displays a compact view of web_fetch with just the URL in a link style
507486
func (wfr webFetchRenderer) Render(v *toolCallCmp) string {
508-
t := styles.CurrentTheme()
509-
var params tools.WebFetchParams
510-
wfr.unmarshalParams(v.call.Input, &params)
511-
512-
width := v.textWidth()
513-
if v.isNested {
514-
width -= 4 // Adjust for nested tool call indentation
515-
}
516-
517-
header := wfr.makeHeader(v, "Fetch", width)
518-
if res, done := earlyState(header, v); v.cancelled && done {
519-
return res
520-
}
521-
522-
// Display URL in a subtle, link-like style
523-
urlStyle := t.S().Muted.Foreground(t.Blue).Underline(true)
524-
urlText := urlStyle.Render(params.URL)
525-
526-
header = lipgloss.JoinHorizontal(lipgloss.Left, header, " ", urlText)
527-
528-
// If nested, return header only (no body content)
529-
if v.isNested {
530-
return v.style().Render(header)
531-
}
532-
533-
if v.result.ToolCallID == "" {
534-
v.spinning = true
535-
return lipgloss.JoinHorizontal(lipgloss.Left, header, " ", v.anim.View())
487+
var params tools.FetchParams
488+
var args []string
489+
if err := wfr.unmarshalParams(v.call.Input, &params); err == nil {
490+
args = newParamBuilder().
491+
addMain(params.URL).
492+
build()
536493
}
537494

538-
v.spinning = false
539-
body := renderMarkdownContent(v, v.result.Content)
540-
return joinHeaderBody(header, body)
495+
return wfr.renderWithParams(v, "Fetch", args, func() string {
496+
return renderMarkdownContent(v, v.result.Content)
497+
})
541498
}
542499

543500
// -----------------------------------------------------------------------------
@@ -699,11 +656,17 @@ type agentRenderer struct {
699656
baseRenderer
700657
}
701658

702-
func RoundedEnumerator(children tree.Children, index int) string {
703-
if children.Length()-1 == index {
704-
return " ╰──"
659+
func RoundedEnumeratorWithWidth(width int) tree.Enumerator {
660+
if width == 0 {
661+
width = 2
662+
}
663+
return func(children tree.Children, index int) string {
664+
line := strings.Repeat("─", width)
665+
if children.Length()-1 == index {
666+
return " ╰" + line
667+
}
668+
return " ├" + line
705669
}
706-
return " ├──"
707670
}
708671

709672
// Render displays agent task parameters and result content
@@ -721,7 +684,7 @@ func (tr agentRenderer) Render(v *toolCallCmp) string {
721684
}
722685
taskTag := t.S().Base.Padding(0, 1).MarginLeft(1).Background(t.BlueLight).Foreground(t.White).Render("Task")
723686
remainingWidth := v.textWidth() - lipgloss.Width(header) - lipgloss.Width(taskTag) - 2
724-
remainingWidth = min(remainingWidth, 120-lipgloss.Width(header)-lipgloss.Width(taskTag)-2)
687+
remainingWidth = min(remainingWidth, 120-lipgloss.Width(taskTag)-2)
725688
prompt = t.S().Muted.Width(remainingWidth).Render(prompt)
726689
header = lipgloss.JoinVertical(
727690
lipgloss.Left,
@@ -737,10 +700,11 @@ func (tr agentRenderer) Render(v *toolCallCmp) string {
737700
childTools := tree.Root(header)
738701

739702
for _, call := range v.nestedToolCalls {
703+
call.SetSize(remainingWidth, 1)
740704
childTools.Child(call.View())
741705
}
742706
parts := []string{
743-
childTools.Enumerator(RoundedEnumerator).String(),
707+
childTools.Enumerator(RoundedEnumeratorWithWidth(lipgloss.Width(taskTag) - 2)).String(),
744708
}
745709

746710
if v.result.ToolCallID == "" {
@@ -891,7 +855,7 @@ func renderMarkdownContent(v *toolCallCmp, content string) string {
891855
width := v.textWidth() - 2
892856
width = min(width, 120)
893857

894-
renderer := styles.GetPlainMarkdownRenderer(width - 2)
858+
renderer := styles.GetPlainMarkdownRenderer(width)
895859
rendered, err := renderer.Render(content)
896860
if err != nil {
897861
return renderPlainContent(v, content)
@@ -914,7 +878,7 @@ func renderMarkdownContent(v *toolCallCmp, content string) string {
914878
Render(fmt.Sprintf("… (%d lines)", len(lines)-responseContextHeight)))
915879
}
916880

917-
return style.PaddingLeft(1).PaddingRight(1).Render(strings.Join(out, "\n"))
881+
return style.Render(strings.Join(out, "\n"))
918882
}
919883

920884
func getDigits(n int) int {

0 commit comments

Comments
 (0)