mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-14 03:30:26 +00:00
refactor(ui): improve message spacing and styling consistency
- Add bottom margin to startup header (KVGroup) - Add bottom margin to thinking/reasoning blocks - Fix thinking block footer to appear on new line without extra spacing - Update spawn_subagent tool output to use bash-style formatting - Add blank line after extension startup messages for visual separation
This commit is contained in:
@@ -1586,6 +1586,7 @@ func runInteractiveModeBubbleTea(_ context.Context, appInstance *app.App, modelN
|
||||
for _, msg := range startupExtensionMessages {
|
||||
fmt.Println(msg)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
program := tea.NewProgram(appModel)
|
||||
|
||||
+21
-11
@@ -43,14 +43,13 @@ type UIMessage struct {
|
||||
|
||||
// toolDisplayNames maps raw tool names to human-friendly display names.
|
||||
var toolDisplayNames = map[string]string{
|
||||
"bash": "Bash",
|
||||
"read": "Read",
|
||||
"write": "Write",
|
||||
"edit": "Edit",
|
||||
"grep": "Grep",
|
||||
"find": "Find",
|
||||
"ls": "Ls",
|
||||
"run_shell_cmd": "Bash",
|
||||
"bash": "Bash",
|
||||
"read": "Read",
|
||||
"write": "Write",
|
||||
"edit": "Edit",
|
||||
"grep": "Grep",
|
||||
"find": "Find",
|
||||
"ls": "Ls",
|
||||
}
|
||||
|
||||
// getTheme returns the current theme (helper for compact_renderer.go)
|
||||
@@ -349,16 +348,27 @@ func (r *MessageRenderer) RenderToolMessage(toolName, toolArgs, toolResult strin
|
||||
}
|
||||
|
||||
var icon string
|
||||
iconColor := GetTheme().Success
|
||||
if isError {
|
||||
icon = "×"
|
||||
iconColor = GetTheme().Error
|
||||
} else {
|
||||
icon = "✓"
|
||||
}
|
||||
|
||||
// Style the tool name with color
|
||||
theme := GetTheme()
|
||||
nameColor := theme.Info
|
||||
if isError {
|
||||
nameColor = theme.Error
|
||||
}
|
||||
styledName := lipgloss.NewStyle().Foreground(nameColor).Bold(true).Render(displayName)
|
||||
styledIcon := lipgloss.NewStyle().Foreground(iconColor).Render(icon)
|
||||
|
||||
// Build the content: icon + name + params on first line, then body
|
||||
headerLine := icon + " " + displayName
|
||||
headerLine := styledIcon + " " + styledName
|
||||
if params != "" {
|
||||
headerLine += " " + params
|
||||
headerLine += " " + lipgloss.NewStyle().Foreground(theme.Muted).Render(params)
|
||||
}
|
||||
|
||||
// Get body content
|
||||
@@ -433,7 +443,7 @@ func (r *MessageRenderer) formatToolResult(toolName, result string) string {
|
||||
}
|
||||
|
||||
if strings.Contains(toolName, "bash") || strings.Contains(toolName, "command") ||
|
||||
strings.Contains(toolName, "shell") || toolName == "run_shell_cmd" {
|
||||
strings.Contains(toolName, "shell") {
|
||||
if strings.Contains(result, "<stdout>") || strings.Contains(result, "<stderr>") {
|
||||
return parseBashOutput(result, GetTheme())
|
||||
}
|
||||
|
||||
@@ -832,6 +832,7 @@ func (m *AppModel) PrintStartupInfo() {
|
||||
|
||||
if len(pairs) > 0 {
|
||||
rendered := ty.KVGroup(pairs)
|
||||
rendered = lipgloss.NewStyle().MarginBottom(1).Render(rendered)
|
||||
fmt.Println(rendered)
|
||||
}
|
||||
}
|
||||
|
||||
+12
-3
@@ -537,7 +537,7 @@ func (s *StreamComponent) renderReasoningBlock(reasoning string) string {
|
||||
content := strings.Join(lines, "\n")
|
||||
parts = append(parts, s.ty.Blockquote(content))
|
||||
|
||||
// Duration footer.
|
||||
// Duration footer with indentation.
|
||||
var duration time.Duration
|
||||
if s.reasoningDuration > 0 {
|
||||
duration = s.reasoningDuration
|
||||
@@ -551,11 +551,20 @@ func (s *StreamComponent) renderReasoningBlock(reasoning string) string {
|
||||
} else {
|
||||
durationStr = fmt.Sprintf("%.1fs", duration.Seconds())
|
||||
}
|
||||
footer := s.ty.Small(fmt.Sprintf("Thought for %s", durationStr))
|
||||
footer := lipgloss.NewStyle().PaddingLeft(2).Render(s.ty.Small(fmt.Sprintf("Thought for %s", durationStr)))
|
||||
parts = append(parts, footer)
|
||||
}
|
||||
|
||||
return s.ty.Compose(parts...)
|
||||
// Concatenate parts with newline between blockquote and footer
|
||||
var result string
|
||||
if len(parts) == 1 {
|
||||
result = parts[0]
|
||||
} else if len(parts) == 2 {
|
||||
result = parts[0] + "\n" + parts[1]
|
||||
} else {
|
||||
result = strings.Join(parts, "\n")
|
||||
}
|
||||
return lipgloss.NewStyle().MarginBottom(1).Render(result)
|
||||
}
|
||||
|
||||
// SetThinkingVisible sets whether reasoning blocks are shown or collapsed.
|
||||
|
||||
@@ -46,7 +46,7 @@ func renderToolBody(toolName, toolArgs, toolResult string, width int) string {
|
||||
if body := renderWriteBody(toolArgs, toolResult, width); body != "" {
|
||||
return body
|
||||
}
|
||||
case toolName == "bash" || toolName == "run_shell_cmd" ||
|
||||
case toolName == "bash" || toolName == "grep" || toolName == "find" ||
|
||||
strings.Contains(toolName, "shell") || strings.Contains(toolName, "command"):
|
||||
if body := renderBashBody(toolResult, width); body != "" {
|
||||
return body
|
||||
@@ -777,7 +777,7 @@ func renderToolBodyCompact(toolName, toolArgs, toolResult string, width int) str
|
||||
return renderReadCompact(toolResult)
|
||||
case toolName == "write":
|
||||
return renderWriteCompact(toolArgs)
|
||||
case toolName == "bash" || toolName == "run_shell_cmd" ||
|
||||
case toolName == "bash" || toolName == "grep" || toolName == "find" ||
|
||||
strings.Contains(toolName, "shell") || strings.Contains(toolName, "command"):
|
||||
return renderBashCompact(toolResult, width)
|
||||
case toolName == "spawn_subagent":
|
||||
@@ -939,8 +939,8 @@ func renderBashCompact(toolResult string, width int) string {
|
||||
// Subagent tool renderers — show only summary, not full output
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// renderSubagentBody renders a clean summary of subagent results.
|
||||
// Extracts timing/token info and shows only a brief summary instead of raw output.
|
||||
// renderSubagentBody renders a clean summary of subagent results with bash-style
|
||||
// background styling for consistency with other tools.
|
||||
func renderSubagentBody(toolResult string, width int) string {
|
||||
theme := getTheme()
|
||||
result := strings.TrimSpace(toolResult)
|
||||
@@ -960,9 +960,19 @@ func renderSubagentBody(toolResult string, width int) string {
|
||||
// First line is always the status summary
|
||||
statusLine := lines[0]
|
||||
|
||||
// Build a clean summary
|
||||
var summary strings.Builder
|
||||
summary.WriteString(lipgloss.NewStyle().Foreground(theme.Muted).Render(statusLine))
|
||||
// Build content lines for display with bash-style background
|
||||
outputStyle := lipgloss.NewStyle().Background(theme.CodeBg).PaddingLeft(1)
|
||||
errorStyle := lipgloss.NewStyle().Foreground(theme.Error).Background(theme.CodeBg).PaddingLeft(1)
|
||||
|
||||
const lineIndent = " "
|
||||
lineWidth := max(width-len(lineIndent), 20)
|
||||
maxLineChars := lineWidth - 1 // account for PaddingLeft(1)
|
||||
|
||||
var contentLines []string
|
||||
|
||||
// Add status line
|
||||
styledStatus := outputStyle.Width(lineWidth).Render(truncateLine(statusLine, maxLineChars))
|
||||
contentLines = append(contentLines, lineIndent+styledStatus)
|
||||
|
||||
// For successful results, extract a brief preview of the actual result
|
||||
if strings.Contains(statusLine, "successfully") {
|
||||
@@ -970,25 +980,45 @@ func renderSubagentBody(toolResult string, width int) string {
|
||||
if _, resultContent, found := strings.Cut(result, "Result:\n"); found {
|
||||
resultContent = strings.TrimSpace(resultContent)
|
||||
if resultContent != "" {
|
||||
// Show first 3 meaningful lines as preview
|
||||
preview := extractSubagentPreview(resultContent, 3, width-4)
|
||||
if preview != "" {
|
||||
summary.WriteString("\n\n")
|
||||
summary.WriteString(lipgloss.NewStyle().
|
||||
Foreground(theme.Muted).
|
||||
Italic(true).
|
||||
Render(preview))
|
||||
// Show first few meaningful lines as preview
|
||||
previewLines := extractSubagentPreviewLines(resultContent, 5, maxLineChars)
|
||||
if len(previewLines) > 0 {
|
||||
// Add blank separator line
|
||||
blankLine := outputStyle.Width(lineWidth).Render("")
|
||||
contentLines = append(contentLines, lineIndent+blankLine)
|
||||
|
||||
for _, line := range previewLines {
|
||||
styled := outputStyle.Width(lineWidth).Render(line)
|
||||
contentLines = append(contentLines, lineIndent+styled)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For failed results, show error info
|
||||
if _, errorContent, found := strings.Cut(result, "Error:\n"); found {
|
||||
errorContent = strings.TrimSpace(errorContent)
|
||||
if errorContent != "" {
|
||||
previewLines := extractSubagentPreviewLines(errorContent, 3, maxLineChars)
|
||||
if len(previewLines) > 0 {
|
||||
blankLine := outputStyle.Width(lineWidth).Render("")
|
||||
contentLines = append(contentLines, lineIndent+blankLine)
|
||||
|
||||
for _, line := range previewLines {
|
||||
styled := errorStyle.Width(lineWidth).Render(line)
|
||||
contentLines = append(contentLines, lineIndent+styled)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return summary.String()
|
||||
return strings.Join(contentLines, "\n")
|
||||
}
|
||||
|
||||
// extractSubagentPreview extracts the first N non-empty lines from content,
|
||||
// truncating each line to maxWidth.
|
||||
func extractSubagentPreview(content string, maxLines, maxWidth int) string {
|
||||
// extractSubagentPreviewLines extracts the first N non-empty lines from content,
|
||||
// truncating each line to maxWidth. Returns as a slice of strings.
|
||||
func extractSubagentPreviewLines(content string, maxLines, maxWidth int) []string {
|
||||
lines := strings.Split(content, "\n")
|
||||
var preview []string
|
||||
|
||||
@@ -1007,12 +1037,6 @@ func extractSubagentPreview(content string, maxLines, maxWidth int) string {
|
||||
}
|
||||
}
|
||||
|
||||
if len(preview) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
result := strings.Join(preview, "\n")
|
||||
|
||||
// Count remaining lines for "more" indicator
|
||||
totalLines := 0
|
||||
for _, line := range lines {
|
||||
@@ -1021,10 +1045,10 @@ func extractSubagentPreview(content string, maxLines, maxWidth int) string {
|
||||
}
|
||||
}
|
||||
if totalLines > maxLines {
|
||||
result += fmt.Sprintf("\n...(%d more lines)", totalLines-maxLines)
|
||||
preview = append(preview, fmt.Sprintf("...(%d more lines)", totalLines-maxLines))
|
||||
}
|
||||
|
||||
return result
|
||||
return preview
|
||||
}
|
||||
|
||||
// renderSubagentCompact returns a brief one-line summary for subagent results.
|
||||
|
||||
Reference in New Issue
Block a user