sparkecoder 0.1.35 → 0.1.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/dist/agent/index.d.ts +2 -2
  2. package/dist/agent/index.js +4 -4
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +92 -4
  5. package/dist/cli.js.map +1 -1
  6. package/dist/{index-BVgU6Fmb.d.ts → index-D2eLXP9V.d.ts} +5 -5
  7. package/dist/index.d.ts +3 -3
  8. package/dist/index.js +92 -4
  9. package/dist/index.js.map +1 -1
  10. package/dist/{search-bazwc4Vi.d.ts → search-D3Wtf7TJ.d.ts} +2 -2
  11. package/dist/server/index.js +92 -4
  12. package/dist/server/index.js.map +1 -1
  13. package/dist/tools/index.d.ts +2 -2
  14. package/dist/tools/index.js +4 -4
  15. package/dist/tools/index.js.map +1 -1
  16. package/package.json +1 -1
  17. package/web/.next/BUILD_ID +1 -1
  18. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  19. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  20. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  21. package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
  22. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  23. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  24. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  25. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  26. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  35. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  36. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  37. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  38. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  41. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
  42. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  43. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
  44. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
  45. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  46. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
  47. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
  48. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  49. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  50. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  52. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
  53. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  54. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
  55. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
  56. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  57. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
  58. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
  59. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  60. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  64. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
  65. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
  66. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  67. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
  68. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
  69. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  70. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  71. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  73. package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
  74. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
  75. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  76. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
  77. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
  78. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  79. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  80. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  81. package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
  82. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  83. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  84. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
  85. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  86. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  87. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  88. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_41a27541._.js → 2374f_147b1db0._.js} +1 -1
  89. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_fb82ac0d._.js → 2374f_36dd81aa._.js} +1 -1
  90. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_4bf2df9d._.js → 2374f_3eca1553._.js} +1 -1
  91. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_c33b095a._.js → 2374f_4d1accac._.js} +1 -1
  92. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_663d1038._.js → 2374f_4e2c9d06._.js} +2 -2
  93. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_244589df._.js → 2374f_b30bed04._.js} +1 -1
  94. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_fa61fbb2._.js → 2374f_c99180da._.js} +1 -1
  95. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_47c9e2d5._.js → 2374f_f5998fca._.js} +1 -1
  96. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__2dbf511a._.js → [root-of-the-server]__a7aab2da._.js} +5 -3
  97. package/web/.next/standalone/web/.next/server/chunks/ssr/web_76ccf09f._.js +1 -1
  98. package/web/.next/standalone/web/.next/server/chunks/ssr/web_76f53fe7._.js +7 -0
  99. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_layout_tsx_453f6492._.js +1 -1
  100. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  101. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  102. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  103. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  104. package/web/.next/standalone/web/.next/static/{static/chunks/6407c045dfc908fe.js → chunks/1e9fdac31c7fd474.js} +3 -3
  105. package/web/.next/standalone/web/.next/static/chunks/{74595088419436d9.js → 6b8642759b62770c.js} +6 -4
  106. package/web/.next/standalone/web/.next/static/chunks/9c5414ca72e69e79.js +1 -0
  107. package/web/.next/standalone/web/.next/static/chunks/9e4b05251574c9ab.js +5 -0
  108. package/web/.next/standalone/web/.next/static/chunks/f81a19eca278424b.css +1 -0
  109. package/web/.next/standalone/web/.next/static/{chunks/6407c045dfc908fe.js → static/chunks/1e9fdac31c7fd474.js} +3 -3
  110. package/web/.next/standalone/web/.next/static/static/chunks/{74595088419436d9.js → 6b8642759b62770c.js} +6 -4
  111. package/web/.next/standalone/web/.next/static/static/chunks/9c5414ca72e69e79.js +1 -0
  112. package/web/.next/standalone/web/.next/static/static/chunks/9e4b05251574c9ab.js +5 -0
  113. package/web/.next/standalone/web/.next/static/static/chunks/f81a19eca278424b.css +1 -0
  114. package/web/.next/standalone/web/src/components/ai-elements/search-tool.tsx +222 -240
  115. package/web/.next/standalone/web/src/components/ai-elements/subagent-modal.tsx +116 -172
  116. package/web/.next/standalone/web/src/components/chat-interface.tsx +68 -3
  117. package/web/.next/standalone/web/src/lib/api.ts +33 -0
  118. package/web/.next/static/chunks/{6407c045dfc908fe.js → 1e9fdac31c7fd474.js} +3 -3
  119. package/web/.next/static/chunks/{74595088419436d9.js → 6b8642759b62770c.js} +6 -4
  120. package/web/.next/static/chunks/9c5414ca72e69e79.js +1 -0
  121. package/web/.next/static/chunks/9e4b05251574c9ab.js +5 -0
  122. package/web/.next/static/chunks/f81a19eca278424b.css +1 -0
  123. package/web/.next/standalone/web/.next/server/chunks/ssr/web_656c1e45._.js +0 -7
  124. package/web/.next/standalone/web/.next/static/chunks/89bc21c0443670f4.js +0 -1
  125. package/web/.next/standalone/web/.next/static/chunks/af22745850132107.css +0 -1
  126. package/web/.next/standalone/web/.next/static/chunks/db9b22c844a35e20.js +0 -5
  127. package/web/.next/standalone/web/.next/static/static/chunks/89bc21c0443670f4.js +0 -1
  128. package/web/.next/standalone/web/.next/static/static/chunks/af22745850132107.css +0 -1
  129. package/web/.next/standalone/web/.next/static/static/chunks/db9b22c844a35e20.js +0 -5
  130. package/web/.next/static/chunks/89bc21c0443670f4.js +0 -1
  131. package/web/.next/static/chunks/af22745850132107.css +0 -1
  132. package/web/.next/static/chunks/db9b22c844a35e20.js +0 -5
  133. /package/web/.next/standalone/web/.next/static/{rUoKuERGJU0zgIZXVv4LR → Px4gKJACBDiCG6uNwuICG}/_buildManifest.js +0 -0
  134. /package/web/.next/standalone/web/.next/static/{rUoKuERGJU0zgIZXVv4LR → Px4gKJACBDiCG6uNwuICG}/_clientMiddlewareManifest.json +0 -0
  135. /package/web/.next/standalone/web/.next/static/{rUoKuERGJU0zgIZXVv4LR → Px4gKJACBDiCG6uNwuICG}/_ssgManifest.js +0 -0
  136. /package/web/.next/standalone/web/.next/static/static/{rUoKuERGJU0zgIZXVv4LR → Px4gKJACBDiCG6uNwuICG}/_buildManifest.js +0 -0
  137. /package/web/.next/standalone/web/.next/static/static/{rUoKuERGJU0zgIZXVv4LR → Px4gKJACBDiCG6uNwuICG}/_clientMiddlewareManifest.json +0 -0
  138. /package/web/.next/standalone/web/.next/static/static/{rUoKuERGJU0zgIZXVv4LR → Px4gKJACBDiCG6uNwuICG}/_ssgManifest.js +0 -0
  139. /package/web/.next/static/{rUoKuERGJU0zgIZXVv4LR → Px4gKJACBDiCG6uNwuICG}/_buildManifest.js +0 -0
  140. /package/web/.next/static/{rUoKuERGJU0zgIZXVv4LR → Px4gKJACBDiCG6uNwuICG}/_clientMiddlewareManifest.json +0 -0
  141. /package/web/.next/static/{rUoKuERGJU0zgIZXVv4LR → Px4gKJACBDiCG6uNwuICG}/_ssgManifest.js +0 -0
@@ -1,6 +1,5 @@
1
1
  "use client";
2
2
 
3
- import { Badge } from "@/components/ui/badge";
4
3
  import { Button } from "@/components/ui/button";
5
4
  import { cn } from "@/lib/utils";
6
5
  import {
@@ -9,17 +8,13 @@ import {
9
8
  ChevronRightIcon,
10
9
  SearchIcon,
11
10
  XCircleIcon,
12
- Loader2Icon,
13
- FileIcon,
14
- CodeIcon,
15
11
  FolderIcon,
16
12
  TerminalIcon,
17
13
  FileTextIcon,
18
- BotIcon,
19
- ExternalLinkIcon,
20
- MaximizeIcon,
14
+ SparklesIcon,
15
+ EyeIcon,
21
16
  } from "lucide-react";
22
- import { useState, useEffect, useRef } from "react";
17
+ import { useState, useEffect, useRef, useMemo } from "react";
23
18
  import { SubagentModal } from "./subagent-modal";
24
19
 
25
20
  export interface SearchInput {
@@ -47,6 +42,8 @@ export interface SearchOutput {
47
42
  formattedResult?: string;
48
43
  executionId?: string;
49
44
  stepsCount?: number;
45
+ // Raw result text from agent
46
+ result?: string;
50
47
  }
51
48
 
52
49
  export interface SubagentStep {
@@ -83,6 +80,53 @@ const getToolIcon = (toolName?: string) => {
83
80
  }
84
81
  };
85
82
 
83
+ /** Truncate text with ellipsis */
84
+ const truncate = (text: string, maxLen: number) => {
85
+ if (!text) return "";
86
+ return text.length > maxLen ? text.slice(0, maxLen) + "..." : text;
87
+ };
88
+
89
+ /** Get the latest meaningful step for display */
90
+ const getLatestStep = (steps: SubagentStep[]) => {
91
+ if (steps.length === 0) return null;
92
+ const latest = steps[steps.length - 1];
93
+ if (latest.type === "tool_call" && latest.toolName) {
94
+ return { type: "tool", name: latest.toolName };
95
+ }
96
+ if (latest.type === "thought" || latest.type === "text") {
97
+ return { type: "text", content: truncate(latest.content, 40) };
98
+ }
99
+ return null;
100
+ };
101
+
102
+ /** Extract stats from steps */
103
+ const getStatsFromSteps = (steps: SubagentStep[]) => {
104
+ const toolCalls = steps.filter(s => s.type === "tool_call");
105
+ const filesRead = new Set<string>();
106
+
107
+ for (const step of toolCalls) {
108
+ if (step.toolName === "read_file" && step.toolInput) {
109
+ const input = step.toolInput as { path?: string };
110
+ if (input.path) filesRead.add(input.path);
111
+ }
112
+ }
113
+
114
+ return {
115
+ toolCallCount: toolCalls.length,
116
+ filesExplored: filesRead.size,
117
+ };
118
+ };
119
+
120
+ /** Get first line or sentence as summary */
121
+ const getFirstSentence = (text: string): string => {
122
+ if (!text) return "";
123
+ // Get first line
124
+ const firstLine = text.split('\n')[0].trim();
125
+ // Or first sentence
126
+ const match = firstLine.match(/^[^.!?]+[.!?]/);
127
+ return match ? match[0] : truncate(firstLine, 100);
128
+ };
129
+
86
130
  export function SearchTool({
87
131
  input,
88
132
  output,
@@ -90,8 +134,8 @@ export function SearchTool({
90
134
  steps = [],
91
135
  className,
92
136
  }: SearchToolProps) {
93
- const [isExpanded, setIsExpanded] = useState(true);
94
- const [isStepsExpanded, setIsStepsExpanded] = useState(true);
137
+ const [isExpanded, setIsExpanded] = useState(false);
138
+ const [isResultExpanded, setIsResultExpanded] = useState(false);
95
139
  const [isModalOpen, setIsModalOpen] = useState(false);
96
140
  const stepsContainerRef = useRef<HTMLDivElement>(null);
97
141
 
@@ -99,100 +143,113 @@ export function SearchTool({
99
143
  const isCompleted = status === "completed";
100
144
  const isError = status === "error" || output?.success === false;
101
145
 
102
- // Auto-open modal when exploration starts (optional - can be removed if not desired)
103
- useEffect(() => {
104
- if (isLoading && steps.length === 1) {
105
- setIsModalOpen(true);
106
- }
107
- }, [isLoading, steps.length]);
146
+ // Calculate stats from steps
147
+ const stats = useMemo(() => getStatsFromSteps(steps), [steps]);
108
148
 
109
- // Auto-scroll steps
149
+ // Auto-scroll steps when expanded
110
150
  useEffect(() => {
111
- if (stepsContainerRef.current && isLoading) {
151
+ if (stepsContainerRef.current && isLoading && isExpanded) {
112
152
  stepsContainerRef.current.scrollTop = stepsContainerRef.current.scrollHeight;
113
153
  }
114
- }, [steps, isLoading]);
154
+ }, [steps, isLoading, isExpanded]);
115
155
 
116
156
  const query = input?.query || "";
117
- const displayQuery = query.length > 80 ? query.slice(0, 80) + "..." : query;
118
-
119
- const getStatusDisplay = () => {
120
- if (isLoading) {
121
- return {
122
- Icon: Loader2Icon,
123
- color: "text-blue-600 dark:text-blue-500",
124
- iconClass: "animate-spin",
125
- label: steps.length > 0 ? `Exploring... (${steps.length} steps)` : "Starting exploration...",
126
- };
127
- }
128
- if (isCompleted && output?.success) {
129
- return {
130
- Icon: CheckCircleIcon,
131
- color: "text-green-600 dark:text-green-500",
132
- iconClass: "",
133
- label: `Found ${output.matchCount || 0} matches`,
134
- };
135
- }
136
- if (isError) {
137
- return {
138
- Icon: XCircleIcon,
139
- color: "text-red-600 dark:text-red-500",
140
- iconClass: "",
141
- label: "Explore failed",
142
- };
143
- }
144
- return {
145
- Icon: SearchIcon,
146
- color: "text-muted-foreground",
147
- iconClass: "",
148
- label: "",
149
- };
150
- };
157
+ const latestStep = getLatestStep(steps);
158
+
159
+ // Get the result text - could be in different fields
160
+ const resultText = useMemo(() => {
161
+ if (!output) return "";
162
+ // Try different possible fields
163
+ if (typeof output === "string") return output;
164
+ if (output.result) return output.result;
165
+ if (output.formattedResult) return output.formattedResult;
166
+ if (output.summary) return output.summary;
167
+ // Check if output has a nested structure
168
+ const anyOutput = output as Record<string, unknown>;
169
+ if (anyOutput.text) return String(anyOutput.text);
170
+ if (anyOutput.content) return String(anyOutput.content);
171
+ return "";
172
+ }, [output]);
151
173
 
152
- const { Icon, color, iconClass, label } = getStatusDisplay();
174
+ const summaryText = getFirstSentence(resultText);
175
+ const hasLongResult = resultText.length > 200;
153
176
 
154
177
  return (
155
178
  <div className={cn("rounded-lg border overflow-hidden bg-background", className)}>
156
- {/* Header */}
157
- <div className="flex items-center justify-between w-full px-3 py-2.5 bg-gradient-to-r from-blue-500/5 to-purple-500/5">
158
- <button
159
- onClick={() => setIsExpanded(!isExpanded)}
160
- className="flex items-center gap-2 min-w-0 flex-1 hover:opacity-80 transition-opacity"
161
- >
162
- <Icon className={cn("size-4 shrink-0", color, iconClass)} />
163
- <BotIcon className="size-3.5 text-muted-foreground shrink-0" />
164
- <span className="text-sm font-medium">Explore Agent</span>
165
- <code className="text-xs text-muted-foreground font-mono truncate ml-1" title={query}>
166
- "{displayQuery}"
167
- </code>
168
- </button>
169
- <div className="flex items-center gap-2 shrink-0">
170
- {label && (
171
- <Badge
172
- variant="secondary"
173
- className={cn(
174
- "text-xs gap-1",
175
- isLoading && "bg-blue-500/10 text-blue-600 dark:text-blue-400",
176
- isCompleted && output?.success && "bg-green-500/10 text-green-600 dark:text-green-400",
177
- isError && "bg-red-500/10 text-red-600 dark:text-red-400"
179
+ {/* Compact Header */}
180
+ <div className="flex items-center gap-3 px-3 py-2.5">
181
+ {/* Status Icon */}
182
+ <div className="shrink-0">
183
+ {isLoading ? (
184
+ <div className="relative">
185
+ <SparklesIcon className="size-4 text-blue-500" />
186
+ <span className="absolute -top-0.5 -right-0.5 size-2 bg-blue-500 rounded-full animate-pulse" />
187
+ </div>
188
+ ) : isCompleted && !isError ? (
189
+ <CheckCircleIcon className="size-4 text-green-500" />
190
+ ) : isError ? (
191
+ <XCircleIcon className="size-4 text-red-500" />
192
+ ) : (
193
+ <SearchIcon className="size-4 text-muted-foreground" />
194
+ )}
195
+ </div>
196
+
197
+ {/* Main Text */}
198
+ <div className="flex-1 min-w-0">
199
+ <div className="text-sm">
200
+ {isLoading ? (
201
+ <span className="text-foreground">
202
+ SparkeCoder is exploring the codebase
203
+ </span>
204
+ ) : isCompleted && !isError ? (
205
+ <span className="text-foreground">
206
+ Exploration complete
207
+ </span>
208
+ ) : isError ? (
209
+ <span className="text-red-500">
210
+ Exploration failed
211
+ </span>
212
+ ) : (
213
+ <span className="text-muted-foreground">
214
+ Exploring...
215
+ </span>
216
+ )}
217
+ </div>
218
+
219
+ {/* Tiny log line while loading */}
220
+ {isLoading && latestStep && (
221
+ <div className="text-xs text-muted-foreground truncate mt-0.5">
222
+ {latestStep.type === "tool" ? (
223
+ <span className="font-mono">{latestStep.name}</span>
224
+ ) : (
225
+ <span className="italic">{latestStep.content}</span>
178
226
  )}
179
- >
180
- {label}
181
- </Badge>
227
+ </div>
228
+ )}
229
+
230
+ {/* Completed stats - derived from actual steps */}
231
+ {isCompleted && !isError && (
232
+ <div className="text-xs text-muted-foreground mt-0.5">
233
+ {stats.toolCallCount} actions · {stats.filesExplored} files explored
234
+ </div>
182
235
  )}
236
+ </div>
237
+
238
+ {/* Actions */}
239
+ <div className="flex items-center gap-1.5 shrink-0">
183
240
  <Button
184
241
  variant="ghost"
185
- size="icon"
186
- className="size-7"
187
- onClick={(e) => {
188
- e.stopPropagation();
189
- setIsModalOpen(true);
190
- }}
191
- title="View in fullscreen"
242
+ size="sm"
243
+ className="h-7 px-2.5 text-xs gap-1.5"
244
+ onClick={() => setIsModalOpen(true)}
192
245
  >
193
- <MaximizeIcon className="size-3.5" />
246
+ <EyeIcon className="size-3" />
247
+ See How
194
248
  </Button>
195
- <button onClick={() => setIsExpanded(!isExpanded)}>
249
+ <button
250
+ onClick={() => setIsExpanded(!isExpanded)}
251
+ className="p-1 hover:bg-muted rounded transition-colors"
252
+ >
196
253
  {isExpanded ? (
197
254
  <ChevronDownIcon className="size-4 text-muted-foreground" />
198
255
  ) : (
@@ -202,108 +259,75 @@ export function SearchTool({
202
259
  </div>
203
260
  </div>
204
261
 
205
- {/* Content */}
262
+ {/* Expanded Content */}
206
263
  {isExpanded && (
207
264
  <div className="border-t">
208
265
  {/* Query */}
209
266
  <div className="px-3 py-2 bg-muted/30 border-b">
210
267
  <div className="flex items-start gap-2">
211
- <SearchIcon className="size-3.5 text-muted-foreground mt-0.5 shrink-0" />
212
- <p className="text-xs text-muted-foreground">{query}</p>
268
+ <SearchIcon className="size-3 text-muted-foreground mt-0.5 shrink-0" />
269
+ <p className="text-xs text-muted-foreground truncate" title={query}>
270
+ {truncate(query, 100)}
271
+ </p>
213
272
  </div>
214
- {input.context && (
215
- <div className="mt-1.5 pl-5 text-xs text-muted-foreground/70 italic">
216
- Context: {input.context}
217
- </div>
218
- )}
219
273
  </div>
220
274
 
221
- {/* Steps - Show subagent activity */}
222
- {steps.length > 0 && (
275
+ {/* Result - clean text with expand */}
276
+ {resultText && (
223
277
  <div className="border-b">
224
- <button
225
- onClick={(e) => {
226
- e.stopPropagation();
227
- setIsStepsExpanded(!isStepsExpanded);
228
- }}
229
- className="flex items-center gap-2 w-full px-3 py-1.5 text-xs text-muted-foreground hover:bg-muted/50"
230
- >
231
- {isStepsExpanded ? (
232
- <ChevronDownIcon className="size-3" />
278
+ <div className="px-3 py-2">
279
+ {isResultExpanded ? (
280
+ <div className="text-sm text-foreground whitespace-pre-wrap break-words max-h-[300px] overflow-auto">
281
+ {resultText}
282
+ </div>
233
283
  ) : (
234
- <ChevronRightIcon className="size-3" />
284
+ <p className="text-sm text-muted-foreground">
285
+ {summaryText}
286
+ </p>
235
287
  )}
236
- <span>Agent Steps ({steps.length})</span>
237
- {isLoading && (
238
- <Loader2Icon className="size-3 animate-spin ml-auto" />
239
- )}
240
- </button>
241
-
242
- {isStepsExpanded && (
243
- <div
244
- ref={stepsContainerRef}
245
- className="max-h-[200px] overflow-auto bg-zinc-950"
288
+ </div>
289
+ {hasLongResult && (
290
+ <button
291
+ onClick={() => setIsResultExpanded(!isResultExpanded)}
292
+ className="w-full px-3 py-1.5 text-xs text-muted-foreground hover:bg-muted/50 flex items-center justify-center gap-1 border-t"
246
293
  >
247
- {steps.map((step, index) => (
248
- <StepItem key={step.id || index} step={step} />
249
- ))}
250
- </div>
294
+ {isResultExpanded ? (
295
+ <>
296
+ <ChevronDownIcon className="size-3" />
297
+ Show less
298
+ </>
299
+ ) : (
300
+ <>
301
+ <ChevronRightIcon className="size-3" />
302
+ Show full result
303
+ </>
304
+ )}
305
+ </button>
251
306
  )}
252
307
  </div>
253
308
  )}
254
309
 
255
- {/* Results */}
256
- {output && (
257
- <div className="p-3">
258
- {output.success ? (
259
- <div className="space-y-3">
260
- {/* Summary */}
261
- {output.summary && (
262
- <div className="text-sm text-foreground">
263
- {output.summary}
264
- </div>
265
- )}
266
-
267
- {/* Findings */}
268
- {output.findings && output.findings.length > 0 && (
269
- <div className="space-y-1">
270
- <div className="text-xs font-medium text-muted-foreground mb-2">
271
- Key Findings ({output.findings.length})
272
- </div>
273
- <div className="space-y-1 max-h-[200px] overflow-auto">
274
- {output.findings.slice(0, 10).map((finding, index) => (
275
- <FindingItem key={index} finding={finding} />
276
- ))}
277
- {output.findings.length > 10 && (
278
- <div className="text-xs text-muted-foreground pl-5">
279
- ... and {output.findings.length - 10} more
280
- </div>
281
- )}
282
- </div>
283
- </div>
284
- )}
285
-
286
- {/* Stats */}
287
- <div className="flex items-center gap-4 text-xs text-muted-foreground pt-2 border-t">
288
- <span>{output.matchCount || 0} matches</span>
289
- <span>{output.filesSearched || 0} files explored</span>
290
- <span>{output.stepsCount || 0} steps</span>
291
- </div>
292
- </div>
293
- ) : (
294
- <div className="flex items-start gap-2 text-red-600 dark:text-red-400 text-sm">
295
- <XCircleIcon className="size-4 mt-0.5 shrink-0" />
296
- <span>{output.error || "Search failed"}</span>
297
- </div>
298
- )}
310
+ {/* Error */}
311
+ {isError && output && (
312
+ <div className="px-3 py-2 text-xs text-red-400 border-b">
313
+ {(output as SearchOutput).error || "Exploration failed"}
299
314
  </div>
300
315
  )}
301
316
 
302
- {/* Loading state */}
303
- {isLoading && !output && steps.length === 0 && (
304
- <div className="p-4 flex items-center justify-center gap-2 text-muted-foreground">
305
- <Loader2Icon className="size-4 animate-spin" />
306
- <span className="text-sm">Initializing explore agent...</span>
317
+ {/* Steps - compact log view */}
318
+ {steps.length > 0 && (
319
+ <div
320
+ ref={stepsContainerRef}
321
+ className="max-h-[100px] overflow-auto bg-zinc-950/50"
322
+ >
323
+ {steps.slice(-6).map((step, index) => (
324
+ <MiniStepItem key={step.id || index} step={step} />
325
+ ))}
326
+ {steps.length > 6 && (
327
+ <div className="px-3 py-1 text-xs text-zinc-500">
328
+ +{steps.length - 6} more steps
329
+ </div>
330
+ )}
307
331
  </div>
308
332
  )}
309
333
  </div>
@@ -323,78 +347,36 @@ export function SearchTool({
323
347
  );
324
348
  }
325
349
 
326
- function StepItem({ step }: { step: SubagentStep }) {
350
+ /** Minimal step item for compact log view */
351
+ function MiniStepItem({ step }: { step: SubagentStep }) {
327
352
  const ToolIcon = getToolIcon(step.toolName);
328
353
 
329
354
  return (
330
- <div className="px-3 py-1.5 text-xs font-mono border-b border-zinc-800 last:border-0">
331
- <div className="flex items-start gap-2">
332
- {step.type === "tool_call" && (
333
- <>
334
- <ToolIcon className="size-3 text-blue-400 mt-0.5 shrink-0" />
335
- <div className="min-w-0">
336
- <span className="text-blue-400">{step.toolName}</span>
337
- {step.toolInput !== undefined && step.toolInput !== null && (
338
- <span className="text-zinc-500 ml-1 truncate">
339
- {(() => {
340
- try {
341
- return JSON.stringify(step.toolInput).slice(0, 50) + "...";
342
- } catch {
343
- return "...";
344
- }
345
- })()}
346
- </span>
347
- )}
348
- </div>
349
- </>
350
- )}
351
- {step.type === "tool_result" && (
352
- <>
353
- <CheckCircleIcon className="size-3 text-green-400 mt-0.5 shrink-0" />
354
- <span className="text-zinc-400 truncate">{step.content}</span>
355
- </>
356
- )}
357
- {step.type === "text" && (
358
- <>
359
- <CodeIcon className="size-3 text-purple-400 mt-0.5 shrink-0" />
360
- <span className="text-zinc-300 truncate">{step.content}</span>
361
- </>
362
- )}
363
- {step.type === "thought" && (
364
- <>
365
- <BotIcon className="size-3 text-amber-400 mt-0.5 shrink-0" />
366
- <span className="text-zinc-400 italic truncate">{step.content}</span>
367
- </>
368
- )}
369
- </div>
370
- </div>
371
- );
372
- }
373
-
374
- function FindingItem({ finding }: { finding: SearchFinding }) {
375
- const Icon = finding.type === "match" ? CodeIcon : FileIcon;
376
- const relevanceColor = {
377
- high: "text-green-500",
378
- medium: "text-amber-500",
379
- low: "text-muted-foreground",
380
- }[finding.relevance];
381
-
382
- return (
383
- <div className="flex items-start gap-2 text-xs py-1 px-2 rounded hover:bg-muted/50">
384
- <Icon className={cn("size-3 mt-0.5 shrink-0", relevanceColor)} />
385
- <div className="min-w-0 flex-1">
386
- <div className="flex items-center gap-2">
387
- <span className="font-mono text-foreground truncate">{finding.path}</span>
388
- {finding.lineNumber && (
389
- <span className="text-muted-foreground">:{finding.lineNumber}</span>
390
- )}
391
- </div>
392
- {finding.content && (
393
- <code className="text-muted-foreground block truncate mt-0.5">
394
- {finding.content}
395
- </code>
396
- )}
397
- </div>
355
+ <div className="px-3 py-1 text-xs font-mono border-b border-zinc-800/50 last:border-0 flex items-center gap-2">
356
+ {step.type === "tool_call" && (
357
+ <>
358
+ <ToolIcon className="size-2.5 text-blue-400 shrink-0" />
359
+ <span className="text-blue-400 truncate">{step.toolName}</span>
360
+ </>
361
+ )}
362
+ {step.type === "tool_result" && (
363
+ <>
364
+ <CheckCircleIcon className="size-2.5 text-green-400 shrink-0" />
365
+ <span className="text-zinc-500 truncate">{truncate(step.content, 50)}</span>
366
+ </>
367
+ )}
368
+ {step.type === "text" && (
369
+ <>
370
+ <SparklesIcon className="size-2.5 text-purple-400 shrink-0" />
371
+ <span className="text-zinc-400 truncate">{truncate(step.content, 50)}</span>
372
+ </>
373
+ )}
374
+ {step.type === "thought" && (
375
+ <>
376
+ <SparklesIcon className="size-2.5 text-amber-400 shrink-0" />
377
+ <span className="text-zinc-500 italic truncate">{truncate(step.content, 50)}</span>
378
+ </>
379
+ )}
398
380
  </div>
399
381
  );
400
382
  }