@blade-hq/agent-react 2610.0.0-beta.4 → 2610.0.0-beta.41
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/README.md +82 -3
- package/dist/components/AgentLoopBlock.d.ts +1 -4
- package/dist/components/AskUserQuestionBlock.d.ts +9 -0
- package/dist/components/AssistantTurnBlock.d.ts +20 -6
- package/dist/components/ChatInput.d.ts +4 -1
- package/dist/components/ChatSurface.d.ts +12 -1
- package/dist/components/ConnectionBanner.d.ts +1 -1
- package/dist/components/ContextCard.d.ts +20 -0
- package/dist/components/MarkdownContent.d.ts +2 -0
- package/dist/components/MessageList.d.ts +8 -1
- package/dist/components/PlanUpdateBlock.d.ts +31 -0
- package/dist/components/SessionMemoryToggle.d.ts +19 -0
- package/dist/components/ToolCallBlock.d.ts +4 -2
- package/dist/components/UserMessageBubble.d.ts +1 -1
- package/dist/components/WhatIfUserBubble.d.ts +7 -0
- package/dist/components/display-utils.d.ts +10 -0
- package/dist/context.d.ts +1 -0
- package/dist/embed/entry.d.ts +24 -0
- package/dist/hooks/use-agent-session.d.ts +5 -0
- package/dist/hooks/use-message-pin.d.ts +22 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +2567 -857
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-computer-command.d.ts +34 -0
- package/dist/lib/utils.d.ts +3 -1
- package/dist/lib/whatif-prompt.d.ts +18 -0
- package/dist/style.css +91 -1
- package/dist/style.full.css +92 -2
- package/package.json +2 -2
- package/public-api.md +527 -7
package/dist/index.js
CHANGED
|
@@ -17,6 +17,9 @@ function useBladeClient() {
|
|
|
17
17
|
}
|
|
18
18
|
return client;
|
|
19
19
|
}
|
|
20
|
+
function useOptionalBladeClient() {
|
|
21
|
+
return useContext(BladeClientContext);
|
|
22
|
+
}
|
|
20
23
|
|
|
21
24
|
// src/hooks/use-agent-session.ts
|
|
22
25
|
import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
@@ -30,11 +33,14 @@ function useAgentSession(sessionId, options = {}) {
|
|
|
30
33
|
const connRef = useRef({
|
|
31
34
|
id: null,
|
|
32
35
|
session: null,
|
|
36
|
+
cleanup: null,
|
|
33
37
|
gen: 0
|
|
34
38
|
});
|
|
35
39
|
const createdIdPromiseRef = useRef(null);
|
|
36
40
|
const onCreatedRef = useRef(options.onSessionCreated);
|
|
37
41
|
onCreatedRef.current = options.onSessionCreated;
|
|
42
|
+
const onConnectedRef = useRef(options.onSessionConnected);
|
|
43
|
+
onConnectedRef.current = options.onSessionConnected;
|
|
38
44
|
const createOptionsRef = useRef(options.createOptions);
|
|
39
45
|
createOptionsRef.current = options.createOptions;
|
|
40
46
|
const sessionIdRef = useRef(sessionId);
|
|
@@ -42,6 +48,7 @@ function useAgentSession(sessionId, options = {}) {
|
|
|
42
48
|
const connect = useMemo(() => {
|
|
43
49
|
return (targetId) => {
|
|
44
50
|
const gen = ++connRef.current.gen;
|
|
51
|
+
let pendingCleanup = null;
|
|
45
52
|
const idPromise = targetId ? Promise.resolve(targetId) : (
|
|
46
53
|
// biome-ignore lint/suspicious/noAssignInExpressions: ??= 挂 ref 是 StrictMode 下"只创建一次"的关键
|
|
47
54
|
createdIdPromiseRef.current ??= client.sessions.create(createOptionsRef.current ?? {}).then((created) => {
|
|
@@ -51,16 +58,25 @@ function useAgentSession(sessionId, options = {}) {
|
|
|
51
58
|
return id;
|
|
52
59
|
})
|
|
53
60
|
);
|
|
54
|
-
idPromise.then(
|
|
61
|
+
idPromise.then(
|
|
62
|
+
(id) => client.hub.connect(id, {
|
|
63
|
+
setup: (next) => {
|
|
64
|
+
pendingCleanup = onConnectedRef.current?.(next) ?? null;
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
).then((next) => {
|
|
55
68
|
if (connRef.current.gen !== gen) {
|
|
69
|
+
pendingCleanup?.();
|
|
56
70
|
next.dispose();
|
|
57
71
|
return;
|
|
58
72
|
}
|
|
59
73
|
connRef.current.id = next.sessionId;
|
|
60
74
|
connRef.current.session = next;
|
|
75
|
+
connRef.current.cleanup = pendingCleanup;
|
|
61
76
|
setSession(next);
|
|
62
77
|
setError(null);
|
|
63
78
|
}).catch((err) => {
|
|
79
|
+
pendingCleanup?.();
|
|
64
80
|
if (connRef.current.gen !== gen) return;
|
|
65
81
|
createdIdPromiseRef.current = null;
|
|
66
82
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -72,8 +88,11 @@ function useAgentSession(sessionId, options = {}) {
|
|
|
72
88
|
return () => {
|
|
73
89
|
connRef.current.gen++;
|
|
74
90
|
const toRelease = connRef.current.session;
|
|
91
|
+
const cleanup = connRef.current.cleanup;
|
|
75
92
|
connRef.current.id = null;
|
|
76
93
|
connRef.current.session = null;
|
|
94
|
+
connRef.current.cleanup = null;
|
|
95
|
+
cleanup?.();
|
|
77
96
|
setSession(null);
|
|
78
97
|
if (toRelease) setTimeout(() => toRelease.dispose(), DISPOSE_DELAY_MS);
|
|
79
98
|
};
|
|
@@ -83,8 +102,11 @@ function useAgentSession(sessionId, options = {}) {
|
|
|
83
102
|
if (connRef.current.id === null) return;
|
|
84
103
|
if (sessionId === connRef.current.id) return;
|
|
85
104
|
const previous = connRef.current.session;
|
|
105
|
+
const cleanup = connRef.current.cleanup;
|
|
86
106
|
connRef.current.id = null;
|
|
87
107
|
connRef.current.session = null;
|
|
108
|
+
connRef.current.cleanup = null;
|
|
109
|
+
cleanup?.();
|
|
88
110
|
if (previous) setTimeout(() => previous.dispose(), DISPOSE_DELAY_MS);
|
|
89
111
|
connect(sessionId);
|
|
90
112
|
}, [sessionId, connect]);
|
|
@@ -245,6 +267,8 @@ function useLlmChat(options) {
|
|
|
245
267
|
const [error, setError] = useState3(null);
|
|
246
268
|
const [isStreaming, setIsStreaming] = useState3(false);
|
|
247
269
|
const abortRef = useRef2(null);
|
|
270
|
+
const activeStartedAtRef = useRef2(null);
|
|
271
|
+
const assistantTimingsRef = useRef2(/* @__PURE__ */ new WeakMap());
|
|
248
272
|
const generationRef = useRef2(0);
|
|
249
273
|
const historyRef = useRef2([]);
|
|
250
274
|
const optionsRef = useRef2(options);
|
|
@@ -263,16 +287,31 @@ function useLlmChat(options) {
|
|
|
263
287
|
setFailedToolIds([]);
|
|
264
288
|
setError(null);
|
|
265
289
|
setIsStreaming(false);
|
|
290
|
+
activeStartedAtRef.current = null;
|
|
291
|
+
assistantTimingsRef.current = /* @__PURE__ */ new WeakMap();
|
|
266
292
|
}, [stop]);
|
|
267
293
|
const send = useCallback2(async (text) => {
|
|
268
294
|
const content = text.trim();
|
|
269
295
|
if (!content || abortRef.current) return false;
|
|
270
296
|
const opts = optionsRef.current;
|
|
271
297
|
const maxRounds = opts.maxToolRounds ?? DEFAULT_MAX_TOOL_ROUNDS;
|
|
298
|
+
const startedAt = Date.now();
|
|
299
|
+
activeStartedAtRef.current = startedAt;
|
|
272
300
|
const commit = (message) => {
|
|
273
301
|
historyRef.current = [...historyRef.current, message];
|
|
274
302
|
setHistory(historyRef.current);
|
|
275
303
|
};
|
|
304
|
+
const latestAssistantTiming = { current: null };
|
|
305
|
+
const commitAssistant = (message) => {
|
|
306
|
+
const timing = { startedAt };
|
|
307
|
+
assistantTimingsRef.current.set(message, timing);
|
|
308
|
+
latestAssistantTiming.current = timing;
|
|
309
|
+
commit(message);
|
|
310
|
+
return timing;
|
|
311
|
+
};
|
|
312
|
+
const finishTiming = (timing) => {
|
|
313
|
+
timing.durationMs = Math.max(0, Date.now() - timing.startedAt);
|
|
314
|
+
};
|
|
276
315
|
setError(null);
|
|
277
316
|
setIsStreaming(true);
|
|
278
317
|
setStreamingText("");
|
|
@@ -298,8 +337,11 @@ function useLlmChat(options) {
|
|
|
298
337
|
};
|
|
299
338
|
setStreamingText(null);
|
|
300
339
|
setStreamingCalls([]);
|
|
301
|
-
|
|
302
|
-
if (!result.toolCalls.length)
|
|
340
|
+
const assistantTiming = commitAssistant(assistant);
|
|
341
|
+
if (!result.toolCalls.length) {
|
|
342
|
+
finishTiming(assistantTiming);
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
303
345
|
const bail = (reason) => {
|
|
304
346
|
for (const call of result.toolCalls) {
|
|
305
347
|
commit({ role: "tool", tool_call_id: call.id, content: JSON.stringify({ error: reason }) });
|
|
@@ -308,11 +350,13 @@ function useLlmChat(options) {
|
|
|
308
350
|
};
|
|
309
351
|
if (!opts.onToolCall) {
|
|
310
352
|
bail("\u8FD9\u4E2A\u5E94\u7528\u6CA1\u6709\u63D0\u4F9B\u5DE5\u5177\u6267\u884C\u5165\u53E3");
|
|
353
|
+
finishTiming(assistantTiming);
|
|
311
354
|
return true;
|
|
312
355
|
}
|
|
313
356
|
if (round >= maxRounds) {
|
|
314
357
|
bail(`\u5DE5\u5177\u8C03\u7528\u5DF2\u8FBE\u4E0A\u9650 ${maxRounds} \u8F6E\uFF0C\u6CA1\u6709\u6267\u884C`);
|
|
315
358
|
setError(`\u5DE5\u5177\u8C03\u7528\u8D85\u8FC7 ${maxRounds} \u8F6E\u4ECD\u672A\u7ED9\u51FA\u7ED3\u8BBA\uFF0C\u5DF2\u505C\u4E0B\u3002`);
|
|
359
|
+
finishTiming(assistantTiming);
|
|
316
360
|
return false;
|
|
317
361
|
}
|
|
318
362
|
for (const call of result.toolCalls) {
|
|
@@ -337,21 +381,39 @@ function useLlmChat(options) {
|
|
|
337
381
|
setStreamingCalls([]);
|
|
338
382
|
if (controller.signal.aborted) {
|
|
339
383
|
if (generation !== generationRef.current) return false;
|
|
340
|
-
|
|
384
|
+
const timing = commitAssistant({
|
|
385
|
+
role: "assistant",
|
|
386
|
+
content: partial ? `${partial}\uFF08\u5DF2\u505C\u6B62\uFF09` : "\uFF08\u5DF2\u505C\u6B62\uFF09"
|
|
387
|
+
});
|
|
388
|
+
timing.status = "interrupted";
|
|
389
|
+
finishTiming(timing);
|
|
341
390
|
return false;
|
|
342
391
|
}
|
|
343
392
|
if (partial && generation === generationRef.current) {
|
|
344
|
-
|
|
393
|
+
const timing = commitAssistant({ role: "assistant", content: partial });
|
|
394
|
+
timing.status = "failed";
|
|
395
|
+
finishTiming(timing);
|
|
396
|
+
} else if (latestAssistantTiming.current) {
|
|
397
|
+
latestAssistantTiming.current.status = "failed";
|
|
398
|
+
finishTiming(latestAssistantTiming.current);
|
|
345
399
|
}
|
|
346
400
|
setError(err instanceof Error && err.message ? err.message : "\u5BF9\u8BDD\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5");
|
|
347
401
|
return false;
|
|
348
402
|
} finally {
|
|
349
403
|
if (abortRef.current === controller) abortRef.current = null;
|
|
404
|
+
activeStartedAtRef.current = null;
|
|
350
405
|
setIsStreaming(false);
|
|
351
406
|
}
|
|
352
407
|
}, []);
|
|
353
408
|
const messages = useMemo2(
|
|
354
|
-
() => toChatMessages(
|
|
409
|
+
() => toChatMessages(
|
|
410
|
+
history,
|
|
411
|
+
streamingText,
|
|
412
|
+
streamingCalls,
|
|
413
|
+
failedToolIds,
|
|
414
|
+
assistantTimingsRef.current,
|
|
415
|
+
activeStartedAtRef.current
|
|
416
|
+
),
|
|
355
417
|
[history, streamingText, streamingCalls, failedToolIds]
|
|
356
418
|
);
|
|
357
419
|
return { messages, isStreaming, error, send, stop, reset };
|
|
@@ -503,7 +565,7 @@ function parseSse(raw) {
|
|
|
503
565
|
if (!delta) return null;
|
|
504
566
|
return { text: delta.content, toolCallDeltas: delta.tool_calls };
|
|
505
567
|
}
|
|
506
|
-
function toChatMessages(history, streamingText, streamingCalls, failedToolIds) {
|
|
568
|
+
function toChatMessages(history, streamingText, streamingCalls, failedToolIds, assistantTimings, activeStartedAt) {
|
|
507
569
|
const results = /* @__PURE__ */ new Map();
|
|
508
570
|
for (const msg of history) {
|
|
509
571
|
if (msg.role === "tool") results.set(msg.tool_call_id, msg.content);
|
|
@@ -515,10 +577,13 @@ function toChatMessages(history, streamingText, streamingCalls, failedToolIds) {
|
|
|
515
577
|
messages.push({ role: "user", content: msg.content, status: "completed" });
|
|
516
578
|
continue;
|
|
517
579
|
}
|
|
580
|
+
const timing = assistantTimings.get(msg);
|
|
518
581
|
messages.push({
|
|
519
582
|
role: "assistant",
|
|
520
583
|
content: msg.content,
|
|
521
|
-
status: "completed",
|
|
584
|
+
status: timing?.status ?? "completed",
|
|
585
|
+
...timing ? { timestamp: new Date(timing.startedAt).toISOString() } : {},
|
|
586
|
+
...timing?.durationMs === void 0 ? {} : { duration_ms: timing.durationMs },
|
|
522
587
|
...msg.tool_calls?.length ? { tool_calls: msg.tool_calls.map((call) => toToolCallInfo(call, results, failedToolIds)) } : {}
|
|
523
588
|
});
|
|
524
589
|
}
|
|
@@ -527,6 +592,7 @@ function toChatMessages(history, streamingText, streamingCalls, failedToolIds) {
|
|
|
527
592
|
role: "assistant",
|
|
528
593
|
content: streamingText,
|
|
529
594
|
status: "streaming",
|
|
595
|
+
...activeStartedAt === null ? {} : { timestamp: new Date(activeStartedAt).toISOString() },
|
|
530
596
|
...streamingCalls.length ? { tool_calls: streamingCalls.map((call) => toToolCallInfo(call, results, failedToolIds)) } : {}
|
|
531
597
|
});
|
|
532
598
|
}
|
|
@@ -544,6 +610,184 @@ function toToolCallInfo(call, results, failedToolIds) {
|
|
|
544
610
|
};
|
|
545
611
|
}
|
|
546
612
|
|
|
613
|
+
// src/hooks/use-message-pin.ts
|
|
614
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
615
|
+
var DEFAULT_MARGIN_PX = 24;
|
|
616
|
+
var MANUAL_SCROLL_TOLERANCE_PX = 2;
|
|
617
|
+
function useMessagePin({
|
|
618
|
+
targetKey,
|
|
619
|
+
pinTarget,
|
|
620
|
+
layoutKey,
|
|
621
|
+
getScrollElement,
|
|
622
|
+
getContentElement,
|
|
623
|
+
getTargetElement,
|
|
624
|
+
getTargetScrollTop,
|
|
625
|
+
getSpacerHeight,
|
|
626
|
+
setSpacerHeight,
|
|
627
|
+
stopAutoScroll,
|
|
628
|
+
scrollToBottom,
|
|
629
|
+
margin = DEFAULT_MARGIN_PX
|
|
630
|
+
}) {
|
|
631
|
+
const pinActiveRef = useRef3(false);
|
|
632
|
+
const frameRef = useRef3(null);
|
|
633
|
+
const retryTimeoutRef = useRef3(null);
|
|
634
|
+
const manualScrollCheckTimeoutRef = useRef3(null);
|
|
635
|
+
const repositionPendingRef = useRef3(false);
|
|
636
|
+
const pinnedTargetScrollTopRef = useRef3(null);
|
|
637
|
+
const appliedTargetKeyRef = useRef3(null);
|
|
638
|
+
const initialObservedTargetKeyRef = useRef3(pinTarget ? null : targetKey);
|
|
639
|
+
const observedTargetKeyRef = useRef3(initialObservedTargetKeyRef.current);
|
|
640
|
+
const setSpacerHeightRef = useRef3(setSpacerHeight);
|
|
641
|
+
setSpacerHeightRef.current = setSpacerHeight;
|
|
642
|
+
const release = useCallback3(() => {
|
|
643
|
+
if (!pinActiveRef.current) return;
|
|
644
|
+
pinActiveRef.current = false;
|
|
645
|
+
repositionPendingRef.current = false;
|
|
646
|
+
pinnedTargetScrollTopRef.current = null;
|
|
647
|
+
if (frameRef.current != null) cancelAnimationFrame(frameRef.current);
|
|
648
|
+
frameRef.current = null;
|
|
649
|
+
if (manualScrollCheckTimeoutRef.current != null) {
|
|
650
|
+
clearTimeout(manualScrollCheckTimeoutRef.current);
|
|
651
|
+
manualScrollCheckTimeoutRef.current = null;
|
|
652
|
+
}
|
|
653
|
+
setSpacerHeightRef.current(0);
|
|
654
|
+
}, []);
|
|
655
|
+
const reposition = useCallback3(
|
|
656
|
+
() => {
|
|
657
|
+
const scroll = getScrollElement();
|
|
658
|
+
const target = getTargetElement();
|
|
659
|
+
if (!scroll || !pinActiveRef.current) return;
|
|
660
|
+
const targetScrollTop = target ? Math.max(
|
|
661
|
+
0,
|
|
662
|
+
scroll.scrollTop + target.getBoundingClientRect().top - scroll.getBoundingClientRect().top - margin
|
|
663
|
+
) : getTargetScrollTop?.(scroll);
|
|
664
|
+
if (targetScrollTop == null) return;
|
|
665
|
+
const previousSpacerHeight = getSpacerHeight();
|
|
666
|
+
const baseScrollHeight = scroll.scrollHeight - previousSpacerHeight;
|
|
667
|
+
const nextSpacerHeight = Math.max(
|
|
668
|
+
0,
|
|
669
|
+
targetScrollTop + scroll.clientHeight - baseScrollHeight
|
|
670
|
+
);
|
|
671
|
+
setSpacerHeightRef.current(nextSpacerHeight);
|
|
672
|
+
pinnedTargetScrollTopRef.current = targetScrollTop;
|
|
673
|
+
stopAutoScroll();
|
|
674
|
+
scroll.scrollTop = targetScrollTop;
|
|
675
|
+
if (nextSpacerHeight === 0 && previousSpacerHeight > 0) {
|
|
676
|
+
pinActiveRef.current = false;
|
|
677
|
+
scrollToBottom();
|
|
678
|
+
}
|
|
679
|
+
},
|
|
680
|
+
[
|
|
681
|
+
getScrollElement,
|
|
682
|
+
getSpacerHeight,
|
|
683
|
+
getTargetElement,
|
|
684
|
+
getTargetScrollTop,
|
|
685
|
+
margin,
|
|
686
|
+
scrollToBottom,
|
|
687
|
+
stopAutoScroll
|
|
688
|
+
]
|
|
689
|
+
);
|
|
690
|
+
const scheduleReposition = useCallback3(
|
|
691
|
+
() => {
|
|
692
|
+
if (!pinActiveRef.current) return;
|
|
693
|
+
repositionPendingRef.current = true;
|
|
694
|
+
if (frameRef.current != null) return;
|
|
695
|
+
frameRef.current = requestAnimationFrame(() => {
|
|
696
|
+
frameRef.current = null;
|
|
697
|
+
const shouldReposition = repositionPendingRef.current;
|
|
698
|
+
repositionPendingRef.current = false;
|
|
699
|
+
if (shouldReposition) reposition();
|
|
700
|
+
});
|
|
701
|
+
},
|
|
702
|
+
[reposition]
|
|
703
|
+
);
|
|
704
|
+
useEffect3(() => {
|
|
705
|
+
if (!targetKey) {
|
|
706
|
+
release();
|
|
707
|
+
appliedTargetKeyRef.current = null;
|
|
708
|
+
observedTargetKeyRef.current = null;
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (!pinTarget) {
|
|
712
|
+
if (pinActiveRef.current && appliedTargetKeyRef.current != null && appliedTargetKeyRef.current !== targetKey) {
|
|
713
|
+
release();
|
|
714
|
+
}
|
|
715
|
+
observedTargetKeyRef.current = targetKey;
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (appliedTargetKeyRef.current === targetKey) {
|
|
719
|
+
observedTargetKeyRef.current = targetKey;
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (observedTargetKeyRef.current === targetKey) return;
|
|
723
|
+
appliedTargetKeyRef.current = targetKey;
|
|
724
|
+
observedTargetKeyRef.current = targetKey;
|
|
725
|
+
pinActiveRef.current = true;
|
|
726
|
+
stopAutoScroll();
|
|
727
|
+
scheduleReposition();
|
|
728
|
+
if (retryTimeoutRef.current != null) clearTimeout(retryTimeoutRef.current);
|
|
729
|
+
retryTimeoutRef.current = window.setTimeout(() => {
|
|
730
|
+
retryTimeoutRef.current = null;
|
|
731
|
+
scheduleReposition();
|
|
732
|
+
}, 80);
|
|
733
|
+
}, [pinTarget, release, scheduleReposition, stopAutoScroll, targetKey]);
|
|
734
|
+
useEffect3(() => {
|
|
735
|
+
if (layoutKey !== void 0) scheduleReposition();
|
|
736
|
+
}, [layoutKey, scheduleReposition]);
|
|
737
|
+
useEffect3(() => {
|
|
738
|
+
const scroll = getScrollElement();
|
|
739
|
+
if (!scroll) return;
|
|
740
|
+
const content = getContentElement?.();
|
|
741
|
+
const observer = new ResizeObserver(scheduleReposition);
|
|
742
|
+
observer.observe(scroll);
|
|
743
|
+
if (content && content !== scroll) observer.observe(content);
|
|
744
|
+
window.addEventListener("resize", scheduleReposition);
|
|
745
|
+
window.visualViewport?.addEventListener("resize", scheduleReposition);
|
|
746
|
+
const handleScroll = () => {
|
|
747
|
+
if (!pinActiveRef.current) return;
|
|
748
|
+
if (manualScrollCheckTimeoutRef.current != null) {
|
|
749
|
+
clearTimeout(manualScrollCheckTimeoutRef.current);
|
|
750
|
+
}
|
|
751
|
+
manualScrollCheckTimeoutRef.current = window.setTimeout(() => {
|
|
752
|
+
manualScrollCheckTimeoutRef.current = null;
|
|
753
|
+
if (!pinActiveRef.current || repositionPendingRef.current) return;
|
|
754
|
+
const targetScrollTop = pinnedTargetScrollTopRef.current;
|
|
755
|
+
if (targetScrollTop != null && Math.abs(scroll.scrollTop - targetScrollTop) > MANUAL_SCROLL_TOLERANCE_PX) {
|
|
756
|
+
release();
|
|
757
|
+
}
|
|
758
|
+
}, 0);
|
|
759
|
+
};
|
|
760
|
+
scroll.addEventListener("scroll", handleScroll, { passive: true });
|
|
761
|
+
return () => {
|
|
762
|
+
observer.disconnect();
|
|
763
|
+
window.removeEventListener("resize", scheduleReposition);
|
|
764
|
+
window.visualViewport?.removeEventListener("resize", scheduleReposition);
|
|
765
|
+
scroll.removeEventListener("scroll", handleScroll);
|
|
766
|
+
};
|
|
767
|
+
}, [getContentElement, getScrollElement, release, scheduleReposition]);
|
|
768
|
+
useEffect3(
|
|
769
|
+
() => () => {
|
|
770
|
+
if (frameRef.current != null) cancelAnimationFrame(frameRef.current);
|
|
771
|
+
frameRef.current = null;
|
|
772
|
+
if (retryTimeoutRef.current != null) clearTimeout(retryTimeoutRef.current);
|
|
773
|
+
retryTimeoutRef.current = null;
|
|
774
|
+
if (manualScrollCheckTimeoutRef.current != null) {
|
|
775
|
+
clearTimeout(manualScrollCheckTimeoutRef.current);
|
|
776
|
+
}
|
|
777
|
+
manualScrollCheckTimeoutRef.current = null;
|
|
778
|
+
repositionPendingRef.current = false;
|
|
779
|
+
pinnedTargetScrollTopRef.current = null;
|
|
780
|
+
appliedTargetKeyRef.current = null;
|
|
781
|
+
observedTargetKeyRef.current = initialObservedTargetKeyRef.current;
|
|
782
|
+
pinActiveRef.current = false;
|
|
783
|
+
setSpacerHeightRef.current(0);
|
|
784
|
+
},
|
|
785
|
+
[]
|
|
786
|
+
);
|
|
787
|
+
const isActive = useCallback3(() => pinActiveRef.current, []);
|
|
788
|
+
return { release, isActive };
|
|
789
|
+
}
|
|
790
|
+
|
|
547
791
|
// src/components/AgentChat.tsx
|
|
548
792
|
import { BladeApiError, latestPostChatFollowup } from "@blade-hq/agent-client";
|
|
549
793
|
|
|
@@ -636,6 +880,18 @@ var ArrowUp = createLucideIcon("ArrowUp", [
|
|
|
636
880
|
["path", { d: "M12 19V5", key: "x0mq9r" }]
|
|
637
881
|
]);
|
|
638
882
|
|
|
883
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/book-open.js
|
|
884
|
+
var BookOpen = createLucideIcon("BookOpen", [
|
|
885
|
+
["path", { d: "M12 7v14", key: "1akyts" }],
|
|
886
|
+
[
|
|
887
|
+
"path",
|
|
888
|
+
{
|
|
889
|
+
d: "M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z",
|
|
890
|
+
key: "ruj8y"
|
|
891
|
+
}
|
|
892
|
+
]
|
|
893
|
+
]);
|
|
894
|
+
|
|
639
895
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/bot.js
|
|
640
896
|
var Bot = createLucideIcon("Bot", [
|
|
641
897
|
["path", { d: "M12 8V4H8", key: "hb8ula" }],
|
|
@@ -646,31 +902,6 @@ var Bot = createLucideIcon("Bot", [
|
|
|
646
902
|
["path", { d: "M9 13v2", key: "rq6x2g" }]
|
|
647
903
|
]);
|
|
648
904
|
|
|
649
|
-
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/brain.js
|
|
650
|
-
var Brain = createLucideIcon("Brain", [
|
|
651
|
-
[
|
|
652
|
-
"path",
|
|
653
|
-
{
|
|
654
|
-
d: "M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z",
|
|
655
|
-
key: "l5xja"
|
|
656
|
-
}
|
|
657
|
-
],
|
|
658
|
-
[
|
|
659
|
-
"path",
|
|
660
|
-
{
|
|
661
|
-
d: "M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z",
|
|
662
|
-
key: "ep3f8r"
|
|
663
|
-
}
|
|
664
|
-
],
|
|
665
|
-
["path", { d: "M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4", key: "1p4c4q" }],
|
|
666
|
-
["path", { d: "M17.599 6.5a3 3 0 0 0 .399-1.375", key: "tmeiqw" }],
|
|
667
|
-
["path", { d: "M6.003 5.125A3 3 0 0 0 6.401 6.5", key: "105sqy" }],
|
|
668
|
-
["path", { d: "M3.477 10.896a4 4 0 0 1 .585-.396", key: "ql3yin" }],
|
|
669
|
-
["path", { d: "M19.938 10.5a4 4 0 0 1 .585.396", key: "1qfode" }],
|
|
670
|
-
["path", { d: "M6 18a4 4 0 0 1-1.967-.516", key: "2e4loj" }],
|
|
671
|
-
["path", { d: "M19.967 17.484A4 4 0 0 1 18 18", key: "159ez6" }]
|
|
672
|
-
]);
|
|
673
|
-
|
|
674
905
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/check.js
|
|
675
906
|
var Check = createLucideIcon("Check", [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]]);
|
|
676
907
|
|
|
@@ -691,17 +922,54 @@ var CircleAlert = createLucideIcon("CircleAlert", [
|
|
|
691
922
|
["line", { x1: "12", x2: "12.01", y1: "16", y2: "16", key: "4dfq90" }]
|
|
692
923
|
]);
|
|
693
924
|
|
|
925
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/circle-dot.js
|
|
926
|
+
var CircleDot = createLucideIcon("CircleDot", [
|
|
927
|
+
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
|
|
928
|
+
["circle", { cx: "12", cy: "12", r: "1", key: "41hilf" }]
|
|
929
|
+
]);
|
|
930
|
+
|
|
931
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/circle.js
|
|
932
|
+
var Circle = createLucideIcon("Circle", [
|
|
933
|
+
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]
|
|
934
|
+
]);
|
|
935
|
+
|
|
694
936
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/copy.js
|
|
695
937
|
var Copy = createLucideIcon("Copy", [
|
|
696
938
|
["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }],
|
|
697
939
|
["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2", key: "zix9uf" }]
|
|
698
940
|
]);
|
|
699
941
|
|
|
700
|
-
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/
|
|
701
|
-
var
|
|
702
|
-
["path", { d: "M21
|
|
703
|
-
[
|
|
704
|
-
|
|
942
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/earth.js
|
|
943
|
+
var Earth = createLucideIcon("Earth", [
|
|
944
|
+
["path", { d: "M21.54 15H17a2 2 0 0 0-2 2v4.54", key: "1djwo0" }],
|
|
945
|
+
[
|
|
946
|
+
"path",
|
|
947
|
+
{
|
|
948
|
+
d: "M7 3.34V5a3 3 0 0 0 3 3a2 2 0 0 1 2 2c0 1.1.9 2 2 2a2 2 0 0 0 2-2c0-1.1.9-2 2-2h3.17",
|
|
949
|
+
key: "1tzkfa"
|
|
950
|
+
}
|
|
951
|
+
],
|
|
952
|
+
["path", { d: "M11 21.95V18a2 2 0 0 0-2-2a2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2H2.05", key: "14pb5j" }],
|
|
953
|
+
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]
|
|
954
|
+
]);
|
|
955
|
+
|
|
956
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/file-pen-line.js
|
|
957
|
+
var FilePenLine = createLucideIcon("FilePenLine", [
|
|
958
|
+
[
|
|
959
|
+
"path",
|
|
960
|
+
{
|
|
961
|
+
d: "m18 5-2.414-2.414A2 2 0 0 0 14.172 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2",
|
|
962
|
+
key: "142zxg"
|
|
963
|
+
}
|
|
964
|
+
],
|
|
965
|
+
[
|
|
966
|
+
"path",
|
|
967
|
+
{
|
|
968
|
+
d: "M21.378 12.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z",
|
|
969
|
+
key: "2t3380"
|
|
970
|
+
}
|
|
971
|
+
],
|
|
972
|
+
["path", { d: "M8 18h1", key: "13wk12" }]
|
|
705
973
|
]);
|
|
706
974
|
|
|
707
975
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/file-text.js
|
|
@@ -713,24 +981,6 @@ var FileText = createLucideIcon("FileText", [
|
|
|
713
981
|
["path", { d: "M16 17H8", key: "z1uh3a" }]
|
|
714
982
|
]);
|
|
715
983
|
|
|
716
|
-
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/file.js
|
|
717
|
-
var File = createLucideIcon("File", [
|
|
718
|
-
["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", key: "1rqfz7" }],
|
|
719
|
-
["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }]
|
|
720
|
-
]);
|
|
721
|
-
|
|
722
|
-
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/film.js
|
|
723
|
-
var Film = createLucideIcon("Film", [
|
|
724
|
-
["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }],
|
|
725
|
-
["path", { d: "M7 3v18", key: "bbkbws" }],
|
|
726
|
-
["path", { d: "M3 7.5h4", key: "zfgn84" }],
|
|
727
|
-
["path", { d: "M3 12h18", key: "1i2n21" }],
|
|
728
|
-
["path", { d: "M3 16.5h4", key: "1230mu" }],
|
|
729
|
-
["path", { d: "M17 3v18", key: "in4fa5" }],
|
|
730
|
-
["path", { d: "M17 7.5h4", key: "myr1c1" }],
|
|
731
|
-
["path", { d: "M17 16.5h4", key: "go4c1d" }]
|
|
732
|
-
]);
|
|
733
|
-
|
|
734
984
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/globe.js
|
|
735
985
|
var Globe = createLucideIcon("Globe", [
|
|
736
986
|
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
|
|
@@ -776,6 +1026,15 @@ var Lightbulb = createLucideIcon("Lightbulb", [
|
|
|
776
1026
|
["path", { d: "M10 22h4", key: "ceow96" }]
|
|
777
1027
|
]);
|
|
778
1028
|
|
|
1029
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/list-checks.js
|
|
1030
|
+
var ListChecks = createLucideIcon("ListChecks", [
|
|
1031
|
+
["path", { d: "m3 17 2 2 4-4", key: "1jhpwq" }],
|
|
1032
|
+
["path", { d: "m3 7 2 2 4-4", key: "1obspn" }],
|
|
1033
|
+
["path", { d: "M13 6h8", key: "15sg57" }],
|
|
1034
|
+
["path", { d: "M13 12h8", key: "h98zly" }],
|
|
1035
|
+
["path", { d: "M13 18h8", key: "oe0vm4" }]
|
|
1036
|
+
]);
|
|
1037
|
+
|
|
779
1038
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/loader-circle.js
|
|
780
1039
|
var LoaderCircle = createLucideIcon("LoaderCircle", [
|
|
781
1040
|
["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]
|
|
@@ -806,6 +1065,20 @@ var Play = createLucideIcon("Play", [
|
|
|
806
1065
|
["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]
|
|
807
1066
|
]);
|
|
808
1067
|
|
|
1068
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/refresh-ccw.js
|
|
1069
|
+
var RefreshCcw = createLucideIcon("RefreshCcw", [
|
|
1070
|
+
["path", { d: "M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8", key: "14sxne" }],
|
|
1071
|
+
["path", { d: "M3 3v5h5", key: "1xhq8a" }],
|
|
1072
|
+
["path", { d: "M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16", key: "1hlbsb" }],
|
|
1073
|
+
["path", { d: "M16 16h5v5", key: "ccwih5" }]
|
|
1074
|
+
]);
|
|
1075
|
+
|
|
1076
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/search.js
|
|
1077
|
+
var Search = createLucideIcon("Search", [
|
|
1078
|
+
["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }],
|
|
1079
|
+
["path", { d: "m21 21-4.3-4.3", key: "1qie3q" }]
|
|
1080
|
+
]);
|
|
1081
|
+
|
|
809
1082
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/settings-2.js
|
|
810
1083
|
var Settings2 = createLucideIcon("Settings2", [
|
|
811
1084
|
["path", { d: "M20 7h-9", key: "3s1dr2" }],
|
|
@@ -834,6 +1107,12 @@ var Square = createLucideIcon("Square", [
|
|
|
834
1107
|
["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }]
|
|
835
1108
|
]);
|
|
836
1109
|
|
|
1110
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/terminal.js
|
|
1111
|
+
var Terminal = createLucideIcon("Terminal", [
|
|
1112
|
+
["polyline", { points: "4 17 10 11 4 5", key: "akl6gq" }],
|
|
1113
|
+
["line", { x1: "12", x2: "20", y1: "19", y2: "19", key: "q2wloq" }]
|
|
1114
|
+
]);
|
|
1115
|
+
|
|
837
1116
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/triangle-alert.js
|
|
838
1117
|
var TriangleAlert = createLucideIcon("TriangleAlert", [
|
|
839
1118
|
[
|
|
@@ -847,6 +1126,17 @@ var TriangleAlert = createLucideIcon("TriangleAlert", [
|
|
|
847
1126
|
["path", { d: "M12 17h.01", key: "p32p05" }]
|
|
848
1127
|
]);
|
|
849
1128
|
|
|
1129
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/wrench.js
|
|
1130
|
+
var Wrench = createLucideIcon("Wrench", [
|
|
1131
|
+
[
|
|
1132
|
+
"path",
|
|
1133
|
+
{
|
|
1134
|
+
d: "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z",
|
|
1135
|
+
key: "cbrjhi"
|
|
1136
|
+
}
|
|
1137
|
+
]
|
|
1138
|
+
]);
|
|
1139
|
+
|
|
850
1140
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/x.js
|
|
851
1141
|
var X = createLucideIcon("X", [
|
|
852
1142
|
["path", { d: "M18 6 6 18", key: "1bl5f8" }],
|
|
@@ -854,19 +1144,35 @@ var X = createLucideIcon("X", [
|
|
|
854
1144
|
]);
|
|
855
1145
|
|
|
856
1146
|
// src/components/AgentChat.tsx
|
|
857
|
-
import { useCallback as
|
|
1147
|
+
import { useCallback as useCallback8, useEffect as useEffect12, useMemo as useMemo8, useState as useState15 } from "react";
|
|
858
1148
|
|
|
859
1149
|
// src/lib/utils.ts
|
|
860
1150
|
function cn(...inputs) {
|
|
861
1151
|
return clsx(inputs);
|
|
862
1152
|
}
|
|
863
1153
|
async function copyToClipboard(text) {
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1154
|
+
const clipboard = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
|
|
1155
|
+
if (clipboard && typeof clipboard.writeText === "function") {
|
|
1156
|
+
try {
|
|
1157
|
+
await clipboard.writeText(text);
|
|
1158
|
+
return true;
|
|
1159
|
+
} catch {
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
if (typeof document === "undefined" || typeof document.execCommand !== "function") {
|
|
868
1163
|
return false;
|
|
869
1164
|
}
|
|
1165
|
+
const textarea = document.createElement("textarea");
|
|
1166
|
+
textarea.value = text;
|
|
1167
|
+
textarea.style.position = "fixed";
|
|
1168
|
+
textarea.style.opacity = "0";
|
|
1169
|
+
document.body.appendChild(textarea);
|
|
1170
|
+
textarea.select();
|
|
1171
|
+
try {
|
|
1172
|
+
return document.execCommand("copy");
|
|
1173
|
+
} finally {
|
|
1174
|
+
document.body.removeChild(textarea);
|
|
1175
|
+
}
|
|
870
1176
|
}
|
|
871
1177
|
|
|
872
1178
|
// src/components/ReplayBar.tsx
|
|
@@ -981,164 +1287,581 @@ function ReplayMismatchPrompt({ mismatch, className }) {
|
|
|
981
1287
|
);
|
|
982
1288
|
}
|
|
983
1289
|
|
|
984
|
-
// src/components/
|
|
985
|
-
import {
|
|
986
|
-
function ChatInput({
|
|
987
|
-
value,
|
|
988
|
-
onValueChange,
|
|
989
|
-
onSend,
|
|
990
|
-
onStop,
|
|
991
|
-
isStreaming,
|
|
992
|
-
isStopping = false,
|
|
993
|
-
placeholder = "\u8F93\u5165\u6D88\u606F\u2026",
|
|
994
|
-
className
|
|
995
|
-
}) {
|
|
996
|
-
const trimmed = value.trim();
|
|
997
|
-
const canSend = trimmed.length > 0 && !isStreaming;
|
|
998
|
-
const handleSend = async () => {
|
|
999
|
-
if (!canSend) return;
|
|
1000
|
-
const accepted = await onSend(trimmed);
|
|
1001
|
-
if (!accepted) return;
|
|
1002
|
-
onValueChange("");
|
|
1003
|
-
};
|
|
1004
|
-
const handleKeyDown = (event) => {
|
|
1005
|
-
if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
|
|
1006
|
-
event.preventDefault();
|
|
1007
|
-
void handleSend();
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
return /* @__PURE__ */ jsx4("div", { className: cn("blade-chat-input border-t border-[hsl(var(--border))] py-3", className), children: /* @__PURE__ */ jsxs3("div", { className: "blade-chat-input-inner mx-auto flex max-w-[748px] items-end gap-2 rounded-2xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-3 py-2", children: [
|
|
1011
|
-
/* @__PURE__ */ jsx4(
|
|
1012
|
-
"textarea",
|
|
1013
|
-
{
|
|
1014
|
-
value,
|
|
1015
|
-
onChange: (event) => onValueChange(event.target.value),
|
|
1016
|
-
onKeyDown: handleKeyDown,
|
|
1017
|
-
onInput: (event) => {
|
|
1018
|
-
const el = event.currentTarget;
|
|
1019
|
-
el.style.height = "auto";
|
|
1020
|
-
el.style.height = `${Math.min(el.scrollHeight, 192)}px`;
|
|
1021
|
-
},
|
|
1022
|
-
rows: 1,
|
|
1023
|
-
placeholder,
|
|
1024
|
-
"aria-label": "\u804A\u5929\u8F93\u5165",
|
|
1025
|
-
className: "blade-chat-textarea max-h-48 min-h-[28px] flex-1 resize-none bg-transparent py-1 text-sm leading-6 text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground)/0.6)]"
|
|
1026
|
-
}
|
|
1027
|
-
),
|
|
1028
|
-
isStreaming ? /* @__PURE__ */ jsx4(
|
|
1029
|
-
"button",
|
|
1030
|
-
{
|
|
1031
|
-
type: "button",
|
|
1032
|
-
onClick: onStop,
|
|
1033
|
-
disabled: isStopping,
|
|
1034
|
-
"aria-label": isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D",
|
|
1035
|
-
title: isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D",
|
|
1036
|
-
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--muted))] text-[hsl(var(--foreground))] transition-opacity hover:opacity-90 disabled:opacity-60",
|
|
1037
|
-
children: isStopping ? /* @__PURE__ */ jsx4(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx4(Square, { size: 12, fill: "currentColor" })
|
|
1038
|
-
}
|
|
1039
|
-
) : /* @__PURE__ */ jsx4(
|
|
1040
|
-
"button",
|
|
1041
|
-
{
|
|
1042
|
-
type: "button",
|
|
1043
|
-
onClick: handleSend,
|
|
1044
|
-
disabled: !canSend,
|
|
1045
|
-
"aria-label": "\u53D1\u9001\u6D88\u606F",
|
|
1046
|
-
title: "\u53D1\u9001\u6D88\u606F",
|
|
1047
|
-
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",
|
|
1048
|
-
children: /* @__PURE__ */ jsx4(ArrowUp, { size: 15 })
|
|
1049
|
-
}
|
|
1050
|
-
)
|
|
1051
|
-
] }) });
|
|
1052
|
-
}
|
|
1290
|
+
// src/components/PlanUpdateBlock.tsx
|
|
1291
|
+
import { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
|
|
1053
1292
|
|
|
1054
|
-
// src/components/
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1293
|
+
// src/components/display-utils.ts
|
|
1294
|
+
var TOOL_NAME_ALIASES = {
|
|
1295
|
+
agent: "Agent",
|
|
1296
|
+
ask_user_question: "AskUserQuestion",
|
|
1297
|
+
bash: "Bash",
|
|
1298
|
+
bg_bash: "BgBash",
|
|
1299
|
+
edit: "Edit",
|
|
1300
|
+
exit_plan_mode: "ExitPlanMode",
|
|
1301
|
+
file_edit: "Edit",
|
|
1302
|
+
file_read: "Read",
|
|
1303
|
+
file_write: "Write",
|
|
1304
|
+
finish_task: "FinishTask",
|
|
1305
|
+
glob: "Glob",
|
|
1306
|
+
grep: "Grep",
|
|
1307
|
+
kb_search: "KbSearch",
|
|
1308
|
+
ls: "Ls",
|
|
1309
|
+
multi_edit: "MultiEdit",
|
|
1310
|
+
read: "Read",
|
|
1311
|
+
read_skill: "ReadSkill",
|
|
1312
|
+
update_plan: "UpdatePlan",
|
|
1313
|
+
web_fetch: "WebFetch",
|
|
1314
|
+
web_search: "WebSearch",
|
|
1315
|
+
write: "Write"
|
|
1316
|
+
};
|
|
1317
|
+
var TOOL_DISPLAY_LABELS = {
|
|
1318
|
+
Bash: "\u6267\u884C\u547D\u4EE4",
|
|
1319
|
+
BgBash: "\u540E\u53F0\u6267\u884C\u547D\u4EE4",
|
|
1320
|
+
Read: "\u8BFB\u53D6\u6587\u4EF6",
|
|
1321
|
+
Write: "\u5199\u5165\u6587\u4EF6",
|
|
1322
|
+
Edit: "\u7F16\u8F91\u6587\u4EF6",
|
|
1323
|
+
MultiEdit: "\u7F16\u8F91\u6587\u4EF6",
|
|
1324
|
+
Ls: "\u5217\u51FA\u76EE\u5F55",
|
|
1325
|
+
Glob: "\u5339\u914D\u6587\u4EF6",
|
|
1326
|
+
Grep: "\u641C\u7D22\u6587\u672C",
|
|
1327
|
+
KbSearch: "\u68C0\u7D22\u77E5\u8BC6\u5E93",
|
|
1328
|
+
WebSearch: "\u641C\u7D22\u7F51\u9875",
|
|
1329
|
+
WebFetch: "\u6574\u7406\u7F51\u9875\u5185\u5BB9",
|
|
1330
|
+
Agent: "\u6D3E\u751F\u5B50\u667A\u80FD\u4F53",
|
|
1331
|
+
AskUserQuestion: "\u5411\u7528\u6237\u63D0\u95EE",
|
|
1332
|
+
ReadSkill: "\u8BFB\u53D6\u6280\u80FD",
|
|
1333
|
+
FinishTask: "\u4EFB\u52A1\u5B8C\u6210",
|
|
1334
|
+
ExitPlanMode: "\u63D0\u4EA4\u8BA1\u5212",
|
|
1335
|
+
ListSessions: "\u5217\u51FA\u5386\u53F2\u4F1A\u8BDD",
|
|
1336
|
+
GetSessionHistory: "\u8BFB\u53D6\u4F1A\u8BDD\u5386\u53F2"
|
|
1337
|
+
};
|
|
1338
|
+
function safeParseJson(value) {
|
|
1339
|
+
if (!value) return null;
|
|
1340
|
+
try {
|
|
1341
|
+
return JSON.parse(value);
|
|
1342
|
+
} catch {
|
|
1058
1343
|
return null;
|
|
1059
1344
|
}
|
|
1060
|
-
const reconnecting = connection === "reconnecting";
|
|
1061
|
-
return /* @__PURE__ */ jsx5("div", { className: cn("blade-chat-banner bg-[hsl(var(--background))] px-5 pt-3", className), children: /* @__PURE__ */ jsxs4(
|
|
1062
|
-
"div",
|
|
1063
|
-
{
|
|
1064
|
-
className: cn(
|
|
1065
|
-
"mx-auto flex max-w-3xl items-start gap-3 rounded-2xl border px-4 py-3",
|
|
1066
|
-
reconnecting ? "border-amber-500/25 bg-amber-500/10 text-amber-100" : "border-rose-500/25 bg-rose-500/10 text-rose-100"
|
|
1067
|
-
),
|
|
1068
|
-
children: [
|
|
1069
|
-
/* @__PURE__ */ jsx5("span", { className: "mt-0.5 shrink-0", children: reconnecting ? /* @__PURE__ */ jsx5(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx5(TriangleAlert, { size: 14 }) }),
|
|
1070
|
-
/* @__PURE__ */ jsxs4("div", { className: "min-w-0", children: [
|
|
1071
|
-
/* @__PURE__ */ jsx5("div", { className: "text-sm font-medium", children: reconnecting ? "\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF0C\u6B63\u5728\u91CD\u8FDE\u2026" : "\u8FDE\u63A5\u5DF2\u65AD\u5F00" }),
|
|
1072
|
-
/* @__PURE__ */ jsx5("div", { className: "text-xs opacity-80", children: "\u6D88\u606F\u540C\u6B65\u53EF\u80FD\u4F1A\u5EF6\u8FDF\uFF0C\u7CFB\u7EDF\u4F1A\u7EE7\u7EED\u81EA\u52A8\u91CD\u8BD5" })
|
|
1073
|
-
] })
|
|
1074
|
-
]
|
|
1075
|
-
}
|
|
1076
|
-
) });
|
|
1077
1345
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1346
|
+
function getStringArgValue(args, key) {
|
|
1347
|
+
const value = args?.[key];
|
|
1348
|
+
return typeof value === "string" ? value.trim() : "";
|
|
1349
|
+
}
|
|
1350
|
+
var SKILL_ENTRY_FILE_NAMES = /* @__PURE__ */ new Set(["skill.md", "command.md"]);
|
|
1351
|
+
var NON_SKILL_DIR_NAMES = /* @__PURE__ */ new Set([".", "..", ".agent", ".agents", ".claude", "skill_data", "skills"]);
|
|
1352
|
+
function getSkillNameFromFilePath(filePath) {
|
|
1353
|
+
if (!filePath) return null;
|
|
1354
|
+
const segments = filePath.split(/[\\/]+/).filter(Boolean);
|
|
1355
|
+
const fileName = segments.pop();
|
|
1356
|
+
if (!fileName || !SKILL_ENTRY_FILE_NAMES.has(fileName.toLowerCase())) return null;
|
|
1357
|
+
const dirName = segments.pop();
|
|
1358
|
+
if (!dirName || NON_SKILL_DIR_NAMES.has(dirName.toLowerCase())) return null;
|
|
1359
|
+
return dirName;
|
|
1360
|
+
}
|
|
1361
|
+
function formatToolName(name) {
|
|
1362
|
+
const trimmed = name.trim();
|
|
1363
|
+
if (!trimmed) return name;
|
|
1364
|
+
const stripped = trimmed.split(":").pop()?.split("/").pop()?.split(".").pop()?.trim() || trimmed;
|
|
1365
|
+
const normalized = stripped.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
1366
|
+
return TOOL_NAME_ALIASES[normalized] ?? stripped;
|
|
1367
|
+
}
|
|
1368
|
+
function getToolDisplayLabel(toolCall) {
|
|
1369
|
+
const normalized = formatToolName(toolCall.name);
|
|
1370
|
+
const args = safeParseJson(toolCall.arguments);
|
|
1371
|
+
const displayName = toolCall.display_name?.trim() ?? "";
|
|
1372
|
+
const baseLabel = displayName || TOOL_DISPLAY_LABELS[normalized] || normalized;
|
|
1373
|
+
const metaDisplayName = getStringArgValue(args, "_meta_display_name");
|
|
1374
|
+
if (metaDisplayName) {
|
|
1375
|
+
return metaDisplayName;
|
|
1376
|
+
}
|
|
1377
|
+
const description = getStringArgValue(args, "description");
|
|
1378
|
+
if (normalized === "BgBash") {
|
|
1379
|
+
return description ? `\u540E\u53F0\u6267\u884C\uFF1A${description}` : "\u540E\u53F0\u6267\u884C\u547D\u4EE4";
|
|
1380
|
+
}
|
|
1381
|
+
if (normalized === "ReadSkill") {
|
|
1382
|
+
const skillName = getStringArgValue(args, "skill") || getStringArgValue(args, "skill_name");
|
|
1383
|
+
return skillName ? `${baseLabel}\u300C${skillName}\u300D` : baseLabel;
|
|
1384
|
+
}
|
|
1385
|
+
if (normalized === "Read") {
|
|
1386
|
+
const skillName = getSkillNameFromFilePath(
|
|
1387
|
+
getStringArgValue(args, "file_path") || getStringArgValue(args, "path")
|
|
1388
|
+
);
|
|
1389
|
+
if (skillName) return `\u8BFB\u53D6\u6280\u80FD\u300C${skillName}\u300D`;
|
|
1390
|
+
}
|
|
1391
|
+
if (normalized === "FinishTask") {
|
|
1392
|
+
const title = getStringArgValue(args, "title");
|
|
1393
|
+
return title ? `${baseLabel}\uFF1A${title}` : baseLabel;
|
|
1394
|
+
}
|
|
1395
|
+
return description || baseLabel;
|
|
1396
|
+
}
|
|
1397
|
+
function getToolTone(status) {
|
|
1398
|
+
if (status === "error" || status === "cancelled") return "red";
|
|
1399
|
+
if (status === "awaiting_answer") return "amber";
|
|
1400
|
+
if (status === "pending") return "blue";
|
|
1401
|
+
return "emerald";
|
|
1402
|
+
}
|
|
1403
|
+
function getToolStatusLabel(status) {
|
|
1404
|
+
if (status === "pending") return "\u8FD0\u884C\u4E2D";
|
|
1405
|
+
if (status === "awaiting_answer") return "\u7B49\u5F85\u56DE\u7B54";
|
|
1406
|
+
if (status === "error") return "\u9519\u8BEF";
|
|
1407
|
+
if (status === "cancelled") return "\u5DF2\u53D6\u6D88";
|
|
1408
|
+
return "\u5B8C\u6210";
|
|
1409
|
+
}
|
|
1410
|
+
function formatToolDuration(ms) {
|
|
1411
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
1412
|
+
const seconds = ms / 1e3;
|
|
1413
|
+
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
1414
|
+
const minutes = Math.floor(seconds / 60);
|
|
1415
|
+
const remainingSeconds = Math.round(seconds % 60);
|
|
1416
|
+
return remainingSeconds > 0 ? `${minutes}m${remainingSeconds}s` : `${minutes}m`;
|
|
1417
|
+
}
|
|
1418
|
+
function formatToolArgs(args) {
|
|
1419
|
+
try {
|
|
1420
|
+
return JSON.stringify(JSON.parse(args), null, 2);
|
|
1421
|
+
} catch {
|
|
1422
|
+
return args;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
var RESULT_PREVIEW_LIMIT = 4e3;
|
|
1426
|
+
function formatToolResult(result) {
|
|
1427
|
+
const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
1428
|
+
if (text == null) return "";
|
|
1429
|
+
if (text.length <= RESULT_PREVIEW_LIMIT) return text;
|
|
1430
|
+
return `${text.slice(0, RESULT_PREVIEW_LIMIT)}
|
|
1431
|
+
\u2026\uFF08\u7ED3\u679C\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\uFF09`;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
// src/components/PlanUpdateBlock.tsx
|
|
1435
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1436
|
+
var PLAN_STEP_STATUSES = /* @__PURE__ */ new Set(["pending", "in_progress", "completed"]);
|
|
1437
|
+
var PLAN_AUTO_COLLAPSE_MS = 5e3;
|
|
1438
|
+
function isPlanUpdateTool(toolCall) {
|
|
1439
|
+
return formatToolName(toolCall.name) === "UpdatePlan";
|
|
1440
|
+
}
|
|
1441
|
+
function getPlanUpdateDisplayState(messages) {
|
|
1442
|
+
let current = null;
|
|
1443
|
+
let latestAttempt = null;
|
|
1444
|
+
let latestAttemptStreaming = false;
|
|
1445
|
+
for (const message of messages) {
|
|
1446
|
+
if ((message.loop_name ?? "root") !== "root") continue;
|
|
1447
|
+
for (const toolCall of message.tool_calls ?? []) {
|
|
1448
|
+
if (!isPlanUpdateTool(toolCall)) continue;
|
|
1449
|
+
latestAttempt = toolCall;
|
|
1450
|
+
latestAttemptStreaming = message.status === "streaming";
|
|
1451
|
+
if (toolCall.status === "done" && parsePlanUpdate(toolCall.arguments)) current = toolCall;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
return {
|
|
1455
|
+
current,
|
|
1456
|
+
updating: latestAttempt?.status === "pending" && latestAttemptStreaming
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
function parsePlanUpdate(argumentsJson) {
|
|
1460
|
+
try {
|
|
1461
|
+
const raw = JSON.parse(argumentsJson);
|
|
1462
|
+
if (!raw || typeof raw !== "object") return null;
|
|
1463
|
+
const candidate = raw;
|
|
1464
|
+
if (!Array.isArray(candidate.plan)) return null;
|
|
1465
|
+
const plan = candidate.plan.map((item) => {
|
|
1466
|
+
if (!item || typeof item !== "object") return null;
|
|
1467
|
+
const step = item.step;
|
|
1468
|
+
const status = item.status;
|
|
1469
|
+
if (typeof step !== "string" || !step.trim() || typeof status !== "string" || !PLAN_STEP_STATUSES.has(status)) {
|
|
1470
|
+
return null;
|
|
1471
|
+
}
|
|
1472
|
+
return { step: step.trim(), status };
|
|
1473
|
+
});
|
|
1474
|
+
if (plan.some((item) => item === null)) return null;
|
|
1475
|
+
if (plan.filter((item) => item?.status === "in_progress").length > 1) return null;
|
|
1476
|
+
return { plan };
|
|
1477
|
+
} catch {
|
|
1478
|
+
return null;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
function pickCurrentPlanStep(plan) {
|
|
1482
|
+
return plan.find((item) => item.status === "in_progress") ?? plan.find((item) => item.status === "pending") ?? plan[plan.length - 1] ?? null;
|
|
1483
|
+
}
|
|
1484
|
+
function PlanStepIcon({
|
|
1485
|
+
status,
|
|
1486
|
+
size = 17,
|
|
1487
|
+
running = false
|
|
1488
|
+
}) {
|
|
1489
|
+
if (status === "completed") {
|
|
1490
|
+
return /* @__PURE__ */ jsx4(Check, { size, strokeWidth: 2, className: "shrink-0 text-emerald-500" });
|
|
1491
|
+
}
|
|
1492
|
+
if (status === "in_progress") {
|
|
1493
|
+
return running ? /* @__PURE__ */ jsx4(LoaderCircle, { size, className: "shrink-0 animate-spin text-[hsl(var(--muted-foreground))]" }) : /* @__PURE__ */ jsx4(CircleDot, { size, className: "shrink-0 text-amber-500" });
|
|
1494
|
+
}
|
|
1495
|
+
return /* @__PURE__ */ jsx4(Circle, { size, className: "shrink-0 text-[hsl(var(--muted-foreground))]/60" });
|
|
1496
|
+
}
|
|
1497
|
+
function PlanUpdateBlock({
|
|
1498
|
+
toolCall,
|
|
1499
|
+
running = false,
|
|
1500
|
+
autoReveal = false
|
|
1501
|
+
}) {
|
|
1502
|
+
const updateKey = `${toolCall.id}:${toolCall.arguments}`;
|
|
1503
|
+
const revealKey = autoReveal ? updateKey : null;
|
|
1504
|
+
const [collapsed, setCollapsed] = useState4(!autoReveal);
|
|
1505
|
+
const collapseTimerRef = useRef4(null);
|
|
1506
|
+
const data = parsePlanUpdate(toolCall.arguments);
|
|
1507
|
+
useEffect4(() => {
|
|
1508
|
+
if (!revealKey) return;
|
|
1509
|
+
if (collapseTimerRef.current) clearTimeout(collapseTimerRef.current);
|
|
1510
|
+
setCollapsed(false);
|
|
1511
|
+
collapseTimerRef.current = setTimeout(() => {
|
|
1512
|
+
setCollapsed(true);
|
|
1513
|
+
collapseTimerRef.current = null;
|
|
1514
|
+
}, PLAN_AUTO_COLLAPSE_MS);
|
|
1515
|
+
}, [revealKey]);
|
|
1516
|
+
useEffect4(
|
|
1517
|
+
() => () => {
|
|
1518
|
+
if (collapseTimerRef.current) clearTimeout(collapseTimerRef.current);
|
|
1519
|
+
},
|
|
1520
|
+
[]
|
|
1521
|
+
);
|
|
1522
|
+
if (!data) return null;
|
|
1523
|
+
const completed = data.plan.filter((item) => item.status === "completed").length;
|
|
1524
|
+
const currentStep = pickCurrentPlanStep(data.plan);
|
|
1525
|
+
const pausedAtCurrentStep = !running && currentStep?.status === "in_progress";
|
|
1526
|
+
return /* @__PURE__ */ jsxs3("section", { className: "overflow-hidden", children: [
|
|
1527
|
+
/* @__PURE__ */ jsxs3(
|
|
1528
|
+
"button",
|
|
1529
|
+
{
|
|
1530
|
+
type: "button",
|
|
1531
|
+
"aria-expanded": !collapsed,
|
|
1532
|
+
onClick: () => {
|
|
1533
|
+
if (collapseTimerRef.current) {
|
|
1534
|
+
clearTimeout(collapseTimerRef.current);
|
|
1535
|
+
collapseTimerRef.current = null;
|
|
1536
|
+
}
|
|
1537
|
+
setCollapsed((value) => !value);
|
|
1538
|
+
},
|
|
1539
|
+
className: cn(
|
|
1540
|
+
"flex w-full items-center gap-2 px-3 py-2 text-left transition-colors hover:bg-[hsl(var(--muted)/0.3)]",
|
|
1541
|
+
!collapsed && "border-b border-[hsl(var(--border))]"
|
|
1542
|
+
),
|
|
1543
|
+
children: [
|
|
1544
|
+
collapsed && currentStep ? /* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 flex-1 items-center gap-1.5 text-xs text-[hsl(var(--foreground))]", children: [
|
|
1545
|
+
/* @__PURE__ */ jsx4(PlanStepIcon, { status: currentStep.status, size: 14, running }),
|
|
1546
|
+
/* @__PURE__ */ jsx4("span", { className: "truncate", children: currentStep.step }),
|
|
1547
|
+
pausedAtCurrentStep ? /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-[11px] text-amber-500", children: "\u5DF2\u6682\u505C" }) : null
|
|
1548
|
+
] }) : /* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 flex-1 items-center gap-1.5 text-[11px] text-[hsl(var(--muted-foreground))]", children: [
|
|
1549
|
+
/* @__PURE__ */ jsx4(ListChecks, { size: 14, className: "shrink-0", "aria-hidden": "true" }),
|
|
1550
|
+
/* @__PURE__ */ jsx4("span", { className: "truncate", children: "\u4EFB\u52A1\u8FDB\u5EA6" }),
|
|
1551
|
+
pausedAtCurrentStep ? /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-amber-500", children: "\u5DF2\u6682\u505C" }) : null
|
|
1552
|
+
] }),
|
|
1553
|
+
/* @__PURE__ */ jsxs3("span", { className: "shrink-0 text-[11px] tabular-nums text-[hsl(var(--muted-foreground))]", children: [
|
|
1554
|
+
completed,
|
|
1555
|
+
"/",
|
|
1556
|
+
data.plan.length
|
|
1557
|
+
] }),
|
|
1558
|
+
/* @__PURE__ */ jsx4(
|
|
1559
|
+
ChevronDown,
|
|
1560
|
+
{
|
|
1561
|
+
size: 14,
|
|
1562
|
+
className: cn(
|
|
1563
|
+
"shrink-0 text-[hsl(var(--muted-foreground))] transition-transform duration-300 ease-out motion-reduce:transition-none",
|
|
1564
|
+
!collapsed && "rotate-180"
|
|
1565
|
+
)
|
|
1566
|
+
}
|
|
1567
|
+
)
|
|
1568
|
+
]
|
|
1569
|
+
}
|
|
1570
|
+
),
|
|
1571
|
+
/* @__PURE__ */ jsx4(
|
|
1572
|
+
"div",
|
|
1573
|
+
{
|
|
1574
|
+
"aria-hidden": collapsed,
|
|
1575
|
+
className: cn(
|
|
1576
|
+
"grid transition-[grid-template-rows,opacity] duration-300 ease-out motion-reduce:transition-none",
|
|
1577
|
+
collapsed ? "grid-rows-[0fr] opacity-0" : "grid-rows-[1fr] opacity-100"
|
|
1578
|
+
),
|
|
1579
|
+
children: /* @__PURE__ */ jsx4("div", { className: "min-h-0 overflow-hidden", children: /* @__PURE__ */ jsx4("div", { className: "flex max-h-40 flex-col gap-0.5 overflow-y-auto px-3 py-2", children: data.plan.length === 0 ? /* @__PURE__ */ jsx4("span", { className: "text-xs text-[hsl(var(--muted-foreground))]", children: "\u6682\u65E0\u4EFB\u52A1\u6B65\u9AA4" }) : data.plan.map((item, index) => /* @__PURE__ */ jsxs3("div", { className: "flex items-start gap-2 py-0.5", children: [
|
|
1580
|
+
/* @__PURE__ */ jsx4("span", { className: "mt-[3px] flex shrink-0", children: /* @__PURE__ */ jsx4(PlanStepIcon, { status: item.status, size: 14, running }) }),
|
|
1581
|
+
/* @__PURE__ */ jsx4(
|
|
1582
|
+
"span",
|
|
1583
|
+
{
|
|
1584
|
+
className: cn(
|
|
1585
|
+
"min-w-0 flex-1 break-words text-[13px] leading-5",
|
|
1586
|
+
item.status === "completed" ? "text-[hsl(var(--muted-foreground))]" : item.status === "in_progress" ? "font-medium text-[hsl(var(--foreground))]" : "text-[hsl(var(--muted-foreground))]"
|
|
1587
|
+
),
|
|
1588
|
+
children: item.step
|
|
1589
|
+
}
|
|
1590
|
+
)
|
|
1591
|
+
] }, `${index}-${item.step}`)) }) })
|
|
1592
|
+
}
|
|
1593
|
+
)
|
|
1594
|
+
] });
|
|
1595
|
+
}
|
|
1596
|
+
function CurrentPlanPanel({
|
|
1597
|
+
messages,
|
|
1598
|
+
running = false,
|
|
1599
|
+
revealRevision = 0,
|
|
1600
|
+
sessionId,
|
|
1601
|
+
className
|
|
1602
|
+
}) {
|
|
1603
|
+
const { current, updating } = getPlanUpdateDisplayState(messages);
|
|
1604
|
+
const revealBaselinesRef = useRef4(/* @__PURE__ */ new Map([[sessionId, revealRevision]]));
|
|
1605
|
+
const autoReveal = (revealBaselinesRef.current.get(sessionId) ?? 0) !== revealRevision;
|
|
1606
|
+
useEffect4(() => {
|
|
1607
|
+
if (!current) return;
|
|
1608
|
+
revealBaselinesRef.current.set(sessionId, revealRevision);
|
|
1609
|
+
}, [current, revealRevision, sessionId]);
|
|
1610
|
+
if (!current && !updating) return null;
|
|
1611
|
+
return /* @__PURE__ */ jsxs3("div", { className: cn("blade-chat-plan mx-auto w-full max-w-[748px] px-4", className), children: [
|
|
1612
|
+
updating ? /* @__PURE__ */ jsxs3("div", { className: "mb-2 flex items-center gap-2 rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-3 py-2 text-xs text-[hsl(var(--muted-foreground))]", children: [
|
|
1613
|
+
/* @__PURE__ */ jsx4(LoaderCircle, { size: 14, className: "shrink-0 animate-spin" }),
|
|
1614
|
+
/* @__PURE__ */ jsx4("span", { children: "\u6B63\u5728\u66F4\u65B0\u4EFB\u52A1\u8FDB\u5EA6\u2026" })
|
|
1615
|
+
] }) : null,
|
|
1616
|
+
current ? /* @__PURE__ */ jsx4(
|
|
1617
|
+
PlanUpdateBlock,
|
|
1618
|
+
{
|
|
1619
|
+
toolCall: current,
|
|
1620
|
+
running,
|
|
1621
|
+
autoReveal
|
|
1622
|
+
},
|
|
1623
|
+
sessionId ?? "current-session"
|
|
1624
|
+
) : null
|
|
1625
|
+
] });
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// src/components/ChatSurface.tsx
|
|
1629
|
+
import { chatErrorForDisplay as chatErrorForDisplay2 } from "@blade-hq/agent-client";
|
|
1630
|
+
|
|
1631
|
+
// src/components/ChatInput.tsx
|
|
1632
|
+
import { useState as useState5 } from "react";
|
|
1633
|
+
import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1634
|
+
function isImeCompositionKey(event) {
|
|
1635
|
+
return event.isComposing || event.keyCode === 229;
|
|
1636
|
+
}
|
|
1637
|
+
function shouldSubmitChatInput(event, menuOpen) {
|
|
1638
|
+
return event.key === "Enter" && !event.shiftKey && !menuOpen && !isImeCompositionKey(event);
|
|
1639
|
+
}
|
|
1640
|
+
function ChatInput({
|
|
1641
|
+
value,
|
|
1642
|
+
onValueChange,
|
|
1643
|
+
onSend,
|
|
1644
|
+
onAppend,
|
|
1645
|
+
onStop,
|
|
1646
|
+
isStreaming,
|
|
1647
|
+
isStopping = false,
|
|
1648
|
+
placeholder = "\u8F93\u5165\u6D88\u606F\u2026",
|
|
1649
|
+
className,
|
|
1650
|
+
queueKey
|
|
1651
|
+
}) {
|
|
1652
|
+
const trimmed = value.trim();
|
|
1653
|
+
const [sendMode, setSendMode] = useState5("direct");
|
|
1654
|
+
void queueKey;
|
|
1655
|
+
const canSend = trimmed.length > 0 && (!isStreaming || sendMode === "queue");
|
|
1656
|
+
const handleSend = async () => {
|
|
1657
|
+
if (!canSend) return;
|
|
1658
|
+
if (isStreaming && sendMode === "queue") {
|
|
1659
|
+
if (onAppend) onAppend(trimmed);
|
|
1660
|
+
onValueChange("");
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
if (isStreaming && sendMode === "direct") {
|
|
1664
|
+
if (!onAppend) return;
|
|
1665
|
+
onAppend(trimmed);
|
|
1666
|
+
onValueChange("");
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
const accepted = await onSend(trimmed);
|
|
1670
|
+
if (!accepted) return;
|
|
1671
|
+
onValueChange("");
|
|
1672
|
+
};
|
|
1673
|
+
const handleKeyDown = (event) => {
|
|
1674
|
+
if (shouldSubmitChatInput({
|
|
1675
|
+
key: event.key,
|
|
1676
|
+
shiftKey: event.shiftKey,
|
|
1677
|
+
isComposing: event.nativeEvent.isComposing,
|
|
1678
|
+
keyCode: event.nativeEvent.keyCode
|
|
1679
|
+
}, false)) {
|
|
1680
|
+
event.preventDefault();
|
|
1681
|
+
void handleSend();
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
return /* @__PURE__ */ jsxs4("div", { className: cn("blade-chat-input border-t border-[hsl(var(--border))] py-3", className), children: [
|
|
1685
|
+
/* @__PURE__ */ jsx5("div", { className: "mx-auto mb-2 flex max-w-[748px] items-center justify-between px-1 text-xs text-[hsl(var(--muted-foreground))]", children: /* @__PURE__ */ jsxs4("fieldset", { className: "flex items-center gap-1 rounded-md border border-[hsl(var(--border))] p-0.5", children: [
|
|
1686
|
+
/* @__PURE__ */ jsx5("legend", { className: "sr-only", children: "\u53D1\u9001\u65B9\u5F0F" }),
|
|
1687
|
+
/* @__PURE__ */ jsx5("button", { type: "button", onClick: () => setSendMode("direct"), "aria-pressed": sendMode === "direct", disabled: isStreaming && !onAppend, className: `rounded px-2 py-1 ${sendMode === "direct" ? "bg-[hsl(var(--accent))] text-[hsl(var(--foreground))]" : ""}`, children: "\u76F4\u63A5\u63D2\u5165" }),
|
|
1688
|
+
/* @__PURE__ */ jsx5("button", { type: "button", onClick: () => setSendMode("queue"), "aria-pressed": sendMode === "queue", className: `rounded px-2 py-1 ${sendMode === "queue" ? "bg-[hsl(var(--accent))] text-[hsl(var(--foreground))]" : ""}`, children: "\u6392\u961F\u6267\u884C" })
|
|
1689
|
+
] }) }),
|
|
1690
|
+
/* @__PURE__ */ jsxs4("div", { className: "blade-chat-input-inner mx-auto flex max-w-[748px] items-end gap-2 rounded-2xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-3 py-2", children: [
|
|
1691
|
+
/* @__PURE__ */ jsx5(
|
|
1692
|
+
"textarea",
|
|
1693
|
+
{
|
|
1694
|
+
value,
|
|
1695
|
+
onChange: (event) => onValueChange(event.target.value),
|
|
1696
|
+
onKeyDown: handleKeyDown,
|
|
1697
|
+
onInput: (event) => {
|
|
1698
|
+
const el = event.currentTarget;
|
|
1699
|
+
el.style.height = "auto";
|
|
1700
|
+
el.style.height = `${Math.min(el.scrollHeight, 192)}px`;
|
|
1701
|
+
},
|
|
1702
|
+
rows: 1,
|
|
1703
|
+
placeholder,
|
|
1704
|
+
"aria-label": "\u804A\u5929\u8F93\u5165",
|
|
1705
|
+
className: "blade-chat-textarea max-h-48 min-h-[28px] flex-1 resize-none bg-transparent py-1 text-sm leading-6 text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground)/0.6)]"
|
|
1706
|
+
}
|
|
1707
|
+
),
|
|
1708
|
+
isStreaming ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1709
|
+
sendMode === "queue" ? /* @__PURE__ */ jsx5("button", { type: "button", onClick: handleSend, disabled: !canSend, "aria-label": "\u52A0\u5165\u6392\u961F", title: "\u52A0\u5165\u6392\u961F", className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] disabled:opacity-40", children: /* @__PURE__ */ jsx5(ArrowUp, { size: 15 }) }) : null,
|
|
1710
|
+
/* @__PURE__ */ jsx5("button", { type: "button", onClick: onStop, disabled: isStopping, "aria-label": isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D", title: isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D", className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--muted))] text-[hsl(var(--foreground))] transition-opacity hover:opacity-90 disabled:opacity-60", children: isStopping ? /* @__PURE__ */ jsx5(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx5(Square, { size: 12, fill: "currentColor" }) })
|
|
1711
|
+
] }) : /* @__PURE__ */ jsx5(
|
|
1712
|
+
"button",
|
|
1713
|
+
{
|
|
1714
|
+
type: "button",
|
|
1715
|
+
onClick: handleSend,
|
|
1716
|
+
disabled: !canSend,
|
|
1717
|
+
"aria-label": "\u53D1\u9001\u6D88\u606F",
|
|
1718
|
+
title: "\u53D1\u9001\u6D88\u606F",
|
|
1719
|
+
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",
|
|
1720
|
+
children: /* @__PURE__ */ jsx5(ArrowUp, { size: 15 })
|
|
1721
|
+
}
|
|
1722
|
+
)
|
|
1723
|
+
] })
|
|
1724
|
+
] });
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
// src/components/ConnectionBanner.tsx
|
|
1728
|
+
import { useEffect as useEffect5, useRef as useRef5, useState as useState6 } from "react";
|
|
1729
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1730
|
+
var CONNECTION_NOTICE_DELAY_MS = 3e3;
|
|
1731
|
+
var CONNECTION_ERROR_DELAY_MS = 15e3;
|
|
1732
|
+
function useConnectionNoticePhase(connected) {
|
|
1733
|
+
const [phase, setPhase] = useState6("hidden");
|
|
1734
|
+
const connectedRef = useRef5(connected);
|
|
1735
|
+
const timersRef = useRef5([]);
|
|
1736
|
+
connectedRef.current = connected;
|
|
1737
|
+
useEffect5(() => {
|
|
1738
|
+
const clearTimers = () => {
|
|
1739
|
+
for (const timer of timersRef.current) clearTimeout(timer);
|
|
1740
|
+
timersRef.current = [];
|
|
1741
|
+
};
|
|
1742
|
+
const startGracePeriod = () => {
|
|
1743
|
+
clearTimers();
|
|
1744
|
+
setPhase("hidden");
|
|
1745
|
+
timersRef.current = [
|
|
1746
|
+
setTimeout(() => setPhase("recovering"), CONNECTION_NOTICE_DELAY_MS),
|
|
1747
|
+
setTimeout(() => setPhase("failed"), CONNECTION_ERROR_DELAY_MS)
|
|
1748
|
+
];
|
|
1749
|
+
};
|
|
1750
|
+
if (connected) {
|
|
1751
|
+
clearTimers();
|
|
1752
|
+
setPhase("hidden");
|
|
1753
|
+
} else {
|
|
1754
|
+
startGracePeriod();
|
|
1755
|
+
}
|
|
1756
|
+
const handleForeground = () => {
|
|
1757
|
+
if (!connectedRef.current) startGracePeriod();
|
|
1758
|
+
};
|
|
1759
|
+
const handleVisibilityChange = () => {
|
|
1760
|
+
if (document.visibilityState === "visible") handleForeground();
|
|
1761
|
+
};
|
|
1762
|
+
window.addEventListener("blade:app-active", handleForeground);
|
|
1763
|
+
window.addEventListener("focus", handleForeground);
|
|
1764
|
+
window.addEventListener("pageshow", handleForeground);
|
|
1765
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
1766
|
+
return () => {
|
|
1767
|
+
clearTimers();
|
|
1768
|
+
window.removeEventListener("blade:app-active", handleForeground);
|
|
1769
|
+
window.removeEventListener("focus", handleForeground);
|
|
1770
|
+
window.removeEventListener("pageshow", handleForeground);
|
|
1771
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
1772
|
+
};
|
|
1773
|
+
}, [connected]);
|
|
1774
|
+
return phase;
|
|
1775
|
+
}
|
|
1776
|
+
function ConnectionBanner({ connection, className }) {
|
|
1777
|
+
const hasConnectedRef = useRef5(connection === "connected" || connection === "reconnecting");
|
|
1778
|
+
if (connection === "connected") hasConnectedRef.current = true;
|
|
1779
|
+
const connected = connection === "connected";
|
|
1780
|
+
const phase = useConnectionNoticePhase(connected);
|
|
1781
|
+
if (connected || phase === "hidden") return null;
|
|
1782
|
+
const recovering = phase === "recovering";
|
|
1783
|
+
const firstConnection = !hasConnectedRef.current;
|
|
1784
|
+
return /* @__PURE__ */ jsx6("div", { className: cn("blade-chat-banner bg-[hsl(var(--background))] px-5 pt-3", className), children: /* @__PURE__ */ jsxs5(
|
|
1785
|
+
"div",
|
|
1786
|
+
{
|
|
1787
|
+
className: cn(
|
|
1788
|
+
"mx-auto flex max-w-3xl items-start gap-3 rounded-2xl border px-4 py-3",
|
|
1789
|
+
recovering ? "border-amber-500/25 bg-amber-500/10 text-amber-100" : "border-rose-500/25 bg-rose-500/10 text-rose-100"
|
|
1790
|
+
),
|
|
1791
|
+
children: [
|
|
1792
|
+
/* @__PURE__ */ jsx6("span", { className: "mt-0.5 shrink-0", children: recovering ? /* @__PURE__ */ jsx6(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx6(TriangleAlert, { size: 14 }) }),
|
|
1793
|
+
/* @__PURE__ */ jsxs5("div", { className: "min-w-0", children: [
|
|
1794
|
+
/* @__PURE__ */ jsx6("div", { className: "text-sm font-medium", children: recovering ? firstConnection ? "\u6B63\u5728\u8FDE\u63A5\u2026" : "\u6B63\u5728\u6062\u590D\u8FDE\u63A5\u2026" : "\u6682\u65F6\u65E0\u6CD5\u8FDE\u63A5" }),
|
|
1795
|
+
/* @__PURE__ */ jsx6("div", { className: "text-xs opacity-80", children: recovering ? "\u6062\u590D\u540E\u4F1A\u81EA\u52A8\u540C\u6B65\u6700\u65B0\u6D88\u606F\uFF0C\u8BF7\u7A0D\u5019" : "\u8BF7\u68C0\u67E5\u7F51\u7EDC\u6216\u670D\u52A1\u72B6\u6001\uFF0C\u7CFB\u7EDF\u4F1A\u7EE7\u7EED\u81EA\u52A8\u91CD\u8BD5" })
|
|
1796
|
+
] })
|
|
1797
|
+
]
|
|
1798
|
+
}
|
|
1799
|
+
) });
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
// src/components/MessageList.tsx
|
|
1803
|
+
import { isHiddenInternalMessage } from "@blade-hq/agent-client";
|
|
1804
|
+
import { useCallback as useCallback7, useEffect as useEffect11, useMemo as useMemo7, useRef as useRef12, useState as useState14 } from "react";
|
|
1805
|
+
|
|
1806
|
+
// ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/useStickToBottom.js
|
|
1807
|
+
import { useCallback as useCallback4, useMemo as useMemo3, useRef as useRef6, useState as useState7 } from "react";
|
|
1808
|
+
var DEFAULT_SPRING_ANIMATION = {
|
|
1809
|
+
/**
|
|
1810
|
+
* A value from 0 to 1, on how much to damp the animation.
|
|
1811
|
+
* 0 means no damping, 1 means full damping.
|
|
1812
|
+
*
|
|
1813
|
+
* @default 0.7
|
|
1814
|
+
*/
|
|
1815
|
+
damping: 0.7,
|
|
1816
|
+
/**
|
|
1817
|
+
* The stiffness of how fast/slow the animation gets up to speed.
|
|
1818
|
+
*
|
|
1819
|
+
* @default 0.05
|
|
1820
|
+
*/
|
|
1821
|
+
stiffness: 0.05,
|
|
1822
|
+
/**
|
|
1823
|
+
* The inertial mass associated with the animation.
|
|
1824
|
+
* Higher numbers make the animation slower.
|
|
1825
|
+
*
|
|
1826
|
+
* @default 1.25
|
|
1827
|
+
*/
|
|
1828
|
+
mass: 1.25
|
|
1829
|
+
};
|
|
1830
|
+
var STICK_TO_BOTTOM_OFFSET_PX = 70;
|
|
1831
|
+
var SIXTY_FPS_INTERVAL_MS = 1e3 / 60;
|
|
1832
|
+
var RETAIN_ANIMATION_DURATION_MS = 350;
|
|
1833
|
+
var mouseDown = false;
|
|
1834
|
+
globalThis.document?.addEventListener("mousedown", () => {
|
|
1835
|
+
mouseDown = true;
|
|
1836
|
+
});
|
|
1837
|
+
globalThis.document?.addEventListener("mouseup", () => {
|
|
1838
|
+
mouseDown = false;
|
|
1839
|
+
});
|
|
1840
|
+
globalThis.document?.addEventListener("click", () => {
|
|
1841
|
+
mouseDown = false;
|
|
1842
|
+
});
|
|
1843
|
+
var useStickToBottom = (options = {}) => {
|
|
1844
|
+
const [escapedFromLock, updateEscapedFromLock] = useState7(false);
|
|
1845
|
+
const [isAtBottom, updateIsAtBottom] = useState7(options.initial !== false);
|
|
1846
|
+
const [isNearBottom, setIsNearBottom] = useState7(false);
|
|
1847
|
+
const optionsRef = useRef6(null);
|
|
1848
|
+
optionsRef.current = options;
|
|
1849
|
+
const isSelecting = useCallback4(() => {
|
|
1850
|
+
if (!mouseDown) {
|
|
1851
|
+
return false;
|
|
1852
|
+
}
|
|
1853
|
+
const selection = window.getSelection();
|
|
1854
|
+
if (!selection || !selection.rangeCount) {
|
|
1855
|
+
return false;
|
|
1856
|
+
}
|
|
1857
|
+
const range = selection.getRangeAt(0);
|
|
1858
|
+
return range.commonAncestorContainer.contains(scrollRef.current) || scrollRef.current?.contains(range.commonAncestorContainer);
|
|
1859
|
+
}, []);
|
|
1860
|
+
const setIsAtBottom = useCallback4((isAtBottom2) => {
|
|
1861
|
+
state.isAtBottom = isAtBottom2;
|
|
1139
1862
|
updateIsAtBottom(isAtBottom2);
|
|
1140
1863
|
}, []);
|
|
1141
|
-
const setEscapedFromLock =
|
|
1864
|
+
const setEscapedFromLock = useCallback4((escapedFromLock2) => {
|
|
1142
1865
|
state.escapedFromLock = escapedFromLock2;
|
|
1143
1866
|
updateEscapedFromLock(escapedFromLock2);
|
|
1144
1867
|
}, []);
|
|
@@ -1195,7 +1918,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
1195
1918
|
}
|
|
1196
1919
|
};
|
|
1197
1920
|
}, []);
|
|
1198
|
-
const scrollToBottom =
|
|
1921
|
+
const scrollToBottom = useCallback4((scrollOptions = {}) => {
|
|
1199
1922
|
if (typeof scrollOptions === "string") {
|
|
1200
1923
|
scrollOptions = { animation: scrollOptions };
|
|
1201
1924
|
}
|
|
@@ -1280,11 +2003,11 @@ var useStickToBottom = (options = {}) => {
|
|
|
1280
2003
|
}
|
|
1281
2004
|
return next();
|
|
1282
2005
|
}, [setIsAtBottom, isSelecting, state]);
|
|
1283
|
-
const stopScroll =
|
|
2006
|
+
const stopScroll = useCallback4(() => {
|
|
1284
2007
|
setEscapedFromLock(true);
|
|
1285
2008
|
setIsAtBottom(false);
|
|
1286
2009
|
}, [setEscapedFromLock, setIsAtBottom]);
|
|
1287
|
-
const handleScroll =
|
|
2010
|
+
const handleScroll = useCallback4(({ target }) => {
|
|
1288
2011
|
if (target !== scrollRef.current) {
|
|
1289
2012
|
return;
|
|
1290
2013
|
}
|
|
@@ -1323,7 +2046,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
1323
2046
|
}
|
|
1324
2047
|
}, 1);
|
|
1325
2048
|
}, [setEscapedFromLock, setIsAtBottom, isSelecting, state]);
|
|
1326
|
-
const handleWheel =
|
|
2049
|
+
const handleWheel = useCallback4(({ target, deltaY }) => {
|
|
1327
2050
|
let element = target;
|
|
1328
2051
|
while (!["scroll", "auto"].includes(getComputedStyle(element).overflow)) {
|
|
1329
2052
|
if (!element.parentElement) {
|
|
@@ -1393,7 +2116,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
1393
2116
|
};
|
|
1394
2117
|
};
|
|
1395
2118
|
function useRefCallback(callback, deps) {
|
|
1396
|
-
const result =
|
|
2119
|
+
const result = useCallback4((ref) => {
|
|
1397
2120
|
result.current = ref;
|
|
1398
2121
|
return callback(ref);
|
|
1399
2122
|
}, deps);
|
|
@@ -1425,11 +2148,11 @@ function mergeAnimations(...animations) {
|
|
|
1425
2148
|
|
|
1426
2149
|
// ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/StickToBottom.js
|
|
1427
2150
|
import * as React from "react";
|
|
1428
|
-
import { createContext as createContext2, useContext as useContext2, useEffect as
|
|
2151
|
+
import { createContext as createContext2, useContext as useContext2, useEffect as useEffect6, useImperativeHandle, useLayoutEffect, useMemo as useMemo4, useRef as useRef7 } from "react";
|
|
1429
2152
|
var StickToBottomContext = createContext2(null);
|
|
1430
|
-
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect :
|
|
2153
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect6;
|
|
1431
2154
|
function StickToBottom({ instance, children, resize, initial, mass, damping, stiffness, targetScrollTop: currentTargetScrollTop, contextRef, ...props }) {
|
|
1432
|
-
const customTargetScrollTop =
|
|
2155
|
+
const customTargetScrollTop = useRef7(null);
|
|
1433
2156
|
const targetScrollTop = React.useCallback((target, elements) => {
|
|
1434
2157
|
const get = context?.targetScrollTop ?? currentTargetScrollTop;
|
|
1435
2158
|
return get?.(target, elements) ?? target;
|
|
@@ -1478,160 +2201,44 @@ function StickToBottom({ instance, children, resize, initial, mass, damping, sti
|
|
|
1478
2201
|
return React.createElement(
|
|
1479
2202
|
StickToBottomContext.Provider,
|
|
1480
2203
|
{ value: context },
|
|
1481
|
-
React.createElement("div", { ...props }, typeof children === "function" ? children(context) : children)
|
|
1482
|
-
);
|
|
1483
|
-
}
|
|
1484
|
-
(function(StickToBottom2) {
|
|
1485
|
-
function Content({ children, scrollClassName, ...props }) {
|
|
1486
|
-
const context = useStickToBottomContext();
|
|
1487
|
-
return React.createElement(
|
|
1488
|
-
"div",
|
|
1489
|
-
{ ref: context.scrollRef, style: {
|
|
1490
|
-
height: "100%",
|
|
1491
|
-
width: "100%",
|
|
1492
|
-
scrollbarGutter: "stable both-edges"
|
|
1493
|
-
}, className: scrollClassName },
|
|
1494
|
-
React.createElement("div", { ...props, ref: context.contentRef }, typeof children === "function" ? children(context) : children)
|
|
1495
|
-
);
|
|
1496
|
-
}
|
|
1497
|
-
StickToBottom2.Content = Content;
|
|
1498
|
-
})(StickToBottom || (StickToBottom = {}));
|
|
1499
|
-
function useStickToBottomContext() {
|
|
1500
|
-
const context = useContext2(StickToBottomContext);
|
|
1501
|
-
if (!context) {
|
|
1502
|
-
throw new Error("use-stick-to-bottom component context must be used within a StickToBottom component");
|
|
1503
|
-
}
|
|
1504
|
-
return context;
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
// src/components/AssistantTurnBlock.tsx
|
|
1508
|
-
import { getTextContent, normalizeMessageContent } from "@blade-hq/agent-client";
|
|
1509
|
-
import { useState as useState9 } from "react";
|
|
1510
|
-
|
|
1511
|
-
// src/components/AgentLoopBlock.tsx
|
|
1512
|
-
import { useState as useState5 } from "react";
|
|
1513
|
-
|
|
1514
|
-
// src/components/display-utils.ts
|
|
1515
|
-
var TOOL_NAME_ALIASES = {
|
|
1516
|
-
agent: "Agent",
|
|
1517
|
-
ask_user_question: "AskUserQuestion",
|
|
1518
|
-
bash: "Bash",
|
|
1519
|
-
bg_bash: "BgBash",
|
|
1520
|
-
edit: "Edit",
|
|
1521
|
-
exit_plan_mode: "ExitPlanMode",
|
|
1522
|
-
file_edit: "Edit",
|
|
1523
|
-
file_read: "Read",
|
|
1524
|
-
file_write: "Write",
|
|
1525
|
-
finish_task: "FinishTask",
|
|
1526
|
-
glob: "Glob",
|
|
1527
|
-
grep: "Grep",
|
|
1528
|
-
ls: "Ls",
|
|
1529
|
-
read: "Read",
|
|
1530
|
-
read_skill: "ReadSkill",
|
|
1531
|
-
web_fetch: "WebFetch",
|
|
1532
|
-
web_search: "WebSearch",
|
|
1533
|
-
write: "Write"
|
|
1534
|
-
};
|
|
1535
|
-
var TOOL_DISPLAY_LABELS = {
|
|
1536
|
-
Bash: "\u6267\u884C\u547D\u4EE4",
|
|
1537
|
-
BgBash: "\u540E\u53F0\u6267\u884C\u547D\u4EE4",
|
|
1538
|
-
Read: "\u8BFB\u53D6\u6587\u4EF6",
|
|
1539
|
-
Write: "\u5199\u5165\u6587\u4EF6",
|
|
1540
|
-
Edit: "\u7F16\u8F91\u6587\u4EF6",
|
|
1541
|
-
Ls: "\u5217\u51FA\u76EE\u5F55",
|
|
1542
|
-
Glob: "\u5339\u914D\u6587\u4EF6",
|
|
1543
|
-
Grep: "\u641C\u7D22\u6587\u672C",
|
|
1544
|
-
WebSearch: "\u641C\u7D22\u7F51\u9875",
|
|
1545
|
-
WebFetch: "\u6574\u7406\u7F51\u9875\u5185\u5BB9",
|
|
1546
|
-
Agent: "\u6D3E\u751F\u5B50\u667A\u80FD\u4F53",
|
|
1547
|
-
AskUserQuestion: "\u5411\u7528\u6237\u63D0\u95EE",
|
|
1548
|
-
ReadSkill: "\u8BFB\u53D6\u6280\u80FD",
|
|
1549
|
-
FinishTask: "\u4EFB\u52A1\u5B8C\u6210",
|
|
1550
|
-
ExitPlanMode: "\u63D0\u4EA4\u8BA1\u5212",
|
|
1551
|
-
ListSessions: "\u5217\u51FA\u5386\u53F2\u4F1A\u8BDD",
|
|
1552
|
-
GetSessionHistory: "\u8BFB\u53D6\u4F1A\u8BDD\u5386\u53F2"
|
|
1553
|
-
};
|
|
1554
|
-
function safeParseJson(value) {
|
|
1555
|
-
if (!value) return null;
|
|
1556
|
-
try {
|
|
1557
|
-
return JSON.parse(value);
|
|
1558
|
-
} catch {
|
|
1559
|
-
return null;
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
function getStringArgValue(args, key) {
|
|
1563
|
-
const value = args?.[key];
|
|
1564
|
-
return typeof value === "string" ? value.trim() : "";
|
|
1565
|
-
}
|
|
1566
|
-
function formatToolName(name) {
|
|
1567
|
-
const trimmed = name.trim();
|
|
1568
|
-
if (!trimmed) return name;
|
|
1569
|
-
const stripped = trimmed.split(":").pop()?.split("/").pop()?.split(".").pop()?.trim() || trimmed;
|
|
1570
|
-
const normalized = stripped.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
1571
|
-
return TOOL_NAME_ALIASES[normalized] ?? stripped;
|
|
1572
|
-
}
|
|
1573
|
-
function getToolDisplayLabel(toolCall) {
|
|
1574
|
-
const normalized = formatToolName(toolCall.name);
|
|
1575
|
-
const args = safeParseJson(toolCall.arguments);
|
|
1576
|
-
const displayName = toolCall.display_name?.trim() ?? "";
|
|
1577
|
-
const baseLabel = displayName || TOOL_DISPLAY_LABELS[normalized] || normalized;
|
|
1578
|
-
const metaDisplayName = getStringArgValue(args, "_meta_display_name");
|
|
1579
|
-
if (metaDisplayName) {
|
|
1580
|
-
return metaDisplayName;
|
|
1581
|
-
}
|
|
1582
|
-
const description = getStringArgValue(args, "description");
|
|
1583
|
-
if (normalized === "BgBash") {
|
|
1584
|
-
return description ? `\u540E\u53F0\u6267\u884C\uFF1A${description}` : "\u540E\u53F0\u6267\u884C\u547D\u4EE4";
|
|
1585
|
-
}
|
|
1586
|
-
if (normalized === "ReadSkill") {
|
|
1587
|
-
const skillName = getStringArgValue(args, "skill") || getStringArgValue(args, "skill_name");
|
|
1588
|
-
return skillName ? `${baseLabel}\u300C${skillName}\u300D` : baseLabel;
|
|
1589
|
-
}
|
|
1590
|
-
if (normalized === "FinishTask") {
|
|
1591
|
-
const title = getStringArgValue(args, "title");
|
|
1592
|
-
return title ? `${baseLabel}\uFF1A${title}` : baseLabel;
|
|
1593
|
-
}
|
|
1594
|
-
return description || baseLabel;
|
|
1595
|
-
}
|
|
1596
|
-
function getToolTone(status) {
|
|
1597
|
-
if (status === "error" || status === "cancelled") return "red";
|
|
1598
|
-
if (status === "awaiting_answer") return "amber";
|
|
1599
|
-
if (status === "pending") return "blue";
|
|
1600
|
-
return "emerald";
|
|
1601
|
-
}
|
|
1602
|
-
function getToolStatusLabel(status) {
|
|
1603
|
-
if (status === "pending") return "\u8FD0\u884C\u4E2D";
|
|
1604
|
-
if (status === "awaiting_answer") return "\u7B49\u5F85\u56DE\u7B54";
|
|
1605
|
-
if (status === "error") return "\u9519\u8BEF";
|
|
1606
|
-
if (status === "cancelled") return "\u5DF2\u53D6\u6D88";
|
|
1607
|
-
return "\u5B8C\u6210";
|
|
1608
|
-
}
|
|
1609
|
-
function formatToolDuration(ms) {
|
|
1610
|
-
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
1611
|
-
const seconds = ms / 1e3;
|
|
1612
|
-
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
1613
|
-
const minutes = Math.floor(seconds / 60);
|
|
1614
|
-
const remainingSeconds = Math.round(seconds % 60);
|
|
1615
|
-
return remainingSeconds > 0 ? `${minutes}m${remainingSeconds}s` : `${minutes}m`;
|
|
2204
|
+
React.createElement("div", { ...props }, typeof children === "function" ? children(context) : children)
|
|
2205
|
+
);
|
|
1616
2206
|
}
|
|
1617
|
-
function
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
2207
|
+
(function(StickToBottom2) {
|
|
2208
|
+
function Content({ children, scrollClassName, ...props }) {
|
|
2209
|
+
const context = useStickToBottomContext();
|
|
2210
|
+
return React.createElement(
|
|
2211
|
+
"div",
|
|
2212
|
+
{ ref: context.scrollRef, style: {
|
|
2213
|
+
height: "100%",
|
|
2214
|
+
width: "100%",
|
|
2215
|
+
scrollbarGutter: "stable both-edges"
|
|
2216
|
+
}, className: scrollClassName },
|
|
2217
|
+
React.createElement("div", { ...props, ref: context.contentRef }, typeof children === "function" ? children(context) : children)
|
|
2218
|
+
);
|
|
1622
2219
|
}
|
|
2220
|
+
StickToBottom2.Content = Content;
|
|
2221
|
+
})(StickToBottom || (StickToBottom = {}));
|
|
2222
|
+
function useStickToBottomContext() {
|
|
2223
|
+
const context = useContext2(StickToBottomContext);
|
|
2224
|
+
if (!context) {
|
|
2225
|
+
throw new Error("use-stick-to-bottom component context must be used within a StickToBottom component");
|
|
2226
|
+
}
|
|
2227
|
+
return context;
|
|
1623
2228
|
}
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
}
|
|
2229
|
+
|
|
2230
|
+
// src/components/AssistantTurnBlock.tsx
|
|
2231
|
+
import {
|
|
2232
|
+
getFileParts,
|
|
2233
|
+
getImageParts,
|
|
2234
|
+
getTextContent,
|
|
2235
|
+
normalizeMessageContent
|
|
2236
|
+
} from "@blade-hq/agent-client";
|
|
2237
|
+
import { useEffect as useEffect9, useRef as useRef10, useState as useState12 } from "react";
|
|
1632
2238
|
|
|
1633
2239
|
// src/components/AgentLoopBlock.tsx
|
|
1634
|
-
import {
|
|
2240
|
+
import { useState as useState8 } from "react";
|
|
2241
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1635
2242
|
function parseAgentDescription(argumentsJson) {
|
|
1636
2243
|
try {
|
|
1637
2244
|
const parsed = JSON.parse(argumentsJson);
|
|
@@ -1641,83 +2248,81 @@ function parseAgentDescription(argumentsJson) {
|
|
|
1641
2248
|
}
|
|
1642
2249
|
}
|
|
1643
2250
|
function AgentLoopBlock({ toolCall }) {
|
|
1644
|
-
const [expanded, setExpanded] =
|
|
2251
|
+
const [expanded, setExpanded] = useState8(false);
|
|
1645
2252
|
const description = parseAgentDescription(toolCall.arguments);
|
|
1646
2253
|
const running = toolCall.status === "pending" || toolCall.status === "awaiting_answer";
|
|
1647
2254
|
const failed = toolCall.status === "error" || toolCall.status === "cancelled";
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
2255
|
+
const hasResult = toolCall.result != null;
|
|
2256
|
+
const iconClass = cn(
|
|
2257
|
+
"size-3.5 shrink-0",
|
|
2258
|
+
failed ? "text-red-500" : "text-[hsl(var(--muted-foreground))]"
|
|
2259
|
+
);
|
|
2260
|
+
return /* @__PURE__ */ jsxs6("div", { className: "blade-chat-agent-loop text-xs leading-[22px]", children: [
|
|
2261
|
+
/* @__PURE__ */ jsxs6(
|
|
2262
|
+
"button",
|
|
1651
2263
|
{
|
|
2264
|
+
type: "button",
|
|
2265
|
+
onClick: () => hasResult && setExpanded(!expanded),
|
|
2266
|
+
disabled: !hasResult,
|
|
2267
|
+
"aria-expanded": hasResult ? expanded : void 0,
|
|
2268
|
+
"data-testid": "execution-tool-intent",
|
|
1652
2269
|
className: cn(
|
|
1653
|
-
"
|
|
1654
|
-
|
|
2270
|
+
"flex min-w-0 items-center gap-1 py-1.5 text-left",
|
|
2271
|
+
hasResult && "cursor-pointer hover:text-[hsl(var(--foreground))]",
|
|
2272
|
+
failed ? "text-red-500" : "text-[hsl(var(--muted-foreground))]"
|
|
1655
2273
|
),
|
|
2274
|
+
title: `\u5B50\u4EFB\u52A1\uFF1A${description}`,
|
|
1656
2275
|
children: [
|
|
1657
|
-
/* @__PURE__ */
|
|
1658
|
-
|
|
2276
|
+
running ? /* @__PURE__ */ jsx7(LoaderCircle, { className: cn(iconClass, "animate-spin"), "aria-hidden": "true" }) : toolCall.status === "error" ? /* @__PURE__ */ jsx7(CircleAlert, { className: iconClass, "aria-hidden": "true" }) : toolCall.status === "cancelled" ? /* @__PURE__ */ jsx7(X, { className: iconClass, "aria-hidden": "true" }) : /* @__PURE__ */ jsx7(Bot, { className: iconClass, "aria-hidden": "true" }),
|
|
2277
|
+
/* @__PURE__ */ jsxs6("span", { className: "min-w-0 truncate", children: [
|
|
2278
|
+
"\u5B50\u4EFB\u52A1\uFF1A",
|
|
2279
|
+
description
|
|
2280
|
+
] }),
|
|
2281
|
+
hasResult ? /* @__PURE__ */ jsx7(
|
|
2282
|
+
ChevronRight,
|
|
1659
2283
|
{
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
className:
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
{
|
|
1668
|
-
size: 11,
|
|
1669
|
-
className: cn(
|
|
1670
|
-
"shrink-0 text-[hsl(var(--muted-foreground))] transition-transform",
|
|
1671
|
-
expanded && "rotate-90"
|
|
1672
|
-
)
|
|
1673
|
-
}
|
|
1674
|
-
),
|
|
1675
|
-
/* @__PURE__ */ jsx6(Bot, { size: 12, className: "shrink-0 text-[hsl(var(--muted-foreground))]" }),
|
|
1676
|
-
/* @__PURE__ */ jsxs5(
|
|
1677
|
-
"span",
|
|
1678
|
-
{
|
|
1679
|
-
className: cn(
|
|
1680
|
-
"flex shrink-0 items-center gap-1 text-[10px]",
|
|
1681
|
-
failed ? "text-[hsl(var(--muted-foreground))]" : running ? "text-blue-300" : "text-[hsl(var(--primary))]"
|
|
1682
|
-
),
|
|
1683
|
-
children: [
|
|
1684
|
-
running ? /* @__PURE__ */ jsx6(LoaderCircle, { size: 11, className: "animate-spin" }) : failed ? /* @__PURE__ */ jsx6(X, { size: 11 }) : /* @__PURE__ */ jsx6(Check, { size: 11 }),
|
|
1685
|
-
/* @__PURE__ */ jsx6("span", { children: running ? "\u6267\u884C\u4E2D" : failed ? "\u5DF2\u7EC8\u6B62" : "\u5B8C\u6210" })
|
|
1686
|
-
]
|
|
1687
|
-
}
|
|
1688
|
-
),
|
|
1689
|
-
/* @__PURE__ */ jsxs5("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: [
|
|
1690
|
-
"\u5B50\u667A\u80FD\u4F53\uFF1A",
|
|
1691
|
-
description
|
|
1692
|
-
] })
|
|
1693
|
-
]
|
|
2284
|
+
size: 14,
|
|
2285
|
+
style: { transitionDuration: "260ms", transitionTimingFunction: "cubic-bezier(0.25, 0.1, 0.25, 1)" },
|
|
2286
|
+
className: cn(
|
|
2287
|
+
"shrink-0 transition-transform",
|
|
2288
|
+
expanded && "rotate-90"
|
|
2289
|
+
),
|
|
2290
|
+
"aria-hidden": "true"
|
|
1694
2291
|
}
|
|
1695
|
-
)
|
|
1696
|
-
typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 && /* @__PURE__ */ jsx6("span", { className: "shrink-0 font-mono text-[10px] text-[hsl(var(--muted-foreground))]", children: formatToolDuration(toolCall.duration_ms) })
|
|
2292
|
+
) : null
|
|
1697
2293
|
]
|
|
1698
2294
|
}
|
|
1699
2295
|
),
|
|
1700
|
-
expanded &&
|
|
1701
|
-
/* @__PURE__ */ jsx6("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u7ED3\u679C" }),
|
|
1702
|
-
/* @__PURE__ */ jsx6("pre", { className: "max-h-[400px] overflow-auto whitespace-pre-wrap rounded-md bg-[hsl(var(--muted))] p-2 font-mono text-[11px] text-[hsl(var(--foreground))]", children: formatToolResult(toolCall.result) })
|
|
1703
|
-
] })
|
|
2296
|
+
expanded && hasResult ? /* @__PURE__ */ jsx7("div", { className: "ml-[18px] mt-1.5 max-h-[400px] overflow-auto whitespace-pre-wrap text-xs leading-[22px] text-[hsl(var(--muted-foreground))]", children: formatToolResult(toolCall.result) }) : null
|
|
1704
2297
|
] });
|
|
1705
2298
|
}
|
|
1706
2299
|
|
|
1707
2300
|
// src/components/MarkdownContent.tsx
|
|
1708
2301
|
import {
|
|
1709
|
-
useEffect as
|
|
2302
|
+
useEffect as useEffect7,
|
|
1710
2303
|
useMemo as useMemo5,
|
|
1711
|
-
useRef as
|
|
1712
|
-
useState as
|
|
2304
|
+
useRef as useRef8,
|
|
2305
|
+
useState as useState9
|
|
1713
2306
|
} from "react";
|
|
1714
|
-
import { jsx as
|
|
2307
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1715
2308
|
var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
|
|
2309
|
+
function normalizeAdjacentUrlFormatting(value) {
|
|
2310
|
+
const protectedSegments = [];
|
|
2311
|
+
const protectedValue = value.replace(/(`{1,3}[\s\S]*?`{1,3}|\[[^\]]*\]\([^)]*\))/g, (segment) => {
|
|
2312
|
+
const index = protectedSegments.push(segment) - 1;
|
|
2313
|
+
return `blade-url-protected-${index}-marker`;
|
|
2314
|
+
});
|
|
2315
|
+
const normalized = protectedValue.replace(
|
|
2316
|
+
/(\*\*|__|~~|\*|_)(https?:\/\/[^\s<>]+?)\1(?=[\s。,、!?;:,.!?;:]|$)/g,
|
|
2317
|
+
(_, marker, url) => `${marker}[${url}](<${url}>)${marker}`
|
|
2318
|
+
);
|
|
2319
|
+
return normalized.replace(/blade-url-protected-(\d+)-marker/g, (_, index) => protectedSegments[Number(index)]);
|
|
2320
|
+
}
|
|
1716
2321
|
function CodeBlockPre({ children, node: _node, ...props }) {
|
|
1717
|
-
const preRef =
|
|
1718
|
-
const [copied, setCopied] =
|
|
1719
|
-
const [language, setLanguage] =
|
|
1720
|
-
|
|
2322
|
+
const preRef = useRef8(null);
|
|
2323
|
+
const [copied, setCopied] = useState9(false);
|
|
2324
|
+
const [language, setLanguage] = useState9("");
|
|
2325
|
+
useEffect7(() => {
|
|
1721
2326
|
const codeEl = preRef.current?.querySelector("code");
|
|
1722
2327
|
setLanguage(codeEl?.className.match(/language-(\S+)/)?.[1] ?? "");
|
|
1723
2328
|
}, []);
|
|
@@ -1728,10 +2333,10 @@ function CodeBlockPre({ children, node: _node, ...props }) {
|
|
|
1728
2333
|
setTimeout(() => setCopied(false), 2e3);
|
|
1729
2334
|
}
|
|
1730
2335
|
};
|
|
1731
|
-
return /* @__PURE__ */
|
|
1732
|
-
/* @__PURE__ */
|
|
1733
|
-
/* @__PURE__ */
|
|
1734
|
-
/* @__PURE__ */
|
|
2336
|
+
return /* @__PURE__ */ jsxs7("div", { className: "blade-chat-codeblock not-prose my-3 overflow-hidden rounded-xl border border-[hsl(var(--border))]", children: [
|
|
2337
|
+
/* @__PURE__ */ jsxs7("div", { className: "blade-chat-codeblock-header flex h-[34px] items-center justify-between border-b border-[hsl(var(--border))] bg-[hsl(var(--muted))/0.5] pl-3.5 pr-1.5", children: [
|
|
2338
|
+
/* @__PURE__ */ jsx8("span", { className: "font-mono text-[12px] text-[hsl(var(--muted-foreground))]", children: language || "code" }),
|
|
2339
|
+
/* @__PURE__ */ jsxs7(
|
|
1735
2340
|
"button",
|
|
1736
2341
|
{
|
|
1737
2342
|
type: "button",
|
|
@@ -1741,13 +2346,13 @@ function CodeBlockPre({ children, node: _node, ...props }) {
|
|
|
1741
2346
|
copied ? "text-[hsl(var(--primary))]" : "text-[hsl(var(--muted-foreground))] hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--foreground))]"
|
|
1742
2347
|
),
|
|
1743
2348
|
children: [
|
|
1744
|
-
copied ? /* @__PURE__ */
|
|
1745
|
-
/* @__PURE__ */
|
|
2349
|
+
copied ? /* @__PURE__ */ jsx8(Check, { size: 12 }) : /* @__PURE__ */ jsx8(Copy, { size: 12 }),
|
|
2350
|
+
/* @__PURE__ */ jsx8("span", { children: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236" })
|
|
1746
2351
|
]
|
|
1747
2352
|
}
|
|
1748
2353
|
)
|
|
1749
2354
|
] }),
|
|
1750
|
-
/* @__PURE__ */
|
|
2355
|
+
/* @__PURE__ */ jsx8(
|
|
1751
2356
|
"pre",
|
|
1752
2357
|
{
|
|
1753
2358
|
ref: preRef,
|
|
@@ -1759,7 +2364,7 @@ function CodeBlockPre({ children, node: _node, ...props }) {
|
|
|
1759
2364
|
] });
|
|
1760
2365
|
}
|
|
1761
2366
|
function ExternalAnchor({ node: _node, children, ...props }) {
|
|
1762
|
-
return /* @__PURE__ */
|
|
2367
|
+
return /* @__PURE__ */ jsx8("a", { ...props, target: "_blank", rel: "noopener noreferrer", children });
|
|
1763
2368
|
}
|
|
1764
2369
|
var MARKDOWN_COMPONENTS = {
|
|
1765
2370
|
pre: CodeBlockPre,
|
|
@@ -1767,9 +2372,9 @@ var MARKDOWN_COMPONENTS = {
|
|
|
1767
2372
|
};
|
|
1768
2373
|
function MarkdownContent({ children, className, mode, sessionId }) {
|
|
1769
2374
|
const resolvedChildren = useMemo5(() => {
|
|
1770
|
-
return children.replace(SYSTEM_REMINDER_RE, "");
|
|
2375
|
+
return normalizeAdjacentUrlFormatting(children.replace(SYSTEM_REMINDER_RE, ""));
|
|
1771
2376
|
}, [children]);
|
|
1772
|
-
return /* @__PURE__ */
|
|
2377
|
+
return /* @__PURE__ */ jsx8(
|
|
1773
2378
|
_r,
|
|
1774
2379
|
{
|
|
1775
2380
|
className: cn("blade-chat-markdown break-words", className),
|
|
@@ -1782,17 +2387,46 @@ function MarkdownContent({ children, className, mode, sessionId }) {
|
|
|
1782
2387
|
}
|
|
1783
2388
|
|
|
1784
2389
|
// src/components/Shimmer.tsx
|
|
1785
|
-
import { jsx as
|
|
2390
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
1786
2391
|
function Shimmer({ children = "\u6B63\u5728\u601D\u8003...", className }) {
|
|
1787
|
-
return /* @__PURE__ */
|
|
2392
|
+
return /* @__PURE__ */ jsx9("span", { className: cn("blade-shimmer-text text-sm font-medium", className), children });
|
|
1788
2393
|
}
|
|
1789
2394
|
|
|
1790
2395
|
// src/components/ToolCallBlock.tsx
|
|
1791
|
-
import { useState as
|
|
2396
|
+
import { useState as useState11 } from "react";
|
|
1792
2397
|
|
|
1793
2398
|
// src/components/AskUserQuestionBlock.tsx
|
|
1794
|
-
import { useEffect as
|
|
1795
|
-
import { jsx as
|
|
2399
|
+
import { useEffect as useEffect8, useMemo as useMemo6, useRef as useRef9, useState as useState10 } from "react";
|
|
2400
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2401
|
+
var CUSTOM_TEXTAREA_MAX_HEIGHT = 160;
|
|
2402
|
+
function resizeCustomTextarea(textarea) {
|
|
2403
|
+
textarea.style.height = "auto";
|
|
2404
|
+
textarea.style.height = `${Math.min(textarea.scrollHeight, CUSTOM_TEXTAREA_MAX_HEIGHT)}px`;
|
|
2405
|
+
textarea.style.overflowY = textarea.scrollHeight > CUSTOM_TEXTAREA_MAX_HEIGHT ? "auto" : "hidden";
|
|
2406
|
+
}
|
|
2407
|
+
function useAutoResizeTextarea(value) {
|
|
2408
|
+
const textareaRef = useRef9(null);
|
|
2409
|
+
useEffect8(() => {
|
|
2410
|
+
const textarea = textareaRef.current;
|
|
2411
|
+
if (textarea?.value === value) resizeCustomTextarea(textarea);
|
|
2412
|
+
}, [value]);
|
|
2413
|
+
useEffect8(() => {
|
|
2414
|
+
const textarea = textareaRef.current;
|
|
2415
|
+
if (!textarea || typeof ResizeObserver === "undefined") return;
|
|
2416
|
+
let previousWidth = textarea.clientWidth;
|
|
2417
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
2418
|
+
if (!entry || entry.contentRect.width === previousWidth) return;
|
|
2419
|
+
previousWidth = entry.contentRect.width;
|
|
2420
|
+
resizeCustomTextarea(textarea);
|
|
2421
|
+
});
|
|
2422
|
+
observer.observe(textarea);
|
|
2423
|
+
return () => observer.disconnect();
|
|
2424
|
+
}, []);
|
|
2425
|
+
return textareaRef;
|
|
2426
|
+
}
|
|
2427
|
+
function indentAnswerContinuationLines(answer) {
|
|
2428
|
+
return answer.replaceAll("\n", "\n ");
|
|
2429
|
+
}
|
|
1796
2430
|
function AskUserQuestionBlock({
|
|
1797
2431
|
data,
|
|
1798
2432
|
answered,
|
|
@@ -1801,18 +2435,19 @@ function AskUserQuestionBlock({
|
|
|
1801
2435
|
answerData,
|
|
1802
2436
|
onAnswer
|
|
1803
2437
|
}) {
|
|
1804
|
-
const [selections, setSelections] =
|
|
1805
|
-
const [customTexts, setCustomTexts] =
|
|
1806
|
-
const [usingCustom, setUsingCustom] =
|
|
1807
|
-
const [
|
|
1808
|
-
|
|
2438
|
+
const [selections, setSelections] = useState10(/* @__PURE__ */ new Map());
|
|
2439
|
+
const [customTexts, setCustomTexts] = useState10(/* @__PURE__ */ new Map());
|
|
2440
|
+
const [usingCustom, setUsingCustom] = useState10(/* @__PURE__ */ new Set());
|
|
2441
|
+
const [note, setNote] = useState10("");
|
|
2442
|
+
const [submitted, setSubmitted] = useState10(false);
|
|
2443
|
+
useEffect8(() => {
|
|
1809
2444
|
if (sessionStatus === "failed" || sessionStatus === "interrupted") {
|
|
1810
2445
|
setSubmitted(false);
|
|
1811
2446
|
}
|
|
1812
2447
|
}, [sessionStatus]);
|
|
1813
2448
|
const displayAnswerState = useMemo6(() => {
|
|
1814
2449
|
if (!(answered && answerData)) {
|
|
1815
|
-
return { selections, customTexts, usingCustom };
|
|
2450
|
+
return { selections, customTexts, usingCustom, note };
|
|
1816
2451
|
}
|
|
1817
2452
|
const nextSelections = /* @__PURE__ */ new Map();
|
|
1818
2453
|
const nextCustomTexts = /* @__PURE__ */ new Map();
|
|
@@ -1828,9 +2463,10 @@ function AskUserQuestionBlock({
|
|
|
1828
2463
|
return {
|
|
1829
2464
|
selections: nextSelections,
|
|
1830
2465
|
customTexts: nextCustomTexts,
|
|
1831
|
-
usingCustom: nextUsingCustom
|
|
2466
|
+
usingCustom: nextUsingCustom,
|
|
2467
|
+
note: answerData.note ?? ""
|
|
1832
2468
|
};
|
|
1833
|
-
}, [answerData, answered, customTexts, selections, usingCustom]);
|
|
2469
|
+
}, [answerData, answered, customTexts, note, selections, usingCustom]);
|
|
1834
2470
|
const toggleOption = (qIdx, optIdx, multi) => {
|
|
1835
2471
|
if (answered || submitted) return;
|
|
1836
2472
|
setSelections((prev) => {
|
|
@@ -1878,6 +2514,7 @@ function AskUserQuestionBlock({
|
|
|
1878
2514
|
const allAnswered = data.questions.every((_, i) => getAnswer(i) !== null);
|
|
1879
2515
|
const handleSubmit = () => {
|
|
1880
2516
|
if (answered || submitted || !allAnswered || !onAnswer) return;
|
|
2517
|
+
const trimmedNote = note.trim();
|
|
1881
2518
|
const nextAnswerData = {
|
|
1882
2519
|
selections: Object.fromEntries(
|
|
1883
2520
|
Array.from(selections.entries()).map(([qIdx, optionIndexes]) => [
|
|
@@ -1887,15 +2524,21 @@ function AskUserQuestionBlock({
|
|
|
1887
2524
|
),
|
|
1888
2525
|
custom: Object.fromEntries(
|
|
1889
2526
|
Array.from(usingCustom).map((qIdx) => [qIdx, (customTexts.get(qIdx) ?? "").trim()]).filter(([, text2]) => text2.length > 0)
|
|
1890
|
-
)
|
|
2527
|
+
),
|
|
2528
|
+
...trimmedNote ? { note: trimmedNote } : {}
|
|
1891
2529
|
};
|
|
1892
|
-
const parts = data.questions.map(
|
|
1893
|
-
|
|
1894
|
-
|
|
2530
|
+
const parts = data.questions.map(
|
|
2531
|
+
(q, i) => `- ${q.question} -> ${indentAnswerContinuationLines(getAnswer(i) ?? "")}`
|
|
2532
|
+
);
|
|
2533
|
+
const text = [
|
|
2534
|
+
`\u5173\u4E8E\u9700\u8981\u786E\u8BA4\u7684\u95EE\u9898\uFF0C\u7528\u6237\u7684\u56DE\u7B54\u5982\u4E0B\uFF1A
|
|
2535
|
+
${parts.join("\n")}`,
|
|
2536
|
+
trimmedNote ? `\u8865\u5145\u8BF4\u660E\uFF1A${indentAnswerContinuationLines(trimmedNote)}` : ""
|
|
2537
|
+
].filter(Boolean).join("\n");
|
|
1895
2538
|
setSubmitted(true);
|
|
1896
2539
|
onAnswer(text, toolCallId, nextAnswerData);
|
|
1897
2540
|
};
|
|
1898
|
-
return /* @__PURE__ */
|
|
2541
|
+
return /* @__PURE__ */ jsxs8(
|
|
1899
2542
|
"div",
|
|
1900
2543
|
{
|
|
1901
2544
|
className: cn(
|
|
@@ -1903,12 +2546,12 @@ ${parts.join("\n")}`;
|
|
|
1903
2546
|
answered ? "max-w-2xl space-y-3 p-3 text-xs text-[hsl(var(--muted-foreground))] opacity-80" : "max-w-lg space-y-5 p-4 text-sm"
|
|
1904
2547
|
),
|
|
1905
2548
|
children: [
|
|
1906
|
-
data.source_loop?.description && /* @__PURE__ */
|
|
2549
|
+
data.source_loop?.description && /* @__PURE__ */ jsxs8("div", { className: "rounded-lg bg-[hsl(var(--muted)/0.35)] px-3 py-2 text-xs text-[hsl(var(--muted-foreground))]", children: [
|
|
1907
2550
|
"\u5B50\u667A\u80FD\u4F53\u300C",
|
|
1908
2551
|
data.source_loop.description,
|
|
1909
2552
|
"\u300D\u5728\u7B49\u5F85\u4F60\u7684\u56DE\u7B54"
|
|
1910
2553
|
] }),
|
|
1911
|
-
data.questions.map((q, qIdx) => /* @__PURE__ */
|
|
2554
|
+
data.questions.map((q, qIdx) => /* @__PURE__ */ jsx10(
|
|
1912
2555
|
QuestionCard,
|
|
1913
2556
|
{
|
|
1914
2557
|
question: q,
|
|
@@ -1923,7 +2566,16 @@ ${parts.join("\n")}`;
|
|
|
1923
2566
|
},
|
|
1924
2567
|
q.question
|
|
1925
2568
|
)),
|
|
1926
|
-
|
|
2569
|
+
/* @__PURE__ */ jsx10(
|
|
2570
|
+
NoteField,
|
|
2571
|
+
{
|
|
2572
|
+
answered,
|
|
2573
|
+
submitted,
|
|
2574
|
+
note: displayAnswerState.note,
|
|
2575
|
+
onChange: setNote
|
|
2576
|
+
}
|
|
2577
|
+
),
|
|
2578
|
+
!answered && !submitted && onAnswer && /* @__PURE__ */ jsx10(
|
|
1927
2579
|
"button",
|
|
1928
2580
|
{
|
|
1929
2581
|
type: "button",
|
|
@@ -1933,14 +2585,14 @@ ${parts.join("\n")}`;
|
|
|
1933
2585
|
children: allAnswered ? "\u786E\u8BA4" : "\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u9009\u9879"
|
|
1934
2586
|
}
|
|
1935
2587
|
),
|
|
1936
|
-
submitted && !answered && /* @__PURE__ */
|
|
2588
|
+
submitted && !answered && /* @__PURE__ */ jsxs8(
|
|
1937
2589
|
"button",
|
|
1938
2590
|
{
|
|
1939
2591
|
type: "button",
|
|
1940
2592
|
disabled: true,
|
|
1941
2593
|
className: "flex w-full items-center justify-center gap-2 rounded-lg bg-[hsl(var(--primary))] px-4 py-2 text-xs font-semibold text-[hsl(var(--primary-foreground))] opacity-80",
|
|
1942
2594
|
children: [
|
|
1943
|
-
/* @__PURE__ */
|
|
2595
|
+
/* @__PURE__ */ jsx10(LoaderCircle, { size: 14, className: "animate-spin" }),
|
|
1944
2596
|
"\u786E\u8BA4\u4E2D"
|
|
1945
2597
|
]
|
|
1946
2598
|
}
|
|
@@ -1961,30 +2613,31 @@ function QuestionCard({
|
|
|
1961
2613
|
onCustomChange
|
|
1962
2614
|
}) {
|
|
1963
2615
|
const multi = question.multiSelect ?? false;
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
2616
|
+
const customTextareaRef = useAutoResizeTextarea(customText);
|
|
2617
|
+
return /* @__PURE__ */ jsxs8("div", { children: [
|
|
2618
|
+
/* @__PURE__ */ jsxs8("div", { className: cn("flex items-start gap-2", answered ? "mb-2" : "mb-3"), children: [
|
|
2619
|
+
/* @__PURE__ */ jsx10(
|
|
1967
2620
|
MessageSquareMore,
|
|
1968
2621
|
{
|
|
1969
2622
|
size: answered ? 12 : 13,
|
|
1970
2623
|
className: "mt-0.5 shrink-0 text-[hsl(var(--primary))]"
|
|
1971
2624
|
}
|
|
1972
2625
|
),
|
|
1973
|
-
/* @__PURE__ */
|
|
2626
|
+
/* @__PURE__ */ jsx10(
|
|
1974
2627
|
"div",
|
|
1975
2628
|
{
|
|
1976
2629
|
className: cn(
|
|
1977
2630
|
"min-w-0 flex-1 font-medium text-[hsl(var(--foreground))]",
|
|
1978
2631
|
answered ? "text-xs" : "text-sm"
|
|
1979
2632
|
),
|
|
1980
|
-
children: /* @__PURE__ */
|
|
2633
|
+
children: /* @__PURE__ */ jsx10(MarkdownContent, { className: "blade-chat-prose", children: question.question })
|
|
1981
2634
|
}
|
|
1982
2635
|
)
|
|
1983
2636
|
] }),
|
|
1984
|
-
/* @__PURE__ */
|
|
2637
|
+
/* @__PURE__ */ jsxs8("div", { className: cn("flex flex-col pl-5", answered ? "gap-1" : "gap-1.5"), children: [
|
|
1985
2638
|
question.options.map((opt, optIdx) => {
|
|
1986
2639
|
const isSel = selected.has(optIdx);
|
|
1987
|
-
return /* @__PURE__ */
|
|
2640
|
+
return /* @__PURE__ */ jsxs8(
|
|
1988
2641
|
"button",
|
|
1989
2642
|
{
|
|
1990
2643
|
type: "button",
|
|
@@ -1998,14 +2651,14 @@ function QuestionCard({
|
|
|
1998
2651
|
answered && "cursor-default opacity-70"
|
|
1999
2652
|
),
|
|
2000
2653
|
children: [
|
|
2001
|
-
multi && /* @__PURE__ */
|
|
2654
|
+
multi && /* @__PURE__ */ jsx10(
|
|
2002
2655
|
"div",
|
|
2003
2656
|
{
|
|
2004
2657
|
className: cn(
|
|
2005
2658
|
"mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded border transition-colors",
|
|
2006
2659
|
isSel && !answered ? "border-[hsl(var(--primary-foreground)/0.6)] bg-[hsl(var(--primary-foreground)/0.2)]" : isSel ? "border-[hsl(var(--primary)/0.45)] bg-[hsl(var(--primary)/0.12)]" : "border-[hsl(var(--border))]"
|
|
2007
2660
|
),
|
|
2008
|
-
children: isSel && /* @__PURE__ */
|
|
2661
|
+
children: isSel && /* @__PURE__ */ jsx10(
|
|
2009
2662
|
Check,
|
|
2010
2663
|
{
|
|
2011
2664
|
size: 9,
|
|
@@ -2014,9 +2667,9 @@ function QuestionCard({
|
|
|
2014
2667
|
)
|
|
2015
2668
|
}
|
|
2016
2669
|
),
|
|
2017
|
-
/* @__PURE__ */
|
|
2018
|
-
/* @__PURE__ */
|
|
2019
|
-
opt.description && /* @__PURE__ */
|
|
2670
|
+
/* @__PURE__ */ jsxs8("div", { className: "min-w-0", children: [
|
|
2671
|
+
/* @__PURE__ */ jsx10("div", { className: cn("font-medium", answered ? "text-xs" : "text-[13px]"), children: opt.label }),
|
|
2672
|
+
opt.description && /* @__PURE__ */ jsx10(
|
|
2020
2673
|
"div",
|
|
2021
2674
|
{
|
|
2022
2675
|
className: cn(
|
|
@@ -2033,29 +2686,30 @@ function QuestionCard({
|
|
|
2033
2686
|
opt.label
|
|
2034
2687
|
);
|
|
2035
2688
|
}),
|
|
2036
|
-
answered && !isCustom ? null : /* @__PURE__ */
|
|
2689
|
+
answered && !isCustom ? null : /* @__PURE__ */ jsxs8(
|
|
2037
2690
|
"div",
|
|
2038
2691
|
{
|
|
2039
2692
|
className: cn(
|
|
2040
|
-
"flex items-
|
|
2693
|
+
"flex items-start gap-2 rounded-lg border transition-all focus-within:ring-2 focus-within:ring-[hsl(var(--ring)/0.35)]",
|
|
2041
2694
|
answered ? "px-2.5 py-1.5" : "px-3 py-2.5",
|
|
2042
2695
|
isCustom ? "border-[hsl(var(--ring)/0.6)] bg-[hsl(var(--accent))]" : "border-[hsl(var(--border))] hover:border-[hsl(var(--ring)/0.3)] hover:bg-[hsl(var(--accent))]",
|
|
2043
2696
|
answered && "cursor-default opacity-70"
|
|
2044
2697
|
),
|
|
2045
2698
|
children: [
|
|
2046
|
-
/* @__PURE__ */
|
|
2047
|
-
/* @__PURE__ */
|
|
2048
|
-
"
|
|
2699
|
+
/* @__PURE__ */ jsx10("span", { className: "shrink-0 pt-1 text-xs text-[hsl(var(--muted-foreground))]", children: "\u5176\u4ED6\uFF1A" }),
|
|
2700
|
+
/* @__PURE__ */ jsx10(
|
|
2701
|
+
"textarea",
|
|
2049
2702
|
{
|
|
2050
|
-
|
|
2703
|
+
ref: customTextareaRef,
|
|
2704
|
+
rows: 2,
|
|
2051
2705
|
value: customText,
|
|
2052
|
-
|
|
2706
|
+
readOnly: answered,
|
|
2053
2707
|
onChange: (e) => onCustomChange(qIdx, e.target.value),
|
|
2054
2708
|
onFocus: () => onCustomFocus(qIdx),
|
|
2055
2709
|
"aria-label": "\u81EA\u5B9A\u4E49\u56DE\u7B54",
|
|
2056
2710
|
placeholder: "\u8F93\u5165\u4F60\u7684\u7B54\u6848...",
|
|
2057
2711
|
className: cn(
|
|
2058
|
-
"min-w-0 flex-1 bg-transparent text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground)/0.5)]",
|
|
2712
|
+
"min-h-10 min-w-0 flex-1 resize-none bg-transparent leading-5 text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground)/0.5)]",
|
|
2059
2713
|
answered ? "text-xs" : "text-sm"
|
|
2060
2714
|
)
|
|
2061
2715
|
}
|
|
@@ -2066,6 +2720,49 @@ function QuestionCard({
|
|
|
2066
2720
|
] })
|
|
2067
2721
|
] });
|
|
2068
2722
|
}
|
|
2723
|
+
function NoteField({
|
|
2724
|
+
answered,
|
|
2725
|
+
submitted,
|
|
2726
|
+
note,
|
|
2727
|
+
onChange
|
|
2728
|
+
}) {
|
|
2729
|
+
const textareaRef = useAutoResizeTextarea(note);
|
|
2730
|
+
const readOnly = answered || submitted;
|
|
2731
|
+
if (answered && !note.trim()) return null;
|
|
2732
|
+
return /* @__PURE__ */ jsxs8(
|
|
2733
|
+
"label",
|
|
2734
|
+
{
|
|
2735
|
+
className: cn(
|
|
2736
|
+
"block rounded-lg border transition-all focus-within:ring-2 focus-within:ring-[hsl(var(--ring)/0.35)]",
|
|
2737
|
+
answered ? "px-2.5 py-1.5" : "px-3 py-2.5",
|
|
2738
|
+
note.trim() ? "border-[hsl(var(--ring)/0.6)] bg-[hsl(var(--accent))]" : "border-[hsl(var(--border))] hover:border-[hsl(var(--ring)/0.3)] hover:bg-[hsl(var(--accent))]",
|
|
2739
|
+
readOnly && "cursor-default opacity-70"
|
|
2740
|
+
),
|
|
2741
|
+
children: [
|
|
2742
|
+
/* @__PURE__ */ jsx10("span", { className: "mb-1.5 block text-xs text-[hsl(var(--muted-foreground))]", children: "\u8865\u5145\u8BF4\u660E\uFF08\u53EF\u9009\uFF09" }),
|
|
2743
|
+
/* @__PURE__ */ jsx10(
|
|
2744
|
+
"textarea",
|
|
2745
|
+
{
|
|
2746
|
+
ref: textareaRef,
|
|
2747
|
+
rows: 2,
|
|
2748
|
+
value: note,
|
|
2749
|
+
readOnly,
|
|
2750
|
+
onChange: (event) => {
|
|
2751
|
+
if (readOnly) return;
|
|
2752
|
+
onChange(event.target.value);
|
|
2753
|
+
},
|
|
2754
|
+
"aria-label": "\u8865\u5145\u8BF4\u660E",
|
|
2755
|
+
placeholder: "\u9009\u5B8C\u8FD8\u53EF\u4EE5\u518D\u8BB2\u4E24\u53E5\uFF0C\u7A7A\u7740\u5C31\u5F53\u6CA1\u6709",
|
|
2756
|
+
className: cn(
|
|
2757
|
+
"min-h-10 w-full resize-none bg-transparent leading-5 text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground)/0.5)]",
|
|
2758
|
+
answered ? "text-xs" : "text-sm"
|
|
2759
|
+
)
|
|
2760
|
+
}
|
|
2761
|
+
)
|
|
2762
|
+
]
|
|
2763
|
+
}
|
|
2764
|
+
);
|
|
2765
|
+
}
|
|
2069
2766
|
function parseAskUserQuestion(toolResult) {
|
|
2070
2767
|
if (!toolResult) return null;
|
|
2071
2768
|
try {
|
|
@@ -2088,6 +2785,26 @@ function parseAskUserQuestion(toolResult) {
|
|
|
2088
2785
|
}
|
|
2089
2786
|
return null;
|
|
2090
2787
|
}
|
|
2788
|
+
function parseAskUserQuestionError(toolResult) {
|
|
2789
|
+
if (!toolResult) return null;
|
|
2790
|
+
try {
|
|
2791
|
+
const parsed = JSON.parse(toolResult);
|
|
2792
|
+
let detail = null;
|
|
2793
|
+
if (typeof parsed.error === "string") detail = parsed.error;
|
|
2794
|
+
if (parsed.error && typeof parsed.error === "object") {
|
|
2795
|
+
const message = parsed.error.message;
|
|
2796
|
+
if (typeof message === "string") detail = message;
|
|
2797
|
+
}
|
|
2798
|
+
if (!detail && typeof parsed.message === "string") detail = parsed.message;
|
|
2799
|
+
if (!detail) return null;
|
|
2800
|
+
return {
|
|
2801
|
+
message: parsed.error_code === "invalid_questions" ? "\u63D0\u95EE\u5185\u5BB9\u4E0D\u5B8C\u6574\uFF0C\u5F53\u524D\u6CA1\u6709\u7B49\u5F85\u4F60\u56DE\u7B54\u3002\u8BF7\u8BA9\u667A\u80FD\u4F53\u91CD\u65B0\u63D0\u95EE\u3002" : "\u8FD9\u6B21\u63D0\u95EE\u6CA1\u6709\u6210\u529F\uFF0C\u5F53\u524D\u6CA1\u6709\u7B49\u5F85\u4F60\u56DE\u7B54\u3002\u8BF7\u8BA9\u667A\u80FD\u4F53\u91CD\u65B0\u63D0\u95EE\u3002",
|
|
2802
|
+
detail
|
|
2803
|
+
};
|
|
2804
|
+
} catch {
|
|
2805
|
+
return null;
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2091
2808
|
function normalizeQuestionItem(value) {
|
|
2092
2809
|
if (!value || typeof value !== "object") return null;
|
|
2093
2810
|
const item = value;
|
|
@@ -2112,13 +2829,14 @@ function normalizeOptionItem(value) {
|
|
|
2112
2829
|
}
|
|
2113
2830
|
|
|
2114
2831
|
// src/components/ToolCallBlock.tsx
|
|
2115
|
-
import { Fragment, jsx as
|
|
2832
|
+
import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2116
2833
|
function resolveAskQuestionState({
|
|
2117
2834
|
toolStatus,
|
|
2118
2835
|
hasAnswerData,
|
|
2119
|
-
fallbackAnswered
|
|
2836
|
+
fallbackAnswered,
|
|
2837
|
+
fallbackAwaiting
|
|
2120
2838
|
}) {
|
|
2121
|
-
const awaitingAnswer = !hasAnswerData && toolStatus === "awaiting_answer";
|
|
2839
|
+
const awaitingAnswer = !hasAnswerData && (toolStatus === "awaiting_answer" || toolStatus === "pending" && fallbackAwaiting === true);
|
|
2122
2840
|
return {
|
|
2123
2841
|
awaitingAnswer,
|
|
2124
2842
|
answered: hasAnswerData || !awaitingAnswer && (Boolean(fallbackAnswered) || toolStatus === "done" || toolStatus === "cancelled" || toolStatus === "error")
|
|
@@ -2130,14 +2848,15 @@ function ToolCallBlock({
|
|
|
2130
2848
|
answered,
|
|
2131
2849
|
answerData,
|
|
2132
2850
|
sessionStatus,
|
|
2851
|
+
isActiveQuestion,
|
|
2133
2852
|
renderer
|
|
2134
2853
|
}) {
|
|
2135
|
-
const [expanded, setExpanded] =
|
|
2854
|
+
const [expanded, setExpanded] = useState11(false);
|
|
2136
2855
|
const normalizedName = formatToolName(toolCall.name);
|
|
2137
2856
|
if (renderer) {
|
|
2138
2857
|
const custom = renderer(toolCall);
|
|
2139
2858
|
if (custom !== null && custom !== void 0) {
|
|
2140
|
-
return /* @__PURE__ */
|
|
2859
|
+
return /* @__PURE__ */ jsx11(Fragment2, { children: custom });
|
|
2141
2860
|
}
|
|
2142
2861
|
}
|
|
2143
2862
|
if (normalizedName === "AskUserQuestion") {
|
|
@@ -2145,11 +2864,12 @@ function ToolCallBlock({
|
|
|
2145
2864
|
const questionState = resolveAskQuestionState({
|
|
2146
2865
|
toolStatus: toolCall.status,
|
|
2147
2866
|
hasAnswerData: Boolean(answerData),
|
|
2148
|
-
fallbackAnswered: answered
|
|
2867
|
+
fallbackAnswered: answered,
|
|
2868
|
+
fallbackAwaiting: isActiveQuestion === true && (sessionStatus === "paused" || sessionStatus === "waiting_for_input")
|
|
2149
2869
|
});
|
|
2150
2870
|
const canAnswer = questionState.awaitingAnswer && Boolean(onAnswer);
|
|
2151
2871
|
if (askData) {
|
|
2152
|
-
return /* @__PURE__ */
|
|
2872
|
+
return /* @__PURE__ */ jsx11(
|
|
2153
2873
|
AskUserQuestionBlock,
|
|
2154
2874
|
{
|
|
2155
2875
|
data: askData,
|
|
@@ -2162,24 +2882,31 @@ function ToolCallBlock({
|
|
|
2162
2882
|
);
|
|
2163
2883
|
}
|
|
2164
2884
|
if (toolCall.status === "pending") {
|
|
2165
|
-
return /* @__PURE__ */
|
|
2166
|
-
/* @__PURE__ */
|
|
2167
|
-
/* @__PURE__ */
|
|
2885
|
+
return /* @__PURE__ */ jsxs9("div", { className: "ml-4 flex max-w-lg items-center gap-2 rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] p-4 text-sm text-[hsl(var(--muted-foreground))]", children: [
|
|
2886
|
+
/* @__PURE__ */ jsx11(LoaderCircle, { size: 14, className: "animate-spin" }),
|
|
2887
|
+
/* @__PURE__ */ jsx11("span", { children: "\u6B63\u5728\u51C6\u5907\u95EE\u9898\u2026" })
|
|
2168
2888
|
] });
|
|
2169
2889
|
}
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2890
|
+
const errorDetail = parseAskUserQuestionError(
|
|
2891
|
+
typeof toolCall.result === "string" ? toolCall.result : null
|
|
2892
|
+
);
|
|
2893
|
+
return /* @__PURE__ */ jsxs9("div", { className: "ml-4 max-w-lg rounded-xl border border-amber-500/35 bg-amber-500/10 p-4 text-sm text-[hsl(var(--foreground))]", children: [
|
|
2894
|
+
/* @__PURE__ */ jsx11("div", { className: "font-semibold", children: "\u9009\u62E9\u9898\u5185\u5BB9\u6682\u65F6\u65E0\u6CD5\u663E\u793A" }),
|
|
2895
|
+
/* @__PURE__ */ jsx11("div", { className: "mt-1 text-xs leading-5 text-[hsl(var(--muted-foreground))]", children: errorDetail?.message ?? "\u6536\u5230\u7684\u4EA4\u4E92\u6570\u636E\u4E0D\u5B8C\u6574\u3002\u8BF7\u8BA9\u667A\u80FD\u4F53\u91CD\u65B0\u63D0\u95EE\u3002" }),
|
|
2896
|
+
errorDetail?.detail ? /* @__PURE__ */ jsxs9("details", { className: "mt-2 text-xs text-[hsl(var(--muted-foreground))]", children: [
|
|
2897
|
+
/* @__PURE__ */ jsx11("summary", { className: "cursor-pointer", children: "\u67E5\u770B\u5177\u4F53\u539F\u56E0" }),
|
|
2898
|
+
/* @__PURE__ */ jsx11("div", { className: "mt-1 break-words font-mono", children: errorDetail.detail })
|
|
2899
|
+
] }) : null
|
|
2173
2900
|
] });
|
|
2174
2901
|
}
|
|
2175
2902
|
const tone = getToolTone(toolCall.status);
|
|
2176
2903
|
const displayName = getToolDisplayLabel(toolCall);
|
|
2177
2904
|
const toneClass = tone === "red" ? "border-l-[hsl(var(--muted-foreground)/0.5)]" : tone === "amber" ? "border-l-amber-400" : tone === "blue" ? "border-l-blue-500" : "border-l-[hsl(var(--primary))]";
|
|
2178
|
-
const statusIcon = toolCall.status === "pending" ? /* @__PURE__ */
|
|
2905
|
+
const statusIcon = toolCall.status === "pending" ? /* @__PURE__ */ jsx11(LoaderCircle, { size: 11, className: "animate-spin" }) : toolCall.status === "awaiting_answer" ? /* @__PURE__ */ jsx11(MessageSquareMore, { size: 11 }) : toolCall.status === "cancelled" || toolCall.status === "error" ? /* @__PURE__ */ jsx11(X, { size: 11 }) : /* @__PURE__ */ jsx11(Check, { size: 11 });
|
|
2179
2906
|
const statusTextClass = tone === "red" ? "text-[hsl(var(--muted-foreground))]" : tone === "amber" ? "text-amber-300" : tone === "blue" ? "text-blue-300" : "text-[hsl(var(--primary))]";
|
|
2180
|
-
return /* @__PURE__ */
|
|
2181
|
-
/* @__PURE__ */
|
|
2182
|
-
/* @__PURE__ */
|
|
2907
|
+
return /* @__PURE__ */ jsxs9("div", { className: "blade-chat-tool ml-4 text-xs", children: [
|
|
2908
|
+
/* @__PURE__ */ jsxs9("div", { className: cn("border-l-[3px] flex items-center gap-2 px-3 py-2", toneClass), children: [
|
|
2909
|
+
/* @__PURE__ */ jsxs9(
|
|
2183
2910
|
"button",
|
|
2184
2911
|
{
|
|
2185
2912
|
type: "button",
|
|
@@ -2187,7 +2914,7 @@ function ToolCallBlock({
|
|
|
2187
2914
|
className: "flex min-w-0 flex-1 items-center gap-2 text-left transition-colors hover:bg-white/3 focus-visible:ring-1 focus-visible:ring-[hsl(var(--ring))] focus:outline-none",
|
|
2188
2915
|
"aria-expanded": expanded,
|
|
2189
2916
|
children: [
|
|
2190
|
-
/* @__PURE__ */
|
|
2917
|
+
/* @__PURE__ */ jsx11(
|
|
2191
2918
|
ChevronRight,
|
|
2192
2919
|
{
|
|
2193
2920
|
size: 11,
|
|
@@ -2197,24 +2924,24 @@ function ToolCallBlock({
|
|
|
2197
2924
|
)
|
|
2198
2925
|
}
|
|
2199
2926
|
),
|
|
2200
|
-
/* @__PURE__ */
|
|
2927
|
+
/* @__PURE__ */ jsxs9("span", { className: cn("flex shrink-0 items-center gap-1 text-[10px]", statusTextClass), children: [
|
|
2201
2928
|
statusIcon,
|
|
2202
|
-
/* @__PURE__ */
|
|
2929
|
+
/* @__PURE__ */ jsx11("span", { children: getToolStatusLabel(toolCall.status) })
|
|
2203
2930
|
] }),
|
|
2204
|
-
/* @__PURE__ */
|
|
2931
|
+
/* @__PURE__ */ jsx11("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: displayName })
|
|
2205
2932
|
]
|
|
2206
2933
|
}
|
|
2207
2934
|
),
|
|
2208
|
-
typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 && /* @__PURE__ */
|
|
2935
|
+
typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 && /* @__PURE__ */ jsx11("span", { className: "shrink-0 font-mono text-[10px] text-[hsl(var(--muted-foreground))]", children: formatToolDuration(toolCall.duration_ms) })
|
|
2209
2936
|
] }),
|
|
2210
|
-
expanded && /* @__PURE__ */
|
|
2211
|
-
/* @__PURE__ */
|
|
2212
|
-
/* @__PURE__ */
|
|
2213
|
-
/* @__PURE__ */
|
|
2214
|
-
/* @__PURE__ */
|
|
2215
|
-
toolCall.result != null && /* @__PURE__ */
|
|
2216
|
-
/* @__PURE__ */
|
|
2217
|
-
/* @__PURE__ */
|
|
2937
|
+
expanded && /* @__PURE__ */ jsxs9("div", { className: "blade-chat-tool-detail ml-4 mt-1 rounded-xl bg-[hsl(var(--card))] px-3 py-3", children: [
|
|
2938
|
+
/* @__PURE__ */ jsx11("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u5DE5\u5177" }),
|
|
2939
|
+
/* @__PURE__ */ jsx11("div", { className: "mb-3 font-mono text-[11px] text-[hsl(var(--foreground))]", children: normalizedName }),
|
|
2940
|
+
/* @__PURE__ */ jsx11("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u53C2\u6570" }),
|
|
2941
|
+
/* @__PURE__ */ jsx11("pre", { className: "overflow-x-auto whitespace-pre-wrap rounded-md bg-[hsl(var(--muted))] p-2 font-mono text-[11px] text-[hsl(var(--foreground))]", children: formatToolArgs(toolCall.arguments) }),
|
|
2942
|
+
toolCall.result != null && /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
2943
|
+
/* @__PURE__ */ jsx11("div", { className: "mb-1 mt-3 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u7ED3\u679C" }),
|
|
2944
|
+
/* @__PURE__ */ jsx11("pre", { className: "max-h-[400px] overflow-auto whitespace-pre-wrap rounded-md bg-[hsl(var(--muted))] p-2 font-mono text-[11px] text-[hsl(var(--foreground))]", children: formatToolResult(toolCall.result) })
|
|
2218
2945
|
] })
|
|
2219
2946
|
] })
|
|
2220
2947
|
] });
|
|
@@ -2232,110 +2959,555 @@ function buildAskUserPayload(argumentsJson) {
|
|
|
2232
2959
|
}
|
|
2233
2960
|
|
|
2234
2961
|
// src/components/AssistantTurnBlock.tsx
|
|
2235
|
-
import { jsx as
|
|
2962
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2236
2963
|
function ThinkingBlock({ reasoning, isStreaming }) {
|
|
2237
|
-
const [open, setOpen] =
|
|
2238
|
-
|
|
2239
|
-
|
|
2964
|
+
const [open, setOpen] = useState12(false);
|
|
2965
|
+
if (!isStreaming) return null;
|
|
2966
|
+
return /* @__PURE__ */ jsxs10("div", { className: "blade-chat-thinking text-xs", children: [
|
|
2967
|
+
/* @__PURE__ */ jsxs10(
|
|
2240
2968
|
"button",
|
|
2241
2969
|
{
|
|
2242
2970
|
type: "button",
|
|
2243
2971
|
onClick: () => setOpen(!open),
|
|
2244
2972
|
"aria-expanded": open,
|
|
2245
|
-
className: "inline-flex items-center gap-1
|
|
2973
|
+
className: "group/thinking inline-flex items-center gap-1 text-[hsl(var(--muted-foreground))] transition-colors hover:text-[hsl(var(--foreground))]",
|
|
2246
2974
|
children: [
|
|
2247
|
-
/* @__PURE__ */
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
"\xB7 ",
|
|
2251
|
-
new Intl.NumberFormat("zh-CN").format(reasoning.length),
|
|
2252
|
-
" \u5B57"
|
|
2253
|
-
] }),
|
|
2254
|
-
/* @__PURE__ */ jsx11(
|
|
2255
|
-
ChevronDown,
|
|
2975
|
+
/* @__PURE__ */ jsx12(Shimmer, { className: "text-xs", children: "\u6B63\u5728\u601D\u8003" }),
|
|
2976
|
+
/* @__PURE__ */ jsx12(
|
|
2977
|
+
ChevronRight,
|
|
2256
2978
|
{
|
|
2257
|
-
size:
|
|
2258
|
-
className: cn(
|
|
2979
|
+
size: 14,
|
|
2980
|
+
className: cn(
|
|
2981
|
+
"shrink-0 opacity-0 transition-[opacity,transform] group-hover/thinking:opacity-100",
|
|
2982
|
+
open && "rotate-90 opacity-100"
|
|
2983
|
+
)
|
|
2259
2984
|
}
|
|
2260
2985
|
)
|
|
2261
2986
|
]
|
|
2262
2987
|
}
|
|
2263
2988
|
),
|
|
2264
|
-
open
|
|
2989
|
+
open ? /* @__PURE__ */ jsx12("div", { className: "mt-1.5 whitespace-pre-wrap text-xs leading-[22px] text-[hsl(var(--muted-foreground))]", children: reasoning }) : null
|
|
2265
2990
|
] });
|
|
2266
2991
|
}
|
|
2267
2992
|
function getMessageText(message) {
|
|
2268
2993
|
return getTextContent(normalizeMessageContent(message.content)).trim();
|
|
2269
2994
|
}
|
|
2995
|
+
function hasRenderableMessageContent(message) {
|
|
2996
|
+
return Boolean(getMessageText(message)) || getImageParts(message.content).length > 0 || getFileParts(message.content).length > 0;
|
|
2997
|
+
}
|
|
2998
|
+
function getLastContentMessage(messages) {
|
|
2999
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
3000
|
+
if (hasRenderableMessageContent(messages[index])) return messages[index];
|
|
3001
|
+
}
|
|
3002
|
+
return null;
|
|
3003
|
+
}
|
|
3004
|
+
function getOrderedMessageParts(message, toolCalls) {
|
|
3005
|
+
const blocks = message.blocks ?? [];
|
|
3006
|
+
if (!blocks.some((block) => block.type === "text") || !blocks.some((block) => block.type === "tool_use")) return [];
|
|
3007
|
+
const toolsById = new Map(toolCalls.map((toolCall) => [toolCall.id, toolCall]));
|
|
3008
|
+
const seenToolIds = /* @__PURE__ */ new Set();
|
|
3009
|
+
const parts = [];
|
|
3010
|
+
for (const [index, block] of blocks.entries()) {
|
|
3011
|
+
if (block.type === "text" && block.content != null && block.content !== "") {
|
|
3012
|
+
const content = Array.isArray(block.content) ? block.content : String(block.content);
|
|
3013
|
+
parts.push({ type: "text", key: `text-${index}`, content });
|
|
3014
|
+
}
|
|
3015
|
+
if (block.type !== "tool_use" || !block.tool_call_id) continue;
|
|
3016
|
+
const toolCall = toolsById.get(block.tool_call_id);
|
|
3017
|
+
if (!toolCall) continue;
|
|
3018
|
+
seenToolIds.add(toolCall.id);
|
|
3019
|
+
const previous = parts[parts.length - 1];
|
|
3020
|
+
if (previous?.type === "tools") previous.toolCalls.push(toolCall);
|
|
3021
|
+
else parts.push({ type: "tools", key: `tools-${index}`, toolCalls: [toolCall] });
|
|
3022
|
+
}
|
|
3023
|
+
const missingTools = toolCalls.filter((toolCall) => !seenToolIds.has(toolCall.id));
|
|
3024
|
+
if (seenToolIds.size === 0) return [];
|
|
3025
|
+
if (missingTools.length > 0) {
|
|
3026
|
+
parts.push({ type: "tools", key: "tools-missing", toolCalls: missingTools });
|
|
3027
|
+
}
|
|
3028
|
+
return parts;
|
|
3029
|
+
}
|
|
2270
3030
|
function findLatestReasoningMessageIndex(messages) {
|
|
2271
3031
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
2272
3032
|
if (messages[index].reasoning) return index;
|
|
2273
3033
|
}
|
|
2274
3034
|
return -1;
|
|
2275
3035
|
}
|
|
2276
|
-
function
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
3036
|
+
function resolveTurnDisplayMode({
|
|
3037
|
+
isStreaming: _isStreaming,
|
|
3038
|
+
displayMode
|
|
3039
|
+
}) {
|
|
3040
|
+
return displayMode;
|
|
3041
|
+
}
|
|
3042
|
+
function formatExecutionDuration(durationMs) {
|
|
3043
|
+
const totalSeconds = Math.max(0, Math.round(durationMs / 1e3));
|
|
3044
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
3045
|
+
const seconds = totalSeconds % 60;
|
|
3046
|
+
return minutes > 0 ? `${minutes}\u5206${seconds}\u79D2` : `${seconds}\u79D2`;
|
|
3047
|
+
}
|
|
3048
|
+
function getExecutionDurationMs({
|
|
3049
|
+
messages,
|
|
3050
|
+
isStreaming,
|
|
3051
|
+
now = Date.now()
|
|
3052
|
+
}) {
|
|
3053
|
+
const knownDuration = messages.reduce(
|
|
3054
|
+
(total, message) => {
|
|
3055
|
+
if (typeof message.duration_ms === "number" && message.duration_ms > 0) {
|
|
3056
|
+
return total + message.duration_ms;
|
|
3057
|
+
}
|
|
3058
|
+
return total + (message.tool_calls ?? []).reduce(
|
|
3059
|
+
(toolTotal, toolCall) => toolTotal + (typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 ? toolCall.duration_ms : 0),
|
|
3060
|
+
0
|
|
3061
|
+
);
|
|
3062
|
+
},
|
|
3063
|
+
0
|
|
3064
|
+
);
|
|
3065
|
+
if (!isStreaming) return knownDuration;
|
|
3066
|
+
const startedAt = messages.map((message) => message.timestamp ? Date.parse(message.timestamp) : Number.NaN).filter((value) => Number.isFinite(value)).sort((a, b) => a - b)[0];
|
|
3067
|
+
if (startedAt === void 0) return knownDuration;
|
|
3068
|
+
return Math.max(knownDuration, now - startedAt);
|
|
3069
|
+
}
|
|
3070
|
+
function findLastExceptionalEvent(messages) {
|
|
3071
|
+
for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex -= 1) {
|
|
3072
|
+
const messageStatus = messages[messageIndex].status;
|
|
3073
|
+
if (messageStatus === "failed") return { messageIndex, status: "error" };
|
|
3074
|
+
if (messageStatus === "interrupted") return { messageIndex, status: "cancelled" };
|
|
3075
|
+
const toolCalls = messages[messageIndex].tool_calls ?? [];
|
|
3076
|
+
for (let toolIndex = toolCalls.length - 1; toolIndex >= 0; toolIndex -= 1) {
|
|
3077
|
+
const status = toolCalls[toolIndex].status;
|
|
3078
|
+
if (status === "error" || status === "cancelled") {
|
|
3079
|
+
return { messageIndex, status };
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
return null;
|
|
3084
|
+
}
|
|
3085
|
+
function executionSummaryLabel({
|
|
3086
|
+
messages,
|
|
3087
|
+
isStreaming,
|
|
3088
|
+
durationMs,
|
|
3089
|
+
sessionStatus,
|
|
3090
|
+
askAnswers
|
|
3091
|
+
}) {
|
|
3092
|
+
if (isStreaming) {
|
|
3093
|
+
return durationMs > 0 ? `\u6B63\u5728\u6267\u884C ${formatExecutionDuration(durationMs)}` : "\u6B63\u5728\u6267\u884C";
|
|
3094
|
+
}
|
|
3095
|
+
if (sessionStatus === "waiting_for_input" && messages.some(
|
|
3096
|
+
(message) => (message.tool_calls ?? []).some(
|
|
3097
|
+
(toolCall) => formatToolName(toolCall.name) === "AskUserQuestion" && toolCall.status === "awaiting_answer" && !askAnswers?.[toolCall.id]
|
|
3098
|
+
)
|
|
3099
|
+
)) {
|
|
3100
|
+
return "\u7B49\u5F85\u8F93\u5165";
|
|
3101
|
+
}
|
|
3102
|
+
const completedLabel = durationMs > 0 ? `\u6267\u884C\u5B8C\u6210 ${formatExecutionDuration(durationMs)}` : "\u6267\u884C\u5B8C\u6210";
|
|
3103
|
+
const lastExceptionalEvent = findLastExceptionalEvent(messages);
|
|
3104
|
+
if (lastExceptionalEvent) {
|
|
3105
|
+
const recovered = messages.slice(lastExceptionalEvent.messageIndex + 1).some(hasRenderableMessageContent);
|
|
3106
|
+
if (lastExceptionalEvent.status === "error") {
|
|
3107
|
+
return recovered ? `${completedLabel} \xB7 \u90E8\u5206\u6B65\u9AA4\u5931\u8D25` : "\u6267\u884C\u5931\u8D25";
|
|
3108
|
+
}
|
|
3109
|
+
return recovered ? `${completedLabel} \xB7 \u90E8\u5206\u6B65\u9AA4\u672A\u5B8C\u6210` : "\u6267\u884C\u5DF2\u4E2D\u65AD";
|
|
3110
|
+
}
|
|
3111
|
+
return completedLabel;
|
|
3112
|
+
}
|
|
3113
|
+
function businessToolDisplayName(toolCall) {
|
|
3114
|
+
const displayName = toolCall.display_name?.trim() ?? "";
|
|
3115
|
+
if (!displayName) return "";
|
|
3116
|
+
const rawName = toolCall.name.trim();
|
|
3117
|
+
return displayName !== rawName && formatToolName(displayName) !== formatToolName(rawName) ? displayName : "";
|
|
3118
|
+
}
|
|
3119
|
+
function executionToolTypeLabel(toolCall) {
|
|
3120
|
+
switch (formatToolName(toolCall.name)) {
|
|
3121
|
+
case "WebSearch":
|
|
3122
|
+
case "WebFetch":
|
|
3123
|
+
return "\u7F51\u7EDC\u68C0\u7D22";
|
|
3124
|
+
case "Bash":
|
|
3125
|
+
case "BgBash":
|
|
3126
|
+
return "\u547D\u4EE4\u6267\u884C";
|
|
3127
|
+
case "Read":
|
|
3128
|
+
case "ReadSkill":
|
|
3129
|
+
return "\u5185\u5BB9\u8BFB\u53D6";
|
|
3130
|
+
case "Write":
|
|
3131
|
+
case "Edit":
|
|
3132
|
+
case "MultiEdit":
|
|
3133
|
+
return "\u6587\u4EF6\u5904\u7406";
|
|
3134
|
+
case "Grep":
|
|
3135
|
+
case "Glob":
|
|
3136
|
+
return "\u5185\u5BB9\u641C\u7D22";
|
|
3137
|
+
case "Agent":
|
|
3138
|
+
return "\u5B50\u4EFB\u52A1";
|
|
3139
|
+
case "search_skills":
|
|
3140
|
+
return "\u6280\u80FD\u68C0\u7D22";
|
|
3141
|
+
case "get_skill_content":
|
|
3142
|
+
return "\u8BFB\u53D6\u6280\u80FD";
|
|
3143
|
+
case "run_skill_tool":
|
|
3144
|
+
return "\u6267\u884C\u6280\u80FD";
|
|
3145
|
+
default:
|
|
3146
|
+
return businessToolDisplayName(toolCall) || "\u6267\u884C\u6B65\u9AA4";
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
function executionToolIntent(toolCall) {
|
|
3150
|
+
const normalizedName = formatToolName(toolCall.name);
|
|
3151
|
+
let args = null;
|
|
3152
|
+
try {
|
|
3153
|
+
const parsed = JSON.parse(toolCall.arguments);
|
|
3154
|
+
args = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
3155
|
+
} catch {
|
|
3156
|
+
args = null;
|
|
3157
|
+
}
|
|
3158
|
+
const getString = (key) => {
|
|
3159
|
+
const value = args?.[key];
|
|
3160
|
+
return typeof value === "string" ? value.trim() : "";
|
|
3161
|
+
};
|
|
3162
|
+
const explicitIntent = getString("description") || getString("_meta_display_name") || getString("display_name") || "";
|
|
3163
|
+
if (explicitIntent) return explicitIntent;
|
|
3164
|
+
if (normalizedName === "search_skills") return getString("query");
|
|
3165
|
+
if (normalizedName === "get_skill_content" || normalizedName === "ReadSkill") {
|
|
3166
|
+
return getString("skill_name") || getString("skill");
|
|
3167
|
+
}
|
|
3168
|
+
if (normalizedName === "FinishTask") return getString("title");
|
|
3169
|
+
return "";
|
|
3170
|
+
}
|
|
3171
|
+
function ExecutionToolRow({ toolCall }) {
|
|
3172
|
+
const normalizedName = formatToolName(toolCall.name);
|
|
3173
|
+
const typeLabel = executionToolTypeLabel(toolCall);
|
|
3174
|
+
const intent = executionToolIntent(toolCall);
|
|
3175
|
+
const label = intent ? `${typeLabel}\uFF1A${intent}` : typeLabel;
|
|
3176
|
+
const failed = toolCall.status === "error" || toolCall.status === "cancelled";
|
|
3177
|
+
const iconClass = cn(
|
|
3178
|
+
"size-3.5 shrink-0",
|
|
3179
|
+
failed ? "text-red-500" : "text-[hsl(var(--muted-foreground))]"
|
|
3180
|
+
);
|
|
3181
|
+
const icon = toolCall.status === "pending" ? /* @__PURE__ */ jsx12(LoaderCircle, { className: cn(iconClass, "animate-spin"), "aria-hidden": "true" }) : toolCall.status === "error" ? /* @__PURE__ */ jsx12(CircleAlert, { className: iconClass, "aria-hidden": "true" }) : toolCall.status === "cancelled" ? /* @__PURE__ */ jsx12(X, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "WebSearch" || normalizedName === "WebFetch" ? /* @__PURE__ */ jsx12(Earth, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "Bash" || normalizedName === "BgBash" ? /* @__PURE__ */ jsx12(Terminal, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "Write" || normalizedName === "Edit" || normalizedName === "MultiEdit" ? /* @__PURE__ */ jsx12(FilePenLine, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "Read" || normalizedName === "ReadSkill" || normalizedName === "get_skill_content" ? /* @__PURE__ */ jsx12(BookOpen, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "Grep" || normalizedName === "Glob" || normalizedName === "search_skills" ? /* @__PURE__ */ jsx12(Search, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "Agent" ? /* @__PURE__ */ jsx12(Bot, { className: iconClass, "aria-hidden": "true" }) : /* @__PURE__ */ jsx12(Wrench, { className: iconClass, "aria-hidden": "true" });
|
|
3182
|
+
const rowClassName = cn(
|
|
3183
|
+
"flex min-w-0 items-center gap-1 py-1.5 text-xs leading-[22px]",
|
|
3184
|
+
failed ? "text-red-500" : "text-[hsl(var(--muted-foreground))]"
|
|
3185
|
+
);
|
|
3186
|
+
return /* @__PURE__ */ jsxs10("div", { "data-testid": "execution-tool-intent", className: rowClassName, title: label, children: [
|
|
3187
|
+
icon,
|
|
3188
|
+
/* @__PURE__ */ jsx12("span", { className: "min-w-0 truncate", children: label })
|
|
3189
|
+
] });
|
|
3190
|
+
}
|
|
3191
|
+
function AssistantTurnBlock({
|
|
3192
|
+
messages,
|
|
3193
|
+
isStreaming = false,
|
|
3194
|
+
askAnswers,
|
|
3195
|
+
onAnswer,
|
|
3196
|
+
sessionStatus,
|
|
3197
|
+
toolCallRenderer,
|
|
3198
|
+
hidePlanUpdateTools = false,
|
|
3199
|
+
sessionId
|
|
3200
|
+
}) {
|
|
3201
|
+
const shouldHideToolCall = (message, toolCall) => {
|
|
3202
|
+
if (!hidePlanUpdateTools || !isPlanUpdateTool(toolCall) || parsePlanUpdate(toolCall.arguments) === null) {
|
|
3203
|
+
return false;
|
|
3204
|
+
}
|
|
3205
|
+
return toolCall.status === "done" || toolCall.status === "pending" && message.status === "streaming";
|
|
3206
|
+
};
|
|
3207
|
+
const hasInterrupted = messages.some((message) => message.status === "interrupted");
|
|
3208
|
+
const hasFailedWithoutContent = messages.some(
|
|
3209
|
+
(message) => message.status === "failed" && !hasRenderableMessageContent(message)
|
|
3210
|
+
);
|
|
3211
|
+
const finalMessage = getLastContentMessage(messages);
|
|
3212
|
+
const turnToolCalls = messages.flatMap((message) => message.tool_calls ?? []);
|
|
3213
|
+
const finalOrderedParts = finalMessage ? getOrderedMessageParts(
|
|
3214
|
+
finalMessage,
|
|
3215
|
+
(finalMessage.tool_calls ?? []).filter(
|
|
3216
|
+
(toolCall) => formatToolName(toolCall.name) !== "AskUserQuestion" && !shouldHideToolCall(finalMessage, toolCall)
|
|
3217
|
+
)
|
|
3218
|
+
) : [];
|
|
3219
|
+
const hasExecutionProcess = messages.some(
|
|
3220
|
+
(message) => message.reasoning || (message.tool_calls ?? []).some((toolCall) => !shouldHideToolCall(message, toolCall))
|
|
3221
|
+
);
|
|
3222
|
+
const latestReasoningIndex = isStreaming ? findLatestReasoningMessageIndex(messages) : -1;
|
|
3223
|
+
const hasActionableToolCall = messages.some(
|
|
3224
|
+
(message) => message.status === "failed" || message.status === "interrupted" || (message.tool_calls ?? []).some(
|
|
3225
|
+
(toolCall) => !shouldHideToolCall(message, toolCall) && (toolCall.status === "error" || toolCall.status === "cancelled")
|
|
3226
|
+
)
|
|
3227
|
+
);
|
|
3228
|
+
const questionToolCalls = messages.flatMap(
|
|
3229
|
+
(message) => (message.tool_calls ?? []).filter(
|
|
3230
|
+
(toolCall) => formatToolName(toolCall.name) === "AskUserQuestion"
|
|
3231
|
+
)
|
|
3232
|
+
);
|
|
3233
|
+
const activeQuestionId = questionToolCalls.filter((toolCall) => toolCall.status === "pending").at(-1)?.id;
|
|
3234
|
+
const [displayMode, setDisplayMode] = useState12(
|
|
3235
|
+
() => isStreaming || hasActionableToolCall ? "detail" : "compact"
|
|
3236
|
+
);
|
|
3237
|
+
const userSelectedDisplayModeRef = useRef10(false);
|
|
3238
|
+
const wasStreamingRef = useRef10(isStreaming);
|
|
3239
|
+
useEffect9(() => {
|
|
3240
|
+
if (wasStreamingRef.current && !isStreaming && !userSelectedDisplayModeRef.current) {
|
|
3241
|
+
setDisplayMode(hasActionableToolCall ? "detail" : "compact");
|
|
3242
|
+
}
|
|
3243
|
+
wasStreamingRef.current = isStreaming;
|
|
3244
|
+
}, [hasActionableToolCall, isStreaming]);
|
|
3245
|
+
const effectiveMode = resolveTurnDisplayMode({ isStreaming, displayMode });
|
|
3246
|
+
const executionDurationMs = getExecutionDurationMs({ messages, isStreaming });
|
|
3247
|
+
const [clock, setClock] = useState12(() => Date.now());
|
|
3248
|
+
const hasLiveStartTime = messages.some(
|
|
3249
|
+
(message) => message.timestamp != null && Number.isFinite(Date.parse(message.timestamp))
|
|
3250
|
+
);
|
|
3251
|
+
useEffect9(() => {
|
|
3252
|
+
if (!isStreaming || !hasLiveStartTime) return;
|
|
3253
|
+
const timer = window.setInterval(() => setClock(Date.now()), 1e3);
|
|
3254
|
+
return () => window.clearInterval(timer);
|
|
3255
|
+
}, [hasLiveStartTime, isStreaming]);
|
|
3256
|
+
const liveExecutionDurationMs = isStreaming ? getExecutionDurationMs({ messages, isStreaming, now: clock }) : executionDurationMs;
|
|
3257
|
+
const memoryRefs = collectMemoryRefs(messages);
|
|
3258
|
+
if (!hasExecutionProcess) {
|
|
3259
|
+
return /* @__PURE__ */ jsxs10(
|
|
3260
|
+
"div",
|
|
3261
|
+
{
|
|
3262
|
+
"aria-busy": isStreaming || void 0,
|
|
3263
|
+
className: "blade-chat-assistant-turn flex flex-col gap-3",
|
|
3264
|
+
children: [
|
|
3265
|
+
memoryRefs.length > 0 ? /* @__PURE__ */ jsx12(MemoryRefsHint, { refs: memoryRefs }) : null,
|
|
3266
|
+
hasInterrupted && /* @__PURE__ */ jsx12("div", { className: "ml-4 w-fit rounded-full border border-amber-500/30 bg-amber-500/10 px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.12em] text-amber-300", children: "\u5DF2\u4E2D\u65AD" }),
|
|
3267
|
+
hasFailedWithoutContent && /* @__PURE__ */ jsx12("div", { className: "ml-4 w-fit rounded-full border border-red-500/30 bg-red-500/10 px-2.5 py-1 text-[10px] font-medium text-red-400", children: "\u751F\u6210\u5931\u8D25" }),
|
|
3268
|
+
messages.map((message, index) => {
|
|
3269
|
+
return hasRenderableMessageContent(message) ? /* @__PURE__ */ jsx12(
|
|
3270
|
+
"div",
|
|
3271
|
+
{
|
|
3272
|
+
className: "flex flex-col gap-3",
|
|
3273
|
+
children: /* @__PURE__ */ jsx12(
|
|
3274
|
+
AssistantMessageContent,
|
|
3275
|
+
{
|
|
3276
|
+
message,
|
|
3277
|
+
sessionId,
|
|
3278
|
+
streaming: isStreaming && index === messages.length - 1
|
|
3279
|
+
}
|
|
3280
|
+
)
|
|
3281
|
+
},
|
|
3282
|
+
message.entry_id ?? `${message.timestamp ?? "assistant"}-${index}`
|
|
3283
|
+
) : null;
|
|
3284
|
+
})
|
|
3285
|
+
]
|
|
3286
|
+
}
|
|
3287
|
+
);
|
|
3288
|
+
}
|
|
3289
|
+
return /* @__PURE__ */ jsxs10(
|
|
3290
|
+
"div",
|
|
3291
|
+
{
|
|
3292
|
+
"aria-busy": isStreaming || void 0,
|
|
3293
|
+
className: "blade-chat-assistant-turn flex flex-col gap-3",
|
|
3294
|
+
children: [
|
|
3295
|
+
memoryRefs.length > 0 ? /* @__PURE__ */ jsx12(MemoryRefsHint, { refs: memoryRefs }) : null,
|
|
3296
|
+
hasInterrupted && /* @__PURE__ */ jsx12("div", { className: "ml-4 w-fit rounded-full border border-amber-500/30 bg-amber-500/10 px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.12em] text-amber-300", children: "\u5DF2\u4E2D\u65AD" }),
|
|
3297
|
+
hasFailedWithoutContent && /* @__PURE__ */ jsx12("div", { className: "ml-4 w-fit rounded-full border border-red-500/30 bg-red-500/10 px-2.5 py-1 text-[10px] font-medium text-red-400", children: "\u751F\u6210\u5931\u8D25" }),
|
|
3298
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex w-full items-start gap-2.5", children: [
|
|
3299
|
+
/* @__PURE__ */ jsx12(
|
|
3300
|
+
"span",
|
|
3301
|
+
{
|
|
3302
|
+
className: "grid size-[30px] shrink-0 place-items-center rounded-full bg-[hsl(var(--muted)/0.55)] text-[hsl(var(--foreground))]",
|
|
3303
|
+
"aria-hidden": "true",
|
|
3304
|
+
children: /* @__PURE__ */ jsx12(Bot, { size: 16 })
|
|
3305
|
+
}
|
|
3306
|
+
),
|
|
3307
|
+
/* @__PURE__ */ jsxs10("div", { className: "min-w-0 flex-1 pt-0.5", children: [
|
|
3308
|
+
/* @__PURE__ */ jsxs10(
|
|
3309
|
+
"button",
|
|
3310
|
+
{
|
|
3311
|
+
type: "button",
|
|
3312
|
+
onClick: () => {
|
|
3313
|
+
userSelectedDisplayModeRef.current = true;
|
|
3314
|
+
setDisplayMode(displayMode === "detail" ? "compact" : "detail");
|
|
3315
|
+
},
|
|
3316
|
+
"aria-expanded": effectiveMode === "detail",
|
|
3317
|
+
"aria-label": effectiveMode === "detail" ? "\u6536\u8D77\u6267\u884C\u8FC7\u7A0B" : "\u5C55\u5F00\u6267\u884C\u8FC7\u7A0B",
|
|
3318
|
+
"data-testid": "assistant-execution-summary",
|
|
3319
|
+
className: "inline-flex min-w-0 max-w-full select-none items-center gap-1 bg-transparent p-0 text-left text-xs leading-[22px] text-[hsl(var(--muted-foreground))] transition-colors hover:text-[hsl(var(--foreground))] focus-visible:ring-1 focus-visible:ring-[hsl(var(--ring))] focus:outline-none",
|
|
3320
|
+
children: [
|
|
3321
|
+
/* @__PURE__ */ jsx12("span", { className: "min-w-0 truncate", children: executionSummaryLabel({
|
|
3322
|
+
messages,
|
|
3323
|
+
isStreaming,
|
|
3324
|
+
durationMs: liveExecutionDurationMs,
|
|
3325
|
+
sessionStatus,
|
|
3326
|
+
askAnswers
|
|
3327
|
+
}) }),
|
|
3328
|
+
/* @__PURE__ */ jsx12(
|
|
3329
|
+
ChevronRight,
|
|
3330
|
+
{
|
|
3331
|
+
size: 14,
|
|
3332
|
+
style: { transitionDuration: "260ms", transitionTimingFunction: "cubic-bezier(0.25, 0.1, 0.25, 1)" },
|
|
3333
|
+
className: cn(
|
|
3334
|
+
"shrink-0 transition-transform",
|
|
3335
|
+
effectiveMode === "detail" && "rotate-90"
|
|
3336
|
+
),
|
|
3337
|
+
"aria-hidden": "true"
|
|
3338
|
+
}
|
|
3339
|
+
)
|
|
3340
|
+
]
|
|
3341
|
+
}
|
|
3342
|
+
),
|
|
3343
|
+
/* @__PURE__ */ jsx12("div", { className: "mt-3 h-px w-full bg-[hsl(var(--border)/0.75)]" })
|
|
3344
|
+
] })
|
|
3345
|
+
] }),
|
|
3346
|
+
effectiveMode === "detail" ? /* @__PURE__ */ jsx12("div", { className: "ml-10 flex flex-col gap-3 pt-1", children: messages.map((message, index) => {
|
|
3347
|
+
const isLast = index === messages.length - 1;
|
|
3348
|
+
const streamingThis = isStreaming && isLast;
|
|
3349
|
+
const text = getMessageText(message);
|
|
3350
|
+
const toolCalls = (message.tool_calls ?? []).filter(
|
|
3351
|
+
(toolCall) => formatToolName(toolCall.name) !== "AskUserQuestion" && !shouldHideToolCall(message, toolCall)
|
|
3352
|
+
);
|
|
3353
|
+
const orderedParts = getOrderedMessageParts(message, toolCalls);
|
|
3354
|
+
const showReasoning = !!message.reasoning && isStreaming && index === latestReasoningIndex;
|
|
3355
|
+
return /* @__PURE__ */ jsxs10(
|
|
3356
|
+
"div",
|
|
3357
|
+
{
|
|
3358
|
+
className: "flex flex-col gap-3",
|
|
3359
|
+
children: [
|
|
3360
|
+
showReasoning && message.reasoning ? /* @__PURE__ */ jsx12(ThinkingBlock, { reasoning: message.reasoning, isStreaming: streamingThis && !text }) : null,
|
|
3361
|
+
orderedParts.length > 0 ? orderedParts.map(
|
|
3362
|
+
(part) => part.type === "text" ? /* @__PURE__ */ jsx12(
|
|
3363
|
+
AssistantMessageContent,
|
|
3364
|
+
{
|
|
3365
|
+
message: { ...message, content: part.content, tool_calls: turnToolCalls },
|
|
3366
|
+
sessionId,
|
|
3367
|
+
streaming: streamingThis,
|
|
3368
|
+
compact: true
|
|
3369
|
+
},
|
|
3370
|
+
part.key
|
|
3371
|
+
) : /* @__PURE__ */ jsx12("div", { className: "flex flex-col gap-0.5", children: part.toolCalls.map((toolCall) => {
|
|
3372
|
+
const custom = toolCallRenderer?.(toolCall);
|
|
3373
|
+
return custom !== null && custom !== void 0 ? /* @__PURE__ */ jsx12("div", { children: custom }, toolCall.id) : formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */ jsx12(AgentLoopBlock, { toolCall }, toolCall.id) : /* @__PURE__ */ jsx12(ExecutionToolRow, { toolCall }, toolCall.id);
|
|
3374
|
+
}) }, part.key)
|
|
3375
|
+
) : hasRenderableMessageContent(message) && message !== finalMessage ? /* @__PURE__ */ jsx12(
|
|
3376
|
+
AssistantMessageContent,
|
|
3377
|
+
{
|
|
3378
|
+
message,
|
|
3379
|
+
sessionId,
|
|
3380
|
+
streaming: streamingThis,
|
|
3381
|
+
compact: true
|
|
3382
|
+
}
|
|
3383
|
+
) : null,
|
|
3384
|
+
orderedParts.length === 0 && toolCalls.length > 0 ? /* @__PURE__ */ jsx12("div", { className: "flex flex-col gap-0.5", children: toolCalls.map((toolCall) => {
|
|
3385
|
+
const custom = toolCallRenderer?.(toolCall);
|
|
3386
|
+
return custom !== null && custom !== void 0 ? /* @__PURE__ */ jsx12("div", { children: custom }, toolCall.id) : formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */ jsx12(AgentLoopBlock, { toolCall }, toolCall.id) : /* @__PURE__ */ jsx12(ExecutionToolRow, { toolCall }, toolCall.id);
|
|
3387
|
+
}) }) : null
|
|
3388
|
+
]
|
|
3389
|
+
},
|
|
3390
|
+
message.entry_id ?? `${message.timestamp ?? "assistant"}-${index}`
|
|
3391
|
+
);
|
|
3392
|
+
}) }) : null,
|
|
3393
|
+
finalMessage && (effectiveMode === "compact" || finalOrderedParts.length === 0) ? /* @__PURE__ */ jsx12("div", { className: "ml-10", children: /* @__PURE__ */ jsx12(
|
|
3394
|
+
AssistantMessageContent,
|
|
3395
|
+
{
|
|
3396
|
+
message: finalMessage,
|
|
3397
|
+
sessionId,
|
|
3398
|
+
streaming: isStreaming && finalMessage === messages[messages.length - 1]
|
|
3399
|
+
}
|
|
3400
|
+
) }) : null,
|
|
3401
|
+
questionToolCalls.map((toolCall) => /* @__PURE__ */ jsx12(
|
|
3402
|
+
ToolCallBlock,
|
|
3403
|
+
{
|
|
3404
|
+
toolCall,
|
|
3405
|
+
answerData: askAnswers?.[toolCall.id],
|
|
3406
|
+
onAnswer,
|
|
3407
|
+
answered: sessionStatus !== "waiting_for_input",
|
|
3408
|
+
sessionStatus,
|
|
3409
|
+
isActiveQuestion: toolCall.id === activeQuestionId,
|
|
3410
|
+
renderer: toolCallRenderer
|
|
3411
|
+
},
|
|
3412
|
+
toolCall.id
|
|
3413
|
+
))
|
|
3414
|
+
]
|
|
3415
|
+
}
|
|
3416
|
+
);
|
|
3417
|
+
}
|
|
3418
|
+
function collectMemoryRefs(messages) {
|
|
3419
|
+
const refs = /* @__PURE__ */ new Map();
|
|
3420
|
+
for (const message of messages) {
|
|
3421
|
+
for (const ref of message.memory_refs ?? []) if (!refs.has(ref.id)) refs.set(ref.id, ref);
|
|
3422
|
+
}
|
|
3423
|
+
return [...refs.values()];
|
|
3424
|
+
}
|
|
3425
|
+
function MemoryRefsHint({ refs }) {
|
|
3426
|
+
const [expanded, setExpanded] = useState12(false);
|
|
3427
|
+
const label = refs.some((ref) => ref.skill_name) ? "\u53C2\u8003\u4E86\u8BE5\u6280\u80FD\u7684\u5386\u53F2\u7ECF\u9A8C" : "\u53C2\u8003\u4E86\u5386\u53F2\u7ECF\u9A8C";
|
|
3428
|
+
return /* @__PURE__ */ jsxs10("div", { className: "blade-chat-memory-refs ml-1 w-full max-w-[680px]", children: [
|
|
3429
|
+
/* @__PURE__ */ jsxs10("button", { type: "button", onClick: () => setExpanded((value) => !value), className: "inline-flex h-8 items-center gap-1.5 rounded-lg border border-[hsl(var(--primary)/0.22)] bg-[hsl(var(--primary)/0.07)] px-3 text-xs font-medium text-[hsl(var(--primary))]", children: [
|
|
3430
|
+
/* @__PURE__ */ jsx12(BookOpen, { size: 12 }),
|
|
3431
|
+
/* @__PURE__ */ jsxs10("span", { children: [
|
|
3432
|
+
label,
|
|
3433
|
+
"\uFF08",
|
|
3434
|
+
refs.length,
|
|
3435
|
+
"\uFF09"
|
|
3436
|
+
] }),
|
|
3437
|
+
/* @__PURE__ */ jsx12(ChevronRight, { size: 10, className: cn("transition-transform", expanded && "rotate-90") })
|
|
3438
|
+
] }),
|
|
3439
|
+
expanded ? /* @__PURE__ */ jsx12("div", { className: "mt-2 flex flex-col gap-2 rounded-xl border border-[hsl(var(--border)/0.8)] bg-[hsl(var(--muted)/0.28)] p-2.5", children: refs.map((ref) => /* @__PURE__ */ jsxs10("div", { className: "rounded-lg border border-[hsl(var(--border)/0.55)] bg-[hsl(var(--background)/0.72)] px-3 py-2.5 text-xs", children: [
|
|
3440
|
+
/* @__PURE__ */ jsx12("p", { className: "line-clamp-2 break-words leading-5", children: ref.content_preview }),
|
|
3441
|
+
ref.skill_name ? /* @__PURE__ */ jsx12("span", { className: "mt-1 inline-flex text-[10px] text-[hsl(var(--primary))]", children: ref.skill_name }) : null
|
|
3442
|
+
] }, ref.id)) }) : null
|
|
3443
|
+
] });
|
|
3444
|
+
}
|
|
3445
|
+
function AssistantMessageContent({
|
|
3446
|
+
message,
|
|
3447
|
+
sessionId,
|
|
3448
|
+
streaming,
|
|
3449
|
+
compact = false
|
|
2284
3450
|
}) {
|
|
2285
|
-
const
|
|
2286
|
-
const
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
const
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
"div",
|
|
3451
|
+
const text = getMessageText(message);
|
|
3452
|
+
const imageParts = getImageParts(message.content);
|
|
3453
|
+
const fileParts = getFileParts(message.content);
|
|
3454
|
+
const failed = message.status === "failed";
|
|
3455
|
+
const failedBadge = failed ? /* @__PURE__ */ jsx12("div", { className: "w-fit rounded-full border border-red-500/30 bg-red-500/10 px-2.5 py-1 text-[10px] font-medium text-red-400", children: "\u751F\u6210\u5931\u8D25" }) : null;
|
|
3456
|
+
const textContent = text ? /* @__PURE__ */ jsx12(
|
|
3457
|
+
"div",
|
|
3458
|
+
{
|
|
3459
|
+
className: cn(
|
|
3460
|
+
"blade-chat-assistant-text",
|
|
3461
|
+
compact ? "text-xs leading-[22px] text-[hsl(var(--foreground))]" : "text-[15px] leading-8 text-[hsl(var(--foreground))]"
|
|
3462
|
+
),
|
|
3463
|
+
children: /* @__PURE__ */ jsx12(
|
|
3464
|
+
MarkdownContent,
|
|
2300
3465
|
{
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
3466
|
+
mode: streaming ? "streaming" : "static",
|
|
3467
|
+
className: "blade-chat-prose",
|
|
3468
|
+
sessionId,
|
|
3469
|
+
children: text
|
|
3470
|
+
}
|
|
3471
|
+
)
|
|
3472
|
+
}
|
|
3473
|
+
) : null;
|
|
3474
|
+
if (imageParts.length === 0 && fileParts.length === 0) {
|
|
3475
|
+
if (!failed) return textContent;
|
|
3476
|
+
return failedBadge || textContent ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-2", children: [
|
|
3477
|
+
failedBadge,
|
|
3478
|
+
textContent
|
|
3479
|
+
] }) : null;
|
|
3480
|
+
}
|
|
3481
|
+
return /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-3", children: [
|
|
3482
|
+
failedBadge,
|
|
3483
|
+
imageParts.length > 0 ? /* @__PURE__ */ jsx12("div", { className: "grid gap-2", children: imageParts.map((part) => /* @__PURE__ */ jsx12(
|
|
3484
|
+
"img",
|
|
3485
|
+
{
|
|
3486
|
+
src: part.image_url.url,
|
|
3487
|
+
alt: "\u6D88\u606F\u9644\u4EF6",
|
|
3488
|
+
className: "max-h-72 rounded-xl border border-[hsl(var(--border))] object-cover"
|
|
3489
|
+
},
|
|
3490
|
+
part.image_url.url
|
|
3491
|
+
)) }) : null,
|
|
3492
|
+
fileParts.length > 0 ? /* @__PURE__ */ jsx12("div", { className: "flex flex-wrap gap-1.5", children: fileParts.map((part) => /* @__PURE__ */ jsxs10(
|
|
3493
|
+
"div",
|
|
3494
|
+
{
|
|
3495
|
+
className: "flex min-w-0 items-center gap-1.5 rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--muted)/0.3)] px-2.5 py-1.5 text-xs text-[hsl(var(--muted-foreground))]",
|
|
3496
|
+
title: part.name,
|
|
3497
|
+
children: [
|
|
3498
|
+
/* @__PURE__ */ jsx12(FileText, { size: 12, className: "shrink-0" }),
|
|
3499
|
+
/* @__PURE__ */ jsx12("span", { className: "max-w-56 truncate", children: part.name })
|
|
3500
|
+
]
|
|
3501
|
+
},
|
|
3502
|
+
`${part.name}-${part.data.slice(0, 32)}`
|
|
3503
|
+
)) }) : null,
|
|
3504
|
+
textContent
|
|
2333
3505
|
] });
|
|
2334
3506
|
}
|
|
2335
3507
|
|
|
2336
3508
|
// src/components/RenderErrorBoundary.tsx
|
|
2337
3509
|
import { Component } from "react";
|
|
2338
|
-
import { jsx as
|
|
3510
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2339
3511
|
function getFirstComponentName(componentStack) {
|
|
2340
3512
|
const match = componentStack.match(/\n\s+at\s+([^\s(]+)/);
|
|
2341
3513
|
return match?.[1] ?? null;
|
|
@@ -2368,26 +3540,26 @@ var RenderErrorBoundary = class extends Component {
|
|
|
2368
3540
|
return children;
|
|
2369
3541
|
}
|
|
2370
3542
|
const componentName = getFirstComponentName(componentStack);
|
|
2371
|
-
return /* @__PURE__ */
|
|
2372
|
-
/* @__PURE__ */
|
|
2373
|
-
/* @__PURE__ */
|
|
2374
|
-
/* @__PURE__ */
|
|
3543
|
+
return /* @__PURE__ */ jsx13("div", { className: "blade-chat-render-error rounded-xl border border-amber-500/30 bg-amber-500/8 px-4 py-3 text-sm text-amber-100", children: /* @__PURE__ */ jsxs11("div", { className: "flex items-start gap-2", children: [
|
|
3544
|
+
/* @__PURE__ */ jsx13(TriangleAlert, { className: "mt-0.5 h-4 w-4 shrink-0 text-amber-300" }),
|
|
3545
|
+
/* @__PURE__ */ jsxs11("div", { className: "min-w-0 flex-1", children: [
|
|
3546
|
+
/* @__PURE__ */ jsxs11("div", { className: "font-medium", children: [
|
|
2375
3547
|
label,
|
|
2376
3548
|
"\u6E32\u67D3\u5931\u8D25"
|
|
2377
3549
|
] }),
|
|
2378
|
-
/* @__PURE__ */
|
|
3550
|
+
/* @__PURE__ */ jsxs11("div", { className: "mt-1 break-words text-xs leading-5 text-amber-100/75", children: [
|
|
2379
3551
|
componentName ? `\u7EC4\u4EF6\uFF1A${componentName}\u3002` : null,
|
|
2380
3552
|
error.message || "\u53D1\u751F\u4E86\u672A\u9884\u671F\u7684\u6E32\u67D3\u9519\u8BEF\u3002"
|
|
2381
3553
|
] }),
|
|
2382
|
-
details ? /* @__PURE__ */
|
|
3554
|
+
details ? /* @__PURE__ */ jsx13("div", { className: "mt-1 truncate text-xs text-amber-100/55", children: details }) : null
|
|
2383
3555
|
] })
|
|
2384
3556
|
] }) });
|
|
2385
3557
|
}
|
|
2386
3558
|
};
|
|
2387
3559
|
|
|
2388
3560
|
// src/components/PostChatFollowupBlock.tsx
|
|
2389
|
-
import { useCallback as
|
|
2390
|
-
import { Fragment as
|
|
3561
|
+
import { useCallback as useCallback6, useEffect as useEffect10, useRef as useRef11, useState as useState13 } from "react";
|
|
3562
|
+
import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2391
3563
|
function emitInteraction(callback, event) {
|
|
2392
3564
|
try {
|
|
2393
3565
|
callback?.(event);
|
|
@@ -2397,9 +3569,6 @@ function emitInteraction(callback, event) {
|
|
|
2397
3569
|
function basename(path) {
|
|
2398
3570
|
return path.split(/[\\/]/).filter(Boolean).pop() || path;
|
|
2399
3571
|
}
|
|
2400
|
-
function isVideo(path) {
|
|
2401
|
-
return /\.(?:mp4|mov|webm|mkv|avi|m4v)$/i.test(path);
|
|
2402
|
-
}
|
|
2403
3572
|
function ArtifactCard({
|
|
2404
3573
|
artifact,
|
|
2405
3574
|
sessionId,
|
|
@@ -2409,10 +3578,10 @@ function ArtifactCard({
|
|
|
2409
3578
|
onArtifactOpened
|
|
2410
3579
|
}) {
|
|
2411
3580
|
const client = useBladeClient();
|
|
2412
|
-
const [downloading, setDownloading] =
|
|
3581
|
+
const [downloading, setDownloading] = useState13(false);
|
|
2413
3582
|
const name = artifact.label || basename(artifact.target);
|
|
2414
3583
|
if (artifact.kind === "link") {
|
|
2415
|
-
return /* @__PURE__ */
|
|
3584
|
+
return /* @__PURE__ */ jsxs12(
|
|
2416
3585
|
"a",
|
|
2417
3586
|
{
|
|
2418
3587
|
href: artifact.target,
|
|
@@ -2423,51 +3592,55 @@ function ArtifactCard({
|
|
|
2423
3592
|
${artifact.target}`,
|
|
2424
3593
|
className: "group relative flex min-w-0 items-center gap-1.5 rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-2 py-1.5 text-xs text-[hsl(var(--card-foreground))] hover:bg-[hsl(var(--accent))]",
|
|
2425
3594
|
children: [
|
|
2426
|
-
/* @__PURE__ */
|
|
2427
|
-
/* @__PURE__ */
|
|
2428
|
-
/* @__PURE__ */
|
|
3595
|
+
/* @__PURE__ */ jsx14(Globe, { size: 15, className: "shrink-0 text-[hsl(var(--primary))]" }),
|
|
3596
|
+
/* @__PURE__ */ jsx14("span", { className: "min-w-0 flex-1 truncate font-medium", children: name }),
|
|
3597
|
+
/* @__PURE__ */ jsx14(ArrowUpRight, { size: 13, className: "absolute right-2 opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100" })
|
|
2429
3598
|
]
|
|
2430
3599
|
}
|
|
2431
3600
|
);
|
|
2432
3601
|
}
|
|
2433
|
-
const
|
|
2434
|
-
|
|
2435
|
-
|
|
3602
|
+
const fileName = basename(artifact.target);
|
|
3603
|
+
const downloadUrl = sessionId ? client.buildAuthedUrl(
|
|
3604
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/files/${encodeURIComponent(artifact.target)}`
|
|
3605
|
+
) : void 0;
|
|
3606
|
+
const handleDownload = async (event) => {
|
|
3607
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
3608
|
+
event.preventDefault();
|
|
3609
|
+
if (!sessionId || downloading) return;
|
|
3610
|
+
setDownloading(true);
|
|
3611
|
+
emitInteraction(onInteraction, {
|
|
3612
|
+
type: "artifact_download_started",
|
|
3613
|
+
sessionId,
|
|
3614
|
+
assistantEntryId,
|
|
3615
|
+
artifactIndex,
|
|
3616
|
+
artifactKind: "file"
|
|
3617
|
+
});
|
|
3618
|
+
try {
|
|
3619
|
+
await client.sessions.downloadFile(sessionId, artifact.target, fileName);
|
|
3620
|
+
emitInteraction(onInteraction, {
|
|
3621
|
+
type: "artifact_download_succeeded",
|
|
3622
|
+
sessionId,
|
|
3623
|
+
assistantEntryId,
|
|
3624
|
+
artifactIndex,
|
|
3625
|
+
artifactKind: "file"
|
|
3626
|
+
});
|
|
3627
|
+
} catch {
|
|
3628
|
+
} finally {
|
|
3629
|
+
setDownloading(false);
|
|
3630
|
+
}
|
|
3631
|
+
};
|
|
3632
|
+
return /* @__PURE__ */ jsx14(
|
|
3633
|
+
"a",
|
|
2436
3634
|
{
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
onClick:
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
artifactIndex,
|
|
2447
|
-
artifactKind: "file"
|
|
2448
|
-
});
|
|
2449
|
-
try {
|
|
2450
|
-
await client.sessions.downloadFile(sessionId, artifact.target, basename(artifact.target));
|
|
2451
|
-
emitInteraction(onInteraction, {
|
|
2452
|
-
type: "artifact_download_succeeded",
|
|
2453
|
-
sessionId,
|
|
2454
|
-
assistantEntryId,
|
|
2455
|
-
artifactIndex,
|
|
2456
|
-
artifactKind: "file"
|
|
2457
|
-
});
|
|
2458
|
-
} catch {
|
|
2459
|
-
} finally {
|
|
2460
|
-
setDownloading(false);
|
|
2461
|
-
}
|
|
2462
|
-
},
|
|
2463
|
-
title: name,
|
|
2464
|
-
"aria-label": `\u4E0B\u8F7D ${name}`,
|
|
2465
|
-
className: "group relative flex min-w-0 items-center gap-1.5 rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-2 py-1.5 text-left text-xs text-[hsl(var(--card-foreground))] hover:bg-[hsl(var(--accent))] disabled:opacity-60",
|
|
2466
|
-
children: [
|
|
2467
|
-
/* @__PURE__ */ jsx13(Icon2, { size: 15, className: "shrink-0 text-[hsl(var(--primary))]" }),
|
|
2468
|
-
/* @__PURE__ */ jsx13("span", { className: "min-w-0 flex-1 truncate font-medium", children: name }),
|
|
2469
|
-
/* @__PURE__ */ jsx13(Download, { size: 13, className: "absolute right-2 opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100" })
|
|
2470
|
-
]
|
|
3635
|
+
href: downloadUrl,
|
|
3636
|
+
download: fileName,
|
|
3637
|
+
onClick: handleDownload,
|
|
3638
|
+
title: fileName,
|
|
3639
|
+
"aria-label": `\u4E0B\u8F7D\u6587\u4EF6\uFF1A${fileName}`,
|
|
3640
|
+
"aria-disabled": !sessionId || void 0,
|
|
3641
|
+
"aria-busy": downloading || void 0,
|
|
3642
|
+
className: "min-w-0 cursor-pointer break-all text-xs text-[hsl(var(--primary))] underline aria-disabled:cursor-not-allowed aria-disabled:opacity-60 aria-busy:cursor-wait",
|
|
3643
|
+
children: fileName
|
|
2471
3644
|
}
|
|
2472
3645
|
);
|
|
2473
3646
|
}
|
|
@@ -2484,17 +3657,17 @@ function feedbackReasonLabel(reason) {
|
|
|
2484
3657
|
}
|
|
2485
3658
|
function HistoricalResultFeedback({ feedback }) {
|
|
2486
3659
|
const label = feedbackReasonLabel(feedback.reason);
|
|
2487
|
-
return /* @__PURE__ */
|
|
3660
|
+
return /* @__PURE__ */ jsxs12(
|
|
2488
3661
|
"section",
|
|
2489
3662
|
{
|
|
2490
3663
|
"aria-label": "\u5386\u53F2\u7ED3\u679C\u53CD\u9988",
|
|
2491
3664
|
className: "mt-3 w-fit max-w-full rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--muted)/0.2)] px-3 py-2 text-xs text-[hsl(var(--muted-foreground))]",
|
|
2492
3665
|
children: [
|
|
2493
|
-
/* @__PURE__ */
|
|
3666
|
+
/* @__PURE__ */ jsxs12("span", { children: [
|
|
2494
3667
|
"\u4F60\u5BF9\u6B64\u8F6E\u7ED3\u679C\u7684\u8BC4\u4EF7\uFF1A",
|
|
2495
3668
|
feedback.helpful ? "\u6709\u5E2E\u52A9" : "\u6CA1\u5E2E\u52A9"
|
|
2496
3669
|
] }),
|
|
2497
|
-
label ? /* @__PURE__ */
|
|
3670
|
+
label ? /* @__PURE__ */ jsxs12("span", { children: [
|
|
2498
3671
|
" \xB7 ",
|
|
2499
3672
|
label
|
|
2500
3673
|
] }) : null
|
|
@@ -2511,15 +3684,15 @@ function ResultFeedback({
|
|
|
2511
3684
|
onFeedbackSaved
|
|
2512
3685
|
}) {
|
|
2513
3686
|
const client = useBladeClient();
|
|
2514
|
-
const [saved, setSaved] =
|
|
2515
|
-
const [helpful, setHelpful] =
|
|
2516
|
-
const [reason, setReason] =
|
|
2517
|
-
const [saving, setSaving] =
|
|
2518
|
-
const [saveError, setSaveError] =
|
|
2519
|
-
const reportedShown =
|
|
2520
|
-
const latestChoice =
|
|
3687
|
+
const [saved, setSaved] = useState13(savedFeedback ?? null);
|
|
3688
|
+
const [helpful, setHelpful] = useState13(savedFeedback?.helpful ?? null);
|
|
3689
|
+
const [reason, setReason] = useState13(savedFeedback?.reason ?? null);
|
|
3690
|
+
const [saving, setSaving] = useState13(false);
|
|
3691
|
+
const [saveError, setSaveError] = useState13(false);
|
|
3692
|
+
const reportedShown = useRef11(false);
|
|
3693
|
+
const latestChoice = useRef11(null);
|
|
2521
3694
|
const eligible = followup.feedback_eligible === true && Boolean(sessionId) && !isViewer;
|
|
2522
|
-
|
|
3695
|
+
useEffect10(() => {
|
|
2523
3696
|
if (!eligible || reportedShown.current) return;
|
|
2524
3697
|
reportedShown.current = true;
|
|
2525
3698
|
emitInteraction(onInteraction, {
|
|
@@ -2528,13 +3701,13 @@ function ResultFeedback({
|
|
|
2528
3701
|
assistantEntryId: followup.assistant_entry_id
|
|
2529
3702
|
});
|
|
2530
3703
|
}, [eligible, followup.assistant_entry_id, onInteraction, sessionId]);
|
|
2531
|
-
|
|
3704
|
+
useEffect10(() => {
|
|
2532
3705
|
if (!savedFeedback || latestChoice.current) return;
|
|
2533
3706
|
setSaved(savedFeedback);
|
|
2534
3707
|
setHelpful(savedFeedback.helpful);
|
|
2535
3708
|
setReason(savedFeedback.reason);
|
|
2536
3709
|
}, [savedFeedback]);
|
|
2537
|
-
const submit =
|
|
3710
|
+
const submit = useCallback6(
|
|
2538
3711
|
async (nextHelpful, nextReason) => {
|
|
2539
3712
|
if (!sessionId) return;
|
|
2540
3713
|
const choice = { helpful: nextHelpful, reason: nextReason };
|
|
@@ -2569,15 +3742,15 @@ function ResultFeedback({
|
|
|
2569
3742
|
[client, followup.assistant_entry_id, onFeedbackSaved, onInteraction, sessionId]
|
|
2570
3743
|
);
|
|
2571
3744
|
if (!eligible) return null;
|
|
2572
|
-
return /* @__PURE__ */
|
|
3745
|
+
return /* @__PURE__ */ jsxs12(
|
|
2573
3746
|
"section",
|
|
2574
3747
|
{
|
|
2575
3748
|
"aria-label": "\u7ED3\u679C\u53CD\u9988",
|
|
2576
3749
|
className: "flex flex-col gap-2 border-t border-[hsl(var(--border))] pt-3",
|
|
2577
3750
|
children: [
|
|
2578
|
-
/* @__PURE__ */
|
|
2579
|
-
/* @__PURE__ */
|
|
2580
|
-
/* @__PURE__ */
|
|
3751
|
+
/* @__PURE__ */ jsx14("div", { className: "text-xs font-medium text-[hsl(var(--muted-foreground))]", children: "\u76EE\u524D\u7684\u6574\u4F53\u7ED3\u679C\u6709\u5E2E\u52A9\u5417\uFF1F" }),
|
|
3752
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap gap-1.5", children: [
|
|
3753
|
+
/* @__PURE__ */ jsx14(
|
|
2581
3754
|
"button",
|
|
2582
3755
|
{
|
|
2583
3756
|
type: "button",
|
|
@@ -2588,7 +3761,7 @@ function ResultFeedback({
|
|
|
2588
3761
|
children: "\u6709\u5E2E\u52A9"
|
|
2589
3762
|
}
|
|
2590
3763
|
),
|
|
2591
|
-
/* @__PURE__ */
|
|
3764
|
+
/* @__PURE__ */ jsx14(
|
|
2592
3765
|
"button",
|
|
2593
3766
|
{
|
|
2594
3767
|
type: "button",
|
|
@@ -2600,7 +3773,7 @@ function ResultFeedback({
|
|
|
2600
3773
|
}
|
|
2601
3774
|
)
|
|
2602
3775
|
] }),
|
|
2603
|
-
helpful === false ? /* @__PURE__ */
|
|
3776
|
+
helpful === false ? /* @__PURE__ */ jsx14("div", { className: "flex flex-wrap gap-1.5", "aria-label": "\u6CA1\u5E2E\u52A9\u7684\u4E3B\u8981\u539F\u56E0", children: FEEDBACK_REASONS.map((item) => /* @__PURE__ */ jsx14(
|
|
2604
3777
|
"button",
|
|
2605
3778
|
{
|
|
2606
3779
|
type: "button",
|
|
@@ -2612,9 +3785,9 @@ function ResultFeedback({
|
|
|
2612
3785
|
},
|
|
2613
3786
|
item.value
|
|
2614
3787
|
)) }) : null,
|
|
2615
|
-
saveError ? /* @__PURE__ */
|
|
2616
|
-
/* @__PURE__ */
|
|
2617
|
-
/* @__PURE__ */
|
|
3788
|
+
saveError ? /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2 text-xs text-[hsl(var(--destructive))]", children: [
|
|
3789
|
+
/* @__PURE__ */ jsx14("span", { children: "\u53CD\u9988\u6682\u672A\u4FDD\u5B58\uFF0C\u53EF\u91CD\u8BD5" }),
|
|
3790
|
+
/* @__PURE__ */ jsx14(
|
|
2618
3791
|
"button",
|
|
2619
3792
|
{
|
|
2620
3793
|
type: "button",
|
|
@@ -2626,7 +3799,7 @@ function ResultFeedback({
|
|
|
2626
3799
|
children: "\u91CD\u8BD5"
|
|
2627
3800
|
}
|
|
2628
3801
|
)
|
|
2629
|
-
] }) : saved ? /* @__PURE__ */
|
|
3802
|
+
] }) : saved ? /* @__PURE__ */ jsx14("div", { className: "text-[11px] text-[hsl(var(--muted-foreground))]", children: "\u5DF2\u4FDD\u5B58\uFF0C\u53EF\u968F\u65F6\u4FEE\u6539" }) : null
|
|
2630
3803
|
]
|
|
2631
3804
|
}
|
|
2632
3805
|
);
|
|
@@ -2640,14 +3813,14 @@ function PostChatFollowupBlock({
|
|
|
2640
3813
|
savedFeedback,
|
|
2641
3814
|
onFeedbackSaved
|
|
2642
3815
|
}) {
|
|
2643
|
-
const [expanded, setExpanded] =
|
|
2644
|
-
const adopted =
|
|
2645
|
-
const reportedSuggestions =
|
|
2646
|
-
const reportedArtifacts =
|
|
2647
|
-
const openedArtifacts =
|
|
3816
|
+
const [expanded, setExpanded] = useState13(false);
|
|
3817
|
+
const adopted = useRef11(/* @__PURE__ */ new Set());
|
|
3818
|
+
const reportedSuggestions = useRef11(false);
|
|
3819
|
+
const reportedArtifacts = useRef11(/* @__PURE__ */ new Set());
|
|
3820
|
+
const openedArtifacts = useRef11(/* @__PURE__ */ new Set());
|
|
2648
3821
|
const artifacts = followup.final_artifacts ?? [];
|
|
2649
3822
|
const visibleArtifacts = expanded ? artifacts : artifacts.slice(0, 3);
|
|
2650
|
-
|
|
3823
|
+
useEffect10(() => {
|
|
2651
3824
|
if (!reportedSuggestions.current && followup.suggestions.length > 0) {
|
|
2652
3825
|
reportedSuggestions.current = true;
|
|
2653
3826
|
emitInteraction(onInteraction, {
|
|
@@ -2677,7 +3850,7 @@ function PostChatFollowupBlock({
|
|
|
2677
3850
|
sessionId,
|
|
2678
3851
|
visibleArtifacts
|
|
2679
3852
|
]);
|
|
2680
|
-
const reportArtifactOpened =
|
|
3853
|
+
const reportArtifactOpened = useCallback6(
|
|
2681
3854
|
(artifactIndex, artifactKind) => {
|
|
2682
3855
|
if (openedArtifacts.current.has(artifactIndex)) return;
|
|
2683
3856
|
openedArtifacts.current.add(artifactIndex);
|
|
@@ -2693,15 +3866,15 @@ function PostChatFollowupBlock({
|
|
|
2693
3866
|
);
|
|
2694
3867
|
if (!followup.recaption && artifacts.length === 0 && followup.suggestions.length === 0 && !followup.feedback_eligible)
|
|
2695
3868
|
return null;
|
|
2696
|
-
return /* @__PURE__ */
|
|
2697
|
-
followup.recaption || artifacts.length > 0 ? /* @__PURE__ */
|
|
2698
|
-
/* @__PURE__ */
|
|
2699
|
-
/* @__PURE__ */
|
|
3869
|
+
return /* @__PURE__ */ jsxs12("div", { className: "mt-3 flex w-fit max-w-full flex-col gap-3 rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--muted)/0.28)] p-3 sm:max-w-[680px]", children: [
|
|
3870
|
+
followup.recaption || artifacts.length > 0 ? /* @__PURE__ */ jsxs12("section", { "aria-label": "\u672C\u8F6E\u5C0F\u7ED3", className: "flex flex-col gap-2", children: [
|
|
3871
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5 text-xs font-medium text-[hsl(var(--muted-foreground))]", children: [
|
|
3872
|
+
/* @__PURE__ */ jsx14(Sparkles, { size: 14 }),
|
|
2700
3873
|
"\u672C\u8F6E\u5C0F\u7ED3"
|
|
2701
3874
|
] }),
|
|
2702
|
-
followup.recaption ? /* @__PURE__ */
|
|
2703
|
-
artifacts.length > 0 ? /* @__PURE__ */
|
|
2704
|
-
/* @__PURE__ */
|
|
3875
|
+
followup.recaption ? /* @__PURE__ */ jsx14("p", { className: "text-[13px] leading-5", children: followup.recaption }) : null,
|
|
3876
|
+
artifacts.length > 0 ? /* @__PURE__ */ jsxs12(Fragment3, { children: [
|
|
3877
|
+
/* @__PURE__ */ jsx14("div", { className: "grid max-w-full grid-cols-3 gap-1.5", children: visibleArtifacts.map((artifact, artifactIndex) => /* @__PURE__ */ jsx14(
|
|
2705
3878
|
ArtifactCard,
|
|
2706
3879
|
{
|
|
2707
3880
|
artifact,
|
|
@@ -2713,7 +3886,7 @@ function PostChatFollowupBlock({
|
|
|
2713
3886
|
},
|
|
2714
3887
|
`${artifact.kind}:${artifactIndex}`
|
|
2715
3888
|
)) }),
|
|
2716
|
-
artifacts.length > 3 ? /* @__PURE__ */
|
|
3889
|
+
artifacts.length > 3 ? /* @__PURE__ */ jsxs12(
|
|
2717
3890
|
"button",
|
|
2718
3891
|
{
|
|
2719
3892
|
type: "button",
|
|
@@ -2722,15 +3895,15 @@ function PostChatFollowupBlock({
|
|
|
2722
3895
|
className: "flex w-fit items-center gap-0.5 text-[11px] text-[hsl(var(--muted-foreground))]",
|
|
2723
3896
|
children: [
|
|
2724
3897
|
expanded ? "\u6536\u8D77" : `\u5C55\u5F00 ${artifacts.length - 3} \u4E2A`,
|
|
2725
|
-
/* @__PURE__ */
|
|
3898
|
+
/* @__PURE__ */ jsx14(ChevronDown, { size: 13, className: expanded ? "rotate-180" : void 0 })
|
|
2726
3899
|
]
|
|
2727
3900
|
}
|
|
2728
3901
|
) : null
|
|
2729
3902
|
] }) : null
|
|
2730
3903
|
] }) : null,
|
|
2731
|
-
followup.suggestions.length > 0 ? /* @__PURE__ */
|
|
2732
|
-
/* @__PURE__ */
|
|
2733
|
-
followup.suggestions.map((suggestion, suggestionIndex) => /* @__PURE__ */
|
|
3904
|
+
followup.suggestions.length > 0 ? /* @__PURE__ */ jsxs12("section", { "aria-label": "\u4E0B\u4E00\u6B65\u5EFA\u8BAE", className: "flex flex-col gap-1.5", children: [
|
|
3905
|
+
/* @__PURE__ */ jsx14("div", { className: "text-xs font-medium text-[hsl(var(--muted-foreground))]", children: "\u4E0B\u4E00\u6B65\u53EF\u4EE5" }),
|
|
3906
|
+
followup.suggestions.map((suggestion, suggestionIndex) => /* @__PURE__ */ jsxs12(
|
|
2734
3907
|
"button",
|
|
2735
3908
|
{
|
|
2736
3909
|
type: "button",
|
|
@@ -2749,14 +3922,14 @@ function PostChatFollowupBlock({
|
|
|
2749
3922
|
},
|
|
2750
3923
|
className: "group flex items-center gap-2 rounded-xl bg-[hsl(var(--muted)/0.62)] px-3 py-2 text-left text-[13px] disabled:cursor-default disabled:opacity-60",
|
|
2751
3924
|
children: [
|
|
2752
|
-
/* @__PURE__ */
|
|
2753
|
-
/* @__PURE__ */
|
|
3925
|
+
/* @__PURE__ */ jsx14("span", { children: suggestion }),
|
|
3926
|
+
/* @__PURE__ */ jsx14(ArrowRight, { size: 14, className: "ml-auto shrink-0" })
|
|
2754
3927
|
]
|
|
2755
3928
|
},
|
|
2756
3929
|
suggestion
|
|
2757
3930
|
))
|
|
2758
3931
|
] }) : null,
|
|
2759
|
-
/* @__PURE__ */
|
|
3932
|
+
/* @__PURE__ */ jsx14(
|
|
2760
3933
|
ResultFeedback,
|
|
2761
3934
|
{
|
|
2762
3935
|
followup,
|
|
@@ -2771,8 +3944,91 @@ function PostChatFollowupBlock({
|
|
|
2771
3944
|
}
|
|
2772
3945
|
|
|
2773
3946
|
// src/components/UserMessageBubble.tsx
|
|
2774
|
-
import {
|
|
2775
|
-
|
|
3947
|
+
import {
|
|
3948
|
+
chatErrorForDisplay,
|
|
3949
|
+
getFileParts as getFileParts2,
|
|
3950
|
+
getImageParts as getImageParts2,
|
|
3951
|
+
getTextContent as getTextContent2
|
|
3952
|
+
} from "@blade-hq/agent-client";
|
|
3953
|
+
|
|
3954
|
+
// src/lib/whatif-prompt.ts
|
|
3955
|
+
var HEADER_RE = /^以下消息和 step 产物标记为 deprecated_by_rerun,请基于最新用户假设从 step(\d+) 开始完整重新推演,不要复用旧结论。$/;
|
|
3956
|
+
var QUOTE_HEADER_RE = /^\[步骤(\d+)\s*·\s*(.+?)\]$/;
|
|
3957
|
+
var USER_INPUT_TAG = "[\u7528\u6237\u8F93\u5165]";
|
|
3958
|
+
function parseWhatIfPrompt(text) {
|
|
3959
|
+
const lines = text.replace(/\r\n/g, "\n").trimEnd().split("\n");
|
|
3960
|
+
const headerMatch = lines[0]?.match(HEADER_RE);
|
|
3961
|
+
if (!headerMatch) return null;
|
|
3962
|
+
const userTagIdx = lines.indexOf(USER_INPUT_TAG);
|
|
3963
|
+
const hasUserTag = userTagIdx >= 0;
|
|
3964
|
+
const quoteBlockEndExclusive = hasUserTag ? userTagIdx : lines.length;
|
|
3965
|
+
const quoteHeaderIdxs = [];
|
|
3966
|
+
let quoteBlockFound = false;
|
|
3967
|
+
for (let i = 1; i < quoteBlockEndExclusive; i++) {
|
|
3968
|
+
if (!quoteBlockFound && lines[i].trim() === "[\u5F15\u7528]") {
|
|
3969
|
+
quoteBlockFound = true;
|
|
3970
|
+
} else if (quoteBlockFound && QUOTE_HEADER_RE.test(lines[i])) {
|
|
3971
|
+
quoteHeaderIdxs.push(i);
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
let legacyUserTextStart = -1;
|
|
3975
|
+
if (!hasUserTag && quoteHeaderIdxs.length > 0) {
|
|
3976
|
+
const lastSnapshotStart = quoteHeaderIdxs.at(-1) + 1;
|
|
3977
|
+
let i = lines.length - 1;
|
|
3978
|
+
while (i >= lastSnapshotStart && lines[i].trim() === "") i -= 1;
|
|
3979
|
+
while (i >= lastSnapshotStart && lines[i].trim() !== "") i -= 1;
|
|
3980
|
+
if (i >= lastSnapshotStart) legacyUserTextStart = i + 1;
|
|
3981
|
+
}
|
|
3982
|
+
const quotes = quoteHeaderIdxs.map((headerIdx, index) => {
|
|
3983
|
+
const match = lines[headerIdx].match(QUOTE_HEADER_RE);
|
|
3984
|
+
const nextHeader = quoteHeaderIdxs[index + 1];
|
|
3985
|
+
const end = nextHeader ?? (legacyUserTextStart >= 0 ? legacyUserTextStart : quoteBlockEndExclusive);
|
|
3986
|
+
const snapshotLines = lines.slice(headerIdx + 1, end);
|
|
3987
|
+
while (snapshotLines.at(-1)?.trim() === "") snapshotLines.pop();
|
|
3988
|
+
return {
|
|
3989
|
+
stepNumber: Number.parseInt(match[1], 10),
|
|
3990
|
+
label: match[2].trim(),
|
|
3991
|
+
snapshot: snapshotLines.join("\n")
|
|
3992
|
+
};
|
|
3993
|
+
});
|
|
3994
|
+
const userTextStart = hasUserTag ? userTagIdx + 1 : legacyUserTextStart;
|
|
3995
|
+
const userLines = userTextStart >= 0 ? lines.slice(userTextStart) : [];
|
|
3996
|
+
while (userLines[0]?.trim() === "") userLines.shift();
|
|
3997
|
+
while (userLines.at(-1)?.trim() === "") userLines.pop();
|
|
3998
|
+
const fromStep = Number.parseInt(headerMatch[1], 10);
|
|
3999
|
+
return {
|
|
4000
|
+
fromStep: Number.isFinite(fromStep) ? fromStep : null,
|
|
4001
|
+
quotes,
|
|
4002
|
+
userText: userLines.join("\n")
|
|
4003
|
+
};
|
|
4004
|
+
}
|
|
4005
|
+
|
|
4006
|
+
// src/components/WhatIfUserBubble.tsx
|
|
4007
|
+
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
4008
|
+
function WhatIfUserBubble({ parsed, onQuoteClick }) {
|
|
4009
|
+
const { fromStep, quotes, userText } = parsed;
|
|
4010
|
+
return /* @__PURE__ */ jsxs13("div", { className: "flex flex-col items-end gap-2", children: [
|
|
4011
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1.5 rounded-full border border-[hsl(var(--border))] bg-[hsl(var(--muted)/0.5)] px-2.5 py-0.5 text-[10px] text-[hsl(var(--muted-foreground))]", children: [
|
|
4012
|
+
/* @__PURE__ */ jsx15(RefreshCcw, { size: 10 }),
|
|
4013
|
+
/* @__PURE__ */ jsx15("span", { children: fromStep != null ? `\u91CD\u8DD1\u81EA step ${fromStep}` : "\u91CD\u8DD1" })
|
|
4014
|
+
] }),
|
|
4015
|
+
quotes.length > 0 && /* @__PURE__ */ jsx15("div", { className: "flex max-w-[min(72vw,42rem)] flex-col items-stretch gap-2", children: quotes.map((quote, index) => {
|
|
4016
|
+
const clickable = quote.stepNumber != null && !!onQuoteClick;
|
|
4017
|
+
const label = quote.stepNumber != null ? `\u6B65\u9AA4${quote.stepNumber} \xB7 ${quote.label}` : quote.label;
|
|
4018
|
+
return /* @__PURE__ */ jsxs13("div", { className: "rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card)/0.8)] px-3 py-2 text-left", children: [
|
|
4019
|
+
/* @__PURE__ */ jsxs13("button", { type: "button", disabled: !clickable, onClick: () => clickable && onQuoteClick(quote.stepNumber), className: "inline-flex max-w-full items-center gap-1 text-[11px] font-medium text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))] disabled:cursor-default", title: clickable ? "\u8DF3\u8F6C\u5230\u5BF9\u5E94\u6B65\u9AA4\u5361\u7247" : void 0, children: [
|
|
4020
|
+
/* @__PURE__ */ jsx15("span", { children: "\u21B3" }),
|
|
4021
|
+
/* @__PURE__ */ jsx15("span", { className: "truncate", children: label })
|
|
4022
|
+
] }),
|
|
4023
|
+
quote.snapshot ? /* @__PURE__ */ jsx15("div", { className: "mt-1 border-l-2 border-[hsl(var(--accent-foreground)/0.35)] pl-2 text-xs leading-relaxed text-[hsl(var(--foreground)/0.8)]", children: /* @__PURE__ */ jsx15(MarkdownContent, { className: "blade-chat-prose", children: quote.snapshot }) }) : null
|
|
4024
|
+
] }, `${quote.stepNumber ?? "x"}-${index}`);
|
|
4025
|
+
}) }),
|
|
4026
|
+
userText && /* @__PURE__ */ jsx15("div", { className: "rounded-2xl border border-[hsl(var(--user-msg-border))] bg-[hsl(var(--user-msg-bg))] px-4 py-2.5 text-sm leading-relaxed text-[hsl(var(--user-msg-fg))]", children: /* @__PURE__ */ jsx15(MarkdownContent, { className: "blade-chat-prose", children: userText }) })
|
|
4027
|
+
] });
|
|
4028
|
+
}
|
|
4029
|
+
|
|
4030
|
+
// src/components/UserMessageBubble.tsx
|
|
4031
|
+
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2776
4032
|
function isUserMessage(message) {
|
|
2777
4033
|
return message.role === "user";
|
|
2778
4034
|
}
|
|
@@ -2782,10 +4038,14 @@ function isErrorMessage(message) {
|
|
|
2782
4038
|
var isSending = (message) => message.status === "streaming";
|
|
2783
4039
|
function UserMessageBubble({ message, className }) {
|
|
2784
4040
|
const text = getTextContent2(message.content).trim();
|
|
2785
|
-
const fileParts =
|
|
2786
|
-
const imageParts =
|
|
2787
|
-
|
|
2788
|
-
|
|
4041
|
+
const fileParts = getFileParts2(message.content);
|
|
4042
|
+
const imageParts = getImageParts2(message.content);
|
|
4043
|
+
const whatifParsed = text && imageParts.length === 0 && fileParts.length === 0 ? parseWhatIfPrompt(text) : null;
|
|
4044
|
+
if (whatifParsed) {
|
|
4045
|
+
return /* @__PURE__ */ jsx16("div", { className: cn("blade-chat-user-row flex justify-end", className), children: /* @__PURE__ */ jsx16("div", { className: "blade-chat-user-col flex max-w-[72%] flex-col items-end gap-3", children: /* @__PURE__ */ jsx16(WhatIfUserBubble, { parsed: whatifParsed }) }) });
|
|
4046
|
+
}
|
|
4047
|
+
return /* @__PURE__ */ jsx16("div", { className: cn("blade-chat-user-row flex justify-end", className), children: /* @__PURE__ */ jsxs14("div", { className: "blade-chat-user-col flex max-w-[72%] flex-col items-end gap-3", children: [
|
|
4048
|
+
imageParts.length > 0 && /* @__PURE__ */ jsx16("div", { className: "grid gap-2", children: imageParts.map((part) => /* @__PURE__ */ jsx16(
|
|
2789
4049
|
"img",
|
|
2790
4050
|
{
|
|
2791
4051
|
src: part.image_url.url,
|
|
@@ -2794,21 +4054,21 @@ function UserMessageBubble({ message, className }) {
|
|
|
2794
4054
|
},
|
|
2795
4055
|
part.image_url.url
|
|
2796
4056
|
)) }),
|
|
2797
|
-
fileParts.length > 0 && /* @__PURE__ */
|
|
4057
|
+
fileParts.length > 0 && /* @__PURE__ */ jsx16("div", { className: "flex flex-col items-end gap-1.5", children: fileParts.map((part) => /* @__PURE__ */ jsxs14(
|
|
2798
4058
|
"div",
|
|
2799
4059
|
{
|
|
2800
4060
|
className: "flex items-center gap-1.5 rounded-lg border border-[hsl(var(--user-msg-border))] bg-[hsl(var(--muted)/0.3)] px-2.5 py-1.5 text-xs text-[hsl(var(--muted-foreground))]",
|
|
2801
4061
|
children: [
|
|
2802
|
-
/* @__PURE__ */
|
|
2803
|
-
/* @__PURE__ */
|
|
4062
|
+
/* @__PURE__ */ jsx16(FileText, { size: 12, className: "shrink-0" }),
|
|
4063
|
+
/* @__PURE__ */ jsx16("span", { className: "max-w-56 truncate", title: part.name, children: part.name })
|
|
2804
4064
|
]
|
|
2805
4065
|
},
|
|
2806
4066
|
`${part.name}-${part.data.length}`
|
|
2807
4067
|
)) }),
|
|
2808
|
-
text && /* @__PURE__ */
|
|
2809
|
-
text && isSending(message) && /* @__PURE__ */
|
|
2810
|
-
/* @__PURE__ */
|
|
2811
|
-
/* @__PURE__ */
|
|
4068
|
+
text && /* @__PURE__ */ jsx16("div", { className: "blade-chat-user-bubble max-w-full rounded-[20px] rounded-br-[6px] border border-[hsl(var(--user-msg-border))] bg-[hsl(var(--user-msg-bg))] px-[18px] py-[13px] text-sm leading-[1.65] text-[hsl(var(--user-msg-fg))]", children: /* @__PURE__ */ jsx16(MarkdownContent, { className: "blade-chat-prose", children: text }) }),
|
|
4069
|
+
text && isSending(message) && /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-1 pr-1 text-[11px] font-medium text-[hsl(var(--muted-foreground))/0.85]", children: [
|
|
4070
|
+
/* @__PURE__ */ jsx16(LoaderCircle, { size: 11, className: "animate-spin", "aria-hidden": "true" }),
|
|
4071
|
+
/* @__PURE__ */ jsx16("span", { children: "\u53D1\u9001\u4E2D" })
|
|
2812
4072
|
] })
|
|
2813
4073
|
] }) });
|
|
2814
4074
|
}
|
|
@@ -2816,12 +4076,12 @@ function ErrorMessageBlock({
|
|
|
2816
4076
|
message,
|
|
2817
4077
|
className
|
|
2818
4078
|
}) {
|
|
2819
|
-
const text = getTextContent2(message.content);
|
|
2820
|
-
return /* @__PURE__ */
|
|
4079
|
+
const text = chatErrorForDisplay(getTextContent2(message.content));
|
|
4080
|
+
return /* @__PURE__ */ jsx16("div", { className: cn("blade-chat-error-row flex min-w-0 justify-start", className), children: /* @__PURE__ */ jsx16("div", { className: "blade-chat-error-block min-w-0 max-w-full whitespace-pre-wrap break-words border-l-[3px] border-[hsl(var(--border))] px-3 py-1 text-left text-sm leading-7 text-[hsl(var(--muted-foreground))] [overflow-wrap:anywhere]", children: text }) });
|
|
2821
4081
|
}
|
|
2822
4082
|
|
|
2823
4083
|
// src/components/MessageList.tsx
|
|
2824
|
-
import { jsx as
|
|
4084
|
+
import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2825
4085
|
function parseModeChange(message) {
|
|
2826
4086
|
if (message.kind !== "mode_change" || typeof message.content !== "string") {
|
|
2827
4087
|
return null;
|
|
@@ -2862,18 +4122,32 @@ function MessageList({
|
|
|
2862
4122
|
askAnswers,
|
|
2863
4123
|
onAnswer,
|
|
2864
4124
|
toolCallRenderer,
|
|
4125
|
+
hidePlanUpdateTools = false,
|
|
2865
4126
|
emptyState,
|
|
2866
4127
|
className,
|
|
2867
4128
|
sessionId,
|
|
2868
4129
|
isViewer = false,
|
|
2869
4130
|
onFollowupInteraction,
|
|
2870
4131
|
resultFeedbackByEntry = /* @__PURE__ */ new Map(),
|
|
2871
|
-
onResultFeedbackSaved
|
|
4132
|
+
onResultFeedbackSaved,
|
|
4133
|
+
historyPaging
|
|
2872
4134
|
}) {
|
|
4135
|
+
const visibleRootMessages = messages.filter((message) => {
|
|
4136
|
+
if ((message.loop_name ?? "root") !== "root") return false;
|
|
4137
|
+
if (isHiddenInternalMessage(message)) return false;
|
|
4138
|
+
if (message.kind === "context") return false;
|
|
4139
|
+
return message.role !== "tool" || getPlanningDividerKind(message) !== null;
|
|
4140
|
+
});
|
|
4141
|
+
const userMessages = visibleRootMessages.filter((message) => isUserMessage(message));
|
|
4142
|
+
const latestUserMessage = userMessages.at(-1);
|
|
4143
|
+
const latestPromptText = latestUserMessage ? (typeof latestUserMessage.content === "string" ? latestUserMessage.content : latestUserMessage.content.filter((part) => part.type === "text").map((part) => part.text).join("")).replace(/\s+/g, " ").trim() : "";
|
|
4144
|
+
const latestPromptPreview = latestPromptText.length > 80 ? `${latestPromptText.slice(0, 80)}\u2026` : latestPromptText;
|
|
4145
|
+
const shouldPinLatestUser = latestUserMessage != null && (latestUserMessage.entry_id == null || latestUserMessage.entry_id.startsWith("local-user-"));
|
|
2873
4146
|
const renderBlocks = useMemo7(() => {
|
|
2874
4147
|
const visible = messages.filter((message) => {
|
|
2875
4148
|
if ((message.loop_name ?? "root") !== "root") return false;
|
|
2876
4149
|
if (isHiddenInternalMessage(message)) return false;
|
|
4150
|
+
if (message.kind === "context") return false;
|
|
2877
4151
|
if (message.kind === "compaction") return true;
|
|
2878
4152
|
return message.role !== "tool" || getPlanningDividerKind(message) !== null;
|
|
2879
4153
|
});
|
|
@@ -2916,7 +4190,7 @@ function MessageList({
|
|
|
2916
4190
|
blocks.push({
|
|
2917
4191
|
type: "message",
|
|
2918
4192
|
message,
|
|
2919
|
-
key: message.entry_id ?? `${message.role}-${blocks.length}`
|
|
4193
|
+
key: message.render_id ?? message.entry_id ?? `${message.role}-${blocks.length}`
|
|
2920
4194
|
});
|
|
2921
4195
|
}
|
|
2922
4196
|
flushAssistant();
|
|
@@ -2943,98 +4217,235 @@ function MessageList({
|
|
|
2943
4217
|
}
|
|
2944
4218
|
return blocks;
|
|
2945
4219
|
}, [messages, isStreaming]);
|
|
2946
|
-
return /* @__PURE__ */
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
{
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
{
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
4220
|
+
return /* @__PURE__ */ jsxs15("div", { className: cn("blade-chat-messages relative min-h-0 flex-1", className), children: [
|
|
4221
|
+
isStreaming ? /* @__PURE__ */ jsx17("output", { className: "sr-only", children: "\u6B63\u5728\u751F\u6210\u56DE\u590D" }) : null,
|
|
4222
|
+
/* @__PURE__ */ jsxs15(
|
|
4223
|
+
StickToBottom,
|
|
4224
|
+
{
|
|
4225
|
+
className: "h-full overflow-y-hidden",
|
|
4226
|
+
initial: "instant",
|
|
4227
|
+
resize: "instant",
|
|
4228
|
+
children: [
|
|
4229
|
+
/* @__PURE__ */ jsx17(StickToBottom.Content, { className: "blade-chat-messages-scroll", children: /* @__PURE__ */ jsxs15("div", { className: "blade-chat-messages-content mx-auto max-w-[748px]", children: [
|
|
4230
|
+
historyPaging && (historyPaging.hasOlder || historyPaging.loading) ? /* @__PURE__ */ jsx17(LoadOlderSentinel, { ...historyPaging }) : null,
|
|
4231
|
+
isStreaming && latestUserMessage && latestPromptPreview ? /* @__PURE__ */ jsx17(
|
|
4232
|
+
"button",
|
|
4233
|
+
{
|
|
4234
|
+
type: "button",
|
|
4235
|
+
className: "sticky top-0 z-20 mb-4 w-full truncate rounded-2xl border border-[hsl(var(--primary)/0.2)] bg-[hsl(var(--background)/0.92)] px-4 py-3 text-left text-sm font-medium shadow-sm backdrop-blur",
|
|
4236
|
+
onClick: () => Array.from(document.querySelectorAll("[data-entry-id]")).find((el) => el.dataset.entryId === latestUserMessage.entry_id)?.scrollIntoView({ behavior: "smooth", block: "start" }),
|
|
4237
|
+
"aria-label": "\u8DF3\u8F6C\u5230\u5F53\u524D\u63D0\u95EE",
|
|
4238
|
+
children: latestPromptPreview
|
|
4239
|
+
}
|
|
4240
|
+
) : null,
|
|
4241
|
+
/* @__PURE__ */ jsxs15("div", { className: "flex min-w-0 flex-col", children: [
|
|
4242
|
+
renderBlocks.length === 0 ? emptyState ?? /* @__PURE__ */ jsxs15("div", { className: "blade-chat-empty", children: [
|
|
4243
|
+
/* @__PURE__ */ jsx17(MessageSquare, { size: 40, strokeWidth: 1.5 }),
|
|
4244
|
+
/* @__PURE__ */ jsx17("span", { className: "text-base font-medium", children: "\u5F00\u59CB\u5BF9\u8BDD" }),
|
|
4245
|
+
/* @__PURE__ */ jsx17("span", { className: "text-sm opacity-60", children: "\u5728\u4E0B\u65B9\u8F93\u5165\u6D88\u606F\u5F00\u59CB\u804A\u5929" })
|
|
4246
|
+
] }) : renderBlocks.map((block) => {
|
|
4247
|
+
if (block.type === "message") {
|
|
4248
|
+
return /* @__PURE__ */ jsx17("div", { "data-entry-id": block.message.entry_id, children: isUserMessage(block.message) ? /* @__PURE__ */ jsx17(UserMessageBubble, { message: block.message }) : isErrorMessage(block.message) ? /* @__PURE__ */ jsx17(ErrorMessageBlock, { message: block.message }) : null }, block.key);
|
|
4249
|
+
}
|
|
4250
|
+
if (block.type === "assistant_turn") {
|
|
4251
|
+
const blockFeedback = block.messages.map(
|
|
4252
|
+
(message) => message.entry_id ? resultFeedbackByEntry.get(message.entry_id) : void 0
|
|
4253
|
+
).find((feedback) => feedback != null);
|
|
4254
|
+
const hasActiveFollowup = Boolean(
|
|
4255
|
+
postChatFollowup && block.messages.some(
|
|
4256
|
+
(message) => message.entry_id === postChatFollowup.assistant_entry_id
|
|
4257
|
+
)
|
|
4258
|
+
);
|
|
4259
|
+
return /* @__PURE__ */ jsx17("div", { "data-entry-id": block.messages[0]?.entry_id, children: /* @__PURE__ */ jsxs15(
|
|
4260
|
+
RenderErrorBoundary,
|
|
4261
|
+
{
|
|
4262
|
+
label: "\u52A9\u624B\u6D88\u606F",
|
|
4263
|
+
details: block.key,
|
|
4264
|
+
resetKey: getMessageResetSignature(block.messages),
|
|
4265
|
+
children: [
|
|
4266
|
+
/* @__PURE__ */ jsx17(
|
|
4267
|
+
AssistantTurnBlock,
|
|
4268
|
+
{
|
|
4269
|
+
messages: block.messages,
|
|
4270
|
+
isStreaming: block.isStreaming,
|
|
4271
|
+
askAnswers,
|
|
4272
|
+
onAnswer,
|
|
4273
|
+
sessionStatus,
|
|
4274
|
+
toolCallRenderer,
|
|
4275
|
+
hidePlanUpdateTools,
|
|
4276
|
+
sessionId
|
|
4277
|
+
}
|
|
4278
|
+
),
|
|
4279
|
+
blockFeedback && !hasActiveFollowup ? /* @__PURE__ */ jsx17(HistoricalResultFeedback, { feedback: blockFeedback }) : null,
|
|
4280
|
+
hasActiveFollowup && postChatFollowup ? /* @__PURE__ */ jsx17(
|
|
4281
|
+
PostChatFollowupBlock,
|
|
4282
|
+
{
|
|
4283
|
+
followup: postChatFollowup,
|
|
4284
|
+
sessionId,
|
|
4285
|
+
onSuggestion,
|
|
4286
|
+
isViewer,
|
|
4287
|
+
onInteraction: onFollowupInteraction,
|
|
4288
|
+
savedFeedback: blockFeedback,
|
|
4289
|
+
onFeedbackSaved: onResultFeedbackSaved
|
|
4290
|
+
}
|
|
4291
|
+
) : null
|
|
4292
|
+
]
|
|
4293
|
+
}
|
|
4294
|
+
) }, block.key);
|
|
4295
|
+
}
|
|
4296
|
+
if (block.type === "compaction") {
|
|
4297
|
+
return /* @__PURE__ */ jsxs15(
|
|
4298
|
+
"div",
|
|
4299
|
+
{
|
|
4300
|
+
className: "flex items-center gap-2 text-xs text-[hsl(var(--muted-foreground))]",
|
|
4301
|
+
children: [
|
|
4302
|
+
/* @__PURE__ */ jsx17(Layers, { size: 12 }),
|
|
4303
|
+
/* @__PURE__ */ jsx17("span", { children: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29" })
|
|
4304
|
+
]
|
|
4305
|
+
},
|
|
4306
|
+
block.key
|
|
4307
|
+
);
|
|
4308
|
+
}
|
|
4309
|
+
return /* @__PURE__ */ jsx17(PlanningDivider, { kind: block.kind }, block.key);
|
|
4310
|
+
}),
|
|
4311
|
+
sessionStatus === "interrupted" && !isStreaming ? /* @__PURE__ */ jsx17("div", { className: "flex", children: /* @__PURE__ */ jsx17("div", { className: "rounded-full border border-amber-500/30 bg-amber-500/10 px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.12em] text-amber-300", children: "\u5DF2\u4E2D\u65AD" }) }) : null
|
|
4312
|
+
] })
|
|
4313
|
+
] }) }),
|
|
4314
|
+
/* @__PURE__ */ jsx17(
|
|
4315
|
+
PinLatestUserMessage,
|
|
3004
4316
|
{
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
/* @__PURE__ */ jsx15("span", { children: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29" })
|
|
3009
|
-
]
|
|
4317
|
+
userMessageCount: userMessages.length,
|
|
4318
|
+
shouldPinLatestUser,
|
|
4319
|
+
targetKey: latestUserMessage?.render_id ?? latestUserMessage?.entry_id ?? (latestUserMessage ? `user:${userMessages.length}` : null)
|
|
3010
4320
|
},
|
|
3011
|
-
|
|
3012
|
-
)
|
|
4321
|
+
sessionId ?? "no-session"
|
|
4322
|
+
),
|
|
4323
|
+
/* @__PURE__ */ jsx17(ScrollToBottomButton, {})
|
|
4324
|
+
]
|
|
4325
|
+
},
|
|
4326
|
+
sessionId ?? "no-session"
|
|
4327
|
+
)
|
|
4328
|
+
] });
|
|
4329
|
+
}
|
|
4330
|
+
function LoadOlderSentinel({
|
|
4331
|
+
hasOlder,
|
|
4332
|
+
loading,
|
|
4333
|
+
loadOlder
|
|
4334
|
+
}) {
|
|
4335
|
+
const { contentRef, scrollRef } = useStickToBottomContext();
|
|
4336
|
+
const sentinelRef = useRef12(null);
|
|
4337
|
+
const stateRef = useRef12({ hasOlder, loading, loadOlder });
|
|
4338
|
+
stateRef.current = { hasOlder, loading, loadOlder };
|
|
4339
|
+
useEffect11(() => {
|
|
4340
|
+
const sentinel = sentinelRef.current;
|
|
4341
|
+
const scroller = scrollRef.current;
|
|
4342
|
+
if (!sentinel || !scroller || typeof IntersectionObserver === "undefined") return;
|
|
4343
|
+
let entered = false;
|
|
4344
|
+
let disposed = false;
|
|
4345
|
+
const firstVisible = () => {
|
|
4346
|
+
const top = scroller.getBoundingClientRect().top;
|
|
4347
|
+
for (const row of contentRef.current?.querySelectorAll("[data-entry-id]") ?? []) {
|
|
4348
|
+
const rect = row.getBoundingClientRect();
|
|
4349
|
+
if (rect.bottom >= top) return { id: row.dataset.entryId, offset: rect.top - top };
|
|
4350
|
+
}
|
|
4351
|
+
return null;
|
|
4352
|
+
};
|
|
4353
|
+
const load = async () => {
|
|
4354
|
+
if (disposed || !stateRef.current.hasOlder || stateRef.current.loading) return;
|
|
4355
|
+
const commitSnapshot = { anchor: null, height: scroller.scrollHeight };
|
|
4356
|
+
let advanced = false;
|
|
4357
|
+
try {
|
|
4358
|
+
advanced = await stateRef.current.loadOlder(() => {
|
|
4359
|
+
commitSnapshot.anchor = firstVisible();
|
|
4360
|
+
commitSnapshot.height = scroller.scrollHeight;
|
|
4361
|
+
});
|
|
4362
|
+
} catch {
|
|
4363
|
+
return;
|
|
4364
|
+
}
|
|
4365
|
+
if (disposed) return;
|
|
4366
|
+
await new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
4367
|
+
if (disposed) return;
|
|
4368
|
+
const anchor = commitSnapshot.anchor;
|
|
4369
|
+
const heightBefore = commitSnapshot.height;
|
|
4370
|
+
const anchored = anchor?.id ? Array.from(contentRef.current?.querySelectorAll("[data-entry-id]") ?? []).find((row) => row.dataset.entryId === anchor.id) : null;
|
|
4371
|
+
if (anchored && anchor) {
|
|
4372
|
+
scroller.scrollTop += anchored.getBoundingClientRect().top - scroller.getBoundingClientRect().top - anchor.offset;
|
|
4373
|
+
} else {
|
|
4374
|
+
scroller.scrollTop += scroller.scrollHeight - heightBefore;
|
|
4375
|
+
}
|
|
4376
|
+
if (!disposed && advanced && stateRef.current.hasOlder && !stateRef.current.loading && scroller.scrollHeight <= scroller.clientHeight) {
|
|
4377
|
+
await load();
|
|
4378
|
+
}
|
|
4379
|
+
};
|
|
4380
|
+
const observer = new IntersectionObserver(
|
|
4381
|
+
([entry]) => {
|
|
4382
|
+
if (!entry?.isIntersecting) {
|
|
4383
|
+
entered = false;
|
|
4384
|
+
return;
|
|
3013
4385
|
}
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
4386
|
+
if (entered) return;
|
|
4387
|
+
entered = true;
|
|
4388
|
+
void load();
|
|
4389
|
+
},
|
|
4390
|
+
{ root: scroller, rootMargin: "160px 0px 0px" }
|
|
4391
|
+
);
|
|
4392
|
+
observer.observe(sentinel);
|
|
4393
|
+
return () => {
|
|
4394
|
+
disposed = true;
|
|
4395
|
+
observer.disconnect();
|
|
4396
|
+
};
|
|
4397
|
+
}, [contentRef, scrollRef]);
|
|
4398
|
+
return /* @__PURE__ */ jsx17("div", { ref: sentinelRef, "data-history-sentinel": true, className: "flex h-6 items-center justify-center", children: loading ? /* @__PURE__ */ jsx17("span", { className: "text-xs text-[hsl(var(--muted-foreground))]", children: "\u6B63\u5728\u52A0\u8F7D\u66F4\u65E9\u6D88\u606F\u2026" }) : null });
|
|
3021
4399
|
}
|
|
3022
|
-
function
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
4400
|
+
function PinLatestUserMessage({
|
|
4401
|
+
userMessageCount,
|
|
4402
|
+
shouldPinLatestUser,
|
|
4403
|
+
targetKey
|
|
4404
|
+
}) {
|
|
4405
|
+
const { contentRef, scrollRef, scrollToBottom, stopScroll } = useStickToBottomContext();
|
|
4406
|
+
const previousCountRef = useRef12(userMessageCount);
|
|
4407
|
+
const spacerHeightRef = useRef12(0);
|
|
4408
|
+
const getScrollElement = useCallback7(() => scrollRef.current, [scrollRef]);
|
|
4409
|
+
const getContentElement = useCallback7(() => contentRef.current, [contentRef]);
|
|
4410
|
+
const getTargetElement = useCallback7(() => {
|
|
4411
|
+
const rows = contentRef.current?.querySelectorAll(".blade-chat-user-row");
|
|
4412
|
+
return rows?.item((rows?.length ?? 0) - 1) ?? null;
|
|
4413
|
+
}, [contentRef]);
|
|
4414
|
+
const getSpacerHeight = useCallback7(() => spacerHeightRef.current, []);
|
|
4415
|
+
const setSpacerHeight = useCallback7(
|
|
4416
|
+
(height) => {
|
|
4417
|
+
spacerHeightRef.current = height;
|
|
4418
|
+
const content = contentRef.current;
|
|
4419
|
+
if (!content) return;
|
|
4420
|
+
if (height > 0) content.style.setProperty("--blade-chat-pin-spacer", `${height}px`);
|
|
4421
|
+
else content.style.removeProperty("--blade-chat-pin-spacer");
|
|
4422
|
+
},
|
|
4423
|
+
[contentRef]
|
|
4424
|
+
);
|
|
4425
|
+
useMessagePin({
|
|
4426
|
+
targetKey,
|
|
4427
|
+
pinTarget: shouldPinLatestUser,
|
|
4428
|
+
getScrollElement,
|
|
4429
|
+
getContentElement,
|
|
4430
|
+
getTargetElement,
|
|
4431
|
+
getSpacerHeight,
|
|
4432
|
+
setSpacerHeight,
|
|
4433
|
+
stopAutoScroll: stopScroll,
|
|
4434
|
+
scrollToBottom
|
|
4435
|
+
});
|
|
4436
|
+
useEffect11(() => {
|
|
4437
|
+
if (userMessageCount > previousCountRef.current && !shouldPinLatestUser) {
|
|
3027
4438
|
scrollToBottom("instant");
|
|
3028
4439
|
}
|
|
3029
4440
|
previousCountRef.current = userMessageCount;
|
|
3030
|
-
}, [
|
|
4441
|
+
}, [scrollToBottom, shouldPinLatestUser, userMessageCount]);
|
|
3031
4442
|
return null;
|
|
3032
4443
|
}
|
|
3033
4444
|
function ScrollToBottomButton() {
|
|
3034
4445
|
const { isAtBottom, scrollToBottom } = useStickToBottomContext();
|
|
3035
|
-
const [visible, setVisible] =
|
|
3036
|
-
const hideTimerRef =
|
|
3037
|
-
|
|
4446
|
+
const [visible, setVisible] = useState14(false);
|
|
4447
|
+
const hideTimerRef = useRef12(null);
|
|
4448
|
+
useEffect11(() => {
|
|
3038
4449
|
if (isAtBottom) {
|
|
3039
4450
|
if (!hideTimerRef.current) {
|
|
3040
4451
|
hideTimerRef.current = setTimeout(() => {
|
|
@@ -3056,7 +4467,7 @@ function ScrollToBottomButton() {
|
|
|
3056
4467
|
}
|
|
3057
4468
|
};
|
|
3058
4469
|
}, [isAtBottom]);
|
|
3059
|
-
const handleClick =
|
|
4470
|
+
const handleClick = useCallback7(() => {
|
|
3060
4471
|
if (hideTimerRef.current) {
|
|
3061
4472
|
clearTimeout(hideTimerRef.current);
|
|
3062
4473
|
hideTimerRef.current = null;
|
|
@@ -3065,7 +4476,7 @@ function ScrollToBottomButton() {
|
|
|
3065
4476
|
scrollToBottom();
|
|
3066
4477
|
}, [scrollToBottom]);
|
|
3067
4478
|
if (!visible) return null;
|
|
3068
|
-
return /* @__PURE__ */
|
|
4479
|
+
return /* @__PURE__ */ jsxs15(
|
|
3069
4480
|
"button",
|
|
3070
4481
|
{
|
|
3071
4482
|
type: "button",
|
|
@@ -3073,25 +4484,25 @@ function ScrollToBottomButton() {
|
|
|
3073
4484
|
"aria-label": "\u6EDA\u52A8\u5230\u5E95\u90E8",
|
|
3074
4485
|
className: "blade-chat-scroll-bottom absolute bottom-4 right-4 flex items-center gap-1 rounded-full border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-3 py-1.5 text-xs text-[hsl(var(--muted-foreground))] shadow-lg transition-colors hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--foreground))]",
|
|
3075
4486
|
children: [
|
|
3076
|
-
/* @__PURE__ */
|
|
3077
|
-
/* @__PURE__ */
|
|
4487
|
+
/* @__PURE__ */ jsx17(ChevronDown, { size: 14 }),
|
|
4488
|
+
/* @__PURE__ */ jsx17("span", { className: "blade-chat-scroll-bottom-label", children: "\u6EDA\u52A8\u5230\u5E95\u90E8" })
|
|
3078
4489
|
]
|
|
3079
4490
|
}
|
|
3080
4491
|
);
|
|
3081
4492
|
}
|
|
3082
4493
|
function PlanningDivider({ kind }) {
|
|
3083
|
-
return /* @__PURE__ */
|
|
3084
|
-
/* @__PURE__ */
|
|
3085
|
-
/* @__PURE__ */
|
|
3086
|
-
/* @__PURE__ */
|
|
3087
|
-
/* @__PURE__ */
|
|
4494
|
+
return /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-3 py-1", children: [
|
|
4495
|
+
/* @__PURE__ */ jsx17("div", { className: "h-px flex-1 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent" }),
|
|
4496
|
+
/* @__PURE__ */ jsxs15("div", { className: "inline-flex items-center gap-1.5 rounded-full border border-amber-500/30 bg-amber-500/10 px-3 py-1 text-[11px] text-amber-300", children: [
|
|
4497
|
+
/* @__PURE__ */ jsx17(Lightbulb, { size: 12 }),
|
|
4498
|
+
/* @__PURE__ */ jsx17("span", { children: kind === "enter" ? "\u8FDB\u5165\u89C4\u5212\u6A21\u5F0F" : "\u89C4\u5212\u5B8C\u6210" })
|
|
3088
4499
|
] }),
|
|
3089
|
-
/* @__PURE__ */
|
|
4500
|
+
/* @__PURE__ */ jsx17("div", { className: "h-px flex-1 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent" })
|
|
3090
4501
|
] });
|
|
3091
4502
|
}
|
|
3092
4503
|
|
|
3093
4504
|
// src/components/ChatSurface.tsx
|
|
3094
|
-
import { jsx as
|
|
4505
|
+
import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
3095
4506
|
function themeAttr(theme) {
|
|
3096
4507
|
return theme === "dark" ? "dark" : void 0;
|
|
3097
4508
|
}
|
|
@@ -3111,6 +4522,7 @@ function ChatSurface({
|
|
|
3111
4522
|
onInputChange,
|
|
3112
4523
|
onSuggestion,
|
|
3113
4524
|
onSend,
|
|
4525
|
+
onAppend,
|
|
3114
4526
|
onStop,
|
|
3115
4527
|
sessionStatus,
|
|
3116
4528
|
askAnswers,
|
|
@@ -3121,9 +4533,12 @@ function ChatSurface({
|
|
|
3121
4533
|
onResultFeedbackSaved,
|
|
3122
4534
|
onFollowupInteraction,
|
|
3123
4535
|
beforeInput,
|
|
4536
|
+
showPlanUpdates = false,
|
|
4537
|
+
planRevealRevision = 0,
|
|
4538
|
+
historyPaging,
|
|
3124
4539
|
banner
|
|
3125
4540
|
}) {
|
|
3126
|
-
return /* @__PURE__ */
|
|
4541
|
+
return /* @__PURE__ */ jsxs16(
|
|
3127
4542
|
"div",
|
|
3128
4543
|
{
|
|
3129
4544
|
"data-theme": themeAttr(theme),
|
|
@@ -3132,14 +4547,14 @@ function ChatSurface({
|
|
|
3132
4547
|
classNames?.root
|
|
3133
4548
|
),
|
|
3134
4549
|
children: [
|
|
3135
|
-
/* @__PURE__ */
|
|
4550
|
+
/* @__PURE__ */ jsx18(ConnectionBanner, { connection, className: classNames?.banner }),
|
|
3136
4551
|
banner,
|
|
3137
|
-
errorMessage && /* @__PURE__ */
|
|
3138
|
-
/* @__PURE__ */
|
|
3139
|
-
/* @__PURE__ */
|
|
4552
|
+
errorMessage && /* @__PURE__ */ jsxs16("div", { className: "blade-chat-error-bar flex items-start gap-2 border-b px-4 py-3 text-sm", children: [
|
|
4553
|
+
/* @__PURE__ */ jsx18(CircleAlert, { size: 16, className: "mt-0.5 shrink-0" }),
|
|
4554
|
+
/* @__PURE__ */ jsx18("span", { className: "min-w-0 whitespace-pre-wrap break-words [overflow-wrap:anywhere]", children: chatErrorForDisplay2(errorMessage) })
|
|
3140
4555
|
] }),
|
|
3141
4556
|
slots?.header,
|
|
3142
|
-
/* @__PURE__ */
|
|
4557
|
+
/* @__PURE__ */ jsx18(
|
|
3143
4558
|
MessageList,
|
|
3144
4559
|
{
|
|
3145
4560
|
messages,
|
|
@@ -3150,26 +4565,40 @@ function ChatSurface({
|
|
|
3150
4565
|
askAnswers,
|
|
3151
4566
|
onAnswer,
|
|
3152
4567
|
toolCallRenderer: renderers?.toolCall,
|
|
4568
|
+
hidePlanUpdateTools: showPlanUpdates,
|
|
3153
4569
|
emptyState: slots?.emptyState,
|
|
3154
4570
|
className: classNames?.messageList,
|
|
3155
4571
|
sessionId,
|
|
3156
4572
|
isViewer,
|
|
3157
4573
|
resultFeedbackByEntry,
|
|
3158
4574
|
onResultFeedbackSaved,
|
|
3159
|
-
onFollowupInteraction
|
|
4575
|
+
onFollowupInteraction,
|
|
4576
|
+
historyPaging
|
|
3160
4577
|
}
|
|
3161
4578
|
),
|
|
4579
|
+
showPlanUpdates ? /* @__PURE__ */ jsx18(
|
|
4580
|
+
CurrentPlanPanel,
|
|
4581
|
+
{
|
|
4582
|
+
messages,
|
|
4583
|
+
running: isStreaming,
|
|
4584
|
+
revealRevision: planRevealRevision,
|
|
4585
|
+
sessionId,
|
|
4586
|
+
className: "border-t border-[hsl(var(--border))]"
|
|
4587
|
+
}
|
|
4588
|
+
) : null,
|
|
3162
4589
|
beforeInput,
|
|
3163
|
-
/* @__PURE__ */
|
|
4590
|
+
/* @__PURE__ */ jsx18(
|
|
3164
4591
|
ChatInput,
|
|
3165
4592
|
{
|
|
3166
4593
|
value: inputText,
|
|
3167
4594
|
onValueChange: onInputChange,
|
|
3168
4595
|
onSend,
|
|
4596
|
+
onAppend,
|
|
3169
4597
|
onStop,
|
|
3170
4598
|
isStreaming,
|
|
3171
4599
|
isStopping,
|
|
3172
4600
|
placeholder,
|
|
4601
|
+
queueKey: sessionId,
|
|
3173
4602
|
className: classNames?.chatInput
|
|
3174
4603
|
}
|
|
3175
4604
|
),
|
|
@@ -3180,13 +4609,13 @@ function ChatSurface({
|
|
|
3180
4609
|
}
|
|
3181
4610
|
|
|
3182
4611
|
// src/components/AgentChat.tsx
|
|
3183
|
-
import { Fragment as
|
|
4612
|
+
import { Fragment as Fragment4, jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
3184
4613
|
function isUnauthorizedError(error) {
|
|
3185
4614
|
return error instanceof BladeApiError && error.status === 401;
|
|
3186
4615
|
}
|
|
3187
4616
|
function LoginCard({ client, onLoggedIn }) {
|
|
3188
|
-
const [loggingIn, setLoggingIn] =
|
|
3189
|
-
const [loginError, setLoginError] =
|
|
4617
|
+
const [loggingIn, setLoggingIn] = useState15(false);
|
|
4618
|
+
const [loginError, setLoginError] = useState15(null);
|
|
3190
4619
|
const handleLogin = async () => {
|
|
3191
4620
|
setLoggingIn(true);
|
|
3192
4621
|
setLoginError(null);
|
|
@@ -3199,11 +4628,11 @@ function LoginCard({ client, onLoggedIn }) {
|
|
|
3199
4628
|
setLoggingIn(false);
|
|
3200
4629
|
}
|
|
3201
4630
|
};
|
|
3202
|
-
return /* @__PURE__ */
|
|
3203
|
-
/* @__PURE__ */
|
|
3204
|
-
/* @__PURE__ */
|
|
3205
|
-
/* @__PURE__ */
|
|
3206
|
-
/* @__PURE__ */
|
|
4631
|
+
return /* @__PURE__ */ jsx19("div", { className: "blade-chat-login flex flex-1 items-center justify-center p-6", children: /* @__PURE__ */ jsxs17("div", { className: "flex w-full max-w-sm flex-col items-center gap-4 rounded-2xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-6 py-8 text-center", children: [
|
|
4632
|
+
/* @__PURE__ */ jsx19(LockKeyhole, { size: 28, className: "text-[hsl(var(--muted-foreground))]" }),
|
|
4633
|
+
/* @__PURE__ */ jsx19("div", { className: "text-base font-medium text-[hsl(var(--foreground))]", children: "\u9700\u8981\u767B\u5F55\u540E\u4F7F\u7528" }),
|
|
4634
|
+
/* @__PURE__ */ jsx19("div", { className: "text-sm text-[hsl(var(--muted-foreground))]", children: "\u767B\u5F55\u540E\u5373\u53EF\u4E0E\u667A\u80FD\u4F53\u5BF9\u8BDD\uFF0C\u4F60\u7684\u4F1A\u8BDD\u5185\u5BB9\u4EC5\u81EA\u5DF1\u53EF\u89C1\u3002" }),
|
|
4635
|
+
/* @__PURE__ */ jsx19(
|
|
3207
4636
|
"button",
|
|
3208
4637
|
{
|
|
3209
4638
|
type: "button",
|
|
@@ -3213,20 +4642,20 @@ function LoginCard({ client, onLoggedIn }) {
|
|
|
3213
4642
|
children: loggingIn ? "\u767B\u5F55\u4E2D\u2026" : "\u767B\u5F55"
|
|
3214
4643
|
}
|
|
3215
4644
|
),
|
|
3216
|
-
loginError && /* @__PURE__ */
|
|
4645
|
+
loginError && /* @__PURE__ */ jsx19("div", { className: "text-xs text-[hsl(var(--destructive))]", children: loginError })
|
|
3217
4646
|
] }) });
|
|
3218
4647
|
}
|
|
3219
4648
|
function AgentChat(props) {
|
|
3220
4649
|
const client = useBladeClient();
|
|
3221
|
-
const [attempt, setAttempt] =
|
|
3222
|
-
const [needLogin, setNeedLogin] =
|
|
4650
|
+
const [attempt, setAttempt] = useState15(0);
|
|
4651
|
+
const [needLogin, setNeedLogin] = useState15(() => !client.hasToken());
|
|
3223
4652
|
if (needLogin) {
|
|
3224
|
-
return /* @__PURE__ */
|
|
4653
|
+
return /* @__PURE__ */ jsx19(
|
|
3225
4654
|
"div",
|
|
3226
4655
|
{
|
|
3227
4656
|
"data-theme": themeAttr(props.theme),
|
|
3228
4657
|
className: cn("blade-chat flex min-h-0 flex-1 flex-col", props.classNames?.root),
|
|
3229
|
-
children: /* @__PURE__ */
|
|
4658
|
+
children: /* @__PURE__ */ jsx19(
|
|
3230
4659
|
LoginCard,
|
|
3231
4660
|
{
|
|
3232
4661
|
client,
|
|
@@ -3239,7 +4668,7 @@ function AgentChat(props) {
|
|
|
3239
4668
|
}
|
|
3240
4669
|
);
|
|
3241
4670
|
}
|
|
3242
|
-
return /* @__PURE__ */
|
|
4671
|
+
return /* @__PURE__ */ jsx19(ChatSessionView, { ...props, onUnauthorized: () => setNeedLogin(true) }, attempt);
|
|
3243
4672
|
}
|
|
3244
4673
|
function ChatSessionView({
|
|
3245
4674
|
sessionId,
|
|
@@ -3256,17 +4685,33 @@ function ChatSessionView({
|
|
|
3256
4685
|
onUnauthorized
|
|
3257
4686
|
}) {
|
|
3258
4687
|
const client = useBladeClient();
|
|
4688
|
+
const [planRevealRevisions, setPlanRevealRevisions] = useState15(
|
|
4689
|
+
() => /* @__PURE__ */ new Map()
|
|
4690
|
+
);
|
|
4691
|
+
const handleSessionConnected = useCallback8((connectedSession) => {
|
|
4692
|
+
return connectedSession.on("toolResult", ({ toolCall, turn, source }) => {
|
|
4693
|
+
if (source === "reconnect_replay" || (turn.loop_id || "root") !== "root" || toolCall.status !== "done" || !isPlanUpdateTool(toolCall) || !parsePlanUpdate(toolCall.arguments)) {
|
|
4694
|
+
return;
|
|
4695
|
+
}
|
|
4696
|
+
setPlanRevealRevisions((current) => {
|
|
4697
|
+
const next = new Map(current);
|
|
4698
|
+
next.set(connectedSession.sessionId, (current.get(connectedSession.sessionId) ?? 0) + 1);
|
|
4699
|
+
return next;
|
|
4700
|
+
});
|
|
4701
|
+
});
|
|
4702
|
+
}, []);
|
|
3259
4703
|
const { session, state, error } = useAgentSession(sessionId, {
|
|
3260
4704
|
createOptions,
|
|
3261
|
-
onSessionCreated
|
|
4705
|
+
onSessionCreated,
|
|
4706
|
+
onSessionConnected: handleSessionConnected
|
|
3262
4707
|
});
|
|
3263
4708
|
const replay = useReplay(session);
|
|
3264
|
-
const [stopRequested, setStopRequested] =
|
|
3265
|
-
const [inputText, setInputText] =
|
|
3266
|
-
const [resultFeedback, setResultFeedback] =
|
|
4709
|
+
const [stopRequested, setStopRequested] = useState15(false);
|
|
4710
|
+
const [inputText, setInputText] = useState15("");
|
|
4711
|
+
const [resultFeedback, setResultFeedback] = useState15([]);
|
|
3267
4712
|
const resolvedSessionId = session?.sessionId;
|
|
3268
4713
|
const isViewer = state?.viewerRole === "viewer";
|
|
3269
|
-
|
|
4714
|
+
useEffect12(() => {
|
|
3270
4715
|
setResultFeedback([]);
|
|
3271
4716
|
if (!resolvedSessionId || isViewer) return;
|
|
3272
4717
|
let cancelled = false;
|
|
@@ -3295,18 +4740,18 @@ function ChatSessionView({
|
|
|
3295
4740
|
() => new Map(resultFeedback.map((item) => [item.assistant_entry_id, item])),
|
|
3296
4741
|
[resultFeedback]
|
|
3297
4742
|
);
|
|
3298
|
-
const handleResultFeedbackSaved =
|
|
4743
|
+
const handleResultFeedbackSaved = useCallback8((saved) => {
|
|
3299
4744
|
setResultFeedback((current) => [
|
|
3300
4745
|
...current.filter((item) => item.assistant_entry_id !== saved.assistant_entry_id),
|
|
3301
4746
|
saved
|
|
3302
4747
|
]);
|
|
3303
4748
|
}, []);
|
|
3304
|
-
|
|
4749
|
+
useEffect12(() => {
|
|
3305
4750
|
if (session) {
|
|
3306
4751
|
onSessionReady?.(session);
|
|
3307
4752
|
}
|
|
3308
4753
|
}, [session, onSessionReady]);
|
|
3309
|
-
|
|
4754
|
+
useEffect12(() => {
|
|
3310
4755
|
if (!session) return;
|
|
3311
4756
|
const offAttach = session.on("attachRequested", ({ label, content }) => {
|
|
3312
4757
|
setInputText((prev) => `${prev ? `${prev}
|
|
@@ -3322,12 +4767,12 @@ ${content}`);
|
|
|
3322
4767
|
offInsert();
|
|
3323
4768
|
};
|
|
3324
4769
|
}, [session]);
|
|
3325
|
-
|
|
4770
|
+
useEffect12(() => {
|
|
3326
4771
|
if (isUnauthorizedError(error)) {
|
|
3327
4772
|
onUnauthorized();
|
|
3328
4773
|
}
|
|
3329
4774
|
}, [error, onUnauthorized]);
|
|
3330
|
-
|
|
4775
|
+
useEffect12(() => {
|
|
3331
4776
|
if (!session || !commands) return;
|
|
3332
4777
|
const unsubscribes = Object.entries(commands).map(
|
|
3333
4778
|
([action, handler]) => session.onCommand(action, (payload) => handler(payload))
|
|
@@ -3337,6 +4782,7 @@ ${content}`);
|
|
|
3337
4782
|
};
|
|
3338
4783
|
}, [session, commands]);
|
|
3339
4784
|
const isStreaming = state?.isStreaming ?? false;
|
|
4785
|
+
const planRevealRevision = resolvedSessionId ? planRevealRevisions.get(resolvedSessionId) ?? 0 : 0;
|
|
3340
4786
|
const isStopping = stopRequested && isStreaming;
|
|
3341
4787
|
const connectError = error && !isUnauthorizedError(error) ? error.message || "\u8FDE\u63A5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" : null;
|
|
3342
4788
|
const errorMessage = connectError ?? state?.errorMessage ?? replay.error?.message ?? null;
|
|
@@ -3348,7 +4794,7 @@ ${content}`);
|
|
|
3348
4794
|
setStopRequested(true);
|
|
3349
4795
|
void session?.stop();
|
|
3350
4796
|
};
|
|
3351
|
-
return /* @__PURE__ */
|
|
4797
|
+
return /* @__PURE__ */ jsx19(
|
|
3352
4798
|
ChatSurface,
|
|
3353
4799
|
{
|
|
3354
4800
|
theme,
|
|
@@ -3358,8 +4804,8 @@ ${content}`);
|
|
|
3358
4804
|
slots,
|
|
3359
4805
|
placeholder,
|
|
3360
4806
|
connection: state?.connection ?? "connecting",
|
|
3361
|
-
banner: /* @__PURE__ */
|
|
3362
|
-
/* @__PURE__ */
|
|
4807
|
+
banner: /* @__PURE__ */ jsxs17(Fragment4, { children: [
|
|
4808
|
+
/* @__PURE__ */ jsx19(
|
|
3363
4809
|
ReplayBar,
|
|
3364
4810
|
{
|
|
3365
4811
|
isReplay: replay.isReplay,
|
|
@@ -3369,7 +4815,7 @@ ${content}`);
|
|
|
3369
4815
|
onExit: () => void replay.exitToAutonomous()
|
|
3370
4816
|
}
|
|
3371
4817
|
),
|
|
3372
|
-
/* @__PURE__ */
|
|
4818
|
+
/* @__PURE__ */ jsx19(ReplayMismatchPrompt, { mismatch: replay.mismatch })
|
|
3373
4819
|
] }),
|
|
3374
4820
|
errorMessage,
|
|
3375
4821
|
messages: state?.messages ?? [],
|
|
@@ -3377,11 +4823,25 @@ ${content}`);
|
|
|
3377
4823
|
resultFeedbackByEntry,
|
|
3378
4824
|
onResultFeedbackSaved: handleResultFeedbackSaved,
|
|
3379
4825
|
isStreaming,
|
|
4826
|
+
showPlanUpdates: true,
|
|
4827
|
+
planRevealRevision,
|
|
4828
|
+
historyPaging: session && state ? {
|
|
4829
|
+
hasOlder: session.hasOlderHistory,
|
|
4830
|
+
loading: state.loadingOlder,
|
|
4831
|
+
loadOlder: async (beforeCommit) => {
|
|
4832
|
+
const before = session.getState().nextBefore;
|
|
4833
|
+
await session.loadOlderHistory({ beforeCommit });
|
|
4834
|
+
return before !== session.getState().nextBefore;
|
|
4835
|
+
}
|
|
4836
|
+
} : void 0,
|
|
3380
4837
|
isStopping,
|
|
3381
4838
|
inputText,
|
|
3382
4839
|
onInputChange: setInputText,
|
|
3383
4840
|
onSuggestion: setInputText,
|
|
3384
4841
|
onSend: handleSend,
|
|
4842
|
+
onAppend: (text) => {
|
|
4843
|
+
void session?.queue(text);
|
|
4844
|
+
},
|
|
3385
4845
|
onStop: handleStop,
|
|
3386
4846
|
sessionStatus: state?.status ?? void 0,
|
|
3387
4847
|
askAnswers: state?.askAnswers,
|
|
@@ -3398,11 +4858,11 @@ ${content}`);
|
|
|
3398
4858
|
}
|
|
3399
4859
|
|
|
3400
4860
|
// src/components/LlmChat.tsx
|
|
3401
|
-
import { useEffect as
|
|
4861
|
+
import { useEffect as useEffect13, useMemo as useMemo9, useState as useState17 } from "react";
|
|
3402
4862
|
|
|
3403
4863
|
// src/components/LlmAdvancedSettings.tsx
|
|
3404
|
-
import { useState as
|
|
3405
|
-
import { jsx as
|
|
4864
|
+
import { useState as useState16 } from "react";
|
|
4865
|
+
import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
3406
4866
|
var FIELDS = [
|
|
3407
4867
|
{ id: "baseURL", label: "\u6A21\u578B\u670D\u52A1\u5730\u5740", placeholder: "http://\u5185\u7F51\u5730\u5740/v1" },
|
|
3408
4868
|
{ id: "model", label: "\u6A21\u578B", placeholder: "\u6A21\u578B\u540D\u79F0" },
|
|
@@ -3448,13 +4908,13 @@ function writeOverride(settings, baseURL, override) {
|
|
|
3448
4908
|
}
|
|
3449
4909
|
function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
3450
4910
|
const normalized = normalizeAdvanced(settings);
|
|
3451
|
-
const [open, setOpen] =
|
|
3452
|
-
const [draft, setDraft] =
|
|
4911
|
+
const [open, setOpen] = useState16(false);
|
|
4912
|
+
const [draft, setDraft] = useState16(override);
|
|
3453
4913
|
if (!normalized) return null;
|
|
3454
4914
|
const fields = FIELDS.filter((field) => normalized[field.id]);
|
|
3455
4915
|
const dirty = Object.keys(override).length > 0;
|
|
3456
|
-
return /* @__PURE__ */
|
|
3457
|
-
/* @__PURE__ */
|
|
4916
|
+
return /* @__PURE__ */ jsxs18("div", { className: "blade-chat-advanced border-t border-[hsl(var(--border))] px-4 py-2 text-xs", children: [
|
|
4917
|
+
/* @__PURE__ */ jsxs18(
|
|
3458
4918
|
"button",
|
|
3459
4919
|
{
|
|
3460
4920
|
type: "button",
|
|
@@ -3464,16 +4924,16 @@ function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
|
3464
4924
|
},
|
|
3465
4925
|
className: "flex items-center gap-1.5 text-[hsl(var(--muted-foreground))] transition-colors hover:text-[hsl(var(--foreground))]",
|
|
3466
4926
|
children: [
|
|
3467
|
-
/* @__PURE__ */
|
|
4927
|
+
/* @__PURE__ */ jsx20(Settings2, { size: 13 }),
|
|
3468
4928
|
"\u9AD8\u7EA7\u8BBE\u7F6E",
|
|
3469
|
-
dirty && /* @__PURE__ */
|
|
4929
|
+
dirty && /* @__PURE__ */ jsx20("span", { className: "text-[hsl(var(--primary))]", children: "\uFF08\u5DF2\u81EA\u5B9A\u4E49\uFF09" })
|
|
3470
4930
|
]
|
|
3471
4931
|
}
|
|
3472
4932
|
),
|
|
3473
|
-
open && /* @__PURE__ */
|
|
3474
|
-
fields.map((field) => /* @__PURE__ */
|
|
3475
|
-
/* @__PURE__ */
|
|
3476
|
-
/* @__PURE__ */
|
|
4933
|
+
open && /* @__PURE__ */ jsxs18("div", { className: "mt-2 flex flex-col gap-2", children: [
|
|
4934
|
+
fields.map((field) => /* @__PURE__ */ jsxs18("label", { className: "flex flex-col gap-1", children: [
|
|
4935
|
+
/* @__PURE__ */ jsx20("span", { className: "text-[hsl(var(--muted-foreground))]", children: field.label }),
|
|
4936
|
+
/* @__PURE__ */ jsx20(
|
|
3477
4937
|
"input",
|
|
3478
4938
|
{
|
|
3479
4939
|
type: field.secret ? "password" : "text",
|
|
@@ -3484,9 +4944,9 @@ function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
|
3484
4944
|
}
|
|
3485
4945
|
)
|
|
3486
4946
|
] }, field.id)),
|
|
3487
|
-
normalized.apiKey && /* @__PURE__ */
|
|
3488
|
-
/* @__PURE__ */
|
|
3489
|
-
/* @__PURE__ */
|
|
4947
|
+
normalized.apiKey && /* @__PURE__ */ jsx20("p", { className: "text-[hsl(var(--muted-foreground))]", children: "\u5BC6\u94A5\u4F1A\u5B58\u5728\u8FD9\u53F0\u6D4F\u89C8\u5668\u91CC\u3002\u53EA\u5728\u4F60\u4FE1\u5F97\u8FC7\u8FD9\u53F0\u673A\u5668\u65F6\u586B\u3002" }),
|
|
4948
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex gap-2", children: [
|
|
4949
|
+
/* @__PURE__ */ jsx20(
|
|
3490
4950
|
"button",
|
|
3491
4951
|
{
|
|
3492
4952
|
type: "button",
|
|
@@ -3501,7 +4961,7 @@ function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
|
3501
4961
|
children: "\u4FDD\u5B58"
|
|
3502
4962
|
}
|
|
3503
4963
|
),
|
|
3504
|
-
/* @__PURE__ */
|
|
4964
|
+
/* @__PURE__ */ jsx20(
|
|
3505
4965
|
"button",
|
|
3506
4966
|
{
|
|
3507
4967
|
type: "button",
|
|
@@ -3520,7 +4980,7 @@ function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
|
3520
4980
|
}
|
|
3521
4981
|
|
|
3522
4982
|
// src/components/LlmChat.tsx
|
|
3523
|
-
import { jsx as
|
|
4983
|
+
import { jsx as jsx21 } from "react/jsx-runtime";
|
|
3524
4984
|
function LlmChat({
|
|
3525
4985
|
classNames,
|
|
3526
4986
|
renderers,
|
|
@@ -3532,11 +4992,11 @@ function LlmChat({
|
|
|
3532
4992
|
onOverrideChange,
|
|
3533
4993
|
...options
|
|
3534
4994
|
}) {
|
|
3535
|
-
const [override, setOverride] =
|
|
4995
|
+
const [override, setOverride] = useState17(() => readOverride(advanced, options.baseURL));
|
|
3536
4996
|
const effective = { ...options, ...override };
|
|
3537
4997
|
const { messages, isStreaming, error, send, stop, reset } = useLlmChat(effective);
|
|
3538
|
-
const [inputText, setInputText] =
|
|
3539
|
-
const [stopRequested, setStopRequested] =
|
|
4998
|
+
const [inputText, setInputText] = useState17("");
|
|
4999
|
+
const [stopRequested, setStopRequested] = useState17(false);
|
|
3540
5000
|
const handle = useMemo9(
|
|
3541
5001
|
() => ({
|
|
3542
5002
|
insertText: (text) => setInputText((prev) => prev ? `${prev}
|
|
@@ -3546,10 +5006,10 @@ ${text}` : text),
|
|
|
3546
5006
|
}),
|
|
3547
5007
|
[send, reset]
|
|
3548
5008
|
);
|
|
3549
|
-
|
|
5009
|
+
useEffect13(() => {
|
|
3550
5010
|
onReady?.(handle);
|
|
3551
5011
|
}, [handle, onReady]);
|
|
3552
|
-
return /* @__PURE__ */
|
|
5012
|
+
return /* @__PURE__ */ jsx21(
|
|
3553
5013
|
ChatSurface,
|
|
3554
5014
|
{
|
|
3555
5015
|
theme,
|
|
@@ -3574,7 +5034,7 @@ ${text}` : text),
|
|
|
3574
5034
|
setStopRequested(true);
|
|
3575
5035
|
stop();
|
|
3576
5036
|
},
|
|
3577
|
-
beforeInput: advanced ? /* @__PURE__ */
|
|
5037
|
+
beforeInput: advanced ? /* @__PURE__ */ jsx21(
|
|
3578
5038
|
LlmAdvancedSettingsBar,
|
|
3579
5039
|
{
|
|
3580
5040
|
settings: advanced,
|
|
@@ -3592,14 +5052,14 @@ ${text}` : text),
|
|
|
3592
5052
|
}
|
|
3593
5053
|
|
|
3594
5054
|
// src/components/ChatView.tsx
|
|
3595
|
-
import { jsx as
|
|
5055
|
+
import { jsx as jsx22 } from "react/jsx-runtime";
|
|
3596
5056
|
function ChatView(props) {
|
|
3597
5057
|
const { mode, llm, onLlmReady, ...rest } = props;
|
|
3598
5058
|
if (mode === "llm") {
|
|
3599
5059
|
if (!llm) {
|
|
3600
5060
|
throw new Error('ChatView: mode="llm" \u9700\u8981\u540C\u65F6\u4F20 llm={{ baseURL, model }}');
|
|
3601
5061
|
}
|
|
3602
|
-
return /* @__PURE__ */
|
|
5062
|
+
return /* @__PURE__ */ jsx22(
|
|
3603
5063
|
LlmChat,
|
|
3604
5064
|
{
|
|
3605
5065
|
...llm,
|
|
@@ -3612,7 +5072,232 @@ function ChatView(props) {
|
|
|
3612
5072
|
}
|
|
3613
5073
|
);
|
|
3614
5074
|
}
|
|
3615
|
-
return /* @__PURE__ */
|
|
5075
|
+
return /* @__PURE__ */ jsx22(AgentChat, { ...rest });
|
|
5076
|
+
}
|
|
5077
|
+
|
|
5078
|
+
// src/components/ContextCard.tsx
|
|
5079
|
+
import {
|
|
5080
|
+
getContextDisplayState,
|
|
5081
|
+
getContextGroupDisplayState
|
|
5082
|
+
} from "@blade-hq/agent-client";
|
|
5083
|
+
import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
5084
|
+
function ContextCard({ context, className }) {
|
|
5085
|
+
const display = getContextDisplayState(context);
|
|
5086
|
+
return /* @__PURE__ */ jsxs19(
|
|
5087
|
+
"details",
|
|
5088
|
+
{
|
|
5089
|
+
className: `blade-chat-context-card group/context-card rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] text-sm ${className ?? ""}`,
|
|
5090
|
+
children: [
|
|
5091
|
+
/* @__PURE__ */ jsxs19("summary", { className: "blade-chat-context-summary flex cursor-pointer list-none items-center gap-2 px-3 py-2.5 [&::-webkit-details-marker]:hidden", children: [
|
|
5092
|
+
/* @__PURE__ */ jsx23(
|
|
5093
|
+
Layers,
|
|
5094
|
+
{
|
|
5095
|
+
size: 15,
|
|
5096
|
+
className: "blade-chat-context-icon shrink-0 text-[hsl(var(--muted-foreground))]",
|
|
5097
|
+
"aria-hidden": "true"
|
|
5098
|
+
}
|
|
5099
|
+
),
|
|
5100
|
+
/* @__PURE__ */ jsxs19("span", { className: "blade-chat-context-copy min-w-0 flex-1", children: [
|
|
5101
|
+
/* @__PURE__ */ jsx23("span", { className: "blade-chat-context-title block font-medium text-[hsl(var(--foreground))]", children: display.title }),
|
|
5102
|
+
/* @__PURE__ */ jsx23("span", { className: "blade-chat-context-status block truncate text-xs text-[hsl(var(--muted-foreground))]", children: display.summary })
|
|
5103
|
+
] }),
|
|
5104
|
+
/* @__PURE__ */ jsx23(
|
|
5105
|
+
ChevronDown,
|
|
5106
|
+
{
|
|
5107
|
+
size: 14,
|
|
5108
|
+
className: "blade-chat-context-chevron shrink-0 text-[hsl(var(--muted-foreground))] transition-transform group-open/context-card:rotate-180",
|
|
5109
|
+
"aria-hidden": "true"
|
|
5110
|
+
}
|
|
5111
|
+
)
|
|
5112
|
+
] }),
|
|
5113
|
+
/* @__PURE__ */ jsx23("div", { className: "blade-chat-context-detail border-t border-[hsl(var(--border))] px-3 py-2.5 text-xs leading-5 text-[hsl(var(--muted-foreground))]", children: display.detail })
|
|
5114
|
+
]
|
|
5115
|
+
}
|
|
5116
|
+
);
|
|
5117
|
+
}
|
|
5118
|
+
function ContextGroupCard({ contexts, className }) {
|
|
5119
|
+
if (contexts.length === 0) return null;
|
|
5120
|
+
const single = contexts.length === 1 ? getContextDisplayState(contexts[0]) : null;
|
|
5121
|
+
const group = single ? null : getContextGroupDisplayState(contexts);
|
|
5122
|
+
return /* @__PURE__ */ jsxs19("details", { className: `blade-chat-context-card group/context-group rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] text-sm ${className ?? ""}`, children: [
|
|
5123
|
+
/* @__PURE__ */ jsxs19("summary", { className: "blade-chat-context-summary flex cursor-pointer list-none items-center gap-2 px-3 py-2.5 [&::-webkit-details-marker]:hidden", children: [
|
|
5124
|
+
/* @__PURE__ */ jsx23(
|
|
5125
|
+
Layers,
|
|
5126
|
+
{
|
|
5127
|
+
size: 15,
|
|
5128
|
+
className: "blade-chat-context-icon shrink-0 text-[hsl(var(--muted-foreground))]",
|
|
5129
|
+
"aria-hidden": "true"
|
|
5130
|
+
}
|
|
5131
|
+
),
|
|
5132
|
+
/* @__PURE__ */ jsxs19("span", { className: "blade-chat-context-copy min-w-0 flex-1", children: [
|
|
5133
|
+
/* @__PURE__ */ jsx23("span", { className: "blade-chat-context-title block font-medium text-[hsl(var(--foreground))]", children: single ? single.title : `${group?.title} \xB7 ${group?.count} \u9879` }),
|
|
5134
|
+
/* @__PURE__ */ jsx23("span", { className: "blade-chat-context-status block truncate text-xs text-[hsl(var(--muted-foreground))]", children: single ? single.summary : group?.summary })
|
|
5135
|
+
] }),
|
|
5136
|
+
/* @__PURE__ */ jsx23(
|
|
5137
|
+
ChevronDown,
|
|
5138
|
+
{
|
|
5139
|
+
size: 14,
|
|
5140
|
+
className: "blade-chat-context-chevron shrink-0 text-[hsl(var(--muted-foreground))] transition-transform group-open/context-group:rotate-180",
|
|
5141
|
+
"aria-hidden": "true"
|
|
5142
|
+
}
|
|
5143
|
+
)
|
|
5144
|
+
] }),
|
|
5145
|
+
single ? /* @__PURE__ */ jsx23("div", { className: "blade-chat-context-detail border-t border-[hsl(var(--border))] px-3 py-2.5 text-xs leading-5 text-[hsl(var(--muted-foreground))]", children: single.detail }) : /* @__PURE__ */ jsx23("div", { className: "blade-chat-context-group-items flex flex-col gap-1.5 border-t border-[hsl(var(--border))] p-2", children: contexts.map((context) => /* @__PURE__ */ jsx23(
|
|
5146
|
+
ContextCard,
|
|
5147
|
+
{
|
|
5148
|
+
context
|
|
5149
|
+
},
|
|
5150
|
+
`${context.context_kind}:${context.context_key}`
|
|
5151
|
+
)) })
|
|
5152
|
+
] });
|
|
5153
|
+
}
|
|
5154
|
+
|
|
5155
|
+
// src/components/SessionMemoryToggle.tsx
|
|
5156
|
+
import { useCallback as useCallback9, useEffect as useEffect14, useRef as useRef13, useState as useState18, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
5157
|
+
import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
5158
|
+
var saveStates = /* @__PURE__ */ new WeakMap();
|
|
5159
|
+
function getSaveState(client, sessionId) {
|
|
5160
|
+
let clientStates = saveStates.get(client);
|
|
5161
|
+
if (!clientStates) {
|
|
5162
|
+
clientStates = /* @__PURE__ */ new Map();
|
|
5163
|
+
saveStates.set(client, clientStates);
|
|
5164
|
+
}
|
|
5165
|
+
let state = clientStates.get(sessionId);
|
|
5166
|
+
if (!state) {
|
|
5167
|
+
state = { saving: false, listeners: /* @__PURE__ */ new Set() };
|
|
5168
|
+
clientStates.set(sessionId, state);
|
|
5169
|
+
}
|
|
5170
|
+
return state;
|
|
5171
|
+
}
|
|
5172
|
+
function notify(state) {
|
|
5173
|
+
for (const listener of state.listeners) listener();
|
|
5174
|
+
}
|
|
5175
|
+
function cleanupSaveState(client, sessionId, state) {
|
|
5176
|
+
if (state.saving || state.listeners.size > 0) return;
|
|
5177
|
+
const clientStates = saveStates.get(client);
|
|
5178
|
+
if (clientStates?.get(sessionId) === state) clientStates.delete(sessionId);
|
|
5179
|
+
}
|
|
5180
|
+
function SessionMemoryToggle({
|
|
5181
|
+
sessionId,
|
|
5182
|
+
enabled,
|
|
5183
|
+
client: clientProp,
|
|
5184
|
+
disabled = false,
|
|
5185
|
+
label = "\u5F53\u524D\u4F1A\u8BDD\u4F7F\u7528\u8BB0\u5FC6",
|
|
5186
|
+
className,
|
|
5187
|
+
labelClassName,
|
|
5188
|
+
inputClassName,
|
|
5189
|
+
onSaved,
|
|
5190
|
+
onError
|
|
5191
|
+
}) {
|
|
5192
|
+
const contextClient = useOptionalBladeClient();
|
|
5193
|
+
const client = clientProp ?? contextClient;
|
|
5194
|
+
if (!client) {
|
|
5195
|
+
throw new Error("SessionMemoryToggle \u5FC5\u987B\u5728 <BladeProvider> \u5185\u4F7F\u7528\u6216\u663E\u5F0F\u4F20\u5165 client");
|
|
5196
|
+
}
|
|
5197
|
+
const saveState = getSaveState(client, sessionId);
|
|
5198
|
+
const subscribe = useCallback9(
|
|
5199
|
+
(listener) => {
|
|
5200
|
+
saveState.listeners.add(listener);
|
|
5201
|
+
return () => {
|
|
5202
|
+
saveState.listeners.delete(listener);
|
|
5203
|
+
cleanupSaveState(client, sessionId, saveState);
|
|
5204
|
+
};
|
|
5205
|
+
},
|
|
5206
|
+
[client, saveState, sessionId]
|
|
5207
|
+
);
|
|
5208
|
+
const getSaving = useCallback9(() => saveState.saving, [saveState]);
|
|
5209
|
+
const saving = useSyncExternalStore2(
|
|
5210
|
+
subscribe,
|
|
5211
|
+
getSaving,
|
|
5212
|
+
getSaving
|
|
5213
|
+
);
|
|
5214
|
+
const [draftEnabled, setDraftEnabled] = useState18(enabled);
|
|
5215
|
+
const activeSessionIdRef = useRef13(sessionId);
|
|
5216
|
+
activeSessionIdRef.current = sessionId;
|
|
5217
|
+
useEffect14(() => {
|
|
5218
|
+
setDraftEnabled(enabled);
|
|
5219
|
+
}, [enabled, sessionId]);
|
|
5220
|
+
const update = useCallback9(
|
|
5221
|
+
(nextEnabled) => {
|
|
5222
|
+
const currentSaveState = getSaveState(client, sessionId);
|
|
5223
|
+
if (currentSaveState.saving) return;
|
|
5224
|
+
currentSaveState.saving = true;
|
|
5225
|
+
notify(currentSaveState);
|
|
5226
|
+
setDraftEnabled(nextEnabled);
|
|
5227
|
+
void client.sessions.updateSessionMemory(sessionId, nextEnabled).then(
|
|
5228
|
+
(updated) => {
|
|
5229
|
+
if (activeSessionIdRef.current === sessionId) {
|
|
5230
|
+
setDraftEnabled(updated.memory_enabled);
|
|
5231
|
+
}
|
|
5232
|
+
onSaved?.(sessionId, updated.memory_enabled);
|
|
5233
|
+
},
|
|
5234
|
+
(error) => {
|
|
5235
|
+
if (activeSessionIdRef.current === sessionId) setDraftEnabled(enabled);
|
|
5236
|
+
onError?.(error);
|
|
5237
|
+
}
|
|
5238
|
+
).finally(() => {
|
|
5239
|
+
currentSaveState.saving = false;
|
|
5240
|
+
notify(currentSaveState);
|
|
5241
|
+
cleanupSaveState(client, sessionId, currentSaveState);
|
|
5242
|
+
});
|
|
5243
|
+
},
|
|
5244
|
+
[client, enabled, onError, onSaved, sessionId]
|
|
5245
|
+
);
|
|
5246
|
+
return /* @__PURE__ */ jsxs20("label", { className: cn("flex items-center justify-between", className), children: [
|
|
5247
|
+
/* @__PURE__ */ jsx24("span", { className: labelClassName, children: label }),
|
|
5248
|
+
/* @__PURE__ */ jsx24(
|
|
5249
|
+
"input",
|
|
5250
|
+
{
|
|
5251
|
+
type: "checkbox",
|
|
5252
|
+
checked: draftEnabled,
|
|
5253
|
+
onChange: (event) => update(event.target.checked),
|
|
5254
|
+
disabled: disabled || saving,
|
|
5255
|
+
className: inputClassName
|
|
5256
|
+
}
|
|
5257
|
+
)
|
|
5258
|
+
] });
|
|
5259
|
+
}
|
|
5260
|
+
|
|
5261
|
+
// src/lib/agent-computer-command.ts
|
|
5262
|
+
var COMPUTER_LAUNCH_COMMAND_PATTERN = /(?:^|[\n;&|(]\s*)computer\s+launch(?:\s|$)/;
|
|
5263
|
+
function isAgentComputerCommand(command) {
|
|
5264
|
+
return COMPUTER_LAUNCH_COMMAND_PATTERN.test(command);
|
|
5265
|
+
}
|
|
5266
|
+
function isAgentComputerToolCall(argumentsJson) {
|
|
5267
|
+
if (!argumentsJson) return false;
|
|
5268
|
+
let command;
|
|
5269
|
+
try {
|
|
5270
|
+
const parsed = JSON.parse(argumentsJson);
|
|
5271
|
+
if (typeof parsed !== "object" || parsed === null) return false;
|
|
5272
|
+
command = parsed.command;
|
|
5273
|
+
} catch {
|
|
5274
|
+
return false;
|
|
5275
|
+
}
|
|
5276
|
+
return typeof command === "string" && isAgentComputerCommand(command);
|
|
5277
|
+
}
|
|
5278
|
+
var LAUNCH_SUCCESS_MARKER = "\u5DF2\u542F\u52A8 ";
|
|
5279
|
+
function resultContainsLaunchSuccessMarker(result, depth = 0) {
|
|
5280
|
+
if (depth > 2) return false;
|
|
5281
|
+
if (typeof result === "string") {
|
|
5282
|
+
if (result.includes(LAUNCH_SUCCESS_MARKER)) return true;
|
|
5283
|
+
try {
|
|
5284
|
+
return resultContainsLaunchSuccessMarker(JSON.parse(result), depth + 1);
|
|
5285
|
+
} catch {
|
|
5286
|
+
return false;
|
|
5287
|
+
}
|
|
5288
|
+
}
|
|
5289
|
+
if (typeof result === "object" && result !== null) {
|
|
5290
|
+
for (const value of Object.values(result)) {
|
|
5291
|
+
if (typeof value === "string" && value.includes(LAUNCH_SUCCESS_MARKER)) return true;
|
|
5292
|
+
}
|
|
5293
|
+
}
|
|
5294
|
+
return false;
|
|
5295
|
+
}
|
|
5296
|
+
function classifyAgentComputerLaunchOutcome(toolCall) {
|
|
5297
|
+
if (toolCall.status === "error" || toolCall.status === "cancelled") return "failed";
|
|
5298
|
+
if (toolCall.status !== "done") return "pending";
|
|
5299
|
+
if (toolCall.result === void 0 || toolCall.result === null) return "unknown";
|
|
5300
|
+
return resultContainsLaunchSuccessMarker(toolCall.result) ? "succeeded" : "failed";
|
|
3616
5301
|
}
|
|
3617
5302
|
|
|
3618
5303
|
// src/index.ts
|
|
@@ -3621,13 +5306,32 @@ export {
|
|
|
3621
5306
|
AgentChat,
|
|
3622
5307
|
BladeProvider,
|
|
3623
5308
|
ChatView,
|
|
5309
|
+
ContextCard,
|
|
5310
|
+
ContextGroupCard,
|
|
5311
|
+
CurrentPlanPanel,
|
|
3624
5312
|
LlmChat,
|
|
3625
5313
|
MarkdownContent,
|
|
5314
|
+
MemoryRefsHint,
|
|
5315
|
+
PLAN_AUTO_COLLAPSE_MS,
|
|
5316
|
+
PlanUpdateBlock,
|
|
3626
5317
|
ReplayBar,
|
|
3627
5318
|
ReplayMismatchPrompt,
|
|
5319
|
+
SessionMemoryToggle,
|
|
5320
|
+
WhatIfUserBubble,
|
|
5321
|
+
classifyAgentComputerLaunchOutcome,
|
|
5322
|
+
collectMemoryRefs,
|
|
5323
|
+
getPlanUpdateDisplayState,
|
|
5324
|
+
isAgentComputerCommand,
|
|
5325
|
+
isAgentComputerToolCall,
|
|
5326
|
+
isPlanUpdateTool,
|
|
5327
|
+
normalizeAdjacentUrlFormatting,
|
|
5328
|
+
parsePlanUpdate,
|
|
5329
|
+
parseWhatIfPrompt,
|
|
5330
|
+
pickCurrentPlanStep,
|
|
3628
5331
|
useAgentSession,
|
|
3629
5332
|
useBladeClient,
|
|
3630
5333
|
useLlmChat,
|
|
5334
|
+
useMessagePin,
|
|
3631
5335
|
useReplay
|
|
3632
5336
|
};
|
|
3633
5337
|
/*! Bundled license information:
|
|
@@ -3639,29 +5343,35 @@ lucide-react/dist/esm/createLucideIcon.js:
|
|
|
3639
5343
|
lucide-react/dist/esm/icons/arrow-right.js:
|
|
3640
5344
|
lucide-react/dist/esm/icons/arrow-up-right.js:
|
|
3641
5345
|
lucide-react/dist/esm/icons/arrow-up.js:
|
|
5346
|
+
lucide-react/dist/esm/icons/book-open.js:
|
|
3642
5347
|
lucide-react/dist/esm/icons/bot.js:
|
|
3643
|
-
lucide-react/dist/esm/icons/brain.js:
|
|
3644
5348
|
lucide-react/dist/esm/icons/check.js:
|
|
3645
5349
|
lucide-react/dist/esm/icons/chevron-down.js:
|
|
3646
5350
|
lucide-react/dist/esm/icons/chevron-right.js:
|
|
3647
5351
|
lucide-react/dist/esm/icons/circle-alert.js:
|
|
5352
|
+
lucide-react/dist/esm/icons/circle-dot.js:
|
|
5353
|
+
lucide-react/dist/esm/icons/circle.js:
|
|
3648
5354
|
lucide-react/dist/esm/icons/copy.js:
|
|
3649
|
-
lucide-react/dist/esm/icons/
|
|
5355
|
+
lucide-react/dist/esm/icons/earth.js:
|
|
5356
|
+
lucide-react/dist/esm/icons/file-pen-line.js:
|
|
3650
5357
|
lucide-react/dist/esm/icons/file-text.js:
|
|
3651
|
-
lucide-react/dist/esm/icons/file.js:
|
|
3652
|
-
lucide-react/dist/esm/icons/film.js:
|
|
3653
5358
|
lucide-react/dist/esm/icons/globe.js:
|
|
3654
5359
|
lucide-react/dist/esm/icons/layers.js:
|
|
3655
5360
|
lucide-react/dist/esm/icons/lightbulb.js:
|
|
5361
|
+
lucide-react/dist/esm/icons/list-checks.js:
|
|
3656
5362
|
lucide-react/dist/esm/icons/loader-circle.js:
|
|
3657
5363
|
lucide-react/dist/esm/icons/lock-keyhole.js:
|
|
3658
5364
|
lucide-react/dist/esm/icons/message-square-more.js:
|
|
3659
5365
|
lucide-react/dist/esm/icons/message-square.js:
|
|
3660
5366
|
lucide-react/dist/esm/icons/play.js:
|
|
5367
|
+
lucide-react/dist/esm/icons/refresh-ccw.js:
|
|
5368
|
+
lucide-react/dist/esm/icons/search.js:
|
|
3661
5369
|
lucide-react/dist/esm/icons/settings-2.js:
|
|
3662
5370
|
lucide-react/dist/esm/icons/sparkles.js:
|
|
3663
5371
|
lucide-react/dist/esm/icons/square.js:
|
|
5372
|
+
lucide-react/dist/esm/icons/terminal.js:
|
|
3664
5373
|
lucide-react/dist/esm/icons/triangle-alert.js:
|
|
5374
|
+
lucide-react/dist/esm/icons/wrench.js:
|
|
3665
5375
|
lucide-react/dist/esm/icons/x.js:
|
|
3666
5376
|
lucide-react/dist/esm/lucide-react.js:
|
|
3667
5377
|
(**
|