sparkecoder 0.1.36 → 0.1.38
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.
- package/dist/agent/index.d.ts +2 -2
- package/dist/agent/index.js +4 -4
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +92 -4
- package/dist/cli.js.map +1 -1
- package/dist/{index-BSbFzE22.d.ts → index-D2eLXP9V.d.ts} +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +92 -4
- package/dist/index.js.map +1 -1
- package/dist/{search-CrICQDSa.d.ts → search-D3Wtf7TJ.d.ts} +1 -1
- package/dist/server/index.js +92 -4
- package/dist/server/index.js.map +1 -1
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +4 -4
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_184c536d._.js → 2374f_147b1db0._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_be7e7ab5._.js → 2374f_36dd81aa._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_4efa2a34._.js → 2374f_3eca1553._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d3aec1c3._.js → 2374f_4d1accac._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ced8bc96._.js → 2374f_4e2c9d06._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_75261269._.js → 2374f_b30bed04._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_84476e55._.js → 2374f_c99180da._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d7e5f4a0._.js → 2374f_f5998fca._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__a7aab2da._.js +4 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_76ccf09f._.js +1 -1
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/{6407c045dfc908fe.js → 1e9fdac31c7fd474.js} +3 -3
- package/web/.next/standalone/web/.next/static/chunks/{165f43d544d3a988.js → 6b8642759b62770c.js} +6 -4
- package/web/.next/standalone/web/.next/static/chunks/f81a19eca278424b.css +1 -0
- package/web/.next/{static/chunks/6407c045dfc908fe.js → standalone/web/.next/static/static/chunks/1e9fdac31c7fd474.js} +3 -3
- package/web/.next/standalone/web/.next/static/static/chunks/{165f43d544d3a988.js → 6b8642759b62770c.js} +6 -4
- package/web/.next/standalone/web/.next/static/static/chunks/f81a19eca278424b.css +1 -0
- package/web/.next/standalone/web/src/components/ai-elements/search-tool.tsx +107 -59
- package/web/.next/standalone/web/src/components/ai-elements/subagent-modal.tsx +116 -172
- package/web/.next/standalone/web/src/components/chat-interface.tsx +68 -3
- package/web/.next/standalone/web/src/lib/api.ts +33 -0
- package/web/.next/{standalone/web/.next/static/static/chunks/6407c045dfc908fe.js → static/chunks/1e9fdac31c7fd474.js} +3 -3
- package/web/.next/static/chunks/{165f43d544d3a988.js → 6b8642759b62770c.js} +6 -4
- package/web/.next/static/chunks/f81a19eca278424b.css +1 -0
- package/web/.next/standalone/web/.next/static/chunks/971d802e445eb147.css +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/971d802e445eb147.css +0 -1
- package/web/.next/static/chunks/971d802e445eb147.css +0 -1
- /package/web/.next/standalone/web/.next/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_ssgManifest.js +0 -0
- /package/web/.next/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_buildManifest.js +0 -0
- /package/web/.next/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{IF96MaB6ualjuLVOykhqq → RyJoI9WcF0eCUVsGPZmWy}/_ssgManifest.js +0 -0
|
@@ -6,10 +6,7 @@ import {
|
|
|
6
6
|
DialogContent,
|
|
7
7
|
DialogHeader,
|
|
8
8
|
DialogTitle,
|
|
9
|
-
DialogDescription,
|
|
10
9
|
} from "@/components/ui/dialog";
|
|
11
|
-
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
12
|
-
import { Badge } from "@/components/ui/badge";
|
|
13
10
|
import { cn } from "@/lib/utils";
|
|
14
11
|
import {
|
|
15
12
|
SearchIcon,
|
|
@@ -19,8 +16,7 @@ import {
|
|
|
19
16
|
CheckCircleIcon,
|
|
20
17
|
XCircleIcon,
|
|
21
18
|
Loader2Icon,
|
|
22
|
-
|
|
23
|
-
CodeIcon,
|
|
19
|
+
SparklesIcon,
|
|
24
20
|
ChevronRightIcon,
|
|
25
21
|
} from "lucide-react";
|
|
26
22
|
|
|
@@ -59,119 +55,108 @@ const getToolIcon = (toolName?: string) => {
|
|
|
59
55
|
}
|
|
60
56
|
};
|
|
61
57
|
|
|
58
|
+
/** Truncate text */
|
|
59
|
+
const truncate = (text: string, maxLen: number) => {
|
|
60
|
+
if (!text) return "";
|
|
61
|
+
return text.length > maxLen ? text.slice(0, maxLen) + "..." : text;
|
|
62
|
+
};
|
|
63
|
+
|
|
62
64
|
export function SubagentModal({
|
|
63
65
|
open,
|
|
64
66
|
onOpenChange,
|
|
65
67
|
title = "Explore Agent",
|
|
66
|
-
description,
|
|
67
68
|
steps,
|
|
68
69
|
status,
|
|
69
70
|
query,
|
|
70
71
|
}: SubagentModalProps) {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
// Auto-scroll to bottom when new steps arrive
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
if (scrollContainerRef.current && status === "running") {
|
|
76
|
-
const container = scrollContainerRef.current;
|
|
77
|
-
container.scrollTop = container.scrollHeight;
|
|
78
|
-
}
|
|
79
|
-
}, [steps, status]);
|
|
72
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
73
|
+
const bottomRef = useRef<HTMLDivElement>(null);
|
|
80
74
|
|
|
81
75
|
const isRunning = status === "running";
|
|
82
76
|
const isCompleted = status === "completed";
|
|
83
77
|
const isError = status === "error";
|
|
84
78
|
|
|
79
|
+
// Auto-scroll to bottom when new steps arrive
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (bottomRef.current && isRunning) {
|
|
82
|
+
bottomRef.current.scrollIntoView({ behavior: "smooth", block: "end" });
|
|
83
|
+
}
|
|
84
|
+
}, [steps, isRunning]);
|
|
85
|
+
|
|
85
86
|
return (
|
|
86
87
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
87
|
-
<DialogContent className="sm:max-w-
|
|
88
|
+
<DialogContent className="sm:max-w-xl max-h-[80vh] flex flex-col gap-0 p-0 overflow-hidden">
|
|
88
89
|
{/* Header */}
|
|
89
|
-
<div className="px-
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
<XCircleIcon className="size-5 text-red-500" />
|
|
104
|
-
)}
|
|
105
|
-
</div>
|
|
106
|
-
<div>
|
|
107
|
-
<DialogTitle className="flex items-center gap-2">
|
|
108
|
-
<BotIcon className="size-4 text-muted-foreground" />
|
|
109
|
-
{title}
|
|
110
|
-
</DialogTitle>
|
|
111
|
-
{description && (
|
|
112
|
-
<DialogDescription className="mt-1">
|
|
113
|
-
{description}
|
|
114
|
-
</DialogDescription>
|
|
115
|
-
)}
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
90
|
+
<div className="px-4 py-3 border-b flex items-center gap-3">
|
|
91
|
+
<div className="shrink-0">
|
|
92
|
+
{isRunning ? (
|
|
93
|
+
<Loader2Icon className="size-4 text-blue-500 animate-spin" />
|
|
94
|
+
) : isCompleted ? (
|
|
95
|
+
<CheckCircleIcon className="size-4 text-green-500" />
|
|
96
|
+
) : (
|
|
97
|
+
<XCircleIcon className="size-4 text-red-500" />
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
<DialogHeader className="flex-1 space-y-0">
|
|
101
|
+
<DialogTitle className="text-sm font-medium">
|
|
102
|
+
{isRunning ? "SparkeCoder is exploring..." : isCompleted ? "Exploration complete" : "Exploration failed"}
|
|
103
|
+
</DialogTitle>
|
|
118
104
|
</DialogHeader>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
<div className="mt-3 flex items-start gap-2 p-3 bg-muted/50 rounded-lg">
|
|
123
|
-
<SearchIcon className="size-4 text-muted-foreground mt-0.5 shrink-0" />
|
|
124
|
-
<p className="text-sm text-foreground">{query}</p>
|
|
125
|
-
</div>
|
|
126
|
-
)}
|
|
105
|
+
<span className="text-xs text-muted-foreground tabular-nums">
|
|
106
|
+
{steps.length} steps
|
|
107
|
+
</span>
|
|
127
108
|
</div>
|
|
128
109
|
|
|
129
|
-
{/*
|
|
130
|
-
|
|
131
|
-
<
|
|
132
|
-
<div
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
110
|
+
{/* Query */}
|
|
111
|
+
{query && (
|
|
112
|
+
<div className="px-4 py-2 border-b bg-muted/30">
|
|
113
|
+
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
114
|
+
<SearchIcon className="size-3 shrink-0" />
|
|
115
|
+
<span className="truncate" title={query}>{truncate(query, 80)}</span>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
{/* Steps - Terminal style log */}
|
|
121
|
+
<div
|
|
122
|
+
ref={scrollRef}
|
|
123
|
+
className="flex-1 overflow-auto bg-zinc-950 font-mono text-xs"
|
|
124
|
+
>
|
|
125
|
+
{steps.length === 0 ? (
|
|
126
|
+
<div className="flex items-center justify-center py-12 text-zinc-500">
|
|
127
|
+
<Loader2Icon className="size-4 animate-spin mr-2" />
|
|
128
|
+
Initializing...
|
|
129
|
+
</div>
|
|
130
|
+
) : (
|
|
131
|
+
<div className="py-2">
|
|
132
|
+
{steps.map((step, index) => (
|
|
133
|
+
<LogLine key={step.id || index} step={step} />
|
|
134
|
+
))}
|
|
135
|
+
|
|
136
|
+
{/* Auto-scroll anchor */}
|
|
137
|
+
<div ref={bottomRef} />
|
|
138
|
+
|
|
139
|
+
{/* Running indicator */}
|
|
140
|
+
{isRunning && (
|
|
141
|
+
<div className="px-3 py-1 flex items-center gap-2 text-zinc-500">
|
|
142
|
+
<Loader2Icon className="size-3 animate-spin" />
|
|
143
|
+
<span className="animate-pulse">_</span>
|
|
149
144
|
</div>
|
|
150
145
|
)}
|
|
151
146
|
</div>
|
|
152
|
-
|
|
147
|
+
)}
|
|
153
148
|
</div>
|
|
154
149
|
|
|
155
|
-
{/* Footer
|
|
156
|
-
<div className="px-
|
|
157
|
-
<div className=
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
)}
|
|
166
|
-
>
|
|
167
|
-
{isRunning ? "Running" : isCompleted ? "Completed" : "Error"}
|
|
168
|
-
</Badge>
|
|
169
|
-
<span className="text-xs text-muted-foreground">
|
|
170
|
-
{steps.length} step{steps.length !== 1 ? "s" : ""}
|
|
171
|
-
</span>
|
|
172
|
-
</div>
|
|
173
|
-
<span className="text-xs text-muted-foreground">
|
|
174
|
-
Powered by Gemini 2.0 Flash
|
|
150
|
+
{/* Footer */}
|
|
151
|
+
<div className="px-4 py-2 border-t bg-zinc-950 flex items-center gap-2">
|
|
152
|
+
<div className={cn(
|
|
153
|
+
"size-2 rounded-full",
|
|
154
|
+
isRunning && "bg-blue-500 animate-pulse",
|
|
155
|
+
isCompleted && "bg-green-500",
|
|
156
|
+
isError && "bg-red-500"
|
|
157
|
+
)} />
|
|
158
|
+
<span className="text-xs text-zinc-500">
|
|
159
|
+
{isRunning ? "Running" : isCompleted ? "Done" : "Failed"}
|
|
175
160
|
</span>
|
|
176
161
|
</div>
|
|
177
162
|
</DialogContent>
|
|
@@ -179,97 +164,56 @@ export function SubagentModal({
|
|
|
179
164
|
);
|
|
180
165
|
}
|
|
181
166
|
|
|
182
|
-
|
|
167
|
+
/** Single log line in terminal style */
|
|
168
|
+
function LogLine({ step }: { step: SubagentStep }) {
|
|
183
169
|
const ToolIcon = getToolIcon(step.toolName);
|
|
184
170
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
</div>
|
|
199
|
-
|
|
200
|
-
{/* Step content */}
|
|
201
|
-
<div className="flex-1 min-w-0">
|
|
202
|
-
{step.type === "tool_call" && (
|
|
203
|
-
<div className="space-y-1">
|
|
204
|
-
<div className="flex items-center gap-2">
|
|
205
|
-
<ToolIcon className="size-4 text-blue-500" />
|
|
206
|
-
<span className="font-medium text-sm text-blue-600">
|
|
207
|
-
{step.toolName}
|
|
208
|
-
</span>
|
|
209
|
-
</div>
|
|
210
|
-
{step.toolInput !== undefined && step.toolInput !== null && (
|
|
211
|
-
<pre className="text-xs text-muted-foreground bg-background/50 rounded p-2 overflow-x-auto">
|
|
212
|
-
{formatToolInput(step.toolInput)}
|
|
213
|
-
</pre>
|
|
214
|
-
)}
|
|
215
|
-
</div>
|
|
216
|
-
)}
|
|
217
|
-
|
|
218
|
-
{step.type === "tool_result" && (
|
|
219
|
-
<div className="space-y-1">
|
|
220
|
-
<div className="flex items-center gap-2">
|
|
221
|
-
<CheckCircleIcon className="size-4 text-green-500" />
|
|
222
|
-
<span className="font-medium text-sm text-green-600">
|
|
223
|
-
Result from {step.toolName}
|
|
224
|
-
</span>
|
|
225
|
-
</div>
|
|
226
|
-
{step.toolOutput !== undefined && step.toolOutput !== null && (
|
|
227
|
-
<pre className="text-xs text-muted-foreground bg-background/50 rounded p-2 overflow-x-auto max-h-[150px] overflow-y-auto">
|
|
228
|
-
{formatToolOutput(step.toolOutput)}
|
|
229
|
-
</pre>
|
|
230
|
-
)}
|
|
231
|
-
</div>
|
|
232
|
-
)}
|
|
171
|
+
if (step.type === "tool_call") {
|
|
172
|
+
const inputStr = formatToolInput(step.toolInput);
|
|
173
|
+
return (
|
|
174
|
+
<div className="px-3 py-0.5 hover:bg-zinc-900/50 flex items-start gap-2">
|
|
175
|
+
<ChevronRightIcon className="size-3 text-blue-400 mt-0.5 shrink-0" />
|
|
176
|
+
<ToolIcon className="size-3 text-blue-400 mt-0.5 shrink-0" />
|
|
177
|
+
<span className="text-blue-400">{step.toolName}</span>
|
|
178
|
+
{inputStr && (
|
|
179
|
+
<span className="text-zinc-600 truncate">{truncate(inputStr, 60)}</span>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
233
184
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
185
|
+
if (step.type === "tool_result") {
|
|
186
|
+
return (
|
|
187
|
+
<div className="px-3 py-0.5 hover:bg-zinc-900/50 flex items-start gap-2">
|
|
188
|
+
<CheckCircleIcon className="size-3 text-green-500 mt-0.5 shrink-0" />
|
|
189
|
+
<span className="text-zinc-500 truncate">{truncate(step.content, 80)}</span>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
240
193
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
)}
|
|
247
|
-
</div>
|
|
248
|
-
</div>
|
|
194
|
+
if (step.type === "thought") {
|
|
195
|
+
return (
|
|
196
|
+
<div className="px-3 py-0.5 hover:bg-zinc-900/50 flex items-start gap-2">
|
|
197
|
+
<SparklesIcon className="size-3 text-amber-400 mt-0.5 shrink-0" />
|
|
198
|
+
<span className="text-zinc-400 italic">{truncate(step.content, 80)}</span>
|
|
249
199
|
</div>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// text type
|
|
204
|
+
return (
|
|
205
|
+
<div className="px-3 py-0.5 hover:bg-zinc-900/50 flex items-start gap-2">
|
|
206
|
+
<span className="text-zinc-300">{truncate(step.content, 100)}</span>
|
|
250
207
|
</div>
|
|
251
208
|
);
|
|
252
209
|
}
|
|
253
210
|
|
|
254
211
|
function formatToolInput(input: unknown): string {
|
|
255
212
|
try {
|
|
213
|
+
if (input === undefined || input === null) return "";
|
|
256
214
|
if (typeof input === "string") return input;
|
|
257
|
-
return JSON.stringify(input
|
|
215
|
+
return JSON.stringify(input);
|
|
258
216
|
} catch {
|
|
259
217
|
return String(input);
|
|
260
218
|
}
|
|
261
219
|
}
|
|
262
|
-
|
|
263
|
-
function formatToolOutput(output: unknown): string {
|
|
264
|
-
try {
|
|
265
|
-
if (typeof output === "string") return output;
|
|
266
|
-
const str = JSON.stringify(output, null, 2);
|
|
267
|
-
// Truncate long outputs
|
|
268
|
-
if (str.length > 1000) {
|
|
269
|
-
return str.slice(0, 1000) + "\n... (truncated)";
|
|
270
|
-
}
|
|
271
|
-
return str;
|
|
272
|
-
} catch {
|
|
273
|
-
return String(output);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
@@ -101,6 +101,8 @@ import {
|
|
|
101
101
|
revertToCheckpoint,
|
|
102
102
|
checkVersion,
|
|
103
103
|
createSession,
|
|
104
|
+
getPendingInput,
|
|
105
|
+
getDevtoolsContext,
|
|
104
106
|
type Session,
|
|
105
107
|
type SSEEvent,
|
|
106
108
|
type PendingApproval,
|
|
@@ -1486,10 +1488,73 @@ export function ChatInterface({ session }: ChatInterfaceProps) {
|
|
|
1486
1488
|
};
|
|
1487
1489
|
}, [session.id, isRunning]);
|
|
1488
1490
|
|
|
1489
|
-
|
|
1491
|
+
// Poll for pending input from devtools (when user selects a component externally)
|
|
1492
|
+
useEffect(() => {
|
|
1493
|
+
let isStale = false;
|
|
1494
|
+
const currentSessionId = session.id;
|
|
1495
|
+
|
|
1496
|
+
const checkForPendingInput = async () => {
|
|
1497
|
+
// Skip if we're running
|
|
1498
|
+
if (isRunning) return;
|
|
1499
|
+
|
|
1500
|
+
try {
|
|
1501
|
+
const pendingResult = await getPendingInput(currentSessionId);
|
|
1502
|
+
|
|
1503
|
+
// Check if stale or running after async
|
|
1504
|
+
if (isStale || isRunning) return;
|
|
1505
|
+
|
|
1506
|
+
if (pendingResult.hasPendingInput && pendingResult.text) {
|
|
1507
|
+
// Populate the input field with the pending text
|
|
1508
|
+
setInput((prev) => {
|
|
1509
|
+
// If there's already text, append with newline
|
|
1510
|
+
if (prev.trim()) {
|
|
1511
|
+
return prev + '\n\n' + pendingResult.text;
|
|
1512
|
+
}
|
|
1513
|
+
return pendingResult.text || '';
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
// Focus the input field - find the textarea and focus it
|
|
1517
|
+
setTimeout(() => {
|
|
1518
|
+
const textarea = document.querySelector('textarea[placeholder*="Ask SparkECoder"]') as HTMLTextAreaElement | null;
|
|
1519
|
+
if (textarea) {
|
|
1520
|
+
textarea.focus();
|
|
1521
|
+
// Move cursor to end
|
|
1522
|
+
textarea.selectionStart = textarea.value.length;
|
|
1523
|
+
textarea.selectionEnd = textarea.value.length;
|
|
1524
|
+
}
|
|
1525
|
+
}, 100);
|
|
1526
|
+
}
|
|
1527
|
+
} catch (err) {
|
|
1528
|
+
// Ignore errors when polling
|
|
1529
|
+
}
|
|
1530
|
+
};
|
|
1531
|
+
|
|
1532
|
+
// Poll every 500ms for responsive feel
|
|
1533
|
+
const interval = setInterval(checkForPendingInput, 500);
|
|
1534
|
+
|
|
1535
|
+
return () => {
|
|
1536
|
+
isStale = true;
|
|
1537
|
+
clearInterval(interval);
|
|
1538
|
+
};
|
|
1539
|
+
}, [session.id, isRunning]);
|
|
1540
|
+
|
|
1541
|
+
const handleSubmit = async (promptText: string, attachments?: RunAgentAttachment[]) => {
|
|
1490
1542
|
if (!promptText.trim() && (!attachments || attachments.length === 0)) return;
|
|
1491
1543
|
if (isRunning) return;
|
|
1492
1544
|
|
|
1545
|
+
// Check if devtools is connected and get current page context
|
|
1546
|
+
let finalPrompt = promptText;
|
|
1547
|
+
try {
|
|
1548
|
+
const devtoolsInfo = await getDevtoolsContext(session.id);
|
|
1549
|
+
if (devtoolsInfo.connected && devtoolsInfo.context) {
|
|
1550
|
+
const ctx = devtoolsInfo.context;
|
|
1551
|
+
const contextPrefix = `[Sparkecode Devtools connected - User is currently viewing: ${ctx.pageName} (${ctx.path})]\n\n`;
|
|
1552
|
+
finalPrompt = contextPrefix + promptText;
|
|
1553
|
+
}
|
|
1554
|
+
} catch {
|
|
1555
|
+
// Ignore devtools context errors
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1493
1558
|
// Convert RunAgentAttachment to UserAttachment for display
|
|
1494
1559
|
const userAttachments: UserAttachment[] | undefined = attachments?.map((a) => ({
|
|
1495
1560
|
type: a.type,
|
|
@@ -1501,7 +1566,7 @@ export function ChatInterface({ session }: ChatInterfaceProps) {
|
|
|
1501
1566
|
const userItem: ChatItem = {
|
|
1502
1567
|
id: `user-${Date.now()}`,
|
|
1503
1568
|
type: 'user-message',
|
|
1504
|
-
content: promptText || '',
|
|
1569
|
+
content: promptText || '', // Show original prompt in UI (without context prefix)
|
|
1505
1570
|
attachments: userAttachments,
|
|
1506
1571
|
};
|
|
1507
1572
|
|
|
@@ -1518,7 +1583,7 @@ export function ChatInterface({ session }: ChatInterfaceProps) {
|
|
|
1518
1583
|
currentReasoningRef.current = '';
|
|
1519
1584
|
toolCallsRef.current = [];
|
|
1520
1585
|
|
|
1521
|
-
const cancel = runAgent(session.id,
|
|
1586
|
+
const cancel = runAgent(session.id, finalPrompt || 'Please analyze the attached files.', handleSSEEvent, {
|
|
1522
1587
|
onStreamId: (id) => setCurrentStreamId(id),
|
|
1523
1588
|
attachments,
|
|
1524
1589
|
});
|
|
@@ -157,6 +157,39 @@ export async function getSessionTodos(sessionId: string): Promise<TodosResponse>
|
|
|
157
157
|
return res.json();
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
export interface PendingInputResponse {
|
|
161
|
+
hasPendingInput: boolean;
|
|
162
|
+
text: string | null;
|
|
163
|
+
createdAt?: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Get pending input for a session (from devtools, etc.)
|
|
167
|
+
// This clears the pending input after reading
|
|
168
|
+
export async function getPendingInput(sessionId: string): Promise<PendingInputResponse> {
|
|
169
|
+
const res = await fetch(`${getApiBase()}/sessions/${sessionId}/pending-input`);
|
|
170
|
+
return res.json();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface DevtoolsContextResponse {
|
|
174
|
+
connected: boolean;
|
|
175
|
+
context: {
|
|
176
|
+
url: string;
|
|
177
|
+
path: string;
|
|
178
|
+
pageName: string;
|
|
179
|
+
lastHeartbeat: string;
|
|
180
|
+
} | null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Get devtools context for a session (current page user is viewing)
|
|
184
|
+
export async function getDevtoolsContext(sessionId: string): Promise<DevtoolsContextResponse> {
|
|
185
|
+
try {
|
|
186
|
+
const res = await fetch(`${getApiBase()}/sessions/${sessionId}/devtools-context`);
|
|
187
|
+
return res.json();
|
|
188
|
+
} catch {
|
|
189
|
+
return { connected: false, context: null };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
160
193
|
export async function updateSession(id: string, updates: { model?: string; name?: string; toolApprovals?: Record<string, boolean> }): Promise<Session> {
|
|
161
194
|
const res = await fetch(`${getApiBase()}/sessions/${id}`, {
|
|
162
195
|
method: 'PATCH',
|