@yumiai/chat-widget 0.1.0

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/index.js ADDED
@@ -0,0 +1,3182 @@
1
+ // src/ChatWidget.tsx
2
+ import React19, { useCallback as useCallback5, useEffect as useEffect4, useMemo as useMemo5, useRef as useRef6, useState as useState11 } from "react";
3
+
4
+ // src/types.ts
5
+ function isTodoNotificationContent(data) {
6
+ if (!data || typeof data !== "object") return false;
7
+ const obj = data;
8
+ return obj.operation === "checklist_edit" && obj.checklist != null;
9
+ }
10
+ var DEFAULT_CONFIG = {
11
+ showPinnedArea: true,
12
+ showPlan: true,
13
+ enableTypewriter: true,
14
+ typewriterSpeed: 30,
15
+ autoScroll: true,
16
+ childAgentMaxHeight: 200,
17
+ theme: "auto",
18
+ displayLevels: [0, 1, 2]
19
+ };
20
+ function mergeConsecutiveThinkMessages(messages) {
21
+ if (messages.length === 0) return messages;
22
+ const result = [];
23
+ let pendingThink = null;
24
+ for (const msg of messages) {
25
+ if (msg.contentType === "think") {
26
+ if (pendingThink) {
27
+ const prev = pendingThink;
28
+ pendingThink = {
29
+ ...prev,
30
+ contentChunks: [...prev.contentChunks, ...msg.contentChunks]
31
+ };
32
+ } else {
33
+ pendingThink = { ...msg, contentChunks: [...msg.contentChunks] };
34
+ }
35
+ } else {
36
+ if (pendingThink) {
37
+ result.push(pendingThink);
38
+ pendingThink = null;
39
+ }
40
+ result.push(msg);
41
+ }
42
+ }
43
+ if (pendingThink) {
44
+ result.push(pendingThink);
45
+ }
46
+ return result;
47
+ }
48
+
49
+ // src/hooks/useMessageAggregator.ts
50
+ import { useCallback, useRef, useState } from "react";
51
+ function extractTaskPurposeFromPreview(preview) {
52
+ const m = preview.match(/"_?task_purpose"\s*:\s*"([^"]*)"/);
53
+ return m?.[1] || void 0;
54
+ }
55
+ function useMessageAggregator() {
56
+ const [state, setState] = useState({
57
+ rounds: /* @__PURE__ */ new Map(),
58
+ currentInteractionId: null
59
+ });
60
+ const [todoMap, setTodoMap] = useState(/* @__PURE__ */ new Map());
61
+ const todoMapRef = useRef(/* @__PURE__ */ new Map());
62
+ const pendingUpdates = useRef([]);
63
+ const rafId = useRef(null);
64
+ const messageIndexRef = useRef(/* @__PURE__ */ new Map());
65
+ const roundIndexRef = useRef(/* @__PURE__ */ new Map());
66
+ const processedChunksRef = useRef(/* @__PURE__ */ new Set());
67
+ const callBatchToRoundRef = useRef(/* @__PURE__ */ new Map());
68
+ const parsePlan = useCallback((content) => {
69
+ try {
70
+ const parsed = JSON.parse(content);
71
+ if (parsed.items && Array.isArray(parsed.items)) {
72
+ return {
73
+ title: parsed.title,
74
+ items: parsed.items.map((item) => ({
75
+ text: item.text || "",
76
+ status: item.status || "pending"
77
+ })),
78
+ progress: {
79
+ completed: parsed.items.filter((i) => i.status === "completed").length,
80
+ total: parsed.items.length
81
+ }
82
+ };
83
+ }
84
+ } catch {
85
+ const lines = content.split("\n").filter(Boolean);
86
+ return {
87
+ items: lines.map((line) => ({
88
+ text: line.replace(/^[-*]\s*/, ""),
89
+ status: "pending"
90
+ })),
91
+ progress: { completed: 0, total: lines.length }
92
+ };
93
+ }
94
+ return null;
95
+ }, []);
96
+ const parseSingleArtifact = useCallback((parsed) => {
97
+ if (parsed.type === "file" && parsed.data) {
98
+ const data = parsed.data;
99
+ const fileSize = data.file_size;
100
+ const sizeStr = fileSize ? fileSize > 1024 ? `${(fileSize / 1024).toFixed(1)} KB` : `${fileSize} B` : void 0;
101
+ return {
102
+ id: parsed.identifier || data.file_id || crypto.randomUUID(),
103
+ name: data.file_name || parsed.description || "Untitled",
104
+ type: data.mime_type || "text/plain",
105
+ size: sizeStr,
106
+ content: parsed.description,
107
+ preview: `${parsed.intention}: ${parsed.description}`,
108
+ metadata: {
109
+ gitPath: data.git_path,
110
+ gitCommitHash: data.git_commit_hash,
111
+ createdByAgentId: data.created_by_agent_id,
112
+ operationType: data.operation_type
113
+ }
114
+ };
115
+ }
116
+ return {
117
+ id: parsed.id || crypto.randomUUID(),
118
+ name: parsed.name || "Untitled",
119
+ type: parsed.type || "text/plain",
120
+ size: parsed.size,
121
+ content: parsed.content,
122
+ preview: parsed.preview
123
+ };
124
+ }, []);
125
+ const parseArtifact = useCallback((content) => {
126
+ try {
127
+ const parsed = JSON.parse(content);
128
+ return parseSingleArtifact(parsed);
129
+ } catch {
130
+ const firstJsonEnd = content.indexOf("}{");
131
+ if (firstJsonEnd > 0) {
132
+ try {
133
+ const firstJson = content.substring(0, firstJsonEnd + 1);
134
+ const parsed = JSON.parse(firstJson);
135
+ return parseSingleArtifact(parsed);
136
+ } catch {
137
+ return null;
138
+ }
139
+ }
140
+ return null;
141
+ }
142
+ }, [parseSingleArtifact]);
143
+ const parseArtifacts = useCallback((content) => {
144
+ const artifacts = [];
145
+ const jsonStrings = content.split(/\}\s*\{/).map((str, index, arr) => {
146
+ if (index === 0) return str + (arr.length > 1 ? "}" : "");
147
+ if (index === arr.length - 1) return "{" + str;
148
+ return "{" + str + "}";
149
+ });
150
+ for (const jsonStr of jsonStrings) {
151
+ try {
152
+ const parsed = JSON.parse(jsonStr);
153
+ const artifact = parseSingleArtifact(parsed);
154
+ if (artifact) {
155
+ artifacts.push(artifact);
156
+ }
157
+ } catch {
158
+ }
159
+ }
160
+ return artifacts;
161
+ }, [parseSingleArtifact]);
162
+ const parseHITLRequest = useCallback((content, contentType) => {
163
+ try {
164
+ const parsed = JSON.parse(content);
165
+ return {
166
+ await_command_uuid: parsed.await_command_uuid || "",
167
+ schema_type: contentType === "html_schema" ? "html_schema" : "json_schema",
168
+ schema: parsed.schema || parsed,
169
+ title: parsed.title,
170
+ description: parsed.description
171
+ };
172
+ } catch {
173
+ return null;
174
+ }
175
+ }, []);
176
+ const flushUpdates = useCallback(() => {
177
+ if (pendingUpdates.current.length === 0) return;
178
+ const updates = [...pendingUpdates.current];
179
+ pendingUpdates.current = [];
180
+ let latestInteractionId = null;
181
+ let todoMapChanged = false;
182
+ for (const msg of updates) {
183
+ const { agent_instance_id, call_batch_id, content_type, content } = msg;
184
+ let { interaction_id } = msg;
185
+ if (msg.notification_type === "node_summary") {
186
+ continue;
187
+ }
188
+ if (msg.notification_type === "compression_started" || msg.notification_type === "compression_completed") {
189
+ continue;
190
+ }
191
+ if (msg.notification_type === "plan_result" && content) {
192
+ try {
193
+ const parsed = typeof content === "string" ? JSON.parse(content) : content;
194
+ if (isTodoNotificationContent(parsed)) {
195
+ const planId = parsed.plan_id || "default";
196
+ todoMapRef.current = new Map(todoMapRef.current).set(planId, parsed.checklist);
197
+ todoMapChanged = true;
198
+ continue;
199
+ }
200
+ } catch {
201
+ }
202
+ }
203
+ const existingRoundForBatch = callBatchToRoundRef.current.get(call_batch_id);
204
+ if (existingRoundForBatch && existingRoundForBatch !== interaction_id) {
205
+ interaction_id = existingRoundForBatch;
206
+ } else if (!existingRoundForBatch) {
207
+ callBatchToRoundRef.current.set(call_batch_id, interaction_id);
208
+ }
209
+ let round = roundIndexRef.current.get(interaction_id);
210
+ if (!round) {
211
+ round = {
212
+ interactionId: interaction_id,
213
+ index: roundIndexRef.current.size + 1,
214
+ userMessage: "",
215
+ timestamp: msg.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
216
+ status: "running",
217
+ messages: []
218
+ };
219
+ roundIndexRef.current.set(interaction_id, round);
220
+ }
221
+ latestInteractionId = interaction_id;
222
+ const notification_type = msg.notification_type;
223
+ const isMcpMessage = notification_type && ["mcp_generating", "mcp_start", "mcp_result", "mcp_end"].includes(notification_type);
224
+ let messageKey = isMcpMessage ? `${interaction_id}-${agent_instance_id}-mcp-${msg.tool_call_id || call_batch_id}` : `${interaction_id}-${agent_instance_id}-${call_batch_id}-${content_type}`;
225
+ if (isMcpMessage && notification_type !== "mcp_generating" && !messageIndexRef.current.has(messageKey)) {
226
+ const fallback = round.messages.find(
227
+ (m) => m.toolPhase && m.toolPhase !== "complete" && m.toolPhase !== "error" && m.callBatchId === call_batch_id && (msg.tool_name ? m.toolName === msg.tool_name : true)
228
+ );
229
+ if (fallback) {
230
+ messageKey = fallback.id;
231
+ }
232
+ }
233
+ if (!isMcpMessage && messageIndexRef.current.has(messageKey)) {
234
+ const baseKey = messageKey;
235
+ const candidateKeys = [baseKey];
236
+ let segIdx = 1;
237
+ while (messageIndexRef.current.has(`${baseKey}-${segIdx}`)) {
238
+ candidateKeys.push(`${baseKey}-${segIdx}`);
239
+ segIdx++;
240
+ }
241
+ const lastMsgId = round.messages.length > 0 ? round.messages[round.messages.length - 1].id : null;
242
+ const mergeTarget = candidateKeys.find((k) => k === lastMsgId);
243
+ if (mergeTarget) {
244
+ messageKey = mergeTarget;
245
+ } else {
246
+ const lastMsg = round.messages.length > 0 ? round.messages[round.messages.length - 1] : null;
247
+ const isAfterToolCard = lastMsg && lastMsg.toolPhase !== void 0;
248
+ const isShortFragment = content.length < 10;
249
+ if (isAfterToolCard && isShortFragment) {
250
+ const latestTextKey = candidateKeys[candidateKeys.length - 1];
251
+ messageKey = latestTextKey;
252
+ } else {
253
+ messageKey = `${baseKey}-${segIdx}`;
254
+ }
255
+ }
256
+ }
257
+ let message = messageIndexRef.current.get(messageKey);
258
+ if (!message) {
259
+ message = {
260
+ id: messageKey,
261
+ agentInstanceId: agent_instance_id,
262
+ callBatchId: call_batch_id,
263
+ parentCallBatchId: msg.parent_call_batch_id,
264
+ level: msg.level,
265
+ agentName: msg.agent_name,
266
+ taskPurpose: msg.task_purpose,
267
+ // 任务目标
268
+ notificationType: msg.notification_type,
269
+ // 顶层通知类型
270
+ toolName: msg.tool_name,
271
+ // 工具名称
272
+ toolCallId: msg.tool_call_id,
273
+ // 工具调用 ID
274
+ toolStatus: msg.tool_status,
275
+ // 工具执行状态
276
+ toolPhase: notification_type === "mcp_generating" ? "generating" : notification_type === "mcp_start" ? "executing" : notification_type === "mcp_end" ? msg.tool_status === "error" ? "error" : "complete" : void 0,
277
+ argsPreview: msg.args_preview,
278
+ uiConfig: msg.ui_config,
279
+ parentToolCallId: msg.parent_tool_call_id,
280
+ contentType: content_type,
281
+ contentChunks: [],
282
+ timestamp: msg.timestamp || (/* @__PURE__ */ new Date()).toISOString()
283
+ };
284
+ messageIndexRef.current.set(messageKey, message);
285
+ if (!round.messages.some((m) => m.id === messageKey)) {
286
+ round.messages.push(message);
287
+ }
288
+ } else if (isMcpMessage) {
289
+ const prev = message;
290
+ message = { ...message, contentChunks: [...message.contentChunks] };
291
+ message.notificationType = msg.notification_type;
292
+ if (msg.tool_status) {
293
+ message.toolStatus = msg.tool_status;
294
+ }
295
+ if (msg.task_purpose && !message.taskPurpose) {
296
+ message.taskPurpose = msg.task_purpose;
297
+ }
298
+ if (!message.taskPurpose && msg.args_preview) {
299
+ const tp = extractTaskPurposeFromPreview(msg.args_preview);
300
+ if (tp) message.taskPurpose = tp;
301
+ }
302
+ const isTerminal = message.toolPhase === "complete" || message.toolPhase === "error";
303
+ if (notification_type === "mcp_generating") {
304
+ if (!isTerminal) {
305
+ message.toolPhase = "generating";
306
+ }
307
+ if (msg.args_preview) {
308
+ message.argsPreview = msg.args_preview;
309
+ }
310
+ if (msg.ui_config && !message.uiConfig) {
311
+ message.uiConfig = msg.ui_config;
312
+ }
313
+ } else if (notification_type === "mcp_start") {
314
+ if (!isTerminal) {
315
+ message.toolPhase = "executing";
316
+ }
317
+ } else if (notification_type === "mcp_end") {
318
+ message.toolPhase = msg.tool_status === "error" ? "error" : "complete";
319
+ }
320
+ messageIndexRef.current.set(messageKey, message);
321
+ const prevIdx = round.messages.indexOf(prev);
322
+ if (prevIdx >= 0) round.messages[prevIdx] = message;
323
+ }
324
+ const skipContent = isMcpMessage && notification_type !== "mcp_result";
325
+ if (!skipContent) {
326
+ const chunkIndex = message.contentChunks.length;
327
+ const chunkId = `${messageKey}-chunk-${chunkIndex}`;
328
+ if (!processedChunksRef.current.has(chunkId)) {
329
+ processedChunksRef.current.add(chunkId);
330
+ const prevMsg = message;
331
+ message = { ...message, contentChunks: [...message.contentChunks, content] };
332
+ messageIndexRef.current.set(messageKey, message);
333
+ const idx = round.messages.indexOf(prevMsg);
334
+ if (idx >= 0) round.messages[idx] = message;
335
+ }
336
+ }
337
+ if (content_type === "plan") {
338
+ const plan = parsePlan(message.contentChunks.join(""));
339
+ if (plan) {
340
+ message.plan = plan;
341
+ round.plan = plan;
342
+ }
343
+ } else if (content_type === "artifact") {
344
+ const allContent = message.contentChunks.join("");
345
+ const artifacts = parseArtifacts(allContent);
346
+ if (artifacts.length > 0) {
347
+ message.artifacts = artifacts;
348
+ message.artifact = artifacts[0];
349
+ }
350
+ } else if (content_type === "json_schema" || content_type === "html_schema" || content_type === "json_schema_wait") {
351
+ message.hitlRequest = parseHITLRequest(message.contentChunks.join(""), content_type) ?? void 0;
352
+ }
353
+ }
354
+ setState(() => {
355
+ const newRounds = new Map(roundIndexRef.current);
356
+ return {
357
+ rounds: newRounds,
358
+ currentInteractionId: latestInteractionId
359
+ };
360
+ });
361
+ if (todoMapChanged) {
362
+ setTodoMap(new Map(todoMapRef.current));
363
+ }
364
+ rafId.current = null;
365
+ }, [parsePlan, parseArtifact, parseHITLRequest]);
366
+ const processMessage = useCallback((message) => {
367
+ pendingUpdates.current.push(message);
368
+ if (rafId.current === null) {
369
+ rafId.current = requestAnimationFrame(flushUpdates);
370
+ }
371
+ }, [flushUpdates]);
372
+ const getRoundsList = useCallback(() => {
373
+ return Array.from(state.rounds.values()).sort((a, b) => a.index - b.index);
374
+ }, [state.rounds]);
375
+ const getCurrentRound = useCallback(() => {
376
+ if (!state.currentInteractionId) return null;
377
+ return state.rounds.get(state.currentInteractionId) || null;
378
+ }, [state.rounds, state.currentInteractionId]);
379
+ const startNewRound = useCallback((interactionId, userMessage) => {
380
+ const round = {
381
+ interactionId,
382
+ index: roundIndexRef.current.size + 1,
383
+ userMessage,
384
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
385
+ status: "running",
386
+ messages: []
387
+ };
388
+ roundIndexRef.current.set(interactionId, round);
389
+ setState((prevState) => {
390
+ const newRounds = new Map(prevState.rounds);
391
+ newRounds.set(interactionId, round);
392
+ return {
393
+ rounds: newRounds,
394
+ currentInteractionId: interactionId
395
+ };
396
+ });
397
+ }, []);
398
+ const loadRounds = useCallback((rounds) => {
399
+ roundIndexRef.current.clear();
400
+ messageIndexRef.current.clear();
401
+ callBatchToRoundRef.current.clear();
402
+ processedChunksRef.current.clear();
403
+ const newRounds = /* @__PURE__ */ new Map();
404
+ for (const round of rounds) {
405
+ newRounds.set(round.interactionId, round);
406
+ roundIndexRef.current.set(round.interactionId, round);
407
+ for (const msg of round.messages) {
408
+ messageIndexRef.current.set(msg.id, msg);
409
+ if (msg.callBatchId) {
410
+ callBatchToRoundRef.current.set(msg.callBatchId, round.interactionId);
411
+ }
412
+ }
413
+ }
414
+ const lastId = rounds.length > 0 ? rounds[rounds.length - 1].interactionId : null;
415
+ setState({ rounds: newRounds, currentInteractionId: lastId });
416
+ }, []);
417
+ const finalizeRound = useCallback((interactionId) => {
418
+ const round = roundIndexRef.current.get(interactionId);
419
+ if (!round) return;
420
+ let changed = false;
421
+ for (let i = 0; i < round.messages.length; i++) {
422
+ const msg = round.messages[i];
423
+ if (msg.toolPhase && msg.toolPhase !== "complete" && msg.toolPhase !== "error") {
424
+ const updated = { ...msg, toolPhase: "complete" };
425
+ round.messages[i] = updated;
426
+ messageIndexRef.current.set(msg.id, updated);
427
+ changed = true;
428
+ }
429
+ }
430
+ if (changed) {
431
+ setState(() => ({
432
+ rounds: new Map(roundIndexRef.current),
433
+ currentInteractionId: interactionId
434
+ }));
435
+ }
436
+ }, []);
437
+ const clear = useCallback(() => {
438
+ pendingUpdates.current = [];
439
+ messageIndexRef.current.clear();
440
+ roundIndexRef.current.clear();
441
+ processedChunksRef.current.clear();
442
+ callBatchToRoundRef.current.clear();
443
+ todoMapRef.current.clear();
444
+ if (rafId.current) {
445
+ cancelAnimationFrame(rafId.current);
446
+ rafId.current = null;
447
+ }
448
+ setState({
449
+ rounds: /* @__PURE__ */ new Map(),
450
+ currentInteractionId: null
451
+ });
452
+ setTodoMap(/* @__PURE__ */ new Map());
453
+ }, []);
454
+ return {
455
+ state,
456
+ todoMap,
457
+ processMessage,
458
+ getRoundsList,
459
+ getCurrentRound,
460
+ startNewRound,
461
+ loadRounds,
462
+ finalizeRound,
463
+ clear
464
+ };
465
+ }
466
+
467
+ // src/hooks/useSSE.ts
468
+ import { useCallback as useCallback2, useEffect, useRef as useRef2, useState as useState2 } from "react";
469
+ function useSSE(options) {
470
+ const {
471
+ adapter,
472
+ autoConnect = false,
473
+ reconnectInterval = 3e3,
474
+ maxReconnectAttempts = 3,
475
+ onMessage,
476
+ onStatusChange,
477
+ onError
478
+ } = options;
479
+ const [status, setStatus] = useState2("idle");
480
+ const eventSourceRef = useRef2(null);
481
+ const abortControllerRef = useRef2(null);
482
+ const reconnectAttemptsRef = useRef2(0);
483
+ const reconnectTimerRef = useRef2(null);
484
+ const updateStatus = useCallback2((newStatus) => {
485
+ setStatus(newStatus);
486
+ onStatusChange?.(newStatus);
487
+ }, [onStatusChange]);
488
+ const disconnect = useCallback2(() => {
489
+ if (reconnectTimerRef.current) {
490
+ clearTimeout(reconnectTimerRef.current);
491
+ reconnectTimerRef.current = null;
492
+ }
493
+ if (eventSourceRef.current) {
494
+ eventSourceRef.current.close();
495
+ eventSourceRef.current = null;
496
+ }
497
+ if (abortControllerRef.current) {
498
+ abortControllerRef.current.abort();
499
+ abortControllerRef.current = null;
500
+ }
501
+ reconnectAttemptsRef.current = 0;
502
+ updateStatus("disconnected");
503
+ }, [updateStatus]);
504
+ const connect = useCallback2((url, body) => {
505
+ disconnect();
506
+ updateStatus("connecting");
507
+ if (body) {
508
+ abortControllerRef.current = new AbortController();
509
+ const doFetch = () => {
510
+ const headers = {
511
+ "Content-Type": "application/json",
512
+ "Accept": "text/event-stream",
513
+ ...adapter.getAuthHeaders(),
514
+ ...adapter.getExtraHeaders?.() ?? {}
515
+ };
516
+ return fetch(url, {
517
+ method: "POST",
518
+ headers,
519
+ body: JSON.stringify(body),
520
+ signal: abortControllerRef.current.signal
521
+ });
522
+ };
523
+ doFetch().then(async (response) => {
524
+ if (response.status === 401) {
525
+ console.warn("[useSSE] Got 401, attempting token refresh via adapter...");
526
+ const refreshed = await adapter.refreshAuth?.();
527
+ if (refreshed) {
528
+ console.log("[useSSE] Token refreshed, retrying SSE connection");
529
+ const retryResponse = await doFetch();
530
+ if (!retryResponse.ok) {
531
+ throw new Error(`HTTP ${retryResponse.status}: ${retryResponse.statusText}`);
532
+ }
533
+ return retryResponse;
534
+ }
535
+ adapter.onAuthFailure?.();
536
+ throw new Error("HTTP 401: Unauthorized");
537
+ }
538
+ if (!response.ok) {
539
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
540
+ }
541
+ return response;
542
+ }).then(async (response) => {
543
+ updateStatus("connected");
544
+ reconnectAttemptsRef.current = 0;
545
+ const reader = response.body?.getReader();
546
+ if (!reader) {
547
+ throw new Error("Response body is not readable");
548
+ }
549
+ const decoder = new TextDecoder();
550
+ let buffer = "";
551
+ while (true) {
552
+ const { done, value } = await reader.read();
553
+ if (done) break;
554
+ buffer += decoder.decode(value, { stream: true });
555
+ const lines = buffer.split("\n");
556
+ buffer = lines.pop() || "";
557
+ for (const line of lines) {
558
+ if (line.startsWith("data:")) {
559
+ const data = line.slice(5).trim();
560
+ if (data && data !== "[DONE]") {
561
+ try {
562
+ const message = JSON.parse(data);
563
+ onMessage?.(message);
564
+ } catch (e) {
565
+ console.warn("Failed to parse SSE message:", data, e);
566
+ }
567
+ }
568
+ }
569
+ }
570
+ }
571
+ updateStatus("disconnected");
572
+ }).catch((error) => {
573
+ if (error.name === "AbortError") {
574
+ updateStatus("disconnected");
575
+ } else {
576
+ const is401 = error instanceof Error && error.message.includes("401");
577
+ console.error("SSE connection error:", error);
578
+ updateStatus("error");
579
+ onError?.(error instanceof Error ? error : new Error(String(error)));
580
+ if (is401) return;
581
+ if (reconnectAttemptsRef.current < maxReconnectAttempts) {
582
+ reconnectAttemptsRef.current++;
583
+ reconnectTimerRef.current = window.setTimeout(() => {
584
+ connect(url, body);
585
+ }, reconnectInterval);
586
+ }
587
+ }
588
+ });
589
+ } else {
590
+ try {
591
+ const eventSource = new EventSource(url);
592
+ eventSourceRef.current = eventSource;
593
+ eventSource.onopen = () => {
594
+ updateStatus("connected");
595
+ reconnectAttemptsRef.current = 0;
596
+ };
597
+ eventSource.onmessage = (event) => {
598
+ if (event.data && event.data !== "[DONE]") {
599
+ try {
600
+ const message = JSON.parse(event.data);
601
+ onMessage?.(message);
602
+ } catch (e) {
603
+ console.warn("Failed to parse SSE message:", event.data, e);
604
+ }
605
+ }
606
+ };
607
+ eventSource.onerror = () => {
608
+ eventSource.close();
609
+ updateStatus("error");
610
+ if (reconnectAttemptsRef.current < maxReconnectAttempts) {
611
+ reconnectAttemptsRef.current++;
612
+ reconnectTimerRef.current = window.setTimeout(() => {
613
+ connect(url);
614
+ }, reconnectInterval);
615
+ }
616
+ };
617
+ } catch (error) {
618
+ updateStatus("error");
619
+ onError?.(error instanceof Error ? error : new Error(String(error)));
620
+ }
621
+ }
622
+ }, [adapter, disconnect, updateStatus, onMessage, onError, maxReconnectAttempts, reconnectInterval]);
623
+ useEffect(() => {
624
+ if (autoConnect) {
625
+ connect(adapter.getStreamUrl());
626
+ }
627
+ return () => {
628
+ disconnect();
629
+ };
630
+ }, []);
631
+ return {
632
+ status,
633
+ connect,
634
+ disconnect
635
+ };
636
+ }
637
+
638
+ // src/components/PinnedArea.tsx
639
+ import "react";
640
+
641
+ // src/components/PlanCard.tsx
642
+ import "react";
643
+ import { jsx, jsxs } from "react/jsx-runtime";
644
+ var PlanCard = ({
645
+ plan,
646
+ status,
647
+ compact = false
648
+ }) => {
649
+ const statusIcons = {
650
+ completed: "\u2713",
651
+ active: "\u2192",
652
+ pending: "\u25CB",
653
+ error: "\u2717"
654
+ };
655
+ if (compact) {
656
+ return /* @__PURE__ */ jsxs("div", { className: "plan-card-compact", children: [
657
+ /* @__PURE__ */ jsx("span", { className: "plan-icon", children: "\u{1F4CB}" }),
658
+ /* @__PURE__ */ jsx("span", { className: "plan-items-inline", children: plan.items.map((item, i) => /* @__PURE__ */ jsxs("span", { className: `plan-item-inline ${item.status}`, children: [
659
+ "[",
660
+ statusIcons[item.status],
661
+ " ",
662
+ item.text,
663
+ "]"
664
+ ] }, i)) })
665
+ ] });
666
+ }
667
+ return /* @__PURE__ */ jsxs("div", { className: `plan-card ${status === "completed" ? "completed" : ""}`, children: [
668
+ /* @__PURE__ */ jsxs("div", { className: "plan-header", children: [
669
+ /* @__PURE__ */ jsxs("div", { className: "plan-title", children: [
670
+ /* @__PURE__ */ jsx("span", { className: "plan-icon", children: "\u{1F4CB}" }),
671
+ /* @__PURE__ */ jsx("span", { children: "\u4EFB\u52A1\u8BA1\u5212" })
672
+ ] }),
673
+ /* @__PURE__ */ jsxs("span", { className: "plan-progress", children: [
674
+ "(",
675
+ plan.progress.completed,
676
+ "/",
677
+ plan.progress.total,
678
+ ")"
679
+ ] })
680
+ ] }),
681
+ /* @__PURE__ */ jsx("ul", { className: "plan-items", children: plan.items.map((item, index) => /* @__PURE__ */ jsxs("li", { className: `plan-item ${item.status}`, children: [
682
+ /* @__PURE__ */ jsx("span", { className: "plan-item-icon", children: statusIcons[item.status] }),
683
+ /* @__PURE__ */ jsx("span", { className: "plan-item-text", children: item.text }),
684
+ item.status === "active" && /* @__PURE__ */ jsx("span", { className: "plan-item-status", children: "\u6267\u884C\u4E2D" })
685
+ ] }, index)) })
686
+ ] });
687
+ };
688
+ var PlanCard_default = PlanCard;
689
+
690
+ // src/components/TodoCard.tsx
691
+ import { useState as useState3, useCallback as useCallback3, useMemo } from "react";
692
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
693
+ var STATUS_ICON = {
694
+ pending: "\u25CB",
695
+ in_progress: "\u2192",
696
+ completed: "\u2713",
697
+ blocked: "\u26A0"
698
+ };
699
+ var MAX_VISIBLE_ITEMS = 8;
700
+ function flattenTasks(todo) {
701
+ const result = [];
702
+ for (const phase of todo.phases) {
703
+ for (const task of phase.tasks) {
704
+ result.push(task);
705
+ }
706
+ for (const group of phase.groups) {
707
+ for (const task of group.tasks) {
708
+ result.push(task);
709
+ }
710
+ }
711
+ }
712
+ return result;
713
+ }
714
+ function findCurrentTask(tasks) {
715
+ return tasks.find((t) => t.status === "in_progress") || null;
716
+ }
717
+ var TaskRow = ({ task, depth = 0 }) => {
718
+ const [expanded, setExpanded] = useState3(
719
+ task.status === "in_progress" && task.children.length > 0
720
+ );
721
+ const hasChildren = task.children.length > 0;
722
+ const toggle = useCallback3(() => setExpanded((v) => !v), []);
723
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
724
+ /* @__PURE__ */ jsxs2(
725
+ "div",
726
+ {
727
+ className: `todo-task-row todo-status-${task.status}`,
728
+ style: { paddingLeft: `${12 + depth * 16}px` },
729
+ onClick: hasChildren ? toggle : void 0,
730
+ role: hasChildren ? "button" : void 0,
731
+ children: [
732
+ /* @__PURE__ */ jsx2("span", { className: "todo-status-icon", children: STATUS_ICON[task.status] }),
733
+ hasChildren && /* @__PURE__ */ jsx2("span", { className: `todo-expand-arrow ${expanded ? "expanded" : ""}`, children: "\u25B8" }),
734
+ /* @__PURE__ */ jsx2("span", { className: "todo-task-desc", children: task.description })
735
+ ]
736
+ }
737
+ ),
738
+ expanded && task.children.map((child, i) => /* @__PURE__ */ jsx2(TaskRow, { task: child, depth: depth + 1 }, child.id || i))
739
+ ] });
740
+ };
741
+ var TodoCardCompact = ({ todo }) => {
742
+ const tasks = useMemo(() => flattenTasks(todo), [todo]);
743
+ const current = findCurrentTask(tasks);
744
+ const { stats } = todo;
745
+ return /* @__PURE__ */ jsxs2("div", { className: "todo-card-compact", children: [
746
+ /* @__PURE__ */ jsx2("span", { className: "todo-compact-icon", children: "\u2630" }),
747
+ /* @__PURE__ */ jsx2("span", { className: "todo-compact-title", children: todo.title }),
748
+ current && /* @__PURE__ */ jsxs2(Fragment, { children: [
749
+ /* @__PURE__ */ jsx2("span", { className: "todo-compact-sep", children: "\u2192" }),
750
+ /* @__PURE__ */ jsx2("span", { className: "todo-compact-current", children: current.description })
751
+ ] }),
752
+ /* @__PURE__ */ jsxs2("span", { className: "todo-compact-progress", children: [
753
+ "\u5DF2\u5B8C\u6210 ",
754
+ stats.completed,
755
+ "/",
756
+ stats.total
757
+ ] })
758
+ ] });
759
+ };
760
+ var TodoCard = ({ todo, compact = false }) => {
761
+ const [expanded, setExpanded] = useState3(false);
762
+ const [showAll, setShowAll] = useState3(false);
763
+ const tasks = useMemo(() => flattenTasks(todo), [todo]);
764
+ const { stats } = todo;
765
+ const progress = stats.total > 0 ? stats.completed / stats.total : 0;
766
+ if (compact) {
767
+ return /* @__PURE__ */ jsx2(TodoCardCompact, { todo });
768
+ }
769
+ const visibleTasks = showAll ? tasks : tasks.slice(0, MAX_VISIBLE_ITEMS);
770
+ const hiddenCount = tasks.length - MAX_VISIBLE_ITEMS;
771
+ const current = useMemo(() => findCurrentTask(tasks), [tasks]);
772
+ const isAllDone = stats.completed === stats.total && stats.total > 0;
773
+ const hasBlocked = stats.blocked > 0;
774
+ return /* @__PURE__ */ jsxs2("div", { className: "todo-card", children: [
775
+ /* @__PURE__ */ jsxs2("div", { className: "todo-header", onClick: () => setExpanded((v) => !v), role: "button", children: [
776
+ /* @__PURE__ */ jsxs2("div", { className: "todo-title", children: [
777
+ /* @__PURE__ */ jsx2("span", { className: `todo-header-arrow ${expanded ? "expanded" : ""}`, children: "\u25B8" }),
778
+ /* @__PURE__ */ jsx2("span", { className: "todo-title-icon", children: "\u2630" }),
779
+ /* @__PURE__ */ jsx2("span", { className: "todo-title-text", children: todo.title })
780
+ ] }),
781
+ !expanded && /* @__PURE__ */ jsx2("div", { className: "todo-header-status", children: isAllDone ? /* @__PURE__ */ jsx2("span", { className: "todo-header-done", children: "\u2713 \u5DF2\u5B8C\u6210" }) : hasBlocked ? /* @__PURE__ */ jsxs2("span", { className: "todo-header-blocked", children: [
782
+ "\u26A0 ",
783
+ stats.blocked,
784
+ " \u9879\u963B\u585E"
785
+ ] }) : current ? /* @__PURE__ */ jsxs2(Fragment, { children: [
786
+ /* @__PURE__ */ jsx2("span", { className: "todo-header-current-sep", children: "\u2192" }),
787
+ /* @__PURE__ */ jsx2("span", { className: "todo-header-current", children: current.description })
788
+ ] }) : null }),
789
+ /* @__PURE__ */ jsxs2("span", { className: "todo-header-count", children: [
790
+ "\u5DF2\u5B8C\u6210 ",
791
+ stats.completed,
792
+ "/",
793
+ stats.total
794
+ ] })
795
+ ] }),
796
+ /* @__PURE__ */ jsx2("div", { className: "todo-progress-bar", children: /* @__PURE__ */ jsx2(
797
+ "div",
798
+ {
799
+ className: "todo-progress-fill",
800
+ style: { width: `${progress * 100}%` }
801
+ }
802
+ ) }),
803
+ expanded && /* @__PURE__ */ jsxs2(Fragment, { children: [
804
+ /* @__PURE__ */ jsx2("div", { className: "todo-list", children: visibleTasks.map((task, i) => /* @__PURE__ */ jsx2(TaskRow, { task }, task.id || i)) }),
805
+ !showAll && hiddenCount > 0 && /* @__PURE__ */ jsxs2("div", { className: "todo-more", onClick: () => setShowAll(true), children: [
806
+ "+ ",
807
+ hiddenCount,
808
+ " more"
809
+ ] })
810
+ ] })
811
+ ] });
812
+ };
813
+ var TodoCardMulti = ({ todoMap, compact = false }) => {
814
+ const entries = useMemo(() => Array.from(todoMap.entries()), [todoMap]);
815
+ const [activeId, setActiveId] = useState3(null);
816
+ if (entries.length === 0) return null;
817
+ if (entries.length === 1) {
818
+ return /* @__PURE__ */ jsx2(TodoCard, { todo: entries[0][1], compact });
819
+ }
820
+ const selectedId = activeId || entries[entries.length - 1][0];
821
+ const selectedTodo = todoMap.get(selectedId);
822
+ if (compact) {
823
+ return /* @__PURE__ */ jsx2("div", { className: "todo-multi-compact", children: entries.map(([id, todo]) => /* @__PURE__ */ jsx2(TodoCardCompact, { todo }, id)) });
824
+ }
825
+ return /* @__PURE__ */ jsxs2("div", { className: "todo-card", children: [
826
+ /* @__PURE__ */ jsx2("div", { className: "todo-multi-tabs", children: entries.map(([id, todo]) => /* @__PURE__ */ jsxs2(
827
+ "button",
828
+ {
829
+ className: `todo-tab ${id === selectedId ? "active" : ""}`,
830
+ onClick: () => setActiveId(id),
831
+ children: [
832
+ todo.title,
833
+ /* @__PURE__ */ jsxs2("span", { className: "todo-tab-progress", children: [
834
+ todo.stats.completed,
835
+ "/",
836
+ todo.stats.total
837
+ ] })
838
+ ]
839
+ },
840
+ id
841
+ )) }),
842
+ selectedTodo && /* @__PURE__ */ jsx2(TodoCard, { todo: selectedTodo })
843
+ ] });
844
+ };
845
+ var TodoCard_default = TodoCard;
846
+
847
+ // src/components/PinnedArea.tsx
848
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
849
+ var PinnedArea = ({
850
+ round,
851
+ isTransitioning,
852
+ todoMap
853
+ }) => {
854
+ if (!round) return null;
855
+ const hasTodo = todoMap && todoMap.size > 0;
856
+ const formatTime = (timestamp) => {
857
+ try {
858
+ return new Date(timestamp).toLocaleTimeString("zh-CN", {
859
+ hour: "2-digit",
860
+ minute: "2-digit",
861
+ second: "2-digit"
862
+ });
863
+ } catch {
864
+ return "";
865
+ }
866
+ };
867
+ return /* @__PURE__ */ jsxs3("div", { className: "chat-pinned-area", children: [
868
+ /* @__PURE__ */ jsxs3("div", { className: `pinned-content ${isTransitioning ? "fade-out" : ""}`, children: [
869
+ /* @__PURE__ */ jsx3("div", { className: "pinned-user-message", children: round.userMessage || "(\u65E0\u6D88\u606F\u5185\u5BB9)" }),
870
+ hasTodo && /* @__PURE__ */ jsx3(TodoCardMulti, { todoMap }),
871
+ !hasTodo && round.plan && /* @__PURE__ */ jsx3(
872
+ PlanCard_default,
873
+ {
874
+ plan: round.plan,
875
+ status: round.status
876
+ }
877
+ )
878
+ ] }),
879
+ /* @__PURE__ */ jsx3("span", { className: "pinned-timestamp", children: formatTime(round.timestamp) })
880
+ ] });
881
+ };
882
+ var PinnedArea_default = PinnedArea;
883
+
884
+ // src/components/RoundHeader.tsx
885
+ import "react";
886
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
887
+ var RoundHeader = ({
888
+ round,
889
+ isPinned,
890
+ todoMap
891
+ }) => {
892
+ const statusLabels = {
893
+ running: { icon: "\u23F3", text: "\u6267\u884C\u4E2D", className: "running" },
894
+ completed: { icon: "\u2713", text: "\u5DF2\u5B8C\u6210", className: "completed" },
895
+ error: { icon: "\u2717", text: "\u6267\u884C\u5931\u8D25", className: "error" }
896
+ };
897
+ const status = statusLabels[round.status];
898
+ const hasTodo = todoMap && todoMap.size > 0;
899
+ return /* @__PURE__ */ jsxs4("div", { className: `round-header ${isPinned ? "pinned" : ""}`, children: [
900
+ /* @__PURE__ */ jsxs4("div", { className: "round-header-title", children: [
901
+ /* @__PURE__ */ jsxs4("div", { className: "user-avatar", children: [
902
+ /* @__PURE__ */ jsx4("span", { className: "avatar-icon", children: "\u{1F464}" }),
903
+ /* @__PURE__ */ jsx4("span", { className: "user-message-text", children: round.userMessage || "(\u65E0\u6D88\u606F)" })
904
+ ] }),
905
+ /* @__PURE__ */ jsxs4("span", { className: `round-status ${status.className}`, children: [
906
+ status.icon,
907
+ " ",
908
+ status.text
909
+ ] })
910
+ ] }),
911
+ hasTodo && /* @__PURE__ */ jsx4(TodoCardMulti, { todoMap, compact: true }),
912
+ !hasTodo && round.plan && /* @__PURE__ */ jsx4(PlanCard_default, { plan: round.plan, status: round.status, compact: true })
913
+ ] });
914
+ };
915
+ var RoundHeader_default = RoundHeader;
916
+
917
+ // src/components/MessageContent.tsx
918
+ import { useEffect as useEffect2, useState as useState9, useRef as useRef4, memo as memo2 } from "react";
919
+ import { Streamdown } from "streamdown";
920
+ import { code } from "@streamdown/code";
921
+ import { mermaid } from "@streamdown/mermaid";
922
+ import { createMathPlugin } from "@streamdown/math";
923
+ import { cjk } from "@streamdown/cjk";
924
+
925
+ // src/components/SchemaFormRenderer.tsx
926
+ import { useState as useState4, useCallback as useCallback4 } from "react";
927
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
928
+ var renderNode = (node, values, onChange, errors) => {
929
+ if (!node) return null;
930
+ const { type, key, name, props, children, options, schema } = node;
931
+ const nodeKey = key || name || Math.random().toString();
932
+ switch (type) {
933
+ case "Card":
934
+ return /* @__PURE__ */ jsx5("div", { className: "schema-card", children: children?.map((child) => renderNode(child, values, onChange, errors)) }, nodeKey);
935
+ case "FormChunk":
936
+ if (schema) {
937
+ return renderNode(schema, values, onChange, errors);
938
+ }
939
+ return children?.map((child) => renderNode(child, values, onChange, errors));
940
+ case "Form":
941
+ return /* @__PURE__ */ jsx5("div", { className: "schema-form", children: children?.map((child) => renderNode(child, values, onChange, errors)) }, nodeKey);
942
+ case "FormItem": {
943
+ const label = props?.label || "";
944
+ const required = node.rules?.some((r) => r.required);
945
+ const error = name ? errors[name] : void 0;
946
+ return /* @__PURE__ */ jsxs5("div", { className: `schema-form-item ${error ? "has-error" : ""}`, children: [
947
+ /* @__PURE__ */ jsxs5("label", { className: "schema-form-label", children: [
948
+ required && /* @__PURE__ */ jsx5("span", { className: "required-mark", children: "*" }),
949
+ label
950
+ ] }),
951
+ /* @__PURE__ */ jsx5("div", { className: "schema-form-control", children: children?.map((child) => renderNode(
952
+ { ...child, name: name || child.name },
953
+ values,
954
+ onChange,
955
+ errors
956
+ )) }),
957
+ error && /* @__PURE__ */ jsx5("div", { className: "schema-form-error", children: error })
958
+ ] }, nodeKey);
959
+ }
960
+ case "RadioGroup": {
961
+ const fieldName = name || "";
962
+ const currentValue = values[fieldName];
963
+ return /* @__PURE__ */ jsx5("div", { className: "schema-radio-group", children: options?.map((option) => /* @__PURE__ */ jsxs5("label", { className: "schema-radio-option", children: [
964
+ /* @__PURE__ */ jsx5(
965
+ "input",
966
+ {
967
+ type: "radio",
968
+ name: fieldName,
969
+ value: option.value,
970
+ checked: currentValue === option.value,
971
+ onChange: () => onChange(fieldName, option.value)
972
+ }
973
+ ),
974
+ /* @__PURE__ */ jsx5("span", { className: "radio-checkmark" }),
975
+ /* @__PURE__ */ jsx5("span", { className: "radio-label", children: option.label })
976
+ ] }, option.value)) }, nodeKey);
977
+ }
978
+ case "CheckboxGroup": {
979
+ const fieldName = name || "";
980
+ const currentValues = values[fieldName] || [];
981
+ return /* @__PURE__ */ jsx5("div", { className: "schema-checkbox-group", children: options?.map((option) => /* @__PURE__ */ jsxs5("label", { className: "schema-checkbox-option", children: [
982
+ /* @__PURE__ */ jsx5(
983
+ "input",
984
+ {
985
+ type: "checkbox",
986
+ value: option.value,
987
+ checked: currentValues.includes(option.value),
988
+ onChange: (e) => {
989
+ const newValues = e.target.checked ? [...currentValues, option.value] : currentValues.filter((v) => v !== option.value);
990
+ onChange(fieldName, newValues);
991
+ }
992
+ }
993
+ ),
994
+ /* @__PURE__ */ jsx5("span", { className: "checkbox-checkmark" }),
995
+ /* @__PURE__ */ jsx5("span", { className: "checkbox-label", children: option.label })
996
+ ] }, option.value)) }, nodeKey);
997
+ }
998
+ case "Input": {
999
+ const fieldName = name || "";
1000
+ const placeholder = props?.placeholder || "";
1001
+ return /* @__PURE__ */ jsx5(
1002
+ "input",
1003
+ {
1004
+ type: "text",
1005
+ className: "schema-input",
1006
+ placeholder,
1007
+ value: values[fieldName] || "",
1008
+ onChange: (e) => onChange(fieldName, e.target.value)
1009
+ },
1010
+ nodeKey
1011
+ );
1012
+ }
1013
+ case "TextArea": {
1014
+ const fieldName = name || "";
1015
+ const placeholder = props?.placeholder || "";
1016
+ const rows = props?.rows || 3;
1017
+ return /* @__PURE__ */ jsx5(
1018
+ "textarea",
1019
+ {
1020
+ className: "schema-textarea",
1021
+ placeholder,
1022
+ rows,
1023
+ value: values[fieldName] || "",
1024
+ onChange: (e) => onChange(fieldName, e.target.value)
1025
+ },
1026
+ nodeKey
1027
+ );
1028
+ }
1029
+ case "Select": {
1030
+ const fieldName = name || "";
1031
+ const placeholder = props?.placeholder || "\u8BF7\u9009\u62E9";
1032
+ return /* @__PURE__ */ jsxs5(
1033
+ "select",
1034
+ {
1035
+ className: "schema-select",
1036
+ value: values[fieldName] || "",
1037
+ onChange: (e) => onChange(fieldName, e.target.value),
1038
+ children: [
1039
+ /* @__PURE__ */ jsx5("option", { value: "", disabled: true, children: placeholder }),
1040
+ options?.map((option) => /* @__PURE__ */ jsx5("option", { value: option.value, children: option.label }, option.value))
1041
+ ]
1042
+ },
1043
+ nodeKey
1044
+ );
1045
+ }
1046
+ default:
1047
+ if (children && children.length > 0) {
1048
+ return children.map((child) => renderNode(child, values, onChange, errors));
1049
+ }
1050
+ return null;
1051
+ }
1052
+ };
1053
+ var extractRules = (node) => {
1054
+ const rules = {};
1055
+ const traverse = (n) => {
1056
+ if (n.name && n.rules) {
1057
+ rules[n.name] = n.rules;
1058
+ }
1059
+ if (n.children) {
1060
+ n.children.forEach(traverse);
1061
+ }
1062
+ if (n.schema) {
1063
+ traverse(n.schema);
1064
+ }
1065
+ };
1066
+ traverse(node);
1067
+ return rules;
1068
+ };
1069
+ var SchemaFormRenderer = ({
1070
+ schema,
1071
+ onSubmit,
1072
+ awaitCommandUuid
1073
+ }) => {
1074
+ const [values, setValues] = useState4({});
1075
+ const [errors, setErrors] = useState4({});
1076
+ const [isSubmitting, setIsSubmitting] = useState4(false);
1077
+ const handleChange = useCallback4((name, value) => {
1078
+ setValues((prev) => ({ ...prev, [name]: value }));
1079
+ setErrors((prev) => {
1080
+ const newErrors = { ...prev };
1081
+ delete newErrors[name];
1082
+ return newErrors;
1083
+ });
1084
+ }, []);
1085
+ const validate = useCallback4(() => {
1086
+ const rules = extractRules(schema);
1087
+ const newErrors = {};
1088
+ Object.entries(rules).forEach(([fieldName, fieldRules]) => {
1089
+ fieldRules.forEach((rule) => {
1090
+ if (rule.required) {
1091
+ const value = values[fieldName];
1092
+ const isEmpty = value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
1093
+ if (isEmpty) {
1094
+ newErrors[fieldName] = rule.message || "\u6B64\u9879\u4E3A\u5FC5\u586B\u9879";
1095
+ }
1096
+ }
1097
+ });
1098
+ });
1099
+ setErrors(newErrors);
1100
+ return Object.keys(newErrors).length === 0;
1101
+ }, [schema, values]);
1102
+ const handleSubmit = useCallback4(async () => {
1103
+ if (!validate()) return;
1104
+ setIsSubmitting(true);
1105
+ try {
1106
+ await onSubmit?.(values);
1107
+ } finally {
1108
+ setIsSubmitting(false);
1109
+ }
1110
+ }, [validate, values, onSubmit]);
1111
+ return /* @__PURE__ */ jsxs5("div", { className: "schema-form-renderer", children: [
1112
+ renderNode(schema, values, handleChange, errors),
1113
+ /* @__PURE__ */ jsx5("div", { className: "schema-form-actions", children: /* @__PURE__ */ jsx5(
1114
+ "button",
1115
+ {
1116
+ className: "schema-submit-btn",
1117
+ onClick: handleSubmit,
1118
+ disabled: isSubmitting,
1119
+ children: isSubmitting ? "\u63D0\u4EA4\u4E2D..." : "\u63D0\u4EA4"
1120
+ }
1121
+ ) }),
1122
+ awaitCommandUuid && /* @__PURE__ */ jsx5("div", { className: "schema-form-meta", children: /* @__PURE__ */ jsxs5("span", { className: "meta-uuid", children: [
1123
+ "\u8BF7\u6C42 ID: ",
1124
+ awaitCommandUuid.slice(0, 8),
1125
+ "..."
1126
+ ] }) })
1127
+ ] });
1128
+ };
1129
+ var SchemaFormRenderer_default = SchemaFormRenderer;
1130
+
1131
+ // src/components/ToolCardBuffering.tsx
1132
+ import { useMemo as useMemo3, useRef as useRef3, memo } from "react";
1133
+
1134
+ // src/components/renderers/utils.tsx
1135
+ import "react";
1136
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1137
+ function parseArgsIncremental(argsPreview, cache) {
1138
+ if (!argsPreview) return null;
1139
+ try {
1140
+ cache.current = null;
1141
+ return JSON.parse(argsPreview);
1142
+ } catch {
1143
+ }
1144
+ const prev = cache.current;
1145
+ if (prev && prev.truncatedKey && prev.truncatedValueStart > 0 && argsPreview.length > prev.lastArgsLen) {
1146
+ prev.lastArgsLen = argsPreview.length;
1147
+ const rawValue = argsPreview.slice(prev.truncatedValueStart);
1148
+ return { ...prev.stableFields, [prev.truncatedKey]: decodeJsonEscapes(rawValue) };
1149
+ }
1150
+ const meta = parsePartialJsonWithMeta(argsPreview);
1151
+ if (!meta) return null;
1152
+ const stableFields = { ...meta.fields };
1153
+ if (meta.truncatedKey) {
1154
+ delete stableFields[meta.truncatedKey];
1155
+ }
1156
+ cache.current = {
1157
+ stableFields,
1158
+ truncatedKey: meta.truncatedKey,
1159
+ truncatedValueStart: meta.truncatedValueStart,
1160
+ lastArgsLen: argsPreview.length
1161
+ };
1162
+ return meta.fields;
1163
+ }
1164
+ function parsePartialJsonWithMeta(raw) {
1165
+ const fields = {};
1166
+ let i = raw.indexOf("{");
1167
+ if (i < 0) return null;
1168
+ i++;
1169
+ let foundAny = false;
1170
+ let truncatedKey = null;
1171
+ let truncatedValueStart = -1;
1172
+ while (i < raw.length) {
1173
+ while (i < raw.length && " \r\n,".includes(raw[i])) i++;
1174
+ if (i >= raw.length || raw[i] === "}") break;
1175
+ if (raw[i] !== '"') break;
1176
+ const keyEnd = findClosingQuote(raw, i);
1177
+ if (keyEnd < 0) break;
1178
+ const key = raw.slice(i + 1, keyEnd);
1179
+ i = keyEnd + 1;
1180
+ while (i < raw.length && " \r\n".includes(raw[i])) i++;
1181
+ if (i >= raw.length || raw[i] !== ":") break;
1182
+ i++;
1183
+ while (i < raw.length && " \r\n".includes(raw[i])) i++;
1184
+ if (i >= raw.length) break;
1185
+ const valueStartPos = raw[i] === '"' ? i + 1 : i;
1186
+ const [value, nextPos, complete] = parseValue(raw, i);
1187
+ if (value !== void 0) {
1188
+ fields[key] = value;
1189
+ foundAny = true;
1190
+ }
1191
+ if (!complete) {
1192
+ truncatedKey = key;
1193
+ truncatedValueStart = valueStartPos;
1194
+ break;
1195
+ }
1196
+ i = nextPos;
1197
+ }
1198
+ return foundAny ? { fields, truncatedKey, truncatedValueStart } : null;
1199
+ }
1200
+ function decodeJsonEscapes(raw) {
1201
+ let result = "";
1202
+ let i = 0;
1203
+ while (i < raw.length) {
1204
+ if (raw[i] === "\\" && i + 1 < raw.length) {
1205
+ const next = raw[i + 1];
1206
+ switch (next) {
1207
+ case "n":
1208
+ result += "\n";
1209
+ break;
1210
+ case "t":
1211
+ result += " ";
1212
+ break;
1213
+ case "r":
1214
+ result += "\r";
1215
+ break;
1216
+ case '"':
1217
+ result += '"';
1218
+ break;
1219
+ case "\\":
1220
+ result += "\\";
1221
+ break;
1222
+ case "/":
1223
+ result += "/";
1224
+ break;
1225
+ default:
1226
+ result += "\\" + next;
1227
+ break;
1228
+ }
1229
+ i += 2;
1230
+ } else {
1231
+ result += raw[i];
1232
+ i++;
1233
+ }
1234
+ }
1235
+ return result;
1236
+ }
1237
+ function findClosingQuote(s, openPos) {
1238
+ let i = openPos + 1;
1239
+ while (i < s.length) {
1240
+ if (s[i] === "\\") {
1241
+ i += 2;
1242
+ continue;
1243
+ }
1244
+ if (s[i] === '"') return i;
1245
+ i++;
1246
+ }
1247
+ return -1;
1248
+ }
1249
+ function parseValue(s, pos) {
1250
+ if (pos >= s.length) return [void 0, pos, false];
1251
+ const ch = s[pos];
1252
+ if (ch === '"') {
1253
+ const end = findClosingQuote(s, pos);
1254
+ if (end < 0) {
1255
+ return [decodeJsonEscapes(s.slice(pos + 1)), s.length, false];
1256
+ }
1257
+ try {
1258
+ const val = JSON.parse(s.slice(pos, end + 1));
1259
+ return [val, end + 1, true];
1260
+ } catch {
1261
+ return [decodeJsonEscapes(s.slice(pos + 1, end)), end + 1, true];
1262
+ }
1263
+ }
1264
+ if ("-0123456789".includes(ch)) {
1265
+ let end = pos + 1;
1266
+ while (end < s.length && "0123456789.eE+-".includes(s[end])) end++;
1267
+ const numStr = s.slice(pos, end);
1268
+ const num = Number(numStr);
1269
+ if (!isNaN(num) && numStr.length > 0) return [num, end, true];
1270
+ return [void 0, end, end < s.length];
1271
+ }
1272
+ if (s.startsWith("true", pos)) return [true, pos + 4, true];
1273
+ if (s.startsWith("false", pos)) return [false, pos + 5, true];
1274
+ if (s.startsWith("null", pos)) return [null, pos + 4, true];
1275
+ if (ch === "{" || ch === "[") {
1276
+ const close = ch === "{" ? "}" : "]";
1277
+ let depth = 1, j = pos + 1, inStr = false;
1278
+ while (j < s.length && depth > 0) {
1279
+ if (inStr) {
1280
+ if (s[j] === "\\") {
1281
+ j += 2;
1282
+ continue;
1283
+ }
1284
+ if (s[j] === '"') inStr = false;
1285
+ } else {
1286
+ if (s[j] === '"') inStr = true;
1287
+ else if (s[j] === ch) depth++;
1288
+ else if (s[j] === close) depth--;
1289
+ }
1290
+ j++;
1291
+ }
1292
+ if (depth === 0) {
1293
+ try {
1294
+ return [JSON.parse(s.slice(pos, j)), j, true];
1295
+ } catch {
1296
+ return [s.slice(pos, j), j, true];
1297
+ }
1298
+ }
1299
+ return [void 0, j, false];
1300
+ }
1301
+ return [void 0, pos + 1, false];
1302
+ }
1303
+ function extractField(args, fieldName) {
1304
+ if (!args || !fieldName) return void 0;
1305
+ return args[fieldName];
1306
+ }
1307
+ function formatValue(value) {
1308
+ if (value === void 0 || value === null) return "";
1309
+ if (typeof value === "string") return value;
1310
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1311
+ return JSON.stringify(value, null, 2);
1312
+ }
1313
+ function buildCompactTags(args, compactFields, hideFields) {
1314
+ if (!args || !compactFields) return [];
1315
+ const hidden = new Set(hideFields || []);
1316
+ return compactFields.filter((f) => !hidden.has(f) && args[f] !== void 0).map((f) => ({ key: f, value: formatValue(args[f]) }));
1317
+ }
1318
+ var phaseConfig = {
1319
+ generating: { icon: "\u2699\uFE0F", label: "\u751F\u6210\u4E2D", className: "phase-generating" },
1320
+ executing: { icon: "\u23F3", label: "\u6267\u884C\u4E2D", className: "phase-executing" },
1321
+ complete: { icon: "\u2705", label: "\u5B8C\u6210", className: "phase-complete" },
1322
+ error: { icon: "\u274C", label: "\u5931\u8D25", className: "phase-error" }
1323
+ };
1324
+ var PhaseLabel = ({ phase }) => {
1325
+ const cfg = phaseConfig[phase];
1326
+ return /* @__PURE__ */ jsxs6("span", { className: "tool-phase-label", children: [
1327
+ cfg?.label || phase,
1328
+ phase === "generating" && /* @__PURE__ */ jsxs6("span", { className: "tool-card-dots", children: [
1329
+ /* @__PURE__ */ jsx6("span", {}),
1330
+ /* @__PURE__ */ jsx6("span", {}),
1331
+ /* @__PURE__ */ jsx6("span", {})
1332
+ ] })
1333
+ ] });
1334
+ };
1335
+ var CompactTags = ({ tags }) => /* @__PURE__ */ jsxs6("div", { className: "compact-tags", children: [
1336
+ tags.slice(0, 5).map((t) => /* @__PURE__ */ jsxs6("span", { className: "compact-tag", children: [
1337
+ t.key,
1338
+ ": ",
1339
+ t.value
1340
+ ] }, t.key)),
1341
+ tags.length > 5 && /* @__PURE__ */ jsxs6("span", { className: "compact-tag", children: [
1342
+ "+",
1343
+ tags.length - 5
1344
+ ] })
1345
+ ] });
1346
+
1347
+ // src/components/renderers/ContentRenderer.tsx
1348
+ import { useState as useState5, useMemo as useMemo2 } from "react";
1349
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1350
+ var MAX_PREVIEW_LINES = 2;
1351
+ var ContentRenderer = ({ message, uiConfig, parsedArgs, phase }) => {
1352
+ const [expanded, setExpanded] = useState5(false);
1353
+ const headerValue = extractField(parsedArgs, uiConfig.header_field);
1354
+ const primaryValue = extractField(parsedArgs, uiConfig.primary_field);
1355
+ const toolName = message.toolName || "tool";
1356
+ const taskPurpose = message.taskPurpose;
1357
+ const isEdit = toolName === "edit" || toolName === "edit_block";
1358
+ const contentStr = primaryValue ? formatValue(primaryValue) : "";
1359
+ const hasContent = contentStr.length > 0;
1360
+ const isGenerating = phase === "generating";
1361
+ const shortPath = useMemo2(() => {
1362
+ if (!headerValue) return void 0;
1363
+ const full = String(headerValue);
1364
+ const parts = full.split("/");
1365
+ return parts.length > 2 ? `.../${parts.slice(-2).join("/")}` : full;
1366
+ }, [headerValue]);
1367
+ const { previewLines, totalLines } = useMemo2(() => {
1368
+ if (!contentStr) return { previewLines: [], totalLines: 0 };
1369
+ const lines = contentStr.split("\n").filter((l) => l.trim());
1370
+ const total = lines.length;
1371
+ if (isGenerating) {
1372
+ return { previewLines: lines.slice(-MAX_PREVIEW_LINES), totalLines: total };
1373
+ }
1374
+ return { previewLines: lines.slice(0, MAX_PREVIEW_LINES), totalLines: total };
1375
+ }, [contentStr, isGenerating]);
1376
+ const oldValue = isEdit ? extractField(parsedArgs, "old_string") : void 0;
1377
+ const oldPreviewLines = useMemo2(() => {
1378
+ if (!oldValue) return [];
1379
+ const s = formatValue(oldValue);
1380
+ return s.split("\n").filter((l) => l.trim()).slice(0, 1);
1381
+ }, [oldValue]);
1382
+ return /* @__PURE__ */ jsxs7(Fragment2, { children: [
1383
+ /* @__PURE__ */ jsxs7(
1384
+ "div",
1385
+ {
1386
+ className: "tool-card-header",
1387
+ onClick: () => hasContent && setExpanded(!expanded),
1388
+ style: { cursor: hasContent ? "pointer" : "default" },
1389
+ children: [
1390
+ /* @__PURE__ */ jsx7("span", { className: "tool-phase-icon", children: "\u{1F4C4}" }),
1391
+ taskPurpose ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
1392
+ /* @__PURE__ */ jsx7("span", { className: "tool-card-name", children: taskPurpose }),
1393
+ /* @__PURE__ */ jsxs7("span", { className: "tool-card-fn", children: [
1394
+ "(",
1395
+ toolName,
1396
+ ")"
1397
+ ] })
1398
+ ] }) : /* @__PURE__ */ jsx7("span", { className: "tool-card-name", children: toolName }),
1399
+ shortPath && /* @__PURE__ */ jsx7("code", { className: "tool-card-path-inline", children: shortPath }),
1400
+ /* @__PURE__ */ jsx7(PhaseLabel, { phase }),
1401
+ hasContent && /* @__PURE__ */ jsx7("span", { className: "tool-card-expand", children: expanded ? "\u25BC" : "\u25B6" })
1402
+ ]
1403
+ }
1404
+ ),
1405
+ (hasContent || isGenerating) && !expanded && /* @__PURE__ */ jsxs7("div", { className: `content-diff-preview${isGenerating ? " streaming" : ""}`, children: [
1406
+ isEdit && !isGenerating && oldPreviewLines.map((line, i) => /* @__PURE__ */ jsxs7("div", { className: "diff-line diff-remove", children: [
1407
+ /* @__PURE__ */ jsx7("span", { className: "diff-sign", children: "-" }),
1408
+ /* @__PURE__ */ jsx7("span", { className: "diff-text", children: line })
1409
+ ] }, `old-${i}`)),
1410
+ previewLines.length > 0 ? previewLines.map((line, i) => /* @__PURE__ */ jsxs7("div", { className: "diff-line diff-add", children: [
1411
+ /* @__PURE__ */ jsx7("span", { className: "diff-sign", children: "+" }),
1412
+ /* @__PURE__ */ jsxs7("span", { className: "diff-text", children: [
1413
+ line,
1414
+ isGenerating && i === previewLines.length - 1 && /* @__PURE__ */ jsx7("span", { className: "streaming-block-cursor" })
1415
+ ] })
1416
+ ] }, i)) : isGenerating && /* @__PURE__ */ jsxs7("div", { className: "diff-line diff-add", children: [
1417
+ /* @__PURE__ */ jsx7("span", { className: "diff-sign", children: "+" }),
1418
+ /* @__PURE__ */ jsx7("span", { className: "diff-text", children: /* @__PURE__ */ jsx7("span", { className: "streaming-block-cursor" }) })
1419
+ ] }),
1420
+ !isGenerating && totalLines > MAX_PREVIEW_LINES && /* @__PURE__ */ jsxs7("div", { className: "diff-more", children: [
1421
+ "... +",
1422
+ totalLines - MAX_PREVIEW_LINES,
1423
+ " lines"
1424
+ ] })
1425
+ ] }),
1426
+ expanded && /* @__PURE__ */ jsxs7("div", { className: "content-renderer-body", children: [
1427
+ headerValue && /* @__PURE__ */ jsx7("div", { className: "content-renderer-path", children: headerValue }),
1428
+ /* @__PURE__ */ jsx7("pre", { className: "content-renderer-code", children: contentStr })
1429
+ ] })
1430
+ ] });
1431
+ };
1432
+ var ContentRenderer_default = ContentRenderer;
1433
+
1434
+ // src/components/renderers/CommandRenderer.tsx
1435
+ import "react";
1436
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1437
+ var CommandRenderer = ({ message, uiConfig, parsedArgs, phase }) => {
1438
+ const command = extractField(parsedArgs, uiConfig.primary_field);
1439
+ const toolName = message.toolName || "tool";
1440
+ const taskPurpose = message.taskPurpose;
1441
+ return /* @__PURE__ */ jsxs8(Fragment3, { children: [
1442
+ /* @__PURE__ */ jsxs8("div", { className: "tool-card-header", children: [
1443
+ /* @__PURE__ */ jsx8("span", { className: "tool-phase-icon", children: "\u{1F4BB}" }),
1444
+ taskPurpose ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
1445
+ /* @__PURE__ */ jsx8("span", { className: "tool-card-name", children: taskPurpose }),
1446
+ /* @__PURE__ */ jsxs8("span", { className: "tool-card-fn", children: [
1447
+ "(",
1448
+ toolName,
1449
+ ")"
1450
+ ] })
1451
+ ] }) : /* @__PURE__ */ jsx8("span", { className: "tool-card-name", children: toolName }),
1452
+ /* @__PURE__ */ jsx8(PhaseLabel, { phase })
1453
+ ] }),
1454
+ command && /* @__PURE__ */ jsxs8("div", { className: "command-renderer-terminal", children: [
1455
+ /* @__PURE__ */ jsx8("span", { className: "command-prompt", children: "$" }),
1456
+ /* @__PURE__ */ jsx8("code", { className: "command-text", children: formatValue(command) })
1457
+ ] })
1458
+ ] });
1459
+ };
1460
+ var CommandRenderer_default = CommandRenderer;
1461
+
1462
+ // src/components/renderers/BrowserRenderer.tsx
1463
+ import "react";
1464
+ import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1465
+ var BROWSER_ICONS = {
1466
+ browser_navigate: "\u{1F310}",
1467
+ browser_navigate1: "\u{1F310}",
1468
+ browser_click: "\u{1F5B1}\uFE0F",
1469
+ browser_click1: "\u{1F5B1}\uFE0F",
1470
+ browser_type: "\u2328\uFE0F",
1471
+ browser_hover: "\u{1F446}",
1472
+ browser_snapshot: "\u{1F4F8}",
1473
+ browser_take_screenshot: "\u{1F4F8}",
1474
+ browser_press_key: "\u2328\uFE0F",
1475
+ browser_drag: "\u2194\uFE0F",
1476
+ browser_tab_new: "\u2795",
1477
+ browser_tab_close: "\u2716\uFE0F",
1478
+ browser_wait: "\u23F1\uFE0F",
1479
+ browser_wait_for: "\u23F1\uFE0F"
1480
+ };
1481
+ var BrowserRenderer = ({ message, uiConfig, parsedArgs, phase }) => {
1482
+ const toolName = message.toolName || "";
1483
+ const taskPurpose = message.taskPurpose;
1484
+ const icon = BROWSER_ICONS[toolName] || "\u{1F310}";
1485
+ const primaryValue = extractField(parsedArgs, uiConfig.primary_field);
1486
+ return /* @__PURE__ */ jsx9(Fragment4, { children: /* @__PURE__ */ jsxs9("div", { className: "tool-card-header", children: [
1487
+ /* @__PURE__ */ jsx9("span", { className: "tool-phase-icon", children: icon }),
1488
+ taskPurpose ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
1489
+ /* @__PURE__ */ jsx9("span", { className: "tool-card-name", children: taskPurpose }),
1490
+ /* @__PURE__ */ jsxs9("span", { className: "tool-card-fn", children: [
1491
+ "(",
1492
+ toolName,
1493
+ ")"
1494
+ ] })
1495
+ ] }) : /* @__PURE__ */ jsx9("span", { className: "tool-card-name", children: toolName }),
1496
+ primaryValue && /* @__PURE__ */ jsx9("span", { className: "browser-primary-value", children: formatValue(primaryValue) }),
1497
+ /* @__PURE__ */ jsx9(PhaseLabel, { phase })
1498
+ ] }) });
1499
+ };
1500
+ var BrowserRenderer_default = BrowserRenderer;
1501
+
1502
+ // src/components/renderers/QueryRenderer.tsx
1503
+ import "react";
1504
+ import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1505
+ var QueryRenderer = ({ message, uiConfig, parsedArgs, phase }) => {
1506
+ const queryValue = extractField(parsedArgs, uiConfig.primary_field);
1507
+ const headerValue = extractField(parsedArgs, uiConfig.header_field);
1508
+ const toolName = message.toolName || "tool";
1509
+ const taskPurpose = message.taskPurpose;
1510
+ return /* @__PURE__ */ jsx10(Fragment5, { children: /* @__PURE__ */ jsxs10("div", { className: "tool-card-header", children: [
1511
+ /* @__PURE__ */ jsx10("span", { className: "tool-phase-icon", children: "\u{1F50D}" }),
1512
+ taskPurpose ? /* @__PURE__ */ jsxs10(Fragment5, { children: [
1513
+ /* @__PURE__ */ jsx10("span", { className: "tool-card-name", children: taskPurpose }),
1514
+ /* @__PURE__ */ jsxs10("span", { className: "tool-card-fn", children: [
1515
+ "(",
1516
+ toolName,
1517
+ ")"
1518
+ ] })
1519
+ ] }) : /* @__PURE__ */ jsx10("span", { className: "tool-card-name", children: toolName }),
1520
+ queryValue && /* @__PURE__ */ jsxs10("span", { className: "query-inline", children: [
1521
+ '"',
1522
+ formatValue(queryValue),
1523
+ '"'
1524
+ ] }),
1525
+ headerValue && /* @__PURE__ */ jsxs10("span", { className: "query-scope-inline", children: [
1526
+ "in ",
1527
+ formatValue(headerValue)
1528
+ ] }),
1529
+ /* @__PURE__ */ jsx10(PhaseLabel, { phase })
1530
+ ] }) });
1531
+ };
1532
+ var QueryRenderer_default = QueryRenderer;
1533
+
1534
+ // src/components/renderers/PathRenderer.tsx
1535
+ import "react";
1536
+ import { Fragment as Fragment6, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1537
+ var PathRenderer = ({ message, uiConfig, parsedArgs, phase }) => {
1538
+ const pathValue = extractField(parsedArgs, uiConfig.primary_field);
1539
+ const toolName = message.toolName || "tool";
1540
+ const taskPurpose = message.taskPurpose;
1541
+ const isDir = ["list_directory", "create_directory", "mkdir", "rmdir"].includes(toolName);
1542
+ const icon = isDir ? "\u{1F4C2}" : "\u{1F4C4}";
1543
+ return /* @__PURE__ */ jsx11(Fragment6, { children: /* @__PURE__ */ jsxs11("div", { className: "tool-card-header", children: [
1544
+ /* @__PURE__ */ jsx11("span", { className: "tool-phase-icon", children: icon }),
1545
+ taskPurpose ? /* @__PURE__ */ jsxs11(Fragment6, { children: [
1546
+ /* @__PURE__ */ jsx11("span", { className: "tool-card-name", children: taskPurpose }),
1547
+ /* @__PURE__ */ jsxs11("span", { className: "tool-card-fn", children: [
1548
+ "(",
1549
+ toolName,
1550
+ ")"
1551
+ ] })
1552
+ ] }) : /* @__PURE__ */ jsx11("span", { className: "tool-card-name", children: toolName }),
1553
+ pathValue && /* @__PURE__ */ jsx11("code", { className: "tool-card-path-inline", children: formatValue(pathValue) }),
1554
+ /* @__PURE__ */ jsx11(PhaseLabel, { phase })
1555
+ ] }) });
1556
+ };
1557
+ var PathRenderer_default = PathRenderer;
1558
+
1559
+ // src/components/renderers/TriggerRenderer.tsx
1560
+ import { useState as useState6 } from "react";
1561
+ import { Fragment as Fragment7, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1562
+ var TriggerRenderer = ({ message, uiConfig, parsedArgs, phase }) => {
1563
+ const [expanded, setExpanded] = useState6(false);
1564
+ const headerValue = extractField(parsedArgs, uiConfig.header_field);
1565
+ const primaryValue = extractField(parsedArgs, uiConfig.primary_field);
1566
+ const tags = buildCompactTags(parsedArgs, uiConfig.compact_fields, uiConfig.hide_fields);
1567
+ const toolLabel = message.taskPurpose || message.toolName || "tool";
1568
+ const hasDetail = !!parsedArgs && Object.keys(parsedArgs).length > 0;
1569
+ return /* @__PURE__ */ jsxs12(Fragment7, { children: [
1570
+ /* @__PURE__ */ jsxs12(
1571
+ "div",
1572
+ {
1573
+ className: "tool-card-header",
1574
+ onClick: () => hasDetail && setExpanded(!expanded),
1575
+ style: { cursor: hasDetail ? "pointer" : "default" },
1576
+ children: [
1577
+ /* @__PURE__ */ jsx12("span", { className: "tool-phase-icon", children: "\u23F0" }),
1578
+ /* @__PURE__ */ jsx12("span", { className: "tool-card-name", children: toolLabel }),
1579
+ (headerValue || primaryValue) && /* @__PURE__ */ jsx12("span", { className: "trigger-title", children: formatValue(headerValue || primaryValue) }),
1580
+ /* @__PURE__ */ jsx12(PhaseLabel, { phase }),
1581
+ hasDetail && /* @__PURE__ */ jsx12("span", { className: "tool-card-expand", children: expanded ? "\u25BC" : "\u25B6" })
1582
+ ]
1583
+ }
1584
+ ),
1585
+ tags.length > 0 && /* @__PURE__ */ jsx12(CompactTags, { tags }),
1586
+ expanded && parsedArgs && /* @__PURE__ */ jsx12("div", { className: "trigger-detail", children: Object.entries(parsedArgs).filter(([k]) => !uiConfig.hide_fields?.includes(k)).map(([k, v]) => /* @__PURE__ */ jsxs12("div", { className: "trigger-row", children: [
1587
+ /* @__PURE__ */ jsx12("span", { className: "trigger-key", children: k }),
1588
+ /* @__PURE__ */ jsx12("span", { className: "trigger-value", children: formatValue(v) })
1589
+ ] }, k)) })
1590
+ ] });
1591
+ };
1592
+ var TriggerRenderer_default = TriggerRenderer;
1593
+
1594
+ // src/components/renderers/ThinkingRenderer.tsx
1595
+ import "react";
1596
+ import { Fragment as Fragment8, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1597
+ var ThinkingRenderer = ({ message, uiConfig, parsedArgs, phase }) => {
1598
+ const thought = extractField(parsedArgs, uiConfig.primary_field);
1599
+ const stepInfo = parsedArgs ? `${parsedArgs.thoughtNumber || "?"}/${parsedArgs.totalThoughts || "?"}` : "";
1600
+ return /* @__PURE__ */ jsxs13(Fragment8, { children: [
1601
+ /* @__PURE__ */ jsxs13("div", { className: "tool-card-header", children: [
1602
+ /* @__PURE__ */ jsx13("span", { className: "tool-phase-icon", children: "\u{1F4AD}" }),
1603
+ /* @__PURE__ */ jsx13("span", { className: "tool-card-name", children: "\u601D\u8003\u63A8\u7406" }),
1604
+ stepInfo && /* @__PURE__ */ jsxs13("span", { className: "thinking-step", children: [
1605
+ "\u6B65\u9AA4 ",
1606
+ stepInfo
1607
+ ] }),
1608
+ /* @__PURE__ */ jsx13(PhaseLabel, { phase })
1609
+ ] }),
1610
+ thought && /* @__PURE__ */ jsx13("div", { className: "thinking-bubble", children: formatValue(thought) })
1611
+ ] });
1612
+ };
1613
+ var ThinkingRenderer_default = ThinkingRenderer;
1614
+
1615
+ // src/components/renderers/ParamsRenderer.tsx
1616
+ import { useState as useState7 } from "react";
1617
+ import { Fragment as Fragment9, jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1618
+ var ParamsRenderer = ({ message, uiConfig, parsedArgs, phase }) => {
1619
+ const [expanded, setExpanded] = useState7(false);
1620
+ const headerValue = extractField(parsedArgs, uiConfig?.header_field);
1621
+ const toolName = message.toolName || "tool";
1622
+ const taskPurpose = message.taskPurpose;
1623
+ const hasArgs = !!parsedArgs && Object.keys(parsedArgs).length > 0;
1624
+ return /* @__PURE__ */ jsxs14(Fragment9, { children: [
1625
+ /* @__PURE__ */ jsxs14(
1626
+ "div",
1627
+ {
1628
+ className: "tool-card-header",
1629
+ onClick: () => hasArgs && setExpanded(!expanded),
1630
+ style: { cursor: hasArgs ? "pointer" : "default" },
1631
+ children: [
1632
+ /* @__PURE__ */ jsx14("span", { className: "tool-phase-icon", children: "\u2699\uFE0F" }),
1633
+ taskPurpose ? /* @__PURE__ */ jsxs14(Fragment9, { children: [
1634
+ /* @__PURE__ */ jsx14("span", { className: "tool-card-name", children: taskPurpose }),
1635
+ /* @__PURE__ */ jsxs14("span", { className: "tool-card-fn", children: [
1636
+ "(",
1637
+ toolName,
1638
+ ")"
1639
+ ] })
1640
+ ] }) : /* @__PURE__ */ jsx14("span", { className: "tool-card-name", children: toolName }),
1641
+ headerValue && /* @__PURE__ */ jsx14("code", { className: "tool-card-path-inline", children: formatValue(headerValue) }),
1642
+ /* @__PURE__ */ jsx14(PhaseLabel, { phase }),
1643
+ hasArgs && /* @__PURE__ */ jsx14("span", { className: "tool-card-expand", children: expanded ? "\u25BC" : "\u25B6" })
1644
+ ]
1645
+ }
1646
+ ),
1647
+ expanded && parsedArgs && /* @__PURE__ */ jsx14("div", { className: "params-detail", children: Object.entries(parsedArgs).filter(([k]) => !uiConfig?.hide_fields?.includes(k)).map(([k, v]) => /* @__PURE__ */ jsxs14("div", { className: "params-row", children: [
1648
+ /* @__PURE__ */ jsxs14("span", { className: "params-key", children: [
1649
+ k,
1650
+ ":"
1651
+ ] }),
1652
+ /* @__PURE__ */ jsx14("span", { className: "params-value", children: formatValue(v) })
1653
+ ] }, k)) })
1654
+ ] });
1655
+ };
1656
+ var ParamsRenderer_default = ParamsRenderer;
1657
+
1658
+ // src/components/renderers/ToolResultRenderer.tsx
1659
+ import { useState as useState8 } from "react";
1660
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1661
+ function tryParseJson(raw) {
1662
+ try {
1663
+ return JSON.parse(raw);
1664
+ } catch {
1665
+ return null;
1666
+ }
1667
+ }
1668
+ function isSearchResultsShape(data) {
1669
+ if (!data || typeof data !== "object") return false;
1670
+ const obj = data;
1671
+ const sr = obj.search_results;
1672
+ return sr != null && Array.isArray(sr.results) && sr.results.length > 0;
1673
+ }
1674
+ function isScrapeResultShape(data) {
1675
+ if (!data || typeof data !== "object") return false;
1676
+ const obj = data;
1677
+ return typeof obj.url === "string" && obj.url.length > 0 && !("search_results" in obj) && (typeof obj.title === "string" || typeof obj.snippet === "string");
1678
+ }
1679
+ var SearchResultsView = ({ query, results }) => /* @__PURE__ */ jsxs15("div", { className: "tool-result-search", children: [
1680
+ query && /* @__PURE__ */ jsxs15("div", { className: "tool-result-search-query", children: [
1681
+ "\u{1F50D} ",
1682
+ query
1683
+ ] }),
1684
+ /* @__PURE__ */ jsxs15("div", { className: "tool-result-search-list", children: [
1685
+ results.slice(0, 8).map((r, i) => /* @__PURE__ */ jsxs15(
1686
+ "a",
1687
+ {
1688
+ className: "tool-result-search-item",
1689
+ href: r.link,
1690
+ target: "_blank",
1691
+ rel: "noopener noreferrer",
1692
+ children: [
1693
+ /* @__PURE__ */ jsx15("div", { className: "search-item-title", children: r.title }),
1694
+ /* @__PURE__ */ jsx15("div", { className: "search-item-snippet", children: r.snippet }),
1695
+ r.date && /* @__PURE__ */ jsx15("span", { className: "search-item-date", children: r.date })
1696
+ ]
1697
+ },
1698
+ i
1699
+ )),
1700
+ results.length > 8 && /* @__PURE__ */ jsxs15("div", { className: "search-item-more", children: [
1701
+ "+",
1702
+ results.length - 8,
1703
+ " more"
1704
+ ] })
1705
+ ] })
1706
+ ] });
1707
+ var ScrapeResultView = ({ result }) => /* @__PURE__ */ jsxs15("div", { className: "tool-result-scrape", children: [
1708
+ /* @__PURE__ */ jsxs15(
1709
+ "a",
1710
+ {
1711
+ className: "scrape-result-link",
1712
+ href: result.url,
1713
+ target: "_blank",
1714
+ rel: "noopener noreferrer",
1715
+ children: [
1716
+ result.title && /* @__PURE__ */ jsx15("div", { className: "scrape-result-title", children: result.title }),
1717
+ /* @__PURE__ */ jsx15("div", { className: "scrape-result-url", children: result.url })
1718
+ ]
1719
+ }
1720
+ ),
1721
+ result.snippet && /* @__PURE__ */ jsx15("div", { className: "scrape-result-snippet", children: result.snippet })
1722
+ ] });
1723
+ var JsonView = ({ data }) => {
1724
+ const formatted = JSON.stringify(data, null, 2);
1725
+ const lines = formatted.split("\n");
1726
+ const needsTruncation = lines.length > 12;
1727
+ const [showAll, setShowAll] = useState8(false);
1728
+ const display = showAll ? formatted : lines.slice(0, 12).join("\n");
1729
+ return /* @__PURE__ */ jsxs15("div", { className: "tool-result-json", children: [
1730
+ /* @__PURE__ */ jsx15("pre", { className: "tool-result-json-code", children: display }),
1731
+ needsTruncation && !showAll && /* @__PURE__ */ jsxs15("div", { className: "tool-result-json-more", onClick: () => setShowAll(true), children: [
1732
+ "\u5C55\u5F00\u5168\u90E8 (",
1733
+ lines.length,
1734
+ " \u884C)"
1735
+ ] })
1736
+ ] });
1737
+ };
1738
+ var ToolResultRenderer = ({ content, defaultExpanded = false }) => {
1739
+ const [isExpanded, setIsExpanded] = useState8(defaultExpanded);
1740
+ if (!content.trim()) return null;
1741
+ const parsed = tryParseJson(content);
1742
+ const summaryText = (() => {
1743
+ if (parsed && isSearchResultsShape(parsed)) {
1744
+ const count = parsed.search_results.results.length;
1745
+ return `\u641C\u7D22\u7ED3\u679C \xB7 ${count} \u6761`;
1746
+ }
1747
+ if (parsed && isScrapeResultShape(parsed)) {
1748
+ const title = parsed.title || new URL(parsed.url).hostname;
1749
+ return `\u6293\u53D6\u7ED3\u679C \xB7 ${title}`;
1750
+ }
1751
+ if (parsed) {
1752
+ const str = JSON.stringify(parsed);
1753
+ return str.length > 60 ? str.slice(0, 60) + "\u2026" : str;
1754
+ }
1755
+ return content.length > 80 ? content.slice(0, 80) + "\u2026" : content;
1756
+ })();
1757
+ return /* @__PURE__ */ jsxs15("div", { className: "tool-result-section", children: [
1758
+ /* @__PURE__ */ jsxs15("div", { className: "tool-result-header", onClick: () => setIsExpanded(!isExpanded), children: [
1759
+ /* @__PURE__ */ jsx15("span", { className: "tool-result-label", children: "\u{1F4CB} \u6267\u884C\u7ED3\u679C" }),
1760
+ /* @__PURE__ */ jsx15("span", { className: "tool-result-summary", children: summaryText }),
1761
+ /* @__PURE__ */ jsx15("span", { className: `collapse-arrow ${isExpanded ? "expanded" : ""}`, children: "\u25BC" })
1762
+ ] }),
1763
+ isExpanded && /* @__PURE__ */ jsx15("div", { className: "tool-result-body", children: parsed && isSearchResultsShape(parsed) ? /* @__PURE__ */ jsx15(SearchResultsView, { query: parsed.query, results: parsed.search_results.results }) : parsed && isScrapeResultShape(parsed) ? /* @__PURE__ */ jsx15(ScrapeResultView, { result: parsed }) : parsed ? /* @__PURE__ */ jsx15(JsonView, { data: parsed }) : /* @__PURE__ */ jsx15("div", { className: "tool-result-text", children: content }) })
1764
+ ] });
1765
+ };
1766
+ var ToolResultRenderer_default = ToolResultRenderer;
1767
+
1768
+ // src/components/ToolCardBuffering.tsx
1769
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1770
+ var renderers = {
1771
+ content: ContentRenderer_default,
1772
+ command: CommandRenderer_default,
1773
+ browser: BrowserRenderer_default,
1774
+ query: QueryRenderer_default,
1775
+ path: PathRenderer_default,
1776
+ trigger: TriggerRenderer_default,
1777
+ thinking: ThinkingRenderer_default,
1778
+ params: ParamsRenderer_default
1779
+ };
1780
+ var ToolCardBuffering = memo(({ message }) => {
1781
+ const phase = message.toolPhase || "generating";
1782
+ const config = phaseConfig[phase];
1783
+ const uiConfig = message.uiConfig || { display_type: "params" };
1784
+ const parseCacheRef = useRef3(null);
1785
+ const parsedArgs = useMemo3(
1786
+ () => parseArgsIncremental(message.argsPreview, parseCacheRef),
1787
+ [message.argsPreview]
1788
+ );
1789
+ const displayType = uiConfig.display_type || "params";
1790
+ const Renderer = renderers[displayType] || ParamsRenderer_default;
1791
+ const resultContent = message.contentChunks.join("");
1792
+ const hasResult = resultContent.trim().length > 0;
1793
+ return /* @__PURE__ */ jsxs16("div", { className: `tool-card-buffering ${config.className}`, children: [
1794
+ /* @__PURE__ */ jsx16(
1795
+ Renderer,
1796
+ {
1797
+ message,
1798
+ uiConfig,
1799
+ parsedArgs,
1800
+ phase
1801
+ }
1802
+ ),
1803
+ hasResult && /* @__PURE__ */ jsx16(ToolResultRenderer_default, { content: resultContent })
1804
+ ] });
1805
+ });
1806
+ var ToolCardBuffering_default = ToolCardBuffering;
1807
+
1808
+ // src/components/MessageContent.tsx
1809
+ import { Fragment as Fragment10, jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
1810
+ var math = createMathPlugin({ singleDollarTextMath: true });
1811
+ var streamdownPlugins = { code, mermaid, math, cjk };
1812
+ function countOccurrences(text, needle) {
1813
+ let count = 0;
1814
+ let pos = 0;
1815
+ while (pos < text.length) {
1816
+ const idx = text.indexOf(needle, pos);
1817
+ if (idx === -1) break;
1818
+ count++;
1819
+ pos = idx + needle.length;
1820
+ }
1821
+ return count;
1822
+ }
1823
+ function snapToSafeBoundary(content, length) {
1824
+ if (length >= content.length) return content.length;
1825
+ if (length === 0) return 0;
1826
+ const slice = content.slice(0, length);
1827
+ const lastNL = slice.lastIndexOf("\n");
1828
+ const currentLine = slice.slice(lastNL + 1);
1829
+ if (/^`{3,}/.test(currentLine)) {
1830
+ const lineEnd = content.indexOf("\n", lastNL + 1);
1831
+ return lineEnd !== -1 ? lineEnd + 1 : content.length;
1832
+ }
1833
+ if (/`{1,2}$/.test(currentLine) && length < content.length && content[length] === "`") {
1834
+ const lineEnd = content.indexOf("\n", lastNL + 1);
1835
+ return lineEnd !== -1 ? lineEnd + 1 : content.length;
1836
+ }
1837
+ if (slice.endsWith("$") && length < content.length && content[length] === "$") {
1838
+ return snapToSafeBoundary(content, length + 1);
1839
+ }
1840
+ const ddCount = countOccurrences(slice, "$$");
1841
+ if (ddCount % 2 === 1) {
1842
+ const closeIdx = content.indexOf("$$", length);
1843
+ if (closeIdx !== -1) {
1844
+ return Math.min(closeIdx + 2, content.length);
1845
+ }
1846
+ }
1847
+ const totalDollars = countOccurrences(slice, "$");
1848
+ const singleDollars = totalDollars - 2 * ddCount;
1849
+ if (singleDollars % 2 === 1) {
1850
+ let searchPos = length;
1851
+ while (searchPos < content.length) {
1852
+ const idx = content.indexOf("$", searchPos);
1853
+ if (idx === -1) break;
1854
+ if (idx + 1 < content.length && content[idx + 1] === "$") {
1855
+ searchPos = idx + 2;
1856
+ continue;
1857
+ }
1858
+ if (idx > 0 && content[idx - 1] === "$") {
1859
+ searchPos = idx + 1;
1860
+ continue;
1861
+ }
1862
+ return Math.min(idx + 1, content.length);
1863
+ }
1864
+ }
1865
+ return length;
1866
+ }
1867
+ function useTypewriter(content, enabled, speed = 30) {
1868
+ const [displayedLength, setDisplayedLength] = useState9(0);
1869
+ const [isTyping, setIsTyping] = useState9(false);
1870
+ const rafRef = useRef4(null);
1871
+ const targetLengthRef = useRef4(0);
1872
+ const displayedLengthRef = useRef4(0);
1873
+ displayedLengthRef.current = displayedLength;
1874
+ useEffect2(() => {
1875
+ if (!enabled) {
1876
+ setDisplayedLength(content.length);
1877
+ targetLengthRef.current = content.length;
1878
+ return;
1879
+ }
1880
+ targetLengthRef.current = content.length;
1881
+ if (rafRef.current) return;
1882
+ if (displayedLengthRef.current >= content.length) return;
1883
+ setIsTyping(true);
1884
+ let lastTime = performance.now();
1885
+ const animate = (currentTime) => {
1886
+ const deltaTime = currentTime - lastTime;
1887
+ lastTime = currentTime;
1888
+ const charsToAdd = Math.max(1, Math.ceil(deltaTime / speed));
1889
+ const currentDisplayed = displayedLengthRef.current;
1890
+ const target = targetLengthRef.current;
1891
+ const raw = Math.min(currentDisplayed + charsToAdd, target);
1892
+ const newLength = snapToSafeBoundary(content, raw);
1893
+ setDisplayedLength(newLength);
1894
+ displayedLengthRef.current = newLength;
1895
+ if (newLength >= target) {
1896
+ setIsTyping(false);
1897
+ rafRef.current = null;
1898
+ } else {
1899
+ rafRef.current = requestAnimationFrame(animate);
1900
+ }
1901
+ };
1902
+ rafRef.current = requestAnimationFrame(animate);
1903
+ return () => {
1904
+ if (rafRef.current) {
1905
+ cancelAnimationFrame(rafRef.current);
1906
+ rafRef.current = null;
1907
+ }
1908
+ };
1909
+ }, [content, enabled, speed]);
1910
+ return [content.slice(0, displayedLength), isTyping];
1911
+ }
1912
+ var MessageContent = memo2(({
1913
+ message,
1914
+ enableTypewriter = true,
1915
+ typewriterSpeed = 30,
1916
+ onArtifactClick
1917
+ }) => {
1918
+ const content = message.contentChunks.join("");
1919
+ const isTextType = message.contentType === "text";
1920
+ const [displayedContent, isTyping] = useTypewriter(
1921
+ content,
1922
+ enableTypewriter && isTextType,
1923
+ typewriterSpeed
1924
+ );
1925
+ if (message.contentType === "think") {
1926
+ return /* @__PURE__ */ jsx17(ThinkChunk, { content, isThinking: isTyping });
1927
+ }
1928
+ if (message.contentType === "json_schema_wait" || message.contentType === "json_schema" || message.contentType === "html_schema") {
1929
+ return /* @__PURE__ */ jsx17(HITLWaitCard, { content, hitlRequest: message.hitlRequest });
1930
+ }
1931
+ if (message.contentType === "artifact" && (message.artifacts || message.artifact)) {
1932
+ const artifacts = message.artifacts || (message.artifact ? [message.artifact] : []);
1933
+ return /* @__PURE__ */ jsx17("div", { className: "artifact-list", children: artifacts.map((artifact, index) => /* @__PURE__ */ jsx17(
1934
+ ArtifactCard,
1935
+ {
1936
+ artifact,
1937
+ onClick: () => onArtifactClick?.(artifact)
1938
+ },
1939
+ artifact.id || index
1940
+ )) });
1941
+ }
1942
+ if (message.toolPhase) {
1943
+ return /* @__PURE__ */ jsx17(ToolCardBuffering_default, { message });
1944
+ }
1945
+ const isMcpEvent = message.notificationType && ["mcp_start", "mcp_result", "mcp_end"].includes(message.notificationType);
1946
+ if (isMcpEvent) {
1947
+ const toolName = message.toolName || (() => {
1948
+ const match = content.match(/[🔧\s]*(.+?)\s*\((\w+)\)/) || content.match(/[🔧\s]*execute tool:\s*(\S+)/);
1949
+ return match ? match[2] || match[1] : null;
1950
+ })();
1951
+ const toolLabelMatch = content.match(/[🔧\s]*(.+?)\s*\(/);
1952
+ const toolLabel = toolLabelMatch ? toolLabelMatch[1].trim() : message.taskPurpose || toolName || content;
1953
+ let statusIcon = "\u{1F527}";
1954
+ let statusClass = "";
1955
+ let statusText = "";
1956
+ if (message.notificationType === "mcp_start") {
1957
+ statusIcon = "\u23F3";
1958
+ statusClass = "tool-running";
1959
+ statusText = "\u6267\u884C\u4E2D";
1960
+ } else if (message.notificationType === "mcp_end") {
1961
+ if (message.toolStatus === "error") {
1962
+ statusIcon = "\u274C";
1963
+ statusClass = "tool-error";
1964
+ statusText = "\u5931\u8D25";
1965
+ } else {
1966
+ statusIcon = "\u2705";
1967
+ statusClass = "tool-success";
1968
+ statusText = "\u5B8C\u6210";
1969
+ }
1970
+ } else {
1971
+ statusIcon = "\u{1F527}";
1972
+ statusClass = "tool-running";
1973
+ }
1974
+ return /* @__PURE__ */ jsxs17("div", { className: `tool-execution ${statusClass}`, children: [
1975
+ /* @__PURE__ */ jsx17("span", { className: "tool-icon", children: statusIcon }),
1976
+ /* @__PURE__ */ jsxs17("span", { className: "tool-text", children: [
1977
+ toolLabel,
1978
+ toolName && toolName !== toolLabel ? ` (${toolName})` : ""
1979
+ ] }),
1980
+ statusText && /* @__PURE__ */ jsx17("span", { className: "tool-status", children: statusText })
1981
+ ] });
1982
+ }
1983
+ const toolLineRegex = /^[🔧\s]*execute tool:\s*(\S+)\s*$/m;
1984
+ const toolMatch = content.match(toolLineRegex);
1985
+ if (toolMatch) {
1986
+ const toolName = toolMatch[1];
1987
+ const matchIndex = content.indexOf(toolMatch[0]);
1988
+ const beforeContent = content.slice(0, matchIndex).trim();
1989
+ const afterContent = content.slice(matchIndex + toolMatch[0].length).trim();
1990
+ return /* @__PURE__ */ jsxs17(Fragment10, { children: [
1991
+ beforeContent && /* @__PURE__ */ jsx17("div", { className: "sd-message", children: /* @__PURE__ */ jsx17(Streamdown, { plugins: streamdownPlugins, children: beforeContent }) }),
1992
+ /* @__PURE__ */ jsxs17("div", { className: "tool-execution", children: [
1993
+ /* @__PURE__ */ jsx17("span", { className: "tool-icon", children: "\u{1F527}" }),
1994
+ /* @__PURE__ */ jsxs17("span", { className: "tool-text", children: [
1995
+ "\u6267\u884C\u5DE5\u5177: ",
1996
+ toolName
1997
+ ] })
1998
+ ] }),
1999
+ afterContent && /* @__PURE__ */ jsx17("div", { className: "sd-message", children: /* @__PURE__ */ jsx17(Streamdown, { plugins: streamdownPlugins, children: afterContent }) })
2000
+ ] });
2001
+ }
2002
+ return /* @__PURE__ */ jsxs17("div", { className: "sd-message", children: [
2003
+ /* @__PURE__ */ jsx17(
2004
+ Streamdown,
2005
+ {
2006
+ plugins: streamdownPlugins,
2007
+ isAnimating: isTyping,
2008
+ children: displayedContent
2009
+ }
2010
+ ),
2011
+ isTyping && /* @__PURE__ */ jsx17("span", { className: "streaming-cursor" })
2012
+ ] });
2013
+ });
2014
+ var ThinkChunk = ({ content, isThinking = false }) => {
2015
+ const [isExpanded, setIsExpanded] = useState9(false);
2016
+ const [manualToggled, setManualToggled] = useState9(false);
2017
+ const [prevContentLength, setPrevContentLength] = useState9(0);
2018
+ const [thinking, setThinking] = useState9(true);
2019
+ const previewRef = useRef4(null);
2020
+ const isActive = thinking || isThinking;
2021
+ const thinkingTimerRef = useRef4(null);
2022
+ useEffect2(() => {
2023
+ if (content.length > prevContentLength) {
2024
+ setThinking(true);
2025
+ setPrevContentLength(content.length);
2026
+ if (thinkingTimerRef.current) {
2027
+ clearTimeout(thinkingTimerRef.current);
2028
+ thinkingTimerRef.current = null;
2029
+ }
2030
+ }
2031
+ thinkingTimerRef.current = setTimeout(() => {
2032
+ if (content.length === prevContentLength) {
2033
+ setThinking(false);
2034
+ }
2035
+ }, 5e3);
2036
+ return () => {
2037
+ if (thinkingTimerRef.current) {
2038
+ clearTimeout(thinkingTimerRef.current);
2039
+ }
2040
+ };
2041
+ }, [content, prevContentLength]);
2042
+ useEffect2(() => {
2043
+ if (previewRef.current && isActive && !isExpanded) {
2044
+ previewRef.current.scrollTop = previewRef.current.scrollHeight;
2045
+ }
2046
+ }, [content, isActive, isExpanded]);
2047
+ const extractHeading = (text) => {
2048
+ const match = text.match(/^#\s+(.+)$/m);
2049
+ return match ? match[1].trim() : null;
2050
+ };
2051
+ const heading = extractHeading(content);
2052
+ const displayText = heading ? heading : isActive ? "\u601D\u8003\u4E2D..." : "\u601D\u8003\u5B8C\u6210";
2053
+ const showPreview = isActive && !manualToggled && !isExpanded && content.length > 0;
2054
+ const [previewVisible, setPreviewVisible] = useState9(false);
2055
+ useEffect2(() => {
2056
+ if (showPreview) {
2057
+ setPreviewVisible(true);
2058
+ } else if (previewVisible) {
2059
+ const t = setTimeout(() => setPreviewVisible(false), 600);
2060
+ return () => clearTimeout(t);
2061
+ }
2062
+ }, [showPreview]);
2063
+ const handleToggle = () => {
2064
+ setManualToggled(true);
2065
+ setPreviewVisible(false);
2066
+ setIsExpanded(!isExpanded);
2067
+ };
2068
+ return /* @__PURE__ */ jsxs17("div", { className: `think-chunk ${isExpanded ? "expanded" : ""} ${isActive ? "thinking" : ""}`, children: [
2069
+ /* @__PURE__ */ jsxs17(
2070
+ "div",
2071
+ {
2072
+ className: "think-header",
2073
+ onClick: handleToggle,
2074
+ children: [
2075
+ /* @__PURE__ */ jsxs17("div", { className: "think-title", children: [
2076
+ /* @__PURE__ */ jsx17("span", { className: `think-icon ${isActive ? "thinking-animation" : ""}`, children: "\u{1F4AD}" }),
2077
+ /* @__PURE__ */ jsx17("span", { className: "think-text", children: displayText }),
2078
+ isActive && /* @__PURE__ */ jsxs17("span", { className: "thinking-dots", children: [
2079
+ /* @__PURE__ */ jsx17("span", { children: "." }),
2080
+ /* @__PURE__ */ jsx17("span", { children: "." }),
2081
+ /* @__PURE__ */ jsx17("span", { children: "." })
2082
+ ] })
2083
+ ] }),
2084
+ /* @__PURE__ */ jsx17("span", { className: `collapse-arrow ${isExpanded || previewVisible ? "expanded" : ""}`, children: "\u25BC" })
2085
+ ]
2086
+ }
2087
+ ),
2088
+ /* @__PURE__ */ jsx17(
2089
+ "div",
2090
+ {
2091
+ className: `think-preview ${previewVisible ? "think-preview-open" : "think-preview-closed"}`,
2092
+ ref: previewRef,
2093
+ children: /* @__PURE__ */ jsx17("div", { className: "sd-think", children: /* @__PURE__ */ jsx17(Streamdown, { plugins: streamdownPlugins, isAnimating: isActive, children: content }) })
2094
+ }
2095
+ ),
2096
+ isExpanded && /* @__PURE__ */ jsx17("div", { className: "think-content", children: /* @__PURE__ */ jsx17("div", { className: "sd-think", children: /* @__PURE__ */ jsx17(Streamdown, { plugins: streamdownPlugins, children: content }) }) })
2097
+ ] });
2098
+ };
2099
+ var HITLWaitCard = ({ content, hitlRequest }) => {
2100
+ const parseFirstJson = (str) => {
2101
+ try {
2102
+ return JSON.parse(str);
2103
+ } catch {
2104
+ let depth = 0;
2105
+ let start = -1;
2106
+ for (let i = 0; i < str.length; i++) {
2107
+ if (str[i] === "{") {
2108
+ if (depth === 0) start = i;
2109
+ depth++;
2110
+ } else if (str[i] === "}") {
2111
+ depth--;
2112
+ if (depth === 0 && start !== -1) {
2113
+ try {
2114
+ return JSON.parse(str.slice(start, i + 1));
2115
+ } catch {
2116
+ start = -1;
2117
+ }
2118
+ }
2119
+ }
2120
+ }
2121
+ return null;
2122
+ }
2123
+ };
2124
+ const getDisplayText = () => {
2125
+ const parsed = parseFirstJson(content);
2126
+ if (!parsed) {
2127
+ return content || "\u7B49\u5F85\u7528\u6237\u8F93\u5165...";
2128
+ }
2129
+ if (parsed.content !== void 0) {
2130
+ let langContent = parsed.content;
2131
+ if (typeof langContent === "string") {
2132
+ try {
2133
+ langContent = JSON.parse(langContent);
2134
+ } catch {
2135
+ return langContent || "\u7B49\u5F85\u7528\u6237\u8F93\u5165...";
2136
+ }
2137
+ }
2138
+ if (typeof langContent === "object" && langContent !== null) {
2139
+ const langObj = langContent;
2140
+ return langObj.zh_CN || langObj.en_US || JSON.stringify(langContent);
2141
+ }
2142
+ return String(langContent) || "\u7B49\u5F85\u7528\u6237\u8F93\u5165...";
2143
+ }
2144
+ if (parsed.zh_CN || parsed.en_US) {
2145
+ return parsed.zh_CN || parsed.en_US;
2146
+ }
2147
+ if (parsed.type && parsed.props?.title) {
2148
+ return parsed.props.title;
2149
+ }
2150
+ return content;
2151
+ };
2152
+ const getFormSchema = () => {
2153
+ const parsed = parseFirstJson(content);
2154
+ if (parsed) {
2155
+ if (parsed.type && (parsed.children || parsed.props)) {
2156
+ return parsed;
2157
+ }
2158
+ }
2159
+ if (hitlRequest?.schema) {
2160
+ return hitlRequest.schema;
2161
+ }
2162
+ return null;
2163
+ };
2164
+ const displayText = getDisplayText();
2165
+ const formSchema = getFormSchema();
2166
+ if (formSchema) {
2167
+ const schemaObj = formSchema;
2168
+ const title = schemaObj.props?.title || hitlRequest?.title || "\u8BF7\u586B\u5199\u8868\u5355";
2169
+ const handleFormSubmit = (values) => {
2170
+ console.log("[HITL] \u8868\u5355\u63D0\u4EA4:", values, "await_command_uuid:", hitlRequest?.await_command_uuid);
2171
+ };
2172
+ return /* @__PURE__ */ jsxs17("div", { className: "hitl-form-card", children: [
2173
+ /* @__PURE__ */ jsxs17("div", { className: "hitl-form-header", children: [
2174
+ /* @__PURE__ */ jsx17("span", { className: "hitl-form-icon", children: "\u{1F4CB}" }),
2175
+ /* @__PURE__ */ jsx17("span", { className: "hitl-form-title", children: title })
2176
+ ] }),
2177
+ /* @__PURE__ */ jsx17("div", { className: "hitl-form-body", children: /* @__PURE__ */ jsx17(
2178
+ SchemaFormRenderer_default,
2179
+ {
2180
+ schema: formSchema,
2181
+ onSubmit: handleFormSubmit,
2182
+ awaitCommandUuid: hitlRequest?.await_command_uuid
2183
+ }
2184
+ ) })
2185
+ ] });
2186
+ }
2187
+ return /* @__PURE__ */ jsxs17("div", { className: "hitl-wait-card", children: [
2188
+ /* @__PURE__ */ jsx17("div", { className: "hitl-icon", children: "\u23F3" }),
2189
+ /* @__PURE__ */ jsxs17("div", { className: "hitl-content", children: [
2190
+ /* @__PURE__ */ jsx17("div", { className: "hitl-text", children: displayText }),
2191
+ hitlRequest?.await_command_uuid && /* @__PURE__ */ jsxs17("div", { className: "hitl-uuid", children: [
2192
+ "ID: ",
2193
+ hitlRequest.await_command_uuid.slice(0, 8),
2194
+ "..."
2195
+ ] })
2196
+ ] })
2197
+ ] });
2198
+ };
2199
+ var ArtifactCard = ({ artifact, onClick }) => {
2200
+ const iconMap = {
2201
+ "text/markdown": "\u{1F4DD}",
2202
+ "text/plain": "\u{1F4C4}",
2203
+ "application/json": "\u{1F4CA}",
2204
+ "image/": "\u{1F5BC}\uFE0F",
2205
+ "application/pdf": "\u{1F4D5}"
2206
+ };
2207
+ const icon = Object.entries(iconMap).find(
2208
+ ([type]) => artifact.type.startsWith(type)
2209
+ )?.[1] || "\u{1F4CE}";
2210
+ return /* @__PURE__ */ jsxs17("div", { className: "artifact-card", onClick, children: [
2211
+ /* @__PURE__ */ jsx17("span", { className: "artifact-icon", children: icon }),
2212
+ /* @__PURE__ */ jsxs17("div", { className: "artifact-info", children: [
2213
+ /* @__PURE__ */ jsx17("div", { className: "artifact-name", children: artifact.name }),
2214
+ /* @__PURE__ */ jsx17("div", { className: "artifact-meta", children: artifact.type })
2215
+ ] }),
2216
+ artifact.size && /* @__PURE__ */ jsx17("span", { className: "artifact-size", children: artifact.size })
2217
+ ] });
2218
+ };
2219
+ var MessageContent_default = MessageContent;
2220
+
2221
+ // src/components/ChildAgentCard.tsx
2222
+ import { useState as useState10, useRef as useRef5, useEffect as useEffect3 } from "react";
2223
+ import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
2224
+ var ChildAgentCard = ({
2225
+ message,
2226
+ children,
2227
+ maxHeight = 200,
2228
+ config,
2229
+ onArtifactClick,
2230
+ defaultExpanded = false,
2231
+ parentTaskPurpose
2232
+ }) => {
2233
+ const [isExpanded, setIsExpanded] = useState10(defaultExpanded);
2234
+ const contentRef = useRef5(null);
2235
+ const hasActiveContent = children.some(
2236
+ (m) => m.contentType === "text" && m.contentChunks.join("").trim()
2237
+ );
2238
+ const status = hasActiveContent ? "completed" : "running";
2239
+ useEffect3(() => {
2240
+ if (isExpanded && contentRef.current) {
2241
+ contentRef.current.scrollTop = contentRef.current.scrollHeight;
2242
+ }
2243
+ }, [isExpanded, children]);
2244
+ const cardTitle = parentTaskPurpose || message.taskPurpose || message.agentName;
2245
+ return /* @__PURE__ */ jsxs18("div", { className: `child-agent-card ${isExpanded ? "expanded" : ""}`, children: [
2246
+ /* @__PURE__ */ jsxs18(
2247
+ "div",
2248
+ {
2249
+ className: "child-agent-header",
2250
+ onClick: () => setIsExpanded(!isExpanded),
2251
+ children: [
2252
+ /* @__PURE__ */ jsx18("div", { className: "child-agent-title", children: /* @__PURE__ */ jsx18("span", { className: "card-title", children: cardTitle }) }),
2253
+ /* @__PURE__ */ jsxs18("div", { className: "child-agent-status", children: [
2254
+ /* @__PURE__ */ jsxs18("span", { className: `status-badge status-${status}`, children: [
2255
+ /* @__PURE__ */ jsx18("span", { className: `status-icon ${status === "running" ? "spinning" : ""}`, children: status === "running" ? "\u23F3" : "\u2713" }),
2256
+ /* @__PURE__ */ jsx18("span", { className: "status-text", children: status === "running" ? "\u6267\u884C\u4E2D" : "\u5DF2\u5B8C\u6210" })
2257
+ ] }),
2258
+ /* @__PURE__ */ jsx18("span", { className: `collapse-arrow ${isExpanded ? "expanded" : ""}`, children: "\u25BC" })
2259
+ ] })
2260
+ ]
2261
+ }
2262
+ ),
2263
+ isExpanded && /* @__PURE__ */ jsxs18(
2264
+ "div",
2265
+ {
2266
+ className: "child-agent-content",
2267
+ ref: contentRef,
2268
+ style: { maxHeight },
2269
+ children: [
2270
+ /* @__PURE__ */ jsxs18("div", { className: "child-agent-inner", children: [
2271
+ message.contentChunks.length > 0 && /* @__PURE__ */ jsx18(
2272
+ MessageContent_default,
2273
+ {
2274
+ message,
2275
+ enableTypewriter: config?.enableTypewriter,
2276
+ typewriterSpeed: config?.typewriterSpeed,
2277
+ onArtifactClick
2278
+ },
2279
+ message.id
2280
+ ),
2281
+ mergeConsecutiveThinkMessages(children).map((childMsg) => /* @__PURE__ */ jsx18(
2282
+ MessageContent_default,
2283
+ {
2284
+ message: childMsg,
2285
+ enableTypewriter: config?.enableTypewriter,
2286
+ typewriterSpeed: config?.typewriterSpeed,
2287
+ onArtifactClick
2288
+ },
2289
+ childMsg.id
2290
+ ))
2291
+ ] }),
2292
+ /* @__PURE__ */ jsxs18("div", { className: "child-agent-footer", children: [
2293
+ /* @__PURE__ */ jsx18("span", { className: "footer-agent-icon", children: "\u{1F916}" }),
2294
+ /* @__PURE__ */ jsx18("span", { className: "footer-agent-name", children: message.agentName })
2295
+ ] })
2296
+ ]
2297
+ }
2298
+ )
2299
+ ] });
2300
+ };
2301
+ var ChildAgentCard_default = ChildAgentCard;
2302
+
2303
+ // src/ChatWidget.tsx
2304
+ import { jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
2305
+ var HYSTERESIS_THRESHOLD = 30;
2306
+ var DEBOUNCE_DELAY = 150;
2307
+ var FADE_DURATION = 150;
2308
+ function convertTurnsToRounds(turns) {
2309
+ const groups = /* @__PURE__ */ new Map();
2310
+ for (const t of turns) {
2311
+ const iid = t.interaction_id;
2312
+ if (!groups.has(iid)) groups.set(iid, []);
2313
+ groups.get(iid).push(t);
2314
+ }
2315
+ const rounds = [];
2316
+ let roundIdx = 0;
2317
+ for (const [interactionId, group] of groups) {
2318
+ const userTurn = group.find((t) => t.type === "user_input");
2319
+ const userText = userTurn ? (userTurn.contents || []).map((c) => c.content || "").join("") : "(\u5386\u53F2\u6D88\u606F)";
2320
+ const agentMessages = [];
2321
+ for (const turn of group) {
2322
+ if (turn.type === "user_input") continue;
2323
+ const contents = turn.contents || [];
2324
+ if (contents.length === 0 && !turn.type.startsWith("mcp_")) continue;
2325
+ const ext = turn.ext || {};
2326
+ if (contents.length > 0) {
2327
+ for (let i = 0; i < contents.length; i++) {
2328
+ const part = contents[i];
2329
+ const partType = part.type || "text";
2330
+ let contentType = "text";
2331
+ if (partType === "think") contentType = "think";
2332
+ else if (partType === "plan") contentType = "plan";
2333
+ else if (partType === "artifact") contentType = "artifact";
2334
+ else if (partType === "json_schema") contentType = "json_schema";
2335
+ else if (partType === "html_schema") contentType = "html_schema";
2336
+ agentMessages.push({
2337
+ id: `hist-${turn.turn_id}-${i}`,
2338
+ agentInstanceId: turn.agent_instance_id || 0,
2339
+ callBatchId: String(turn.call_batch_id || 0),
2340
+ parentCallBatchId: turn.parent_call_batch_id != null ? String(turn.parent_call_batch_id) : void 0,
2341
+ level: turn.level ?? 0,
2342
+ agentName: turn.card_title || turn.agent_id || "Assistant",
2343
+ notificationType: turn.type,
2344
+ toolName: ext.tool_name,
2345
+ toolCallId: ext.tool_call_id,
2346
+ toolStatus: ext.status,
2347
+ taskPurpose: ext.task_purpose || ext._task_purpose,
2348
+ uiConfig: ext.ui_config,
2349
+ parentToolCallId: ext.parent_tool_call_id,
2350
+ contentType,
2351
+ contentChunks: [part.content || ""],
2352
+ timestamp: String(turn.timestamp || ""),
2353
+ toolPhase: turn.type.startsWith("mcp_") ? "complete" : void 0
2354
+ });
2355
+ }
2356
+ } else {
2357
+ agentMessages.push({
2358
+ id: `hist-${turn.turn_id}`,
2359
+ agentInstanceId: turn.agent_instance_id || 0,
2360
+ callBatchId: String(turn.call_batch_id || 0),
2361
+ parentCallBatchId: turn.parent_call_batch_id != null ? String(turn.parent_call_batch_id) : void 0,
2362
+ level: turn.level ?? 0,
2363
+ agentName: turn.card_title || turn.agent_id || "Assistant",
2364
+ notificationType: turn.type,
2365
+ toolName: ext.tool_name,
2366
+ toolCallId: ext.tool_call_id,
2367
+ toolStatus: ext.status,
2368
+ taskPurpose: ext.task_purpose || ext._task_purpose,
2369
+ uiConfig: ext.ui_config,
2370
+ parentToolCallId: ext.parent_tool_call_id,
2371
+ contentType: "text",
2372
+ contentChunks: [],
2373
+ timestamp: String(turn.timestamp || ""),
2374
+ toolPhase: "complete"
2375
+ });
2376
+ }
2377
+ }
2378
+ roundIdx++;
2379
+ const ts = group[0]?.timestamp_start || group[0]?.timestamp;
2380
+ rounds.push({
2381
+ interactionId,
2382
+ index: roundIdx,
2383
+ userMessage: userText,
2384
+ timestamp: ts ? new Date(ts).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
2385
+ status: "completed",
2386
+ messages: agentMessages
2387
+ });
2388
+ }
2389
+ return rounds;
2390
+ }
2391
+ var ChatWidget = ({
2392
+ adapter,
2393
+ sessionId: propSessionId,
2394
+ initialMessages: _initialMessages,
2395
+ config: userConfig,
2396
+ renderHeaderExtra,
2397
+ renderFooter,
2398
+ onArtifactClick,
2399
+ onHITLSubmit: _onHITLSubmit,
2400
+ onStatusChange,
2401
+ onError,
2402
+ onReady,
2403
+ className,
2404
+ style,
2405
+ height = 600
2406
+ }) => {
2407
+ const config = useMemo5(() => ({
2408
+ ...DEFAULT_CONFIG,
2409
+ ...userConfig
2410
+ }), [userConfig]);
2411
+ const {
2412
+ state: aggregatorState,
2413
+ todoMap,
2414
+ processMessage,
2415
+ getRoundsList,
2416
+ startNewRound,
2417
+ loadRounds,
2418
+ finalizeRound
2419
+ } = useMessageAggregator();
2420
+ const [pinnedInteractionId, setPinnedInteractionId] = useState11(null);
2421
+ const [isTransitioning, setIsTransitioning] = useState11(false);
2422
+ const [executionStatus, setExecutionStatus] = useState11("idle");
2423
+ const [currentSessionId, setCurrentSessionId] = useState11(null);
2424
+ const [sessionTitle, setSessionTitle] = useState11("");
2425
+ const [displayedTitle, setDisplayedTitle] = useState11("");
2426
+ const [isTitleAnimating, setIsTitleAnimating] = useState11(false);
2427
+ const sessionTitleFetchedRef = useRef6(false);
2428
+ const agentInstanceMapRef = useRef6(/* @__PURE__ */ new Map());
2429
+ const currentAgentIdRef = useRef6("agent-666");
2430
+ const historyLoadedRef = useRef6(false);
2431
+ useEffect4(() => {
2432
+ if (!propSessionId) return;
2433
+ const numericId = typeof propSessionId === "string" ? parseInt(propSessionId, 10) : propSessionId;
2434
+ if (isNaN(numericId)) return;
2435
+ setCurrentSessionId(numericId);
2436
+ if (historyLoadedRef.current) return;
2437
+ historyLoadedRef.current = true;
2438
+ const loadHistory = async () => {
2439
+ try {
2440
+ console.log("[ChatWidget] Loading session history for:", numericId);
2441
+ const [turns, detail] = await Promise.all([
2442
+ adapter.getSessionHistory(numericId),
2443
+ adapter.getSessionDetail(numericId).catch(() => null)
2444
+ ]);
2445
+ if (detail?.session_title) {
2446
+ setSessionTitle(detail.session_title);
2447
+ sessionTitleFetchedRef.current = true;
2448
+ }
2449
+ if (!turns || turns.length === 0) return;
2450
+ for (const t of turns) {
2451
+ if ((t.level === 0 || t.level == null) && t.agent_instance_id && t.agent_id) {
2452
+ agentInstanceMapRef.current.set(t.agent_id, t.agent_instance_id);
2453
+ }
2454
+ }
2455
+ if (agentInstanceMapRef.current.size > 0) {
2456
+ console.log("[ChatWidget] Restored agent instance map from history:", Object.fromEntries(agentInstanceMapRef.current));
2457
+ }
2458
+ const rounds2 = convertTurnsToRounds(turns);
2459
+ if (rounds2.length > 0) {
2460
+ loadRounds(rounds2);
2461
+ setExecutionStatus("completed");
2462
+ setPinnedInteractionId(rounds2[rounds2.length - 1].interactionId);
2463
+ }
2464
+ console.log("[ChatWidget] Loaded", rounds2.length, "history rounds from", turns.length, "turns");
2465
+ } catch (err) {
2466
+ console.warn("[ChatWidget] Failed to load session history:", err);
2467
+ }
2468
+ };
2469
+ loadHistory();
2470
+ }, [propSessionId, loadRounds]);
2471
+ const handleSSEMessage = useCallback5((message) => {
2472
+ if (message.type === "session_info" && message.session_id) {
2473
+ setCurrentSessionId(message.session_id);
2474
+ sessionTitleFetchedRef.current = false;
2475
+ return;
2476
+ }
2477
+ if (message.notification_type === "session_title" && message.content) {
2478
+ console.log("[ChatWidget] Received session_title:", message.content);
2479
+ setSessionTitle(message.content);
2480
+ sessionTitleFetchedRef.current = true;
2481
+ return;
2482
+ }
2483
+ const msgType = message.type || message.notification_type;
2484
+ if (msgType === "compression_started") {
2485
+ setExecutionStatus("compressing");
2486
+ return;
2487
+ }
2488
+ if (msgType === "compression_completed") {
2489
+ setExecutionStatus("running");
2490
+ return;
2491
+ }
2492
+ if (message.type === "finish") {
2493
+ const iid = message.interaction_id || aggregatorState.currentInteractionId;
2494
+ if (iid) {
2495
+ finalizeRound(iid);
2496
+ }
2497
+ setExecutionStatus("completed");
2498
+ return;
2499
+ }
2500
+ if (message.type === "error") {
2501
+ setExecutionStatus("error");
2502
+ onError?.(new Error(message.content || "Unknown error"));
2503
+ return;
2504
+ }
2505
+ if (message.interaction_id && message.content_type) {
2506
+ if (message.level === 0 && message.agent_instance_id) {
2507
+ const prevId = agentInstanceMapRef.current.get(currentAgentIdRef.current);
2508
+ if (prevId !== message.agent_instance_id) {
2509
+ agentInstanceMapRef.current.set(currentAgentIdRef.current, message.agent_instance_id);
2510
+ console.log("[ChatWidget] Captured level-0 agent instance:", currentAgentIdRef.current, "\u2192", message.agent_instance_id);
2511
+ }
2512
+ }
2513
+ processMessage(message);
2514
+ }
2515
+ }, [processMessage, onError, finalizeRound, aggregatorState.currentInteractionId]);
2516
+ const { status: connectionStatus, connect, disconnect: _disconnect } = useSSE({
2517
+ adapter,
2518
+ onMessage: handleSSEMessage,
2519
+ onError
2520
+ });
2521
+ const [pendingScrollInteractionId, setPendingScrollInteractionId] = useState11(null);
2522
+ const sendMessage = useCallback5((message, agentId = "agent-666") => {
2523
+ const interactionId = crypto.randomUUID();
2524
+ console.log("====== [sendMessage v2] \u53D1\u9001\u65B0\u6D88\u606F ======", { interactionId, message });
2525
+ currentAgentIdRef.current = agentId;
2526
+ sessionTitleFetchedRef.current = false;
2527
+ startNewRound(interactionId, message);
2528
+ console.log("[sendMessage] \u5207\u6362\u7F6E\u9876\u5230:", interactionId);
2529
+ setPinnedInteractionId(interactionId);
2530
+ isUserAtBottomRef.current = true;
2531
+ setPendingScrollInteractionId(interactionId);
2532
+ setExecutionStatus("running");
2533
+ const knownInstanceId = agentInstanceMapRef.current.get(agentId);
2534
+ if (knownInstanceId) {
2535
+ console.log("[sendMessage] \u590D\u7528 agent \u5B9E\u4F8B:", agentId, "\u2192", knownInstanceId);
2536
+ }
2537
+ connect(adapter.getStreamUrl(), {
2538
+ message,
2539
+ agent_id: agentId,
2540
+ agent_instance_id: knownInstanceId,
2541
+ session_id: currentSessionId,
2542
+ interaction_id: interactionId
2543
+ });
2544
+ }, [adapter, currentSessionId, startNewRound, connect]);
2545
+ const messageListRef = useRef6(null);
2546
+ const scrollDebounceTimer = useRef6(null);
2547
+ const lastScrollTop = useRef6(0);
2548
+ const isUserAtBottomRef = useRef6(true);
2549
+ const isProgrammaticScrollRef = useRef6(false);
2550
+ const clearScreenScrollTopRef = useRef6(null);
2551
+ const rounds = useMemo5(() => getRoundsList(), [getRoundsList, aggregatorState]);
2552
+ const getCurrentRound = useCallback5(() => {
2553
+ if (rounds.length === 0) return null;
2554
+ return rounds[rounds.length - 1];
2555
+ }, [rounds]);
2556
+ const pinnedRound = useMemo5(() => {
2557
+ if (!pinnedInteractionId && rounds.length > 0) {
2558
+ return rounds[rounds.length - 1];
2559
+ }
2560
+ return rounds.find((r) => r.interactionId === pinnedInteractionId) || null;
2561
+ }, [rounds, pinnedInteractionId]);
2562
+ const calculatePinnedRound = useCallback5(() => {
2563
+ if (!messageListRef.current || rounds.length === 0) {
2564
+ return rounds[rounds.length - 1]?.interactionId || null;
2565
+ }
2566
+ const listRect = messageListRef.current.getBoundingClientRect();
2567
+ const viewportTop = listRect.top;
2568
+ for (let i = rounds.length - 1; i >= 0; i--) {
2569
+ const round = rounds[i];
2570
+ const container = messageListRef.current.querySelector(
2571
+ `[data-interaction-id="${round.interactionId}"]`
2572
+ );
2573
+ if (!container) continue;
2574
+ const rect = container.getBoundingClientRect();
2575
+ if (rect.top <= viewportTop + 50) {
2576
+ return round.interactionId;
2577
+ }
2578
+ if (i > 0) {
2579
+ if (rect.top <= viewportTop + 100) {
2580
+ return rounds[i - 1].interactionId;
2581
+ }
2582
+ }
2583
+ }
2584
+ return rounds[0]?.interactionId || null;
2585
+ }, [rounds]);
2586
+ const updatePinnedArea = useCallback5((interactionId) => {
2587
+ if (isTransitioning || interactionId === pinnedInteractionId) return;
2588
+ setIsTransitioning(true);
2589
+ setTimeout(() => {
2590
+ setPinnedInteractionId(interactionId);
2591
+ setTimeout(() => {
2592
+ setIsTransitioning(false);
2593
+ }, FADE_DURATION);
2594
+ }, FADE_DURATION);
2595
+ }, [isTransitioning, pinnedInteractionId]);
2596
+ const handleScroll = useCallback5((e) => {
2597
+ const target = e.currentTarget;
2598
+ const currentScrollTop = target.scrollTop;
2599
+ const scrollDelta = Math.abs(currentScrollTop - lastScrollTop.current);
2600
+ if (isProgrammaticScrollRef.current) {
2601
+ lastScrollTop.current = currentScrollTop;
2602
+ return;
2603
+ }
2604
+ const scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight;
2605
+ isUserAtBottomRef.current = scrollBottom < 50;
2606
+ if (scrollBottom < 50 && clearScreenScrollTopRef.current !== null) {
2607
+ clearScreenScrollTopRef.current = null;
2608
+ }
2609
+ if (scrollDebounceTimer.current) {
2610
+ clearTimeout(scrollDebounceTimer.current);
2611
+ }
2612
+ const newPinnedRound = calculatePinnedRound();
2613
+ if (isTransitioning) {
2614
+ lastScrollTop.current = currentScrollTop;
2615
+ return;
2616
+ }
2617
+ if (newPinnedRound !== pinnedInteractionId) {
2618
+ if (scrollDelta >= HYSTERESIS_THRESHOLD) {
2619
+ updatePinnedArea(newPinnedRound);
2620
+ lastScrollTop.current = currentScrollTop;
2621
+ } else {
2622
+ scrollDebounceTimer.current = window.setTimeout(() => {
2623
+ if (!isTransitioning && !isProgrammaticScrollRef.current) {
2624
+ const confirmedRound = calculatePinnedRound();
2625
+ if (confirmedRound !== pinnedInteractionId) {
2626
+ updatePinnedArea(confirmedRound);
2627
+ }
2628
+ }
2629
+ lastScrollTop.current = target.scrollTop;
2630
+ }, DEBOUNCE_DELAY);
2631
+ }
2632
+ } else {
2633
+ lastScrollTop.current = currentScrollTop;
2634
+ }
2635
+ }, [calculatePinnedRound, updatePinnedArea, isTransitioning, pinnedInteractionId]);
2636
+ const organizeMessagesByLevel = useCallback5((messages) => {
2637
+ const rootMessages = [];
2638
+ const childrenMap = /* @__PURE__ */ new Map();
2639
+ const isChildMessage = (msg) => {
2640
+ const pcb = msg.parentCallBatchId;
2641
+ if (pcb == null || pcb === "" || pcb === "-1") return false;
2642
+ const pcbNum = Number(pcb);
2643
+ if (pcbNum <= 0 || isNaN(pcbNum)) return false;
2644
+ return true;
2645
+ };
2646
+ messages.forEach((msg) => {
2647
+ if (isChildMessage(msg)) {
2648
+ const key = msg.parentToolCallId || String(msg.parentCallBatchId);
2649
+ const children = childrenMap.get(key) || [];
2650
+ children.push(msg);
2651
+ childrenMap.set(key, children);
2652
+ } else {
2653
+ rootMessages.push(msg);
2654
+ }
2655
+ });
2656
+ return { rootMessages, childrenMap };
2657
+ }, []);
2658
+ const [viewerOpen, setViewerOpen] = useState11(false);
2659
+ const [viewerLoading, setViewerLoading] = useState11(false);
2660
+ const [viewerContent, setViewerContent] = useState11(null);
2661
+ const [viewerError, setViewerError] = useState11(null);
2662
+ const [viewerArtifact, setViewerArtifact] = useState11(null);
2663
+ const handleArtifactClick = useCallback5((artifact) => {
2664
+ onArtifactClick?.(artifact);
2665
+ if (!currentSessionId) return;
2666
+ setViewerArtifact(artifact);
2667
+ setViewerOpen(true);
2668
+ setViewerLoading(true);
2669
+ setViewerContent(null);
2670
+ setViewerError(null);
2671
+ adapter.getResourceContent(currentSessionId, artifact.id).then((data) => {
2672
+ setViewerContent(data);
2673
+ setViewerLoading(false);
2674
+ }).catch((err) => {
2675
+ console.error("[ChatWidget] Failed to fetch resource content:", err);
2676
+ setViewerError(err?.message || "\u65E0\u6CD5\u83B7\u53D6\u6587\u4EF6\u5185\u5BB9");
2677
+ setViewerLoading(false);
2678
+ });
2679
+ }, [onArtifactClick, currentSessionId]);
2680
+ const renderRoundMessages = useCallback5((round) => {
2681
+ const { rootMessages: rawRootMessages, childrenMap } = organizeMessagesByLevel(round.messages);
2682
+ const rootMessages = mergeConsecutiveThinkMessages(rawRootMessages);
2683
+ const elements = [];
2684
+ const renderedChildKeys = /* @__PURE__ */ new Set();
2685
+ const isCallAgentToolCard = (msg) => {
2686
+ if (!msg.toolPhase) return false;
2687
+ const name = (msg.toolName || "").toLowerCase();
2688
+ return name.includes("call_agent") || name.includes("callagent");
2689
+ };
2690
+ rootMessages.forEach((msg) => {
2691
+ if (isCallAgentToolCard(msg)) {
2692
+ const childKey = msg.toolCallId && childrenMap.has(msg.toolCallId) ? msg.toolCallId : childrenMap.has(String(msg.callBatchId)) ? String(msg.callBatchId) : null;
2693
+ if (childKey && !renderedChildKeys.has(childKey)) {
2694
+ renderedChildKeys.add(childKey);
2695
+ const children = childrenMap.get(childKey);
2696
+ const childGroups = /* @__PURE__ */ new Map();
2697
+ children.forEach((child) => {
2698
+ const group = childGroups.get(child.agentInstanceId) || [];
2699
+ group.push(child);
2700
+ childGroups.set(child.agentInstanceId, group);
2701
+ });
2702
+ childGroups.forEach((groupChildren, agentInstanceId) => {
2703
+ const firstChild = groupChildren[0];
2704
+ elements.push(
2705
+ /* @__PURE__ */ jsx19(
2706
+ ChildAgentCard_default,
2707
+ {
2708
+ message: firstChild,
2709
+ children: groupChildren.slice(1),
2710
+ maxHeight: config.childAgentMaxHeight,
2711
+ config,
2712
+ onArtifactClick: handleArtifactClick,
2713
+ defaultExpanded: true,
2714
+ parentTaskPurpose: msg.taskPurpose
2715
+ },
2716
+ `child-${childKey}-${agentInstanceId}`
2717
+ )
2718
+ );
2719
+ });
2720
+ return;
2721
+ }
2722
+ }
2723
+ elements.push(
2724
+ /* @__PURE__ */ jsx19(
2725
+ MessageContent_default,
2726
+ {
2727
+ message: msg,
2728
+ enableTypewriter: config.enableTypewriter,
2729
+ typewriterSpeed: config.typewriterSpeed,
2730
+ onArtifactClick: handleArtifactClick
2731
+ },
2732
+ msg.id
2733
+ )
2734
+ );
2735
+ });
2736
+ return elements;
2737
+ }, [organizeMessagesByLevel, config, handleArtifactClick]);
2738
+ useEffect4(() => {
2739
+ if (rounds.length > 0 && !pinnedInteractionId) {
2740
+ setPinnedInteractionId(rounds[rounds.length - 1].interactionId);
2741
+ }
2742
+ }, [rounds, pinnedInteractionId]);
2743
+ const [containerHeight, setContainerHeight] = useState11(0);
2744
+ const [roundHeights, setRoundHeights] = useState11(/* @__PURE__ */ new Map());
2745
+ useEffect4(() => {
2746
+ if (!messageListRef.current) return;
2747
+ const updateHeight = () => {
2748
+ if (messageListRef.current) {
2749
+ setContainerHeight(messageListRef.current.clientHeight);
2750
+ }
2751
+ };
2752
+ updateHeight();
2753
+ window.addEventListener("resize", updateHeight);
2754
+ return () => window.removeEventListener("resize", updateHeight);
2755
+ }, []);
2756
+ useEffect4(() => {
2757
+ if (!messageListRef.current) return;
2758
+ const roundContainers = messageListRef.current.querySelectorAll(".round-container");
2759
+ if (roundContainers.length === 0) return;
2760
+ const resizeObserver = new ResizeObserver((entries) => {
2761
+ const newHeights = new Map(roundHeights);
2762
+ let changed = false;
2763
+ entries.forEach((entry) => {
2764
+ const interactionId = entry.target.dataset.interactionId;
2765
+ if (interactionId) {
2766
+ const height2 = entry.contentRect.height;
2767
+ if (newHeights.get(interactionId) !== height2) {
2768
+ newHeights.set(interactionId, height2);
2769
+ changed = true;
2770
+ }
2771
+ }
2772
+ });
2773
+ if (changed) {
2774
+ setRoundHeights(newHeights);
2775
+ }
2776
+ });
2777
+ roundContainers.forEach((container) => {
2778
+ resizeObserver.observe(container);
2779
+ });
2780
+ return () => resizeObserver.disconnect();
2781
+ }, [rounds.length]);
2782
+ const getSpacerHeight = useCallback5((interactionId) => {
2783
+ const roundHeight = roundHeights.get(interactionId) || 0;
2784
+ return Math.max(0, containerHeight - roundHeight);
2785
+ }, [containerHeight, roundHeights]);
2786
+ const processedScrollRef = useRef6(null);
2787
+ useEffect4(() => {
2788
+ if (!pendingScrollInteractionId || !messageListRef.current) return;
2789
+ if (processedScrollRef.current === pendingScrollInteractionId) return;
2790
+ const roundExists = rounds.some((r) => r.interactionId === pendingScrollInteractionId);
2791
+ if (!roundExists) return;
2792
+ const targetInteractionId = pendingScrollInteractionId;
2793
+ processedScrollRef.current = targetInteractionId;
2794
+ setPendingScrollInteractionId(null);
2795
+ isProgrammaticScrollRef.current = true;
2796
+ clearScreenScrollTopRef.current = 0;
2797
+ const scrollToRoundTop = () => {
2798
+ if (!messageListRef.current) return;
2799
+ const container = messageListRef.current;
2800
+ const roundElement = container.querySelector(
2801
+ `[data-interaction-id="${targetInteractionId}"]`
2802
+ );
2803
+ if (roundElement) {
2804
+ const targetScrollTop = roundElement.offsetTop - 16 + 20;
2805
+ container.scrollTop = Math.max(0, targetScrollTop);
2806
+ clearScreenScrollTopRef.current = container.scrollTop;
2807
+ }
2808
+ };
2809
+ requestAnimationFrame(scrollToRoundTop);
2810
+ setTimeout(() => {
2811
+ isProgrammaticScrollRef.current = false;
2812
+ }, 200);
2813
+ }, [rounds, pendingScrollInteractionId]);
2814
+ useEffect4(() => {
2815
+ if (isProgrammaticScrollRef.current) return;
2816
+ if (!messageListRef.current || executionStatus !== "running" && executionStatus !== "compressing") return;
2817
+ requestAnimationFrame(() => {
2818
+ if (!messageListRef.current || isProgrammaticScrollRef.current) return;
2819
+ const container = messageListRef.current;
2820
+ const maxScrollTop = container.scrollHeight - container.clientHeight;
2821
+ const currentScrollTop = container.scrollTop;
2822
+ if (clearScreenScrollTopRef.current !== null) {
2823
+ const roundContainers = container.querySelectorAll(".round-container");
2824
+ const currentRoundElement = roundContainers[roundContainers.length - 1];
2825
+ if (currentRoundElement) {
2826
+ const roundBottom = currentRoundElement.offsetTop + currentRoundElement.offsetHeight;
2827
+ const visibleBottom = currentScrollTop + container.clientHeight;
2828
+ if (roundBottom > visibleBottom) {
2829
+ const targetScrollTop = roundBottom - container.clientHeight;
2830
+ const finalScrollTop = Math.max(targetScrollTop, clearScreenScrollTopRef.current);
2831
+ container.scrollTop = finalScrollTop;
2832
+ }
2833
+ }
2834
+ return;
2835
+ }
2836
+ if (isUserAtBottomRef.current) {
2837
+ container.scrollTop = maxScrollTop;
2838
+ }
2839
+ });
2840
+ }, [aggregatorState, executionStatus]);
2841
+ const scrollToBottom = useCallback5(() => {
2842
+ if (!messageListRef.current) return;
2843
+ messageListRef.current.scrollTop = messageListRef.current.scrollHeight;
2844
+ }, []);
2845
+ const getStatus = useCallback5(() => executionStatus, [executionStatus]);
2846
+ const widgetAPI = useMemo5(() => ({
2847
+ sendMessage,
2848
+ getCurrentRound,
2849
+ getAllRounds: getRoundsList,
2850
+ scrollToBottom,
2851
+ getStatus
2852
+ }), [sendMessage, getCurrentRound, getRoundsList, scrollToBottom, getStatus]);
2853
+ useEffect4(() => {
2854
+ onReady?.(widgetAPI);
2855
+ }, [onReady, widgetAPI]);
2856
+ useEffect4(() => {
2857
+ onStatusChange?.(executionStatus);
2858
+ }, [executionStatus, onStatusChange]);
2859
+ useEffect4(() => {
2860
+ if (!sessionTitle) {
2861
+ setDisplayedTitle("");
2862
+ setIsTitleAnimating(false);
2863
+ return;
2864
+ }
2865
+ if (sessionTitle !== displayedTitle) {
2866
+ setIsTitleAnimating(true);
2867
+ let currentIndex = 0;
2868
+ const animateTitle = () => {
2869
+ if (currentIndex <= sessionTitle.length) {
2870
+ setDisplayedTitle(sessionTitle.slice(0, currentIndex));
2871
+ currentIndex++;
2872
+ requestAnimationFrame(() => {
2873
+ setTimeout(animateTitle, 50);
2874
+ });
2875
+ } else {
2876
+ setIsTitleAnimating(false);
2877
+ }
2878
+ };
2879
+ setDisplayedTitle("");
2880
+ requestAnimationFrame(animateTitle);
2881
+ }
2882
+ }, [sessionTitle]);
2883
+ const fetchSessionTitle = useCallback5(async (sessionId) => {
2884
+ if (sessionTitleFetchedRef.current) return;
2885
+ try {
2886
+ console.log("[ChatWidget] Fetching session title for session:", sessionId);
2887
+ const detail = await adapter.getSessionDetail(sessionId);
2888
+ if (detail.session_title) {
2889
+ console.log("[ChatWidget] Fetched session title:", detail.session_title);
2890
+ setSessionTitle(detail.session_title);
2891
+ sessionTitleFetchedRef.current = true;
2892
+ }
2893
+ } catch (error) {
2894
+ console.warn("[ChatWidget] Failed to fetch session title:", error);
2895
+ }
2896
+ }, []);
2897
+ useEffect4(() => {
2898
+ if (executionStatus === "completed" && currentSessionId && !sessionTitle && !sessionTitleFetchedRef.current) {
2899
+ const timer = setTimeout(() => {
2900
+ fetchSessionTitle(currentSessionId);
2901
+ }, 500);
2902
+ return () => clearTimeout(timer);
2903
+ }
2904
+ }, [executionStatus, currentSessionId, sessionTitle, fetchSessionTitle]);
2905
+ useEffect4(() => {
2906
+ if (connectionStatus === "connected") {
2907
+ setExecutionStatus("running");
2908
+ } else if (connectionStatus === "disconnected") {
2909
+ setExecutionStatus("completed");
2910
+ } else if (connectionStatus === "error") {
2911
+ setExecutionStatus("error");
2912
+ }
2913
+ }, [connectionStatus]);
2914
+ useEffect4(() => {
2915
+ return () => {
2916
+ if (scrollDebounceTimer.current) {
2917
+ clearTimeout(scrollDebounceTimer.current);
2918
+ }
2919
+ };
2920
+ }, []);
2921
+ const getStatusDisplay = () => {
2922
+ if (executionStatus === "compressing") {
2923
+ return { icon: "\u{1F5DC}\uFE0F", text: "\u4E0A\u4E0B\u6587\u538B\u7F29\u4E2D..." };
2924
+ }
2925
+ switch (connectionStatus) {
2926
+ case "connecting":
2927
+ return { icon: "\u23F3", text: "\u8FDE\u63A5\u4E2D..." };
2928
+ case "connected":
2929
+ return { icon: "\u23F3", text: "\u6267\u884C\u4E2D..." };
2930
+ case "error":
2931
+ return { icon: "\u2717", text: "\u8FDE\u63A5\u9519\u8BEF" };
2932
+ default:
2933
+ return { icon: "\u25CB", text: "\u5F85\u547D" };
2934
+ }
2935
+ };
2936
+ const statusDisplay = getStatusDisplay();
2937
+ return /* @__PURE__ */ jsxs19(
2938
+ "div",
2939
+ {
2940
+ className: `chat-widget ${config.theme} ${className || ""}`,
2941
+ style: { ...style, height },
2942
+ children: [
2943
+ /* @__PURE__ */ jsxs19("div", { className: "chat-header", children: [
2944
+ /* @__PURE__ */ jsxs19("div", { className: "chat-header-title", children: [
2945
+ /* @__PURE__ */ jsx19("span", { className: "header-icon", children: "\u{1F916}" }),
2946
+ /* @__PURE__ */ jsx19("span", { children: "AI \u52A9\u624B" })
2947
+ ] }),
2948
+ displayedTitle && /* @__PURE__ */ jsxs19("div", { className: `session-title ${isTitleAnimating ? "animating" : ""}`, children: [
2949
+ /* @__PURE__ */ jsx19("span", { className: "session-title-text", children: displayedTitle }),
2950
+ isTitleAnimating && /* @__PURE__ */ jsx19("span", { className: "title-cursor", children: "|" })
2951
+ ] }),
2952
+ /* @__PURE__ */ jsxs19("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
2953
+ currentSessionId != null && renderHeaderExtra?.(currentSessionId),
2954
+ /* @__PURE__ */ jsxs19("div", { className: `status-badge status-${connectionStatus}`, children: [
2955
+ /* @__PURE__ */ jsx19("span", { className: "status-icon", children: statusDisplay.icon }),
2956
+ /* @__PURE__ */ jsx19("span", { children: statusDisplay.text })
2957
+ ] })
2958
+ ] })
2959
+ ] }),
2960
+ /* @__PURE__ */ jsxs19("div", { className: "message-list-container", children: [
2961
+ config.showPinnedArea && /* @__PURE__ */ jsx19(
2962
+ PinnedArea_default,
2963
+ {
2964
+ round: pinnedRound,
2965
+ isTransitioning,
2966
+ config,
2967
+ todoMap
2968
+ }
2969
+ ),
2970
+ /* @__PURE__ */ jsxs19(
2971
+ "div",
2972
+ {
2973
+ className: "message-list",
2974
+ ref: messageListRef,
2975
+ onScroll: handleScroll,
2976
+ children: [
2977
+ rounds.map((round, index) => /* @__PURE__ */ jsxs19(React19.Fragment, { children: [
2978
+ index > 0 && /* @__PURE__ */ jsx19("div", { className: "round-separator", children: "\u2191 \u4E0A\u4E00\u8F6E\u5BF9\u8BDD \u2191" }),
2979
+ /* @__PURE__ */ jsxs19(
2980
+ "div",
2981
+ {
2982
+ className: "round-container",
2983
+ "data-round": round.index,
2984
+ "data-interaction-id": round.interactionId,
2985
+ children: [
2986
+ /* @__PURE__ */ jsx19(
2987
+ RoundHeader_default,
2988
+ {
2989
+ round,
2990
+ isPinned: round.interactionId === pinnedInteractionId,
2991
+ config,
2992
+ todoMap
2993
+ }
2994
+ ),
2995
+ renderRoundMessages(round)
2996
+ ]
2997
+ }
2998
+ ),
2999
+ index > 0 && /* @__PURE__ */ jsx19(
3000
+ "div",
3001
+ {
3002
+ className: `clear-screen-spacer ${index < rounds.length - 1 || getSpacerHeight(round.interactionId) <= 0 ? "spacer-collapsed" : ""}`,
3003
+ style: { height: index < rounds.length - 1 ? 0 : Math.max(0, getSpacerHeight(round.interactionId)) },
3004
+ "data-spacer-for": round.interactionId
3005
+ }
3006
+ )
3007
+ ] }, round.interactionId)),
3008
+ rounds.length === 0 && /* @__PURE__ */ jsxs19("div", { className: "empty-state", children: [
3009
+ /* @__PURE__ */ jsx19("span", { className: "empty-icon", children: "\u{1F4AC}" }),
3010
+ /* @__PURE__ */ jsx19("span", { className: "empty-text", children: "\u7B49\u5F85\u5BF9\u8BDD\u5F00\u59CB..." })
3011
+ ] })
3012
+ ]
3013
+ }
3014
+ )
3015
+ ] }),
3016
+ /* @__PURE__ */ jsx19("div", { className: "chat-footer", children: renderFooter ? renderFooter(widgetAPI) : "\u7531 JetAgents \u9A71\u52A8" }),
3017
+ viewerOpen && /* @__PURE__ */ jsx19("div", { className: "artifact-viewer-overlay", onClick: () => setViewerOpen(false), children: /* @__PURE__ */ jsxs19("div", { className: "artifact-viewer-panel", onClick: (e) => e.stopPropagation(), children: [
3018
+ /* @__PURE__ */ jsxs19("div", { className: "artifact-viewer-header", children: [
3019
+ /* @__PURE__ */ jsxs19("div", { className: "artifact-viewer-title", children: [
3020
+ /* @__PURE__ */ jsx19("span", { className: "artifact-viewer-icon", children: "\u{1F4C4}" }),
3021
+ /* @__PURE__ */ jsx19("span", { children: viewerArtifact?.name || "\u6587\u4EF6\u5185\u5BB9" })
3022
+ ] }),
3023
+ /* @__PURE__ */ jsx19("button", { className: "artifact-viewer-close", onClick: () => setViewerOpen(false), children: "\u2715" })
3024
+ ] }),
3025
+ viewerContent && /* @__PURE__ */ jsxs19("div", { className: "artifact-viewer-meta", children: [
3026
+ /* @__PURE__ */ jsx19("span", { children: viewerContent.mime_type || "\u672A\u77E5\u7C7B\u578B" }),
3027
+ viewerContent.file_size != null && /* @__PURE__ */ jsx19("span", { children: viewerContent.file_size > 1024 ? `${(viewerContent.file_size / 1024).toFixed(1)} KB` : `${viewerContent.file_size} B` }),
3028
+ /* @__PURE__ */ jsx19("span", { className: "artifact-viewer-path", children: viewerContent.git_path })
3029
+ ] }),
3030
+ /* @__PURE__ */ jsxs19("div", { className: "artifact-viewer-body", children: [
3031
+ viewerLoading && /* @__PURE__ */ jsxs19("div", { className: "artifact-viewer-loading", children: [
3032
+ /* @__PURE__ */ jsx19("span", { className: "loading-spinner" }),
3033
+ /* @__PURE__ */ jsx19("span", { children: "\u52A0\u8F7D\u4E2D..." })
3034
+ ] }),
3035
+ viewerError && /* @__PURE__ */ jsx19("div", { className: "artifact-viewer-error", children: viewerError }),
3036
+ viewerContent && !viewerContent.is_binary && /* @__PURE__ */ jsx19("pre", { className: "artifact-viewer-content", children: viewerContent.content }),
3037
+ viewerContent?.is_binary && /* @__PURE__ */ jsx19("div", { className: "artifact-viewer-binary", children: "\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\u76F4\u63A5\u9884\u89C8" })
3038
+ ] })
3039
+ ] }) })
3040
+ ]
3041
+ }
3042
+ );
3043
+ };
3044
+ var ChatWidget_default = ChatWidget;
3045
+
3046
+ // src/errors.ts
3047
+ var ChatWidgetAuthError = class extends Error {
3048
+ name = "ChatWidgetAuthError";
3049
+ constructor(message = "Authentication failed") {
3050
+ super(message);
3051
+ }
3052
+ };
3053
+ var ChatWidgetNetworkError = class extends Error {
3054
+ constructor(message, statusCode) {
3055
+ super(message);
3056
+ this.statusCode = statusCode;
3057
+ }
3058
+ name = "ChatWidgetNetworkError";
3059
+ };
3060
+
3061
+ // src/adapters/DefaultJetAgentsAdapter.ts
3062
+ var DefaultJetAgentsAdapter = class {
3063
+ baseUrl;
3064
+ options;
3065
+ refreshPromise = null;
3066
+ constructor(options) {
3067
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
3068
+ this.options = options;
3069
+ }
3070
+ async createSession(agentId) {
3071
+ const res = await this.fetchJSON(
3072
+ "/api/session/create",
3073
+ {
3074
+ method: "POST",
3075
+ body: JSON.stringify({ agent_id: agentId, type: "user_input", contents: [{ type: "text", content: "" }] })
3076
+ }
3077
+ );
3078
+ return { sessionId: res.session_id, instanceId: res.user_caller_instance };
3079
+ }
3080
+ async getSessionDetail(sessionId) {
3081
+ const res = await this.fetchJSON(`/api/session/detail/${sessionId}`);
3082
+ return res;
3083
+ }
3084
+ async getSessionHistory(sessionId) {
3085
+ const res = await this.fetchJSON(`/api/session/messages/${sessionId}`);
3086
+ return res.turns ?? (Array.isArray(res) ? res : []);
3087
+ }
3088
+ async getResourceContent(sessionId, resourceId) {
3089
+ const res = await this.fetchJSON(`/api/workspace/resource/${sessionId}/${resourceId}/content`);
3090
+ return res.data ?? res;
3091
+ }
3092
+ async submitHITLResponse(response) {
3093
+ await this.fetchJSON("/api/human-response", {
3094
+ method: "POST",
3095
+ body: JSON.stringify(response)
3096
+ });
3097
+ }
3098
+ getStreamUrl() {
3099
+ return `${this.baseUrl}/api/session/send_message`;
3100
+ }
3101
+ getAuthHeaders() {
3102
+ const headers = {};
3103
+ const token = this.options.getToken();
3104
+ if (token) {
3105
+ headers["Authorization"] = `Bearer ${token}`;
3106
+ }
3107
+ const orgId = this.options.getOrgId?.();
3108
+ if (orgId) {
3109
+ headers["X-Org-Id"] = orgId;
3110
+ }
3111
+ return headers;
3112
+ }
3113
+ onAuthFailure() {
3114
+ this.options.onAuthFailure?.();
3115
+ }
3116
+ async refreshAuth() {
3117
+ if (!this.options.refreshToken) return false;
3118
+ if (this.refreshPromise) {
3119
+ return this.refreshPromise;
3120
+ }
3121
+ this.refreshPromise = this.options.refreshToken().then((token) => token !== null).finally(() => {
3122
+ this.refreshPromise = null;
3123
+ });
3124
+ return this.refreshPromise;
3125
+ }
3126
+ async fetchJSON(path, init) {
3127
+ const url = `${this.baseUrl}${path}`;
3128
+ const headers = {
3129
+ "Content-Type": "application/json",
3130
+ ...this.getAuthHeaders()
3131
+ };
3132
+ const response = await fetch(url, {
3133
+ ...init,
3134
+ headers: { ...headers, ...init?.headers }
3135
+ });
3136
+ if (response.status === 401) {
3137
+ const refreshed = await this.refreshAuth();
3138
+ if (refreshed) {
3139
+ const retryHeaders = {
3140
+ "Content-Type": "application/json",
3141
+ ...this.getAuthHeaders()
3142
+ };
3143
+ const retryResponse = await fetch(url, {
3144
+ ...init,
3145
+ headers: { ...retryHeaders, ...init?.headers }
3146
+ });
3147
+ if (retryResponse.ok) {
3148
+ return retryResponse.json();
3149
+ }
3150
+ }
3151
+ this.onAuthFailure();
3152
+ throw new ChatWidgetAuthError("Authentication failed");
3153
+ }
3154
+ if (!response.ok) {
3155
+ throw new ChatWidgetNetworkError(
3156
+ `Request failed: ${response.status} ${response.statusText}`,
3157
+ response.status
3158
+ );
3159
+ }
3160
+ return response.json();
3161
+ }
3162
+ };
3163
+ export {
3164
+ ChatWidget_default as ChatWidget,
3165
+ ChatWidgetAuthError,
3166
+ ChatWidgetNetworkError,
3167
+ ChildAgentCard_default as ChildAgentCard,
3168
+ DEFAULT_CONFIG,
3169
+ DefaultJetAgentsAdapter,
3170
+ MessageContent_default as MessageContent,
3171
+ PinnedArea_default as PinnedArea,
3172
+ PlanCard_default as PlanCard,
3173
+ RoundHeader_default as RoundHeader,
3174
+ SchemaFormRenderer_default as SchemaFormRenderer,
3175
+ TodoCard_default as TodoCard,
3176
+ ToolCardBuffering_default as ToolCardBuffering,
3177
+ isTodoNotificationContent,
3178
+ mergeConsecutiveThinkMessages,
3179
+ useMessageAggregator,
3180
+ useSSE
3181
+ };
3182
+ //# sourceMappingURL=index.js.map