@ash-ai/ui 0.0.2
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/LICENSE +21 -0
- package/dist/index.cjs +1781 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +322 -0
- package/dist/index.d.ts +322 -0
- package/dist/index.js +1726 -0
- package/dist/index.js.map +1 -0
- package/dist/styles-full.css +1199 -0
- package/dist/styles.css +890 -0
- package/package.json +68 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1726 @@
|
|
|
1
|
+
// src/utils.ts
|
|
2
|
+
import { clsx } from "clsx";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
4
|
+
function cn(...inputs) {
|
|
5
|
+
return twMerge(clsx(inputs));
|
|
6
|
+
}
|
|
7
|
+
function formatBytes(bytes) {
|
|
8
|
+
if (bytes === 0) return "0 B";
|
|
9
|
+
const k = 1024;
|
|
10
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
11
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
12
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
|
|
13
|
+
}
|
|
14
|
+
function formatTime(ts) {
|
|
15
|
+
return new Date(ts).toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit" });
|
|
16
|
+
}
|
|
17
|
+
function formatTimestamp(ts) {
|
|
18
|
+
return new Date(ts).toLocaleTimeString("en-US", {
|
|
19
|
+
hour12: false,
|
|
20
|
+
hour: "2-digit",
|
|
21
|
+
minute: "2-digit",
|
|
22
|
+
second: "2-digit"
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function buildFileTree(files) {
|
|
26
|
+
const root = [];
|
|
27
|
+
for (const file of files) {
|
|
28
|
+
const parts = file.path.split("/");
|
|
29
|
+
let current = root;
|
|
30
|
+
for (let i = 0; i < parts.length; i++) {
|
|
31
|
+
const name = parts[i];
|
|
32
|
+
const isLast = i === parts.length - 1;
|
|
33
|
+
const partialPath = parts.slice(0, i + 1).join("/");
|
|
34
|
+
let existing = current.find((n) => n.name === name && n.isDir === !isLast);
|
|
35
|
+
if (!existing) {
|
|
36
|
+
existing = {
|
|
37
|
+
name,
|
|
38
|
+
path: partialPath,
|
|
39
|
+
isDir: !isLast,
|
|
40
|
+
size: isLast ? file.size : void 0,
|
|
41
|
+
modifiedAt: isLast ? file.modifiedAt : void 0,
|
|
42
|
+
children: []
|
|
43
|
+
};
|
|
44
|
+
current.push(existing);
|
|
45
|
+
}
|
|
46
|
+
current = existing.children;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function sortNodes(nodes) {
|
|
50
|
+
nodes.sort((a, b) => {
|
|
51
|
+
if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
|
|
52
|
+
return a.name.localeCompare(b.name);
|
|
53
|
+
});
|
|
54
|
+
for (const n of nodes) {
|
|
55
|
+
if (n.children.length > 0) sortNodes(n.children);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
sortNodes(root);
|
|
59
|
+
return root;
|
|
60
|
+
}
|
|
61
|
+
function parseContentBlocks(content) {
|
|
62
|
+
let blocks = null;
|
|
63
|
+
if (typeof content === "string") {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(content);
|
|
66
|
+
if (Array.isArray(parsed)) {
|
|
67
|
+
blocks = parsed;
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
return { text: content, toolCalls: [] };
|
|
71
|
+
}
|
|
72
|
+
} else if (Array.isArray(content)) {
|
|
73
|
+
blocks = content;
|
|
74
|
+
}
|
|
75
|
+
if (!blocks) {
|
|
76
|
+
return { text: typeof content === "string" ? content : "", toolCalls: [] };
|
|
77
|
+
}
|
|
78
|
+
let text = "";
|
|
79
|
+
const toolCalls = [];
|
|
80
|
+
for (const block of blocks) {
|
|
81
|
+
const b = block;
|
|
82
|
+
if (b.type === "text" && typeof b.text === "string") {
|
|
83
|
+
text += (text ? "\n" : "") + b.text;
|
|
84
|
+
} else if (b.type === "tool_use") {
|
|
85
|
+
toolCalls.push({
|
|
86
|
+
id: b.id || `tool-${toolCalls.length}`,
|
|
87
|
+
name: b.name || "unknown",
|
|
88
|
+
input: b.input,
|
|
89
|
+
state: "completed"
|
|
90
|
+
});
|
|
91
|
+
} else if (b.type === "tool_result") {
|
|
92
|
+
const match = toolCalls.find((tc) => tc.id === b.tool_use_id);
|
|
93
|
+
if (match) {
|
|
94
|
+
match.output = b.content;
|
|
95
|
+
match.isError = b.is_error ?? false;
|
|
96
|
+
match.state = b.is_error ? "error" : "completed";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { text, toolCalls };
|
|
101
|
+
}
|
|
102
|
+
var IMAGE_EXTS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "svg", "webp", "ico"]);
|
|
103
|
+
function isImageFile(name) {
|
|
104
|
+
const ext = name.split(".").pop()?.toLowerCase();
|
|
105
|
+
return IMAGE_EXTS.has(ext || "");
|
|
106
|
+
}
|
|
107
|
+
function getFileLanguage(name) {
|
|
108
|
+
const ext = name.split(".").pop()?.toLowerCase();
|
|
109
|
+
const map = {
|
|
110
|
+
ts: "typescript",
|
|
111
|
+
tsx: "typescript",
|
|
112
|
+
js: "javascript",
|
|
113
|
+
jsx: "javascript",
|
|
114
|
+
py: "python",
|
|
115
|
+
rs: "rust",
|
|
116
|
+
go: "go",
|
|
117
|
+
rb: "ruby",
|
|
118
|
+
json: "json",
|
|
119
|
+
yaml: "yaml",
|
|
120
|
+
yml: "yaml",
|
|
121
|
+
toml: "toml",
|
|
122
|
+
md: "markdown",
|
|
123
|
+
sh: "bash",
|
|
124
|
+
bash: "bash",
|
|
125
|
+
css: "css",
|
|
126
|
+
html: "html",
|
|
127
|
+
sql: "sql",
|
|
128
|
+
xml: "xml"
|
|
129
|
+
};
|
|
130
|
+
return map[ext || ""] || "text";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/context/PlaygroundContext.tsx
|
|
134
|
+
import { createContext, useContext, useState as useState5, useCallback as useCallback4 } from "react";
|
|
135
|
+
|
|
136
|
+
// src/hooks/usePlaygroundChat.ts
|
|
137
|
+
import { useState, useCallback, useRef } from "react";
|
|
138
|
+
function usePlaygroundChat({
|
|
139
|
+
client,
|
|
140
|
+
agentSlug,
|
|
141
|
+
initialSessionId,
|
|
142
|
+
onSessionStart,
|
|
143
|
+
onError
|
|
144
|
+
}) {
|
|
145
|
+
const [messages, setMessages] = useState([]);
|
|
146
|
+
const [input, setInput] = useState("");
|
|
147
|
+
const [sending, setSending] = useState(false);
|
|
148
|
+
const [loading, setLoading] = useState(false);
|
|
149
|
+
const [error, setError] = useState(null);
|
|
150
|
+
const [sessionId, setSessionId] = useState(initialSessionId ?? null);
|
|
151
|
+
const [attachedFiles, setAttachedFiles] = useState([]);
|
|
152
|
+
const sendingRef = useRef(false);
|
|
153
|
+
const startNewChat = useCallback(() => {
|
|
154
|
+
setSessionId(null);
|
|
155
|
+
setMessages([]);
|
|
156
|
+
setAttachedFiles([]);
|
|
157
|
+
setError(null);
|
|
158
|
+
}, []);
|
|
159
|
+
const loadSession = useCallback(async (sid) => {
|
|
160
|
+
setSessionId(sid);
|
|
161
|
+
setLoading(true);
|
|
162
|
+
setError(null);
|
|
163
|
+
try {
|
|
164
|
+
const sdkMessages = await client.listMessages(sid);
|
|
165
|
+
const msgs = [];
|
|
166
|
+
for (let i = 0; i < sdkMessages.length; i++) {
|
|
167
|
+
const m = sdkMessages[i];
|
|
168
|
+
let text = "";
|
|
169
|
+
const tools = [];
|
|
170
|
+
let parsed;
|
|
171
|
+
try {
|
|
172
|
+
parsed = JSON.parse(m.content);
|
|
173
|
+
} catch {
|
|
174
|
+
parsed = m.content;
|
|
175
|
+
}
|
|
176
|
+
const data = parsed;
|
|
177
|
+
if (data.type === "assistant" && Array.isArray(data.message?.content)) {
|
|
178
|
+
for (const b of data.message.content) {
|
|
179
|
+
if (b.type === "text" && b.text) {
|
|
180
|
+
text += (text ? "\n" : "") + b.text;
|
|
181
|
+
} else if (b.type === "tool_use") {
|
|
182
|
+
tools.push({
|
|
183
|
+
id: b.id || `tool-${tools.length}`,
|
|
184
|
+
name: b.name || "unknown",
|
|
185
|
+
input: b.input,
|
|
186
|
+
state: "completed"
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (data.type === "user" && data.tool_use_result) {
|
|
192
|
+
const r = data.tool_use_result;
|
|
193
|
+
const match = tools.find((tc) => tc.id === r.tool_use_id) || (msgs.length > 0 ? msgs[msgs.length - 1].toolCalls?.find((tc) => tc.id === r.tool_use_id) : void 0);
|
|
194
|
+
if (match) {
|
|
195
|
+
match.output = r.stdout ?? r.content;
|
|
196
|
+
match.isError = r.is_error ?? false;
|
|
197
|
+
match.state = r.is_error ? "error" : "completed";
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (data.type === "user" && !data.tool_use_result) {
|
|
202
|
+
const content = Array.isArray(data.content) ? data.content.filter((b) => b.type === "text").map((b) => b.text).join("\n") : typeof data.content === "string" ? data.content : "";
|
|
203
|
+
if (content) {
|
|
204
|
+
msgs.push({
|
|
205
|
+
id: m.id || `msg-${i}`,
|
|
206
|
+
role: "user",
|
|
207
|
+
content,
|
|
208
|
+
timestamp: m.createdAt
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (data.type === "result" && typeof data.result === "string") {
|
|
214
|
+
text = data.result;
|
|
215
|
+
}
|
|
216
|
+
if (text || tools.length > 0) {
|
|
217
|
+
msgs.push({
|
|
218
|
+
id: m.id || `msg-${i}`,
|
|
219
|
+
role: m.role === "user" ? "user" : "assistant",
|
|
220
|
+
content: text,
|
|
221
|
+
toolCalls: tools.length > 0 ? tools : void 0,
|
|
222
|
+
timestamp: m.createdAt
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
setMessages(msgs);
|
|
227
|
+
} catch {
|
|
228
|
+
setMessages([{ id: "err", role: "system", content: "Failed to load session messages." }]);
|
|
229
|
+
} finally {
|
|
230
|
+
setLoading(false);
|
|
231
|
+
}
|
|
232
|
+
}, [client]);
|
|
233
|
+
const send = useCallback(async () => {
|
|
234
|
+
const textContent = input.trim();
|
|
235
|
+
const readyFiles = attachedFiles.filter((f) => !f.uploading);
|
|
236
|
+
if (!textContent && readyFiles.length === 0 || !agentSlug || sendingRef.current) return;
|
|
237
|
+
let content = textContent;
|
|
238
|
+
if (readyFiles.length > 0) {
|
|
239
|
+
const fileRefs = readyFiles.map((f) => `[Attached file: ${f.filename}](${f.url})`).join("\n");
|
|
240
|
+
content = content ? `${content}
|
|
241
|
+
|
|
242
|
+
${fileRefs}` : fileRefs;
|
|
243
|
+
}
|
|
244
|
+
let activeSessionId = sessionId;
|
|
245
|
+
if (!activeSessionId) {
|
|
246
|
+
setSending(true);
|
|
247
|
+
sendingRef.current = true;
|
|
248
|
+
setError(null);
|
|
249
|
+
try {
|
|
250
|
+
const session = await client.createSession(agentSlug);
|
|
251
|
+
activeSessionId = session.id;
|
|
252
|
+
setSessionId(activeSessionId);
|
|
253
|
+
onSessionStart?.(activeSessionId);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
const msg = err instanceof Error ? err.message : "Failed to create session";
|
|
256
|
+
setError(msg);
|
|
257
|
+
onError?.(msg);
|
|
258
|
+
setSending(false);
|
|
259
|
+
sendingRef.current = false;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
setInput("");
|
|
264
|
+
setAttachedFiles([]);
|
|
265
|
+
const userMsg = {
|
|
266
|
+
id: `user-${Date.now()}`,
|
|
267
|
+
role: "user",
|
|
268
|
+
content,
|
|
269
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
270
|
+
};
|
|
271
|
+
setMessages((prev) => [...prev, userMsg]);
|
|
272
|
+
setSending(true);
|
|
273
|
+
sendingRef.current = true;
|
|
274
|
+
const assistantId = `assistant-${Date.now()}`;
|
|
275
|
+
setMessages((prev) => [...prev, { id: assistantId, role: "assistant", content: "", isStreaming: true }]);
|
|
276
|
+
try {
|
|
277
|
+
let fullContent = "";
|
|
278
|
+
const toolCalls = [];
|
|
279
|
+
for await (const event of client.sendMessageStream(activeSessionId, content, { includePartialMessages: true })) {
|
|
280
|
+
if (event.type === "text_delta") {
|
|
281
|
+
const delta = event.data.delta || "";
|
|
282
|
+
fullContent += delta;
|
|
283
|
+
setMessages((prev) => prev.map(
|
|
284
|
+
(m) => m.id === assistantId ? { ...m, content: fullContent, toolCalls: [...toolCalls], isStreaming: true } : m
|
|
285
|
+
));
|
|
286
|
+
} else if (event.type === "tool_use") {
|
|
287
|
+
const data = event.data;
|
|
288
|
+
const tc = {
|
|
289
|
+
id: data.id || `tool-${Date.now()}-${toolCalls.length}`,
|
|
290
|
+
name: data.name || "unknown",
|
|
291
|
+
input: data.input,
|
|
292
|
+
state: "running"
|
|
293
|
+
};
|
|
294
|
+
toolCalls.push(tc);
|
|
295
|
+
setMessages((prev) => prev.map(
|
|
296
|
+
(m) => m.id === assistantId ? { ...m, content: fullContent, toolCalls: [...toolCalls], isStreaming: true } : m
|
|
297
|
+
));
|
|
298
|
+
} else if (event.type === "tool_result") {
|
|
299
|
+
const data = event.data;
|
|
300
|
+
const matchIdx = toolCalls.findIndex((tc) => tc.id === data.tool_use_id);
|
|
301
|
+
if (matchIdx !== -1) {
|
|
302
|
+
toolCalls[matchIdx] = {
|
|
303
|
+
...toolCalls[matchIdx],
|
|
304
|
+
output: data.content,
|
|
305
|
+
isError: data.is_error ?? false,
|
|
306
|
+
state: data.is_error ? "error" : "completed"
|
|
307
|
+
};
|
|
308
|
+
} else {
|
|
309
|
+
toolCalls.push({
|
|
310
|
+
id: data.tool_use_id || `result-${Date.now()}`,
|
|
311
|
+
name: "tool_result",
|
|
312
|
+
input: void 0,
|
|
313
|
+
output: data.content,
|
|
314
|
+
isError: data.is_error ?? false,
|
|
315
|
+
state: data.is_error ? "error" : "completed"
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
setMessages((prev) => prev.map(
|
|
319
|
+
(m) => m.id === assistantId ? { ...m, content: fullContent, toolCalls: [...toolCalls], isStreaming: true } : m
|
|
320
|
+
));
|
|
321
|
+
} else if (event.type === "turn_complete" || event.type === "done") {
|
|
322
|
+
const data = event.data;
|
|
323
|
+
if (!fullContent && data.result) {
|
|
324
|
+
fullContent = data.result;
|
|
325
|
+
}
|
|
326
|
+
for (const tc of toolCalls) {
|
|
327
|
+
if (tc.state === "running" || tc.state === "pending") tc.state = "completed";
|
|
328
|
+
}
|
|
329
|
+
setMessages((prev) => prev.map(
|
|
330
|
+
(m) => m.id === assistantId ? { ...m, content: fullContent || "(no response)", toolCalls: [...toolCalls], isStreaming: false, timestamp: (/* @__PURE__ */ new Date()).toISOString() } : m
|
|
331
|
+
));
|
|
332
|
+
} else if (event.type === "error") {
|
|
333
|
+
const data = event.data;
|
|
334
|
+
const errText = data.error || "Unknown error";
|
|
335
|
+
setMessages((prev) => prev.map(
|
|
336
|
+
(m) => m.id === assistantId ? { ...m, role: "system", content: `Error: ${errText}`, isStreaming: false } : m
|
|
337
|
+
));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
for (const tc of toolCalls) {
|
|
341
|
+
if (tc.state === "running" || tc.state === "pending") tc.state = "completed";
|
|
342
|
+
}
|
|
343
|
+
setMessages((prev) => prev.map(
|
|
344
|
+
(m) => m.id === assistantId && m.isStreaming ? { ...m, content: fullContent || "(no response)", toolCalls: toolCalls.length > 0 ? [...toolCalls] : void 0, isStreaming: false, timestamp: (/* @__PURE__ */ new Date()).toISOString() } : m
|
|
345
|
+
));
|
|
346
|
+
} catch (err) {
|
|
347
|
+
const errMsg = err instanceof Error ? err.message : "Failed to send";
|
|
348
|
+
setMessages((prev) => prev.map(
|
|
349
|
+
(m) => m.id === assistantId ? { ...m, role: "system", content: `Error: ${errMsg}`, isStreaming: false } : m
|
|
350
|
+
));
|
|
351
|
+
} finally {
|
|
352
|
+
setSending(false);
|
|
353
|
+
sendingRef.current = false;
|
|
354
|
+
}
|
|
355
|
+
}, [input, attachedFiles, sessionId, agentSlug, client, onSessionStart, onError]);
|
|
356
|
+
return {
|
|
357
|
+
messages,
|
|
358
|
+
input,
|
|
359
|
+
setInput,
|
|
360
|
+
sending,
|
|
361
|
+
loading,
|
|
362
|
+
error,
|
|
363
|
+
sessionId,
|
|
364
|
+
attachedFiles,
|
|
365
|
+
setAttachedFiles,
|
|
366
|
+
send,
|
|
367
|
+
loadSession,
|
|
368
|
+
startNewChat,
|
|
369
|
+
setMessages
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/hooks/useAgents.ts
|
|
374
|
+
import { useEffect, useState as useState2 } from "react";
|
|
375
|
+
function useAgents({ client }) {
|
|
376
|
+
const [agents, setAgents] = useState2([]);
|
|
377
|
+
const [loading, setLoading] = useState2(true);
|
|
378
|
+
const [error, setError] = useState2(null);
|
|
379
|
+
const fetch_ = async () => {
|
|
380
|
+
setLoading(true);
|
|
381
|
+
setError(null);
|
|
382
|
+
try {
|
|
383
|
+
const result = await client.listAgents();
|
|
384
|
+
setAgents(result);
|
|
385
|
+
} catch (err) {
|
|
386
|
+
setError(err instanceof Error ? err.message : "Failed to fetch agents");
|
|
387
|
+
} finally {
|
|
388
|
+
setLoading(false);
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
useEffect(() => {
|
|
392
|
+
fetch_();
|
|
393
|
+
}, []);
|
|
394
|
+
return { agents, loading, error, refresh: fetch_ };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/hooks/useSessions.ts
|
|
398
|
+
import { useEffect as useEffect2, useState as useState3, useCallback as useCallback2 } from "react";
|
|
399
|
+
function useSessions({
|
|
400
|
+
client,
|
|
401
|
+
agent,
|
|
402
|
+
limit = 20,
|
|
403
|
+
enabled = true
|
|
404
|
+
}) {
|
|
405
|
+
const [sessions, setSessions] = useState3([]);
|
|
406
|
+
const [loading, setLoading] = useState3(false);
|
|
407
|
+
const [error, setError] = useState3(null);
|
|
408
|
+
const fetch_ = useCallback2(async () => {
|
|
409
|
+
setLoading(true);
|
|
410
|
+
setError(null);
|
|
411
|
+
try {
|
|
412
|
+
const result = await client.listSessions({ agent, limit });
|
|
413
|
+
setSessions(result);
|
|
414
|
+
} catch (err) {
|
|
415
|
+
setError(err instanceof Error ? err.message : "Failed to fetch sessions");
|
|
416
|
+
} finally {
|
|
417
|
+
setLoading(false);
|
|
418
|
+
}
|
|
419
|
+
}, [client, agent, limit]);
|
|
420
|
+
useEffect2(() => {
|
|
421
|
+
if (enabled) fetch_();
|
|
422
|
+
}, [enabled, fetch_]);
|
|
423
|
+
return { sessions, loading, error, refresh: fetch_ };
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/hooks/useHealthCheck.ts
|
|
427
|
+
import { useEffect as useEffect3, useState as useState4 } from "react";
|
|
428
|
+
function useHealthCheck({ client }) {
|
|
429
|
+
const [connected, setConnected] = useState4(null);
|
|
430
|
+
const check = async () => {
|
|
431
|
+
try {
|
|
432
|
+
await client.health();
|
|
433
|
+
setConnected(true);
|
|
434
|
+
} catch {
|
|
435
|
+
setConnected(false);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
useEffect3(() => {
|
|
439
|
+
check();
|
|
440
|
+
}, []);
|
|
441
|
+
return { connected, refresh: check };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/hooks/useFileUpload.ts
|
|
445
|
+
import { useCallback as useCallback3 } from "react";
|
|
446
|
+
function useFileUpload({
|
|
447
|
+
client,
|
|
448
|
+
onFilesChange,
|
|
449
|
+
onError
|
|
450
|
+
}) {
|
|
451
|
+
const handleFileSelect = useCallback3(async (e) => {
|
|
452
|
+
const files = e.target.files;
|
|
453
|
+
if (!files || files.length === 0) return;
|
|
454
|
+
for (const file of Array.from(files)) {
|
|
455
|
+
const tempId = `uploading-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
456
|
+
onFilesChange((prev) => [...prev, { id: tempId, filename: file.name, url: "", uploading: true }]);
|
|
457
|
+
try {
|
|
458
|
+
const base64 = await new Promise((resolve, reject) => {
|
|
459
|
+
const reader = new FileReader();
|
|
460
|
+
reader.onload = () => {
|
|
461
|
+
const result = reader.result;
|
|
462
|
+
resolve(result.split(",")[1]);
|
|
463
|
+
};
|
|
464
|
+
reader.onerror = () => reject(new Error("Failed to read file"));
|
|
465
|
+
reader.readAsDataURL(file);
|
|
466
|
+
});
|
|
467
|
+
const uploaded = await client.uploadFile({
|
|
468
|
+
filename: file.name,
|
|
469
|
+
content: base64,
|
|
470
|
+
mimeType: file.type || void 0,
|
|
471
|
+
ttl: "24h"
|
|
472
|
+
});
|
|
473
|
+
const url = await client.getFileUrl(uploaded.id);
|
|
474
|
+
onFilesChange(
|
|
475
|
+
(prev) => prev.map((f) => f.id === tempId ? { id: uploaded.id, filename: file.name, url, uploading: false } : f)
|
|
476
|
+
);
|
|
477
|
+
} catch {
|
|
478
|
+
onFilesChange((prev) => prev.filter((f) => f.id !== tempId));
|
|
479
|
+
onError?.(`Failed to upload ${file.name}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
e.target.value = "";
|
|
483
|
+
}, [client, onFilesChange, onError]);
|
|
484
|
+
const removeFile = useCallback3((fileId) => {
|
|
485
|
+
onFilesChange((prev) => prev.filter((f) => f.id !== fileId));
|
|
486
|
+
}, [onFilesChange]);
|
|
487
|
+
return { handleFileSelect, removeFile };
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// src/context/PlaygroundContext.tsx
|
|
491
|
+
import { jsx } from "react/jsx-runtime";
|
|
492
|
+
var PlaygroundCtx = createContext(null);
|
|
493
|
+
function usePlaygroundContext() {
|
|
494
|
+
const ctx = useContext(PlaygroundCtx);
|
|
495
|
+
if (!ctx) throw new Error("usePlaygroundContext must be used within <PlaygroundProvider>");
|
|
496
|
+
return ctx;
|
|
497
|
+
}
|
|
498
|
+
function PlaygroundProvider({ client, defaultAgent, children }) {
|
|
499
|
+
const [selectedAgent, setSelectedAgentRaw] = useState5(defaultAgent || "");
|
|
500
|
+
const [showHistory, setShowHistory] = useState5(false);
|
|
501
|
+
const [terminalOpen, setTerminalOpen] = useState5(false);
|
|
502
|
+
const [filesOpen, setFilesOpen] = useState5(false);
|
|
503
|
+
const agentsHook = useAgents({ client });
|
|
504
|
+
const health = useHealthCheck({ client });
|
|
505
|
+
const resolvedAgent = selectedAgent || agentsHook.agents[0]?.slug || agentsHook.agents[0]?.name || "";
|
|
506
|
+
const chat = usePlaygroundChat({
|
|
507
|
+
client,
|
|
508
|
+
agentSlug: resolvedAgent
|
|
509
|
+
});
|
|
510
|
+
const sessions = useSessions({
|
|
511
|
+
client,
|
|
512
|
+
agent: resolvedAgent,
|
|
513
|
+
enabled: showHistory
|
|
514
|
+
});
|
|
515
|
+
const fileUpload = useFileUpload({
|
|
516
|
+
client,
|
|
517
|
+
onFilesChange: chat.setAttachedFiles,
|
|
518
|
+
onError: (err) => {
|
|
519
|
+
chat.setMessages((prev) => [
|
|
520
|
+
...prev,
|
|
521
|
+
{ id: `err-${Date.now()}`, role: "system", content: err }
|
|
522
|
+
]);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
const setSelectedAgent = useCallback4((slug) => {
|
|
526
|
+
setSelectedAgentRaw(slug);
|
|
527
|
+
chat.startNewChat();
|
|
528
|
+
}, [chat]);
|
|
529
|
+
const value = {
|
|
530
|
+
client,
|
|
531
|
+
chat,
|
|
532
|
+
agents: agentsHook,
|
|
533
|
+
sessions,
|
|
534
|
+
health,
|
|
535
|
+
fileUpload,
|
|
536
|
+
selectedAgent: resolvedAgent,
|
|
537
|
+
setSelectedAgent,
|
|
538
|
+
showHistory,
|
|
539
|
+
setShowHistory,
|
|
540
|
+
terminalOpen,
|
|
541
|
+
setTerminalOpen,
|
|
542
|
+
filesOpen,
|
|
543
|
+
setFilesOpen
|
|
544
|
+
};
|
|
545
|
+
return /* @__PURE__ */ jsx(PlaygroundCtx.Provider, { value, children });
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/icons.tsx
|
|
549
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
550
|
+
function icon(d, opts) {
|
|
551
|
+
const Component = (props) => /* @__PURE__ */ jsx2(
|
|
552
|
+
"svg",
|
|
553
|
+
{
|
|
554
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
555
|
+
width: "24",
|
|
556
|
+
height: "24",
|
|
557
|
+
viewBox: opts?.viewBox ?? "0 0 24 24",
|
|
558
|
+
fill: opts?.fill ? "currentColor" : "none",
|
|
559
|
+
stroke: "currentColor",
|
|
560
|
+
strokeWidth: "2",
|
|
561
|
+
strokeLinecap: "round",
|
|
562
|
+
strokeLinejoin: "round",
|
|
563
|
+
...props,
|
|
564
|
+
children: /* @__PURE__ */ jsx2("path", { d })
|
|
565
|
+
}
|
|
566
|
+
);
|
|
567
|
+
return Component;
|
|
568
|
+
}
|
|
569
|
+
var Bot = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
570
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 8V4H8" }),
|
|
571
|
+
/* @__PURE__ */ jsx2("rect", { width: "16", height: "12", x: "4", y: "8", rx: "2" }),
|
|
572
|
+
/* @__PURE__ */ jsx2("path", { d: "M2 14h2" }),
|
|
573
|
+
/* @__PURE__ */ jsx2("path", { d: "M20 14h2" }),
|
|
574
|
+
/* @__PURE__ */ jsx2("path", { d: "M15 13v2" }),
|
|
575
|
+
/* @__PURE__ */ jsx2("path", { d: "M9 13v2" })
|
|
576
|
+
] });
|
|
577
|
+
var User = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
578
|
+
/* @__PURE__ */ jsx2("path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" }),
|
|
579
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "7", r: "4" })
|
|
580
|
+
] });
|
|
581
|
+
var Send = icon("m22 2-7 20-4-9-9-4Z M22 2 11 13");
|
|
582
|
+
var MessageSquare = (props) => /* @__PURE__ */ jsx2("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: /* @__PURE__ */ jsx2("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) });
|
|
583
|
+
var Terminal = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
584
|
+
/* @__PURE__ */ jsx2("polyline", { points: "4 17 10 11 4 5" }),
|
|
585
|
+
/* @__PURE__ */ jsx2("line", { x1: "12", x2: "20", y1: "19", y2: "19" })
|
|
586
|
+
] });
|
|
587
|
+
var FolderOpen = (props) => /* @__PURE__ */ jsx2("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: /* @__PURE__ */ jsx2("path", { d: "m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2" }) });
|
|
588
|
+
var Folder = (props) => /* @__PURE__ */ jsx2("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: /* @__PURE__ */ jsx2("path", { d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" }) });
|
|
589
|
+
var FileText = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
590
|
+
/* @__PURE__ */ jsx2("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
|
|
591
|
+
/* @__PURE__ */ jsx2("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }),
|
|
592
|
+
/* @__PURE__ */ jsx2("path", { d: "M10 9H8" }),
|
|
593
|
+
/* @__PURE__ */ jsx2("path", { d: "M16 13H8" }),
|
|
594
|
+
/* @__PURE__ */ jsx2("path", { d: "M16 17H8" })
|
|
595
|
+
] });
|
|
596
|
+
var FileCode = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
597
|
+
/* @__PURE__ */ jsx2("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
|
|
598
|
+
/* @__PURE__ */ jsx2("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }),
|
|
599
|
+
/* @__PURE__ */ jsx2("path", { d: "m10 13-2 2 2 2" }),
|
|
600
|
+
/* @__PURE__ */ jsx2("path", { d: "m14 17 2-2-2-2" })
|
|
601
|
+
] });
|
|
602
|
+
var FileJson = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
603
|
+
/* @__PURE__ */ jsx2("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
|
|
604
|
+
/* @__PURE__ */ jsx2("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }),
|
|
605
|
+
/* @__PURE__ */ jsx2("path", { d: "M10 12a1 1 0 0 0-1 1v1a1 1 0 0 1-1 1 1 1 0 0 1 1 1v1a1 1 0 0 0 1 1" }),
|
|
606
|
+
/* @__PURE__ */ jsx2("path", { d: "M14 18a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1 1 1 0 0 1-1-1v-1a1 1 0 0 0-1-1" })
|
|
607
|
+
] });
|
|
608
|
+
var ImageIcon = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
609
|
+
/* @__PURE__ */ jsx2("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
|
|
610
|
+
/* @__PURE__ */ jsx2("circle", { cx: "9", cy: "9", r: "2" }),
|
|
611
|
+
/* @__PURE__ */ jsx2("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
|
|
612
|
+
] });
|
|
613
|
+
var Loader2 = (props) => /* @__PURE__ */ jsx2("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: /* @__PURE__ */ jsx2("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
|
|
614
|
+
var ChevronDown = icon("m6 9 6 6 6-6");
|
|
615
|
+
var ChevronUp = icon("m18 15-6-6-6 6");
|
|
616
|
+
var ChevronRight = icon("m9 18 6-6-6-6");
|
|
617
|
+
var Plus = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
618
|
+
/* @__PURE__ */ jsx2("path", { d: "M5 12h14" }),
|
|
619
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 5v14" })
|
|
620
|
+
] });
|
|
621
|
+
var X = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
622
|
+
/* @__PURE__ */ jsx2("path", { d: "M18 6 6 18" }),
|
|
623
|
+
/* @__PURE__ */ jsx2("path", { d: "m6 6 12 12" })
|
|
624
|
+
] });
|
|
625
|
+
var Paperclip = icon("m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48");
|
|
626
|
+
var Clock = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
627
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "10" }),
|
|
628
|
+
/* @__PURE__ */ jsx2("polyline", { points: "12 6 12 12 16 14" })
|
|
629
|
+
] });
|
|
630
|
+
var Wifi = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
631
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 20h.01" }),
|
|
632
|
+
/* @__PURE__ */ jsx2("path", { d: "M2 8.82a15 15 0 0 1 20 0" }),
|
|
633
|
+
/* @__PURE__ */ jsx2("path", { d: "M5 12.859a10 10 0 0 1 14 0" }),
|
|
634
|
+
/* @__PURE__ */ jsx2("path", { d: "M8.5 16.429a5 5 0 0 1 7 0" })
|
|
635
|
+
] });
|
|
636
|
+
var WifiOff = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
637
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 20h.01" }),
|
|
638
|
+
/* @__PURE__ */ jsx2("path", { d: "M8.5 16.429a5 5 0 0 1 7 0" }),
|
|
639
|
+
/* @__PURE__ */ jsx2("path", { d: "M5 12.859a10 10 0 0 1 5.17-2.69" }),
|
|
640
|
+
/* @__PURE__ */ jsx2("path", { d: "M19 12.859a10 10 0 0 0-2.007-1.523" }),
|
|
641
|
+
/* @__PURE__ */ jsx2("path", { d: "M2 8.82a15 15 0 0 1 4.177-2.643" }),
|
|
642
|
+
/* @__PURE__ */ jsx2("path", { d: "M22 8.82a15 15 0 0 0-11.288-3.764" }),
|
|
643
|
+
/* @__PURE__ */ jsx2("path", { d: "m2 2 20 20" })
|
|
644
|
+
] });
|
|
645
|
+
var Wrench = (props) => /* @__PURE__ */ jsx2("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: /* @__PURE__ */ jsx2("path", { d: "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" }) });
|
|
646
|
+
var CheckCircle2 = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
647
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "10" }),
|
|
648
|
+
/* @__PURE__ */ jsx2("path", { d: "m9 12 2 2 4-4" })
|
|
649
|
+
] });
|
|
650
|
+
var XCircle = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
651
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "10" }),
|
|
652
|
+
/* @__PURE__ */ jsx2("path", { d: "m15 9-6 6" }),
|
|
653
|
+
/* @__PURE__ */ jsx2("path", { d: "m9 9 6 6" })
|
|
654
|
+
] });
|
|
655
|
+
var Search = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
656
|
+
/* @__PURE__ */ jsx2("circle", { cx: "11", cy: "11", r: "8" }),
|
|
657
|
+
/* @__PURE__ */ jsx2("path", { d: "m21 21-4.3-4.3" })
|
|
658
|
+
] });
|
|
659
|
+
var Trash2 = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
660
|
+
/* @__PURE__ */ jsx2("path", { d: "M3 6h18" }),
|
|
661
|
+
/* @__PURE__ */ jsx2("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
|
|
662
|
+
/* @__PURE__ */ jsx2("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }),
|
|
663
|
+
/* @__PURE__ */ jsx2("line", { x1: "10", x2: "10", y1: "11", y2: "17" }),
|
|
664
|
+
/* @__PURE__ */ jsx2("line", { x1: "14", x2: "14", y1: "11", y2: "17" })
|
|
665
|
+
] });
|
|
666
|
+
var ArrowDown = icon("M12 5v14 m7-7-7 7-7-7");
|
|
667
|
+
var Download = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
668
|
+
/* @__PURE__ */ jsx2("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
669
|
+
/* @__PURE__ */ jsx2("polyline", { points: "7 10 12 15 17 10" }),
|
|
670
|
+
/* @__PURE__ */ jsx2("line", { x1: "12", x2: "12", y1: "15", y2: "3" })
|
|
671
|
+
] });
|
|
672
|
+
var RefreshCw = (props) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [
|
|
673
|
+
/* @__PURE__ */ jsx2("path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" }),
|
|
674
|
+
/* @__PURE__ */ jsx2("path", { d: "M21 3v5h-5" }),
|
|
675
|
+
/* @__PURE__ */ jsx2("path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }),
|
|
676
|
+
/* @__PURE__ */ jsx2("path", { d: "M8 16H3v5" })
|
|
677
|
+
] });
|
|
678
|
+
function getFileIcon(name) {
|
|
679
|
+
const ext = name.split(".").pop()?.toLowerCase();
|
|
680
|
+
if (["ts", "tsx", "js", "jsx", "py", "rs", "go", "rb", "sh", "bash"].includes(ext || ""))
|
|
681
|
+
return FileCode;
|
|
682
|
+
if (["json", "yaml", "yml", "toml"].includes(ext || ""))
|
|
683
|
+
return FileJson;
|
|
684
|
+
if (["png", "jpg", "jpeg", "gif", "svg", "webp", "ico"].includes(ext || ""))
|
|
685
|
+
return ImageIcon;
|
|
686
|
+
return FileText;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// src/components/StatusIndicator.tsx
|
|
690
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
691
|
+
function StatusIndicator({ connected, className }) {
|
|
692
|
+
if (connected === null) return null;
|
|
693
|
+
return /* @__PURE__ */ jsx3("div", { className: cn(
|
|
694
|
+
"flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[10px] font-medium border",
|
|
695
|
+
connected ? "bg-green-500/10 text-green-400 border-green-500/20" : "bg-red-500/10 text-red-400 border-red-500/20",
|
|
696
|
+
className
|
|
697
|
+
), children: connected ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
698
|
+
/* @__PURE__ */ jsx3(Wifi, { className: "h-3 w-3" }),
|
|
699
|
+
" Runtime connected"
|
|
700
|
+
] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
701
|
+
/* @__PURE__ */ jsx3(WifiOff, { className: "h-3 w-3" }),
|
|
702
|
+
" Runtime disconnected"
|
|
703
|
+
] }) });
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// src/components/PlaygroundHeader.tsx
|
|
707
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
708
|
+
function PlaygroundHeader({
|
|
709
|
+
agents,
|
|
710
|
+
selectedAgent,
|
|
711
|
+
onAgentChange,
|
|
712
|
+
runtimeConnected,
|
|
713
|
+
showHistory,
|
|
714
|
+
onToggleHistory,
|
|
715
|
+
sessionId,
|
|
716
|
+
onNewChat,
|
|
717
|
+
className
|
|
718
|
+
}) {
|
|
719
|
+
return /* @__PURE__ */ jsxs3("div", { className: cn("shrink-0 flex items-center justify-between border-b border-white/10 pb-3 mb-4", className), children: [
|
|
720
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3", children: [
|
|
721
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2 text-xs font-medium text-white/40 uppercase tracking-wider", children: [
|
|
722
|
+
/* @__PURE__ */ jsx4("span", { className: "w-1.5 h-1.5 rounded-full bg-accent animate-pulse" }),
|
|
723
|
+
"PLAYGROUND"
|
|
724
|
+
] }),
|
|
725
|
+
/* @__PURE__ */ jsxs3(
|
|
726
|
+
"select",
|
|
727
|
+
{
|
|
728
|
+
value: selectedAgent,
|
|
729
|
+
onChange: (e) => onAgentChange(e.target.value),
|
|
730
|
+
className: "rounded-lg border border-white/10 bg-white/5 px-3 py-1.5 text-sm text-white focus:border-accent/50 focus:outline-none",
|
|
731
|
+
children: [
|
|
732
|
+
/* @__PURE__ */ jsx4("option", { value: "", disabled: true, children: "Select agent..." }),
|
|
733
|
+
agents.map((a) => /* @__PURE__ */ jsx4("option", { value: a.slug || a.name, children: a.name }, a.id))
|
|
734
|
+
]
|
|
735
|
+
}
|
|
736
|
+
),
|
|
737
|
+
/* @__PURE__ */ jsx4(StatusIndicator, { connected: runtimeConnected })
|
|
738
|
+
] }),
|
|
739
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
|
|
740
|
+
/* @__PURE__ */ jsxs3(
|
|
741
|
+
"button",
|
|
742
|
+
{
|
|
743
|
+
onClick: onToggleHistory,
|
|
744
|
+
className: cn(
|
|
745
|
+
"flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium border transition-all",
|
|
746
|
+
showHistory ? "bg-accent/10 text-accent border-accent/30" : "bg-white/5 text-white/50 border-white/10 hover:bg-white/10"
|
|
747
|
+
),
|
|
748
|
+
children: [
|
|
749
|
+
/* @__PURE__ */ jsx4(Clock, { className: "h-3 w-3" }),
|
|
750
|
+
"History"
|
|
751
|
+
]
|
|
752
|
+
}
|
|
753
|
+
),
|
|
754
|
+
selectedAgent && /* @__PURE__ */ jsxs3(
|
|
755
|
+
"button",
|
|
756
|
+
{
|
|
757
|
+
onClick: onNewChat,
|
|
758
|
+
className: cn(
|
|
759
|
+
"flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium border transition-all",
|
|
760
|
+
!sessionId ? "bg-accent/10 text-accent border-accent/30" : "bg-white/5 text-white/50 border-white/10 hover:bg-white/10"
|
|
761
|
+
),
|
|
762
|
+
children: [
|
|
763
|
+
/* @__PURE__ */ jsx4(Plus, { className: "h-3 w-3" }),
|
|
764
|
+
"New Chat"
|
|
765
|
+
]
|
|
766
|
+
}
|
|
767
|
+
)
|
|
768
|
+
] })
|
|
769
|
+
] });
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// src/components/ChatMessages.tsx
|
|
773
|
+
import { useEffect as useEffect4, useRef as useRef2 } from "react";
|
|
774
|
+
|
|
775
|
+
// src/components/ToolCallBlock.tsx
|
|
776
|
+
import { useState as useState6 } from "react";
|
|
777
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
778
|
+
function ToolCallBlock({ tool }) {
|
|
779
|
+
const [open, setOpen] = useState6(false);
|
|
780
|
+
const isRunning = tool.state === "running" || tool.state === "pending";
|
|
781
|
+
const isError = tool.state === "error";
|
|
782
|
+
const isDone = tool.state === "completed";
|
|
783
|
+
return /* @__PURE__ */ jsxs4("div", { className: "my-2 rounded-lg border border-white/10 bg-white/[0.03] overflow-hidden", children: [
|
|
784
|
+
/* @__PURE__ */ jsxs4(
|
|
785
|
+
"button",
|
|
786
|
+
{
|
|
787
|
+
onClick: () => setOpen(!open),
|
|
788
|
+
className: "flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-white/5 transition-colors",
|
|
789
|
+
children: [
|
|
790
|
+
/* @__PURE__ */ jsx5(Wrench, { className: "h-3.5 w-3.5 shrink-0 text-white/40" }),
|
|
791
|
+
/* @__PURE__ */ jsx5("span", { className: "text-xs font-medium text-white/70 truncate", children: tool.name }),
|
|
792
|
+
/* @__PURE__ */ jsxs4("span", { className: "ml-auto flex items-center gap-1.5", children: [
|
|
793
|
+
isRunning && /* @__PURE__ */ jsxs4("span", { className: "flex items-center gap-1 rounded-full bg-blue-500/10 border border-blue-500/20 px-2 py-0.5 text-[10px] font-medium text-blue-400", children: [
|
|
794
|
+
/* @__PURE__ */ jsx5(Loader2, { className: "h-3 w-3 animate-spin" }),
|
|
795
|
+
"Running"
|
|
796
|
+
] }),
|
|
797
|
+
isDone && /* @__PURE__ */ jsxs4("span", { className: "flex items-center gap-1 rounded-full bg-green-500/10 border border-green-500/20 px-2 py-0.5 text-[10px] font-medium text-green-400", children: [
|
|
798
|
+
/* @__PURE__ */ jsx5(CheckCircle2, { className: "h-3 w-3" }),
|
|
799
|
+
"Done"
|
|
800
|
+
] }),
|
|
801
|
+
isError && /* @__PURE__ */ jsxs4("span", { className: "flex items-center gap-1 rounded-full bg-red-500/10 border border-red-500/20 px-2 py-0.5 text-[10px] font-medium text-red-400", children: [
|
|
802
|
+
/* @__PURE__ */ jsx5(XCircle, { className: "h-3 w-3" }),
|
|
803
|
+
"Error"
|
|
804
|
+
] }),
|
|
805
|
+
open ? /* @__PURE__ */ jsx5(ChevronDown, { className: "h-3 w-3 text-white/30" }) : /* @__PURE__ */ jsx5(ChevronRight, { className: "h-3 w-3 text-white/30" })
|
|
806
|
+
] })
|
|
807
|
+
]
|
|
808
|
+
}
|
|
809
|
+
),
|
|
810
|
+
open && /* @__PURE__ */ jsxs4("div", { className: "border-t border-white/10 px-3 py-2 space-y-2", children: [
|
|
811
|
+
tool.input != null && /* @__PURE__ */ jsxs4("div", { children: [
|
|
812
|
+
/* @__PURE__ */ jsx5("div", { className: "text-[10px] font-medium text-white/30 uppercase tracking-wider mb-1", children: "Input" }),
|
|
813
|
+
/* @__PURE__ */ jsx5("pre", { className: "rounded-md bg-black/30 px-3 py-2 text-[11px] text-white/60 overflow-x-auto max-h-48 scrollbar-thin", children: typeof tool.input === "string" ? tool.input : JSON.stringify(tool.input, null, 2) })
|
|
814
|
+
] }),
|
|
815
|
+
tool.output != null && /* @__PURE__ */ jsxs4("div", { children: [
|
|
816
|
+
/* @__PURE__ */ jsx5("div", { className: "text-[10px] font-medium text-white/30 uppercase tracking-wider mb-1", children: isError ? "Error" : "Output" }),
|
|
817
|
+
/* @__PURE__ */ jsx5(
|
|
818
|
+
"pre",
|
|
819
|
+
{
|
|
820
|
+
className: cn(
|
|
821
|
+
"rounded-md px-3 py-2 text-[11px] overflow-x-auto max-h-64 scrollbar-thin",
|
|
822
|
+
isError ? "bg-red-500/10 text-red-400" : "bg-black/30 text-white/60"
|
|
823
|
+
),
|
|
824
|
+
children: typeof tool.output === "string" ? tool.output : JSON.stringify(tool.output, null, 2)
|
|
825
|
+
}
|
|
826
|
+
)
|
|
827
|
+
] })
|
|
828
|
+
] })
|
|
829
|
+
] });
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// src/components/ChatMessage.tsx
|
|
833
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
834
|
+
function ChatMessage({ message: msg }) {
|
|
835
|
+
const isUser = msg.role === "user";
|
|
836
|
+
const isSystem = msg.role === "system";
|
|
837
|
+
if (isSystem) {
|
|
838
|
+
return /* @__PURE__ */ jsx6("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx6("span", { className: "max-w-lg rounded-xl bg-red-500/10 border border-red-500/20 px-4 py-2 text-xs text-red-400", children: msg.content }) });
|
|
839
|
+
}
|
|
840
|
+
return /* @__PURE__ */ jsxs5("div", { className: cn("flex gap-3", isUser ? "flex-row-reverse" : "flex-row"), children: [
|
|
841
|
+
/* @__PURE__ */ jsx6("div", { className: cn(
|
|
842
|
+
"flex h-8 w-8 shrink-0 items-center justify-center rounded-full",
|
|
843
|
+
isUser ? "bg-accent/20" : "bg-white/10"
|
|
844
|
+
), children: isUser ? /* @__PURE__ */ jsx6(User, { className: "h-4 w-4 text-accent" }) : /* @__PURE__ */ jsx6(Bot, { className: "h-4 w-4 text-white/60" }) }),
|
|
845
|
+
/* @__PURE__ */ jsxs5("div", { className: cn(
|
|
846
|
+
"max-w-[75%] rounded-2xl px-4 py-2.5",
|
|
847
|
+
isUser ? "bg-accent/10 border border-accent/20 text-white" : "bg-white/5 border border-white/10 text-white/80"
|
|
848
|
+
), children: [
|
|
849
|
+
msg.content && /* @__PURE__ */ jsxs5("div", { className: "whitespace-pre-wrap text-sm leading-relaxed", children: [
|
|
850
|
+
msg.content,
|
|
851
|
+
msg.isStreaming && !msg.toolCalls?.some((tc) => tc.state === "running") && /* @__PURE__ */ jsx6("span", { className: "ml-1 inline-block h-4 w-0.5 animate-pulse bg-accent align-text-bottom" })
|
|
852
|
+
] }),
|
|
853
|
+
msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsxs5("div", { className: cn(msg.content && "mt-2"), children: [
|
|
854
|
+
msg.toolCalls.map((tc) => /* @__PURE__ */ jsx6(ToolCallBlock, { tool: tc }, tc.id)),
|
|
855
|
+
msg.isStreaming && msg.toolCalls.some((tc) => tc.state === "running") && /* @__PURE__ */ jsx6("span", { className: "ml-1 inline-block h-4 w-0.5 animate-pulse bg-accent align-text-bottom" })
|
|
856
|
+
] }),
|
|
857
|
+
msg.isStreaming && !msg.content && (!msg.toolCalls || msg.toolCalls.length === 0) && /* @__PURE__ */ jsx6("span", { className: "inline-block h-4 w-0.5 animate-pulse bg-accent align-text-bottom" }),
|
|
858
|
+
msg.timestamp && /* @__PURE__ */ jsx6("div", { className: "mt-1 text-right text-xs text-white/30", children: formatTime(msg.timestamp) })
|
|
859
|
+
] })
|
|
860
|
+
] });
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// src/components/ChatMessages.tsx
|
|
864
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
865
|
+
function ChatMessages({ messages, className }) {
|
|
866
|
+
const chatRef = useRef2(null);
|
|
867
|
+
useEffect4(() => {
|
|
868
|
+
if (chatRef.current) {
|
|
869
|
+
chatRef.current.scrollTo({ top: chatRef.current.scrollHeight, behavior: "smooth" });
|
|
870
|
+
}
|
|
871
|
+
}, [messages]);
|
|
872
|
+
return /* @__PURE__ */ jsx7("div", { ref: chatRef, className: className ?? "flex-1 overflow-auto p-4 space-y-3 scrollbar-thin", children: messages.length === 0 ? /* @__PURE__ */ jsxs6("div", { className: "flex h-full flex-col items-center justify-center text-center", children: [
|
|
873
|
+
/* @__PURE__ */ jsx7(MessageSquare, { className: "mb-3 h-8 w-8 text-white/20" }),
|
|
874
|
+
/* @__PURE__ */ jsx7("p", { className: "text-sm text-white/40", children: "No messages yet" }),
|
|
875
|
+
/* @__PURE__ */ jsx7("p", { className: "mt-1 text-xs text-white/30", children: "Send a message to start the conversation" })
|
|
876
|
+
] }) : messages.map((msg) => /* @__PURE__ */ jsx7(ChatMessage, { message: msg }, msg.id)) });
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// src/components/ChatInput.tsx
|
|
880
|
+
import { useRef as useRef3 } from "react";
|
|
881
|
+
import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
882
|
+
function ChatInput({
|
|
883
|
+
input,
|
|
884
|
+
onInputChange,
|
|
885
|
+
onSend,
|
|
886
|
+
sending,
|
|
887
|
+
disabled,
|
|
888
|
+
placeholder = "Send a message...",
|
|
889
|
+
attachedFiles = [],
|
|
890
|
+
onFileSelect,
|
|
891
|
+
onRemoveFile,
|
|
892
|
+
className
|
|
893
|
+
}) {
|
|
894
|
+
const textareaRef = useRef3(null);
|
|
895
|
+
const fileInputRef = useRef3(null);
|
|
896
|
+
const handleKeyDown = (e) => {
|
|
897
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
898
|
+
e.preventDefault();
|
|
899
|
+
onSend();
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
const readyFiles = attachedFiles.filter((f) => !f.uploading);
|
|
903
|
+
const canSend = (input.trim() || readyFiles.length > 0) && !sending && !disabled;
|
|
904
|
+
return /* @__PURE__ */ jsxs7("div", { className: cn("shrink-0 border-t border-white/10 p-4", className), children: [
|
|
905
|
+
attachedFiles.length > 0 && /* @__PURE__ */ jsx8("div", { className: "mb-2 flex flex-wrap gap-2", children: attachedFiles.map((file) => /* @__PURE__ */ jsxs7(
|
|
906
|
+
"div",
|
|
907
|
+
{
|
|
908
|
+
className: cn(
|
|
909
|
+
"flex items-center gap-1.5 rounded-lg border px-2.5 py-1 text-xs",
|
|
910
|
+
file.uploading ? "border-white/10 bg-white/5 text-white/40" : "border-accent/20 bg-accent/5 text-accent"
|
|
911
|
+
),
|
|
912
|
+
children: [
|
|
913
|
+
file.uploading ? /* @__PURE__ */ jsx8(Loader2, { className: "h-3 w-3 animate-spin" }) : /* @__PURE__ */ jsx8(Paperclip, { className: "h-3 w-3" }),
|
|
914
|
+
/* @__PURE__ */ jsx8("span", { className: "max-w-[180px] truncate", children: file.filename }),
|
|
915
|
+
!file.uploading && onRemoveFile && /* @__PURE__ */ jsx8(
|
|
916
|
+
"button",
|
|
917
|
+
{
|
|
918
|
+
onClick: () => onRemoveFile(file.id),
|
|
919
|
+
className: "ml-0.5 rounded-full p-0.5 hover:bg-white/10 transition-colors",
|
|
920
|
+
children: /* @__PURE__ */ jsx8(X, { className: "h-3 w-3" })
|
|
921
|
+
}
|
|
922
|
+
)
|
|
923
|
+
]
|
|
924
|
+
},
|
|
925
|
+
file.id
|
|
926
|
+
)) }),
|
|
927
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-end gap-3", children: [
|
|
928
|
+
onFileSelect && /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
929
|
+
/* @__PURE__ */ jsx8(
|
|
930
|
+
"input",
|
|
931
|
+
{
|
|
932
|
+
ref: fileInputRef,
|
|
933
|
+
type: "file",
|
|
934
|
+
multiple: true,
|
|
935
|
+
onChange: onFileSelect,
|
|
936
|
+
className: "hidden"
|
|
937
|
+
}
|
|
938
|
+
),
|
|
939
|
+
/* @__PURE__ */ jsx8(
|
|
940
|
+
"button",
|
|
941
|
+
{
|
|
942
|
+
onClick: () => fileInputRef.current?.click(),
|
|
943
|
+
disabled: sending || disabled,
|
|
944
|
+
className: cn(
|
|
945
|
+
"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl border transition-colors",
|
|
946
|
+
"border-white/10 bg-white/5 text-white/40 hover:bg-white/10 hover:text-white/60",
|
|
947
|
+
(sending || disabled) && "opacity-50 cursor-not-allowed"
|
|
948
|
+
),
|
|
949
|
+
title: "Attach files",
|
|
950
|
+
children: /* @__PURE__ */ jsx8(Paperclip, { className: "h-4 w-4" })
|
|
951
|
+
}
|
|
952
|
+
)
|
|
953
|
+
] }),
|
|
954
|
+
/* @__PURE__ */ jsx8(
|
|
955
|
+
"textarea",
|
|
956
|
+
{
|
|
957
|
+
ref: textareaRef,
|
|
958
|
+
value: input,
|
|
959
|
+
onChange: (e) => {
|
|
960
|
+
onInputChange(e.target.value);
|
|
961
|
+
const el = e.target;
|
|
962
|
+
el.style.height = "auto";
|
|
963
|
+
el.style.height = Math.min(el.scrollHeight, 160) + "px";
|
|
964
|
+
},
|
|
965
|
+
onKeyDown: handleKeyDown,
|
|
966
|
+
placeholder,
|
|
967
|
+
rows: 1,
|
|
968
|
+
className: cn(
|
|
969
|
+
"w-full resize-none rounded-xl border bg-white/5 px-4 py-3 text-sm text-white",
|
|
970
|
+
"border-white/10 placeholder:text-white/40",
|
|
971
|
+
"focus:border-accent/50 focus:outline-none focus:ring-0"
|
|
972
|
+
)
|
|
973
|
+
}
|
|
974
|
+
),
|
|
975
|
+
/* @__PURE__ */ jsx8(
|
|
976
|
+
"button",
|
|
977
|
+
{
|
|
978
|
+
onClick: onSend,
|
|
979
|
+
disabled: !canSend,
|
|
980
|
+
className: cn(
|
|
981
|
+
"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl transition-colors",
|
|
982
|
+
canSend ? "bg-accent text-white hover:bg-accent/90" : "bg-white/5 text-white/20 cursor-not-allowed"
|
|
983
|
+
),
|
|
984
|
+
children: sending ? /* @__PURE__ */ jsx8(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx8(Send, { className: "h-4 w-4" })
|
|
985
|
+
}
|
|
986
|
+
)
|
|
987
|
+
] })
|
|
988
|
+
] });
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// src/components/Chat.tsx
|
|
992
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
993
|
+
function Chat({
|
|
994
|
+
messages,
|
|
995
|
+
input,
|
|
996
|
+
onInputChange,
|
|
997
|
+
onSend,
|
|
998
|
+
sending,
|
|
999
|
+
loading,
|
|
1000
|
+
error,
|
|
1001
|
+
isActive,
|
|
1002
|
+
agentName,
|
|
1003
|
+
attachedFiles,
|
|
1004
|
+
onFileSelect,
|
|
1005
|
+
onRemoveFile,
|
|
1006
|
+
className
|
|
1007
|
+
}) {
|
|
1008
|
+
return /* @__PURE__ */ jsxs8("div", { className: cn("flex flex-col overflow-hidden", className), children: [
|
|
1009
|
+
loading ? /* @__PURE__ */ jsxs8("div", { className: "flex flex-1 items-center justify-center", children: [
|
|
1010
|
+
/* @__PURE__ */ jsx9(Loader2, { className: "mr-2 h-5 w-5 animate-spin text-white/40" }),
|
|
1011
|
+
/* @__PURE__ */ jsx9("span", { className: "text-sm text-white/50", children: "Loading session..." })
|
|
1012
|
+
] }) : !isActive ? /* @__PURE__ */ jsxs8("div", { className: "flex flex-1 flex-col items-center justify-center text-center px-8", children: [
|
|
1013
|
+
/* @__PURE__ */ jsx9("div", { className: "mb-4 flex h-16 w-16 items-center justify-center rounded-2xl bg-accent/10 border border-accent/20", children: /* @__PURE__ */ jsx9(MessageSquare, { className: "h-8 w-8 text-accent" }) }),
|
|
1014
|
+
/* @__PURE__ */ jsx9("h2", { className: "text-xl font-semibold text-white", children: "Agent Playground" }),
|
|
1015
|
+
/* @__PURE__ */ jsx9("p", { className: "mt-2 max-w-md text-sm text-white/50", children: agentName ? `Start a new conversation with ${agentName}, or resume a past session from History.` : "Select an agent above to get started." }),
|
|
1016
|
+
agentName && /* @__PURE__ */ jsxs8("div", { className: "mt-4 inline-flex items-center gap-2 rounded-full bg-white/10 px-4 py-2 text-sm text-white/70", children: [
|
|
1017
|
+
/* @__PURE__ */ jsx9(Bot, { className: "h-4 w-4 text-accent" }),
|
|
1018
|
+
agentName
|
|
1019
|
+
] })
|
|
1020
|
+
] }) : /* @__PURE__ */ jsx9(ChatMessages, { messages }),
|
|
1021
|
+
error && /* @__PURE__ */ jsx9("div", { className: "shrink-0 mx-4 mb-2 rounded-xl border border-red-500/20 bg-red-500/10 px-4 py-2.5 text-sm text-red-400", children: error }),
|
|
1022
|
+
agentName && /* @__PURE__ */ jsx9(
|
|
1023
|
+
ChatInput,
|
|
1024
|
+
{
|
|
1025
|
+
input,
|
|
1026
|
+
onInputChange,
|
|
1027
|
+
onSend,
|
|
1028
|
+
sending,
|
|
1029
|
+
placeholder: isActive ? "Send a message..." : "Send a message to start a new session...",
|
|
1030
|
+
attachedFiles,
|
|
1031
|
+
onFileSelect,
|
|
1032
|
+
onRemoveFile
|
|
1033
|
+
}
|
|
1034
|
+
)
|
|
1035
|
+
] });
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// src/components/SessionHistory.tsx
|
|
1039
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1040
|
+
function SessionHistory({ sessions, activeSessionId, onSelectSession, className }) {
|
|
1041
|
+
return /* @__PURE__ */ jsxs9("div", { className: cn("w-64 shrink-0 rounded-lg border border-white/10 bg-white/[0.02] flex flex-col overflow-hidden", className), children: [
|
|
1042
|
+
/* @__PURE__ */ jsx10("div", { className: "px-3 py-2 border-b border-white/10 text-xs font-medium text-white/40 uppercase tracking-wider", children: "Recent Sessions" }),
|
|
1043
|
+
/* @__PURE__ */ jsx10("div", { className: "flex-1 overflow-auto scrollbar-thin", children: sessions.length === 0 ? /* @__PURE__ */ jsx10("p", { className: "px-3 py-6 text-center text-xs text-white/30", children: "No sessions yet" }) : sessions.map((s) => /* @__PURE__ */ jsxs9(
|
|
1044
|
+
"button",
|
|
1045
|
+
{
|
|
1046
|
+
onClick: () => onSelectSession(s),
|
|
1047
|
+
className: cn(
|
|
1048
|
+
"w-full text-left px-3 py-2.5 border-b border-white/5 hover:bg-white/5 transition-colors",
|
|
1049
|
+
activeSessionId === s.id && "border-l-2 border-l-accent bg-accent/5"
|
|
1050
|
+
),
|
|
1051
|
+
children: [
|
|
1052
|
+
/* @__PURE__ */ jsx10("div", { className: "text-xs font-medium text-white truncate", children: s.agentName }),
|
|
1053
|
+
/* @__PURE__ */ jsxs9("div", { className: "text-[10px] text-white/30 mt-0.5", children: [
|
|
1054
|
+
s.createdAt ? new Date(s.createdAt).toLocaleDateString() : "No events",
|
|
1055
|
+
/* @__PURE__ */ jsx10("span", { className: "mx-1 text-white/15", children: "|" }),
|
|
1056
|
+
/* @__PURE__ */ jsx10("span", { className: cn(
|
|
1057
|
+
s.status === "active" ? "text-green-400" : "text-white/30"
|
|
1058
|
+
), children: s.status })
|
|
1059
|
+
] })
|
|
1060
|
+
]
|
|
1061
|
+
},
|
|
1062
|
+
s.id
|
|
1063
|
+
)) })
|
|
1064
|
+
] });
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// src/components/Terminal.tsx
|
|
1068
|
+
import { useEffect as useEffect5, useState as useState7, useRef as useRef4, useCallback as useCallback5 } from "react";
|
|
1069
|
+
import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1070
|
+
var levelColors = {
|
|
1071
|
+
stdout: "text-green-300",
|
|
1072
|
+
stderr: "text-red-400",
|
|
1073
|
+
system: "text-white/40"
|
|
1074
|
+
};
|
|
1075
|
+
function Terminal2({ logs, connected, onClear, className }) {
|
|
1076
|
+
const [filter, setFilter] = useState7("");
|
|
1077
|
+
const [showTimestamps, setShowTimestamps] = useState7(false);
|
|
1078
|
+
const [autoScroll, setAutoScroll] = useState7(true);
|
|
1079
|
+
const containerRef = useRef4(null);
|
|
1080
|
+
useEffect5(() => {
|
|
1081
|
+
if (autoScroll && containerRef.current) {
|
|
1082
|
+
containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
|
1083
|
+
}
|
|
1084
|
+
}, [logs, autoScroll]);
|
|
1085
|
+
const handleScroll = useCallback5(() => {
|
|
1086
|
+
if (!containerRef.current) return;
|
|
1087
|
+
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
|
|
1088
|
+
const atBottom = scrollHeight - scrollTop - clientHeight < 40;
|
|
1089
|
+
setAutoScroll(atBottom);
|
|
1090
|
+
}, []);
|
|
1091
|
+
const scrollToBottom = () => {
|
|
1092
|
+
if (containerRef.current) {
|
|
1093
|
+
containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
|
1094
|
+
setAutoScroll(true);
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
const filteredLogs = filter ? logs.filter((l) => l.text.toLowerCase().includes(filter.toLowerCase())) : logs;
|
|
1098
|
+
return /* @__PURE__ */ jsxs10("div", { className: cn("flex flex-col bg-[#0d1117] rounded-lg border border-white/10 overflow-hidden", className), children: [
|
|
1099
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 border-b border-white/10 px-3 py-2 bg-[#161b22]", children: [
|
|
1100
|
+
/* @__PURE__ */ jsx11(Terminal, { className: "h-3.5 w-3.5 text-white/50" }),
|
|
1101
|
+
/* @__PURE__ */ jsx11("span", { className: "text-xs font-medium text-white/60", children: "Terminal" }),
|
|
1102
|
+
/* @__PURE__ */ jsx11("div", { className: cn(
|
|
1103
|
+
"ml-1 flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium",
|
|
1104
|
+
connected === true ? "text-green-400" : connected === false ? "text-red-400" : "text-white/30"
|
|
1105
|
+
), children: connected === true ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
1106
|
+
/* @__PURE__ */ jsx11(Wifi, { className: "h-2.5 w-2.5" }),
|
|
1107
|
+
" Live"
|
|
1108
|
+
] }) : connected === false ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
1109
|
+
/* @__PURE__ */ jsx11(WifiOff, { className: "h-2.5 w-2.5" }),
|
|
1110
|
+
" Disconnected"
|
|
1111
|
+
] }) : "Connecting..." }),
|
|
1112
|
+
/* @__PURE__ */ jsx11("div", { className: "flex-1" }),
|
|
1113
|
+
/* @__PURE__ */ jsxs10("span", { className: "text-[10px] text-white/30", children: [
|
|
1114
|
+
filteredLogs.length.toLocaleString(),
|
|
1115
|
+
" line",
|
|
1116
|
+
filteredLogs.length !== 1 ? "s" : ""
|
|
1117
|
+
] }),
|
|
1118
|
+
/* @__PURE__ */ jsx11(
|
|
1119
|
+
"button",
|
|
1120
|
+
{
|
|
1121
|
+
onClick: () => setShowTimestamps(!showTimestamps),
|
|
1122
|
+
className: cn(
|
|
1123
|
+
"rounded p-1 text-white/40 hover:bg-white/10 hover:text-white/70 transition-colors",
|
|
1124
|
+
showTimestamps && "bg-white/10 text-white/70"
|
|
1125
|
+
),
|
|
1126
|
+
title: "Toggle timestamps",
|
|
1127
|
+
children: /* @__PURE__ */ jsx11(Clock, { className: "h-3 w-3" })
|
|
1128
|
+
}
|
|
1129
|
+
),
|
|
1130
|
+
/* @__PURE__ */ jsxs10("div", { className: "relative", children: [
|
|
1131
|
+
/* @__PURE__ */ jsx11(Search, { className: "absolute left-2 top-1/2 h-3 w-3 -translate-y-1/2 text-white/30" }),
|
|
1132
|
+
/* @__PURE__ */ jsx11(
|
|
1133
|
+
"input",
|
|
1134
|
+
{
|
|
1135
|
+
type: "text",
|
|
1136
|
+
value: filter,
|
|
1137
|
+
onChange: (e) => setFilter(e.target.value),
|
|
1138
|
+
placeholder: "Filter...",
|
|
1139
|
+
className: "h-6 w-32 rounded border border-white/10 bg-white/5 pl-7 pr-2 text-[11px] text-white placeholder:text-white/30 focus:border-white/20 focus:outline-none"
|
|
1140
|
+
}
|
|
1141
|
+
)
|
|
1142
|
+
] }),
|
|
1143
|
+
!autoScroll && /* @__PURE__ */ jsx11(
|
|
1144
|
+
"button",
|
|
1145
|
+
{
|
|
1146
|
+
onClick: scrollToBottom,
|
|
1147
|
+
className: "rounded p-1 text-white/40 hover:bg-white/10 hover:text-white/70 transition-colors",
|
|
1148
|
+
title: "Scroll to bottom",
|
|
1149
|
+
children: /* @__PURE__ */ jsx11(ArrowDown, { className: "h-3 w-3" })
|
|
1150
|
+
}
|
|
1151
|
+
),
|
|
1152
|
+
onClear && /* @__PURE__ */ jsx11(
|
|
1153
|
+
"button",
|
|
1154
|
+
{
|
|
1155
|
+
onClick: onClear,
|
|
1156
|
+
className: "rounded p-1 text-white/40 hover:bg-white/10 hover:text-white/70 transition-colors",
|
|
1157
|
+
title: "Clear",
|
|
1158
|
+
children: /* @__PURE__ */ jsx11(Trash2, { className: "h-3 w-3" })
|
|
1159
|
+
}
|
|
1160
|
+
)
|
|
1161
|
+
] }),
|
|
1162
|
+
/* @__PURE__ */ jsx11(
|
|
1163
|
+
"div",
|
|
1164
|
+
{
|
|
1165
|
+
ref: containerRef,
|
|
1166
|
+
onScroll: handleScroll,
|
|
1167
|
+
className: "flex-1 overflow-auto p-3 font-mono text-xs leading-5 scrollbar-thin min-h-[120px]",
|
|
1168
|
+
children: filteredLogs.length === 0 ? /* @__PURE__ */ jsx11("div", { className: "flex h-full items-center justify-center text-white/20 text-xs", children: logs.length === 0 ? "Waiting for sandbox output..." : "No matching lines" }) : filteredLogs.map((entry) => /* @__PURE__ */ jsxs10("div", { className: "flex hover:bg-white/5 rounded px-1 -mx-1", children: [
|
|
1169
|
+
showTimestamps && /* @__PURE__ */ jsx11("span", { className: "mr-3 shrink-0 select-none text-white/20", children: formatTimestamp(entry.ts) }),
|
|
1170
|
+
/* @__PURE__ */ jsx11("span", { className: cn(
|
|
1171
|
+
"whitespace-pre-wrap break-all",
|
|
1172
|
+
levelColors[entry.level] || "text-white/60"
|
|
1173
|
+
), children: entry.text })
|
|
1174
|
+
] }, entry.index))
|
|
1175
|
+
}
|
|
1176
|
+
)
|
|
1177
|
+
] });
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// src/components/FileBrowser.tsx
|
|
1181
|
+
import { useRef as useRef5 } from "react";
|
|
1182
|
+
|
|
1183
|
+
// src/components/FileTree.tsx
|
|
1184
|
+
import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1185
|
+
function TreeItem({
|
|
1186
|
+
node,
|
|
1187
|
+
depth,
|
|
1188
|
+
selectedPath,
|
|
1189
|
+
expandedDirs,
|
|
1190
|
+
onSelectFile,
|
|
1191
|
+
onToggleDir
|
|
1192
|
+
}) {
|
|
1193
|
+
const isExpanded = expandedDirs.has(node.path);
|
|
1194
|
+
const isSelected = selectedPath === node.path;
|
|
1195
|
+
const Icon = node.isDir ? isExpanded ? FolderOpen : Folder : getFileIcon(node.name);
|
|
1196
|
+
return /* @__PURE__ */ jsxs11(Fragment4, { children: [
|
|
1197
|
+
/* @__PURE__ */ jsxs11(
|
|
1198
|
+
"button",
|
|
1199
|
+
{
|
|
1200
|
+
onClick: () => node.isDir ? onToggleDir(node.path) : onSelectFile(node.path),
|
|
1201
|
+
className: cn(
|
|
1202
|
+
"flex items-center gap-1.5 w-full text-left py-1 pr-2 text-xs transition-colors hover:bg-white/5",
|
|
1203
|
+
isSelected && !node.isDir && "bg-accent/10 text-accent",
|
|
1204
|
+
!isSelected && "text-white/60"
|
|
1205
|
+
),
|
|
1206
|
+
style: { paddingLeft: `${depth * 16 + 8}px` },
|
|
1207
|
+
children: [
|
|
1208
|
+
node.isDir ? isExpanded ? /* @__PURE__ */ jsx12(ChevronDown, { className: "h-3 w-3 shrink-0 text-white/30" }) : /* @__PURE__ */ jsx12(ChevronRight, { className: "h-3 w-3 shrink-0 text-white/30" }) : /* @__PURE__ */ jsx12("span", { className: "w-3" }),
|
|
1209
|
+
/* @__PURE__ */ jsx12(Icon, { className: cn(
|
|
1210
|
+
"h-3.5 w-3.5 shrink-0",
|
|
1211
|
+
node.isDir ? "text-accent" : "text-white/40"
|
|
1212
|
+
) }),
|
|
1213
|
+
/* @__PURE__ */ jsx12("span", { className: "truncate", children: node.name }),
|
|
1214
|
+
node.isDir && node.children.length > 0 && /* @__PURE__ */ jsx12("span", { className: "ml-auto text-[10px] text-white/20 shrink-0", children: node.children.length })
|
|
1215
|
+
]
|
|
1216
|
+
}
|
|
1217
|
+
),
|
|
1218
|
+
node.isDir && isExpanded && node.children.map((child) => /* @__PURE__ */ jsx12(
|
|
1219
|
+
TreeItem,
|
|
1220
|
+
{
|
|
1221
|
+
node: child,
|
|
1222
|
+
depth: depth + 1,
|
|
1223
|
+
selectedPath,
|
|
1224
|
+
expandedDirs,
|
|
1225
|
+
onSelectFile,
|
|
1226
|
+
onToggleDir
|
|
1227
|
+
},
|
|
1228
|
+
child.path
|
|
1229
|
+
))
|
|
1230
|
+
] });
|
|
1231
|
+
}
|
|
1232
|
+
function FileTree({ nodes, selectedPath, expandedDirs, onSelectFile, onToggleDir }) {
|
|
1233
|
+
return /* @__PURE__ */ jsx12("div", { className: "overflow-y-auto scrollbar-thin", children: nodes.map((node) => /* @__PURE__ */ jsx12(
|
|
1234
|
+
TreeItem,
|
|
1235
|
+
{
|
|
1236
|
+
node,
|
|
1237
|
+
depth: 0,
|
|
1238
|
+
selectedPath,
|
|
1239
|
+
expandedDirs,
|
|
1240
|
+
onSelectFile,
|
|
1241
|
+
onToggleDir
|
|
1242
|
+
},
|
|
1243
|
+
node.path
|
|
1244
|
+
)) });
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// src/components/FileBrowser.tsx
|
|
1248
|
+
import { Fragment as Fragment5, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1249
|
+
function FileBrowser({
|
|
1250
|
+
files,
|
|
1251
|
+
source,
|
|
1252
|
+
loading,
|
|
1253
|
+
selectedPath,
|
|
1254
|
+
fileContent,
|
|
1255
|
+
fileLoading,
|
|
1256
|
+
fileError,
|
|
1257
|
+
expandedDirs,
|
|
1258
|
+
filter,
|
|
1259
|
+
onFilterChange,
|
|
1260
|
+
onSelectFile,
|
|
1261
|
+
onToggleDir,
|
|
1262
|
+
onRefresh,
|
|
1263
|
+
fileBaseUrl,
|
|
1264
|
+
className
|
|
1265
|
+
}) {
|
|
1266
|
+
const contentRef = useRef5(null);
|
|
1267
|
+
if (loading) {
|
|
1268
|
+
return /* @__PURE__ */ jsxs12("div", { className: cn("flex items-center justify-center py-12", className), children: [
|
|
1269
|
+
/* @__PURE__ */ jsx13(Loader2, { className: "mr-2 h-4 w-4 animate-spin text-white/40" }),
|
|
1270
|
+
/* @__PURE__ */ jsx13("span", { className: "text-sm text-white/50", children: "Loading files..." })
|
|
1271
|
+
] });
|
|
1272
|
+
}
|
|
1273
|
+
if (files.length === 0) {
|
|
1274
|
+
return /* @__PURE__ */ jsxs12("div", { className: cn("flex flex-col items-center justify-center py-12 text-center", className), children: [
|
|
1275
|
+
/* @__PURE__ */ jsx13(FolderOpen, { className: "mb-3 h-10 w-10 text-white/20" }),
|
|
1276
|
+
/* @__PURE__ */ jsx13("p", { className: "text-sm font-medium text-white/50", children: "No files in workspace" }),
|
|
1277
|
+
/* @__PURE__ */ jsx13("p", { className: "mt-1 text-xs text-white/40", children: "Files created by the agent will appear here" })
|
|
1278
|
+
] });
|
|
1279
|
+
}
|
|
1280
|
+
const filteredFiles = filter ? files.filter((f) => f.path.toLowerCase().includes(filter.toLowerCase())) : files;
|
|
1281
|
+
const tree = buildFileTree(filteredFiles);
|
|
1282
|
+
const selectedFile = files.find((f) => f.path === selectedPath);
|
|
1283
|
+
const downloadUrl = fileBaseUrl && selectedPath ? `${fileBaseUrl}/${selectedPath}` : void 0;
|
|
1284
|
+
return /* @__PURE__ */ jsxs12("div", { className: cn("flex flex-col h-full", className), children: [
|
|
1285
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2 border-b border-white/10 px-3 py-2 shrink-0", children: [
|
|
1286
|
+
source && /* @__PURE__ */ jsx13("span", { className: cn(
|
|
1287
|
+
"rounded-full px-2 py-0.5 text-[10px] font-medium border",
|
|
1288
|
+
source === "sandbox" ? "bg-green-500/10 text-green-400 border-green-500/20" : "bg-white/5 text-white/40 border-white/10"
|
|
1289
|
+
), children: source }),
|
|
1290
|
+
/* @__PURE__ */ jsxs12("span", { className: "text-xs text-white/40", children: [
|
|
1291
|
+
files.length,
|
|
1292
|
+
" files"
|
|
1293
|
+
] }),
|
|
1294
|
+
/* @__PURE__ */ jsx13("div", { className: "flex-1" }),
|
|
1295
|
+
/* @__PURE__ */ jsxs12("div", { className: "relative", children: [
|
|
1296
|
+
/* @__PURE__ */ jsx13(Search, { className: "absolute left-2 top-1/2 h-3 w-3 -translate-y-1/2 text-white/30" }),
|
|
1297
|
+
/* @__PURE__ */ jsx13(
|
|
1298
|
+
"input",
|
|
1299
|
+
{
|
|
1300
|
+
type: "text",
|
|
1301
|
+
value: filter,
|
|
1302
|
+
onChange: (e) => onFilterChange(e.target.value),
|
|
1303
|
+
placeholder: "Filter...",
|
|
1304
|
+
className: "w-40 rounded-md border border-white/10 bg-white/5 pl-7 pr-2 py-1 text-xs text-white placeholder:text-white/30 focus:border-accent/50 focus:outline-none"
|
|
1305
|
+
}
|
|
1306
|
+
),
|
|
1307
|
+
filter && /* @__PURE__ */ jsx13(
|
|
1308
|
+
"button",
|
|
1309
|
+
{
|
|
1310
|
+
onClick: () => onFilterChange(""),
|
|
1311
|
+
className: "absolute right-1.5 top-1/2 -translate-y-1/2 text-white/30 hover:text-white",
|
|
1312
|
+
children: /* @__PURE__ */ jsx13(X, { className: "h-3 w-3" })
|
|
1313
|
+
}
|
|
1314
|
+
)
|
|
1315
|
+
] }),
|
|
1316
|
+
/* @__PURE__ */ jsx13(
|
|
1317
|
+
"button",
|
|
1318
|
+
{
|
|
1319
|
+
onClick: onRefresh,
|
|
1320
|
+
className: "rounded-md p-1 text-white/40 hover:bg-white/5 hover:text-white transition-colors",
|
|
1321
|
+
title: "Refresh",
|
|
1322
|
+
children: /* @__PURE__ */ jsx13(RefreshCw, { className: "h-3.5 w-3.5" })
|
|
1323
|
+
}
|
|
1324
|
+
)
|
|
1325
|
+
] }),
|
|
1326
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex flex-1 min-h-0", children: [
|
|
1327
|
+
/* @__PURE__ */ jsx13("div", { className: "w-64 shrink-0 border-r border-white/10 overflow-y-auto scrollbar-thin", children: /* @__PURE__ */ jsx13(
|
|
1328
|
+
FileTree,
|
|
1329
|
+
{
|
|
1330
|
+
nodes: tree,
|
|
1331
|
+
selectedPath,
|
|
1332
|
+
expandedDirs,
|
|
1333
|
+
onSelectFile,
|
|
1334
|
+
onToggleDir
|
|
1335
|
+
}
|
|
1336
|
+
) }),
|
|
1337
|
+
/* @__PURE__ */ jsx13("div", { className: "flex-1 min-w-0 flex flex-col overflow-hidden", children: !selectedPath ? /* @__PURE__ */ jsx13("div", { className: "flex flex-1 items-center justify-center text-center", children: /* @__PURE__ */ jsxs12("div", { children: [
|
|
1338
|
+
/* @__PURE__ */ jsx13(FileText, { className: "mx-auto mb-2 h-8 w-8 text-white/15" }),
|
|
1339
|
+
/* @__PURE__ */ jsx13("p", { className: "text-xs text-white/30", children: "Select a file to view its contents" })
|
|
1340
|
+
] }) }) : /* @__PURE__ */ jsxs12(Fragment5, { children: [
|
|
1341
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between border-b border-white/10 px-4 py-2 shrink-0", children: [
|
|
1342
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
1343
|
+
/* @__PURE__ */ jsx13("span", { className: "font-mono text-xs text-white/60 truncate", children: selectedPath }),
|
|
1344
|
+
selectedFile && /* @__PURE__ */ jsx13("span", { className: "text-[10px] text-white/30 shrink-0", children: formatBytes(selectedFile.size) })
|
|
1345
|
+
] }),
|
|
1346
|
+
downloadUrl && /* @__PURE__ */ jsx13(
|
|
1347
|
+
"a",
|
|
1348
|
+
{
|
|
1349
|
+
href: downloadUrl,
|
|
1350
|
+
download: true,
|
|
1351
|
+
className: "rounded-md p-1.5 text-white/40 hover:bg-white/5 hover:text-white transition-colors shrink-0",
|
|
1352
|
+
title: "Download",
|
|
1353
|
+
children: /* @__PURE__ */ jsx13(Download, { className: "h-3.5 w-3.5" })
|
|
1354
|
+
}
|
|
1355
|
+
)
|
|
1356
|
+
] }),
|
|
1357
|
+
/* @__PURE__ */ jsx13("div", { className: "flex-1 overflow-auto bg-[#0d1117]", children: fileLoading ? /* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-center py-12", children: [
|
|
1358
|
+
/* @__PURE__ */ jsx13(Loader2, { className: "mr-2 h-4 w-4 animate-spin text-white/40" }),
|
|
1359
|
+
/* @__PURE__ */ jsx13("span", { className: "text-xs text-white/50", children: "Loading..." })
|
|
1360
|
+
] }) : fileError ? /* @__PURE__ */ jsxs12("div", { className: "flex flex-col items-center justify-center py-12 text-center px-4", children: [
|
|
1361
|
+
/* @__PURE__ */ jsx13("p", { className: "text-xs text-white/40", children: fileError }),
|
|
1362
|
+
downloadUrl && /* @__PURE__ */ jsx13(
|
|
1363
|
+
"a",
|
|
1364
|
+
{
|
|
1365
|
+
href: downloadUrl,
|
|
1366
|
+
download: true,
|
|
1367
|
+
className: "mt-2 text-xs text-accent hover:underline",
|
|
1368
|
+
children: "Download file"
|
|
1369
|
+
}
|
|
1370
|
+
)
|
|
1371
|
+
] }) : isImageFile(selectedPath) ? /* @__PURE__ */ jsx13("div", { className: "flex items-center justify-center p-4", children: downloadUrl && /* @__PURE__ */ jsx13(
|
|
1372
|
+
"img",
|
|
1373
|
+
{
|
|
1374
|
+
src: downloadUrl,
|
|
1375
|
+
alt: selectedPath,
|
|
1376
|
+
className: "max-w-full max-h-[60vh] object-contain rounded"
|
|
1377
|
+
}
|
|
1378
|
+
) }) : /* @__PURE__ */ jsx13(
|
|
1379
|
+
"pre",
|
|
1380
|
+
{
|
|
1381
|
+
ref: contentRef,
|
|
1382
|
+
className: "p-4 text-xs font-mono text-white/70 leading-relaxed whitespace-pre-wrap break-words",
|
|
1383
|
+
children: fileContent
|
|
1384
|
+
}
|
|
1385
|
+
) })
|
|
1386
|
+
] }) })
|
|
1387
|
+
] })
|
|
1388
|
+
] });
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
// src/components/BottomPanels.tsx
|
|
1392
|
+
import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1393
|
+
function BottomPanels({
|
|
1394
|
+
terminalOpen,
|
|
1395
|
+
onToggleTerminal,
|
|
1396
|
+
filesOpen,
|
|
1397
|
+
onToggleFiles,
|
|
1398
|
+
className
|
|
1399
|
+
}) {
|
|
1400
|
+
return /* @__PURE__ */ jsxs13("div", { className: cn("flex border-t border-white/10 bg-[#161b22] rounded-b-lg", className), children: [
|
|
1401
|
+
/* @__PURE__ */ jsxs13(
|
|
1402
|
+
"button",
|
|
1403
|
+
{
|
|
1404
|
+
onClick: onToggleTerminal,
|
|
1405
|
+
className: cn(
|
|
1406
|
+
"flex flex-1 items-center justify-center gap-1.5 py-1 text-[10px] font-medium transition-colors",
|
|
1407
|
+
terminalOpen ? "text-accent hover:text-accent/80" : "text-white/40 hover:text-white/60 hover:bg-[#1c2128]"
|
|
1408
|
+
),
|
|
1409
|
+
children: [
|
|
1410
|
+
/* @__PURE__ */ jsx14(Terminal, { className: "h-3 w-3" }),
|
|
1411
|
+
"Terminal",
|
|
1412
|
+
terminalOpen ? /* @__PURE__ */ jsx14(ChevronDown, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx14(ChevronUp, { className: "h-3 w-3" })
|
|
1413
|
+
]
|
|
1414
|
+
}
|
|
1415
|
+
),
|
|
1416
|
+
/* @__PURE__ */ jsx14("div", { className: "w-px bg-white/10" }),
|
|
1417
|
+
/* @__PURE__ */ jsxs13(
|
|
1418
|
+
"button",
|
|
1419
|
+
{
|
|
1420
|
+
onClick: onToggleFiles,
|
|
1421
|
+
className: cn(
|
|
1422
|
+
"flex flex-1 items-center justify-center gap-1.5 py-1 text-[10px] font-medium transition-colors",
|
|
1423
|
+
filesOpen ? "text-accent hover:text-accent/80" : "text-white/40 hover:text-white/60 hover:bg-[#1c2128]"
|
|
1424
|
+
),
|
|
1425
|
+
children: [
|
|
1426
|
+
/* @__PURE__ */ jsx14(FolderOpen, { className: "h-3 w-3" }),
|
|
1427
|
+
"Files",
|
|
1428
|
+
filesOpen ? /* @__PURE__ */ jsx14(ChevronDown, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx14(ChevronUp, { className: "h-3 w-3" })
|
|
1429
|
+
]
|
|
1430
|
+
}
|
|
1431
|
+
)
|
|
1432
|
+
] });
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
// src/hooks/useTerminal.ts
|
|
1436
|
+
import { useEffect as useEffect6, useState as useState8, useRef as useRef6, useCallback as useCallback6 } from "react";
|
|
1437
|
+
function useTerminal({
|
|
1438
|
+
client,
|
|
1439
|
+
sessionId,
|
|
1440
|
+
historical,
|
|
1441
|
+
pollInterval = 500
|
|
1442
|
+
}) {
|
|
1443
|
+
const [logs, setLogs] = useState8([]);
|
|
1444
|
+
const [connected, setConnected] = useState8(null);
|
|
1445
|
+
const lastIndexRef = useRef6(-1);
|
|
1446
|
+
const pollingRef = useRef6(null);
|
|
1447
|
+
const fetchLogs = useCallback6(async () => {
|
|
1448
|
+
try {
|
|
1449
|
+
const afterOpt = lastIndexRef.current >= 0 ? { after: lastIndexRef.current } : void 0;
|
|
1450
|
+
const data = await client.getSessionLogs(sessionId, afterOpt);
|
|
1451
|
+
setConnected(true);
|
|
1452
|
+
if (data.logs && data.logs.length > 0) {
|
|
1453
|
+
setLogs((prev) => [...prev, ...data.logs]);
|
|
1454
|
+
lastIndexRef.current = data.logs[data.logs.length - 1].index;
|
|
1455
|
+
}
|
|
1456
|
+
} catch {
|
|
1457
|
+
setConnected(false);
|
|
1458
|
+
}
|
|
1459
|
+
}, [client, sessionId]);
|
|
1460
|
+
useEffect6(() => {
|
|
1461
|
+
setLogs([]);
|
|
1462
|
+
lastIndexRef.current = -1;
|
|
1463
|
+
setConnected(null);
|
|
1464
|
+
fetchLogs();
|
|
1465
|
+
if (!historical) {
|
|
1466
|
+
pollingRef.current = setInterval(fetchLogs, pollInterval);
|
|
1467
|
+
return () => {
|
|
1468
|
+
if (pollingRef.current) clearInterval(pollingRef.current);
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
}, [sessionId, historical, pollInterval, fetchLogs]);
|
|
1472
|
+
const clearLogs = useCallback6(() => {
|
|
1473
|
+
setLogs([]);
|
|
1474
|
+
}, []);
|
|
1475
|
+
return { logs, connected, clearLogs };
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
// src/hooks/useFileBrowser.ts
|
|
1479
|
+
import { useEffect as useEffect7, useState as useState9, useCallback as useCallback7 } from "react";
|
|
1480
|
+
function useFileBrowser({
|
|
1481
|
+
client,
|
|
1482
|
+
sessionId
|
|
1483
|
+
}) {
|
|
1484
|
+
const [files, setFiles] = useState9([]);
|
|
1485
|
+
const [source, setSource] = useState9(null);
|
|
1486
|
+
const [loading, setLoading] = useState9(true);
|
|
1487
|
+
const [selectedPath, setSelectedPath] = useState9(null);
|
|
1488
|
+
const [fileContent, setFileContent] = useState9(null);
|
|
1489
|
+
const [fileLoading, setFileLoading] = useState9(false);
|
|
1490
|
+
const [fileError, setFileError] = useState9(null);
|
|
1491
|
+
const [expandedDirs, setExpandedDirs] = useState9(/* @__PURE__ */ new Set());
|
|
1492
|
+
const [filter, setFilter] = useState9("");
|
|
1493
|
+
const fetchFiles = useCallback7(async () => {
|
|
1494
|
+
setLoading(true);
|
|
1495
|
+
try {
|
|
1496
|
+
const data = await client.getSessionFiles(sessionId);
|
|
1497
|
+
setFiles(data.files || []);
|
|
1498
|
+
setSource(data.source || null);
|
|
1499
|
+
const topDirs = /* @__PURE__ */ new Set();
|
|
1500
|
+
for (const f of data.files || []) {
|
|
1501
|
+
const first = f.path.split("/")[0];
|
|
1502
|
+
if (f.path.includes("/")) topDirs.add(first);
|
|
1503
|
+
}
|
|
1504
|
+
setExpandedDirs(topDirs);
|
|
1505
|
+
} catch {
|
|
1506
|
+
} finally {
|
|
1507
|
+
setLoading(false);
|
|
1508
|
+
}
|
|
1509
|
+
}, [client, sessionId]);
|
|
1510
|
+
useEffect7(() => {
|
|
1511
|
+
fetchFiles();
|
|
1512
|
+
}, [fetchFiles]);
|
|
1513
|
+
const selectFile = useCallback7(async (path) => {
|
|
1514
|
+
setSelectedPath(path);
|
|
1515
|
+
setFileContent(null);
|
|
1516
|
+
setFileError(null);
|
|
1517
|
+
const ext = path.split(".").pop()?.toLowerCase();
|
|
1518
|
+
const imageExts = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "svg", "webp", "ico"]);
|
|
1519
|
+
if (imageExts.has(ext || "")) return;
|
|
1520
|
+
setFileLoading(true);
|
|
1521
|
+
try {
|
|
1522
|
+
const data = await client.getSessionFile(sessionId, path);
|
|
1523
|
+
setFileContent(data.content || "");
|
|
1524
|
+
} catch (err) {
|
|
1525
|
+
setFileError("Failed to load file content");
|
|
1526
|
+
} finally {
|
|
1527
|
+
setFileLoading(false);
|
|
1528
|
+
}
|
|
1529
|
+
}, [client, sessionId]);
|
|
1530
|
+
const toggleDir = useCallback7((path) => {
|
|
1531
|
+
setExpandedDirs((prev) => {
|
|
1532
|
+
const next = new Set(prev);
|
|
1533
|
+
if (next.has(path)) next.delete(path);
|
|
1534
|
+
else next.add(path);
|
|
1535
|
+
return next;
|
|
1536
|
+
});
|
|
1537
|
+
}, []);
|
|
1538
|
+
return {
|
|
1539
|
+
files,
|
|
1540
|
+
source,
|
|
1541
|
+
loading,
|
|
1542
|
+
selectedPath,
|
|
1543
|
+
fileContent,
|
|
1544
|
+
fileLoading,
|
|
1545
|
+
fileError,
|
|
1546
|
+
expandedDirs,
|
|
1547
|
+
filter,
|
|
1548
|
+
setFilter,
|
|
1549
|
+
selectFile,
|
|
1550
|
+
toggleDir,
|
|
1551
|
+
refresh: fetchFiles
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// src/components/Playground.tsx
|
|
1556
|
+
import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1557
|
+
function PlaygroundInner({ className }) {
|
|
1558
|
+
const ctx = usePlaygroundContext();
|
|
1559
|
+
const {
|
|
1560
|
+
client,
|
|
1561
|
+
chat,
|
|
1562
|
+
agents,
|
|
1563
|
+
sessions,
|
|
1564
|
+
health,
|
|
1565
|
+
fileUpload,
|
|
1566
|
+
selectedAgent,
|
|
1567
|
+
setSelectedAgent,
|
|
1568
|
+
showHistory,
|
|
1569
|
+
setShowHistory,
|
|
1570
|
+
terminalOpen,
|
|
1571
|
+
setTerminalOpen,
|
|
1572
|
+
filesOpen,
|
|
1573
|
+
setFilesOpen
|
|
1574
|
+
} = ctx;
|
|
1575
|
+
const selectedAgentObj = agents.agents.find((a) => (a.slug || a.name) === selectedAgent);
|
|
1576
|
+
const isActive = chat.sessionId !== null;
|
|
1577
|
+
return /* @__PURE__ */ jsxs14("div", { className: cn("flex flex-col", className), children: [
|
|
1578
|
+
/* @__PURE__ */ jsx15(
|
|
1579
|
+
PlaygroundHeader,
|
|
1580
|
+
{
|
|
1581
|
+
agents: agents.agents,
|
|
1582
|
+
selectedAgent,
|
|
1583
|
+
onAgentChange: setSelectedAgent,
|
|
1584
|
+
runtimeConnected: health.connected,
|
|
1585
|
+
showHistory,
|
|
1586
|
+
onToggleHistory: () => setShowHistory(!showHistory),
|
|
1587
|
+
sessionId: chat.sessionId,
|
|
1588
|
+
onNewChat: chat.startNewChat
|
|
1589
|
+
}
|
|
1590
|
+
),
|
|
1591
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex flex-1 gap-4 min-h-0", children: [
|
|
1592
|
+
showHistory && /* @__PURE__ */ jsx15(
|
|
1593
|
+
SessionHistory,
|
|
1594
|
+
{
|
|
1595
|
+
sessions: sessions.sessions,
|
|
1596
|
+
activeSessionId: chat.sessionId,
|
|
1597
|
+
onSelectSession: (s) => {
|
|
1598
|
+
chat.loadSession(s.id);
|
|
1599
|
+
setShowHistory(false);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
),
|
|
1603
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex-1 flex flex-col min-w-0 gap-0", children: [
|
|
1604
|
+
/* @__PURE__ */ jsx15(
|
|
1605
|
+
Chat,
|
|
1606
|
+
{
|
|
1607
|
+
messages: chat.messages,
|
|
1608
|
+
input: chat.input,
|
|
1609
|
+
onInputChange: chat.setInput,
|
|
1610
|
+
onSend: chat.send,
|
|
1611
|
+
sending: chat.sending,
|
|
1612
|
+
loading: chat.loading,
|
|
1613
|
+
error: chat.error,
|
|
1614
|
+
isActive,
|
|
1615
|
+
agentName: selectedAgentObj?.name,
|
|
1616
|
+
attachedFiles: chat.attachedFiles,
|
|
1617
|
+
onFileSelect: fileUpload.handleFileSelect,
|
|
1618
|
+
onRemoveFile: fileUpload.removeFile,
|
|
1619
|
+
className: cn(
|
|
1620
|
+
"rounded-lg border border-white/10 bg-white/[0.02]",
|
|
1621
|
+
terminalOpen && chat.sessionId ? "flex-1 min-h-0" : "flex-1"
|
|
1622
|
+
)
|
|
1623
|
+
}
|
|
1624
|
+
),
|
|
1625
|
+
chat.sessionId && /* @__PURE__ */ jsx15("div", { className: cn(
|
|
1626
|
+
"shrink-0 transition-all",
|
|
1627
|
+
terminalOpen ? "h-64" : "h-0"
|
|
1628
|
+
), children: terminalOpen && /* @__PURE__ */ jsx15(
|
|
1629
|
+
TerminalPanel,
|
|
1630
|
+
{
|
|
1631
|
+
client,
|
|
1632
|
+
sessionId: chat.sessionId
|
|
1633
|
+
}
|
|
1634
|
+
) }),
|
|
1635
|
+
chat.sessionId && /* @__PURE__ */ jsx15("div", { className: cn(
|
|
1636
|
+
"shrink-0 transition-all",
|
|
1637
|
+
filesOpen ? "h-72" : "h-0"
|
|
1638
|
+
), children: filesOpen && /* @__PURE__ */ jsx15(
|
|
1639
|
+
FileBrowserPanel,
|
|
1640
|
+
{
|
|
1641
|
+
client,
|
|
1642
|
+
sessionId: chat.sessionId
|
|
1643
|
+
}
|
|
1644
|
+
) }),
|
|
1645
|
+
chat.sessionId && /* @__PURE__ */ jsx15(
|
|
1646
|
+
BottomPanels,
|
|
1647
|
+
{
|
|
1648
|
+
terminalOpen,
|
|
1649
|
+
onToggleTerminal: () => setTerminalOpen(!terminalOpen),
|
|
1650
|
+
filesOpen,
|
|
1651
|
+
onToggleFiles: () => setFilesOpen(!filesOpen)
|
|
1652
|
+
}
|
|
1653
|
+
)
|
|
1654
|
+
] })
|
|
1655
|
+
] })
|
|
1656
|
+
] });
|
|
1657
|
+
}
|
|
1658
|
+
function TerminalPanel({ client, sessionId }) {
|
|
1659
|
+
const { logs, connected, clearLogs } = useTerminal({ client, sessionId });
|
|
1660
|
+
return /* @__PURE__ */ jsx15(
|
|
1661
|
+
Terminal2,
|
|
1662
|
+
{
|
|
1663
|
+
logs,
|
|
1664
|
+
connected,
|
|
1665
|
+
onClear: clearLogs,
|
|
1666
|
+
className: "h-full rounded-t-none border-t-0"
|
|
1667
|
+
}
|
|
1668
|
+
);
|
|
1669
|
+
}
|
|
1670
|
+
function FileBrowserPanel({ client, sessionId }) {
|
|
1671
|
+
const fb = useFileBrowser({ client, sessionId });
|
|
1672
|
+
return /* @__PURE__ */ jsx15(
|
|
1673
|
+
FileBrowser,
|
|
1674
|
+
{
|
|
1675
|
+
files: fb.files,
|
|
1676
|
+
source: fb.source,
|
|
1677
|
+
loading: fb.loading,
|
|
1678
|
+
selectedPath: fb.selectedPath,
|
|
1679
|
+
fileContent: fb.fileContent,
|
|
1680
|
+
fileLoading: fb.fileLoading,
|
|
1681
|
+
fileError: fb.fileError,
|
|
1682
|
+
expandedDirs: fb.expandedDirs,
|
|
1683
|
+
filter: fb.filter,
|
|
1684
|
+
onFilterChange: fb.setFilter,
|
|
1685
|
+
onSelectFile: fb.selectFile,
|
|
1686
|
+
onToggleDir: fb.toggleDir,
|
|
1687
|
+
onRefresh: fb.refresh,
|
|
1688
|
+
className: "h-full border-t border-white/10"
|
|
1689
|
+
}
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
function Playground({ client, defaultAgent, className }) {
|
|
1693
|
+
return /* @__PURE__ */ jsx15(PlaygroundProvider, { client, defaultAgent, children: /* @__PURE__ */ jsx15(PlaygroundInner, { className }) });
|
|
1694
|
+
}
|
|
1695
|
+
export {
|
|
1696
|
+
BottomPanels,
|
|
1697
|
+
Chat,
|
|
1698
|
+
ChatInput,
|
|
1699
|
+
ChatMessage,
|
|
1700
|
+
ChatMessages,
|
|
1701
|
+
FileBrowser,
|
|
1702
|
+
FileTree,
|
|
1703
|
+
Playground,
|
|
1704
|
+
PlaygroundHeader,
|
|
1705
|
+
PlaygroundProvider,
|
|
1706
|
+
SessionHistory,
|
|
1707
|
+
StatusIndicator,
|
|
1708
|
+
Terminal2 as Terminal,
|
|
1709
|
+
ToolCallBlock,
|
|
1710
|
+
buildFileTree,
|
|
1711
|
+
cn,
|
|
1712
|
+
formatBytes,
|
|
1713
|
+
formatTime,
|
|
1714
|
+
getFileLanguage,
|
|
1715
|
+
isImageFile,
|
|
1716
|
+
parseContentBlocks,
|
|
1717
|
+
useAgents,
|
|
1718
|
+
useFileBrowser,
|
|
1719
|
+
useFileUpload,
|
|
1720
|
+
useHealthCheck,
|
|
1721
|
+
usePlaygroundChat,
|
|
1722
|
+
usePlaygroundContext,
|
|
1723
|
+
useSessions,
|
|
1724
|
+
useTerminal
|
|
1725
|
+
};
|
|
1726
|
+
//# sourceMappingURL=index.js.map
|