@blade-hq/agent-react 2610.0.0-beta.8 → 2610.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -2
- 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 +6 -1
- package/dist/components/ContextCard.d.ts +20 -0
- package/dist/components/MarkdownContent.d.ts +2 -0
- package/dist/components/MessageList.d.ts +2 -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 +13 -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 +2356 -758
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-computer-command.d.ts +34 -0
- package/dist/lib/whatif-prompt.d.ts +18 -0
- package/dist/style.css +78 -2
- package/dist/style.full.css +79 -3
- package/package.json +2 -2
- package/public-api.md +423 -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,7 +1144,7 @@ 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 useEffect13, useMemo as useMemo8, useState as useState15 } from "react";
|
|
858
1148
|
|
|
859
1149
|
// src/lib/utils.ts
|
|
860
1150
|
function cn(...inputs) {
|
|
@@ -981,87 +1271,491 @@ function ReplayMismatchPrompt({ mismatch, className }) {
|
|
|
981
1271
|
);
|
|
982
1272
|
}
|
|
983
1273
|
|
|
984
|
-
// src/components/
|
|
1274
|
+
// src/components/PlanUpdateBlock.tsx
|
|
1275
|
+
import { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
|
|
1276
|
+
|
|
1277
|
+
// src/components/display-utils.ts
|
|
1278
|
+
var TOOL_NAME_ALIASES = {
|
|
1279
|
+
agent: "Agent",
|
|
1280
|
+
ask_user_question: "AskUserQuestion",
|
|
1281
|
+
bash: "Bash",
|
|
1282
|
+
bg_bash: "BgBash",
|
|
1283
|
+
edit: "Edit",
|
|
1284
|
+
exit_plan_mode: "ExitPlanMode",
|
|
1285
|
+
file_edit: "Edit",
|
|
1286
|
+
file_read: "Read",
|
|
1287
|
+
file_write: "Write",
|
|
1288
|
+
finish_task: "FinishTask",
|
|
1289
|
+
glob: "Glob",
|
|
1290
|
+
grep: "Grep",
|
|
1291
|
+
kb_search: "KbSearch",
|
|
1292
|
+
ls: "Ls",
|
|
1293
|
+
multi_edit: "MultiEdit",
|
|
1294
|
+
read: "Read",
|
|
1295
|
+
read_skill: "ReadSkill",
|
|
1296
|
+
update_plan: "UpdatePlan",
|
|
1297
|
+
web_fetch: "WebFetch",
|
|
1298
|
+
web_search: "WebSearch",
|
|
1299
|
+
write: "Write"
|
|
1300
|
+
};
|
|
1301
|
+
var TOOL_DISPLAY_LABELS = {
|
|
1302
|
+
Bash: "\u6267\u884C\u547D\u4EE4",
|
|
1303
|
+
BgBash: "\u540E\u53F0\u6267\u884C\u547D\u4EE4",
|
|
1304
|
+
Read: "\u8BFB\u53D6\u6587\u4EF6",
|
|
1305
|
+
Write: "\u5199\u5165\u6587\u4EF6",
|
|
1306
|
+
Edit: "\u7F16\u8F91\u6587\u4EF6",
|
|
1307
|
+
MultiEdit: "\u7F16\u8F91\u6587\u4EF6",
|
|
1308
|
+
Ls: "\u5217\u51FA\u76EE\u5F55",
|
|
1309
|
+
Glob: "\u5339\u914D\u6587\u4EF6",
|
|
1310
|
+
Grep: "\u641C\u7D22\u6587\u672C",
|
|
1311
|
+
KbSearch: "\u68C0\u7D22\u77E5\u8BC6\u5E93",
|
|
1312
|
+
WebSearch: "\u641C\u7D22\u7F51\u9875",
|
|
1313
|
+
WebFetch: "\u6574\u7406\u7F51\u9875\u5185\u5BB9",
|
|
1314
|
+
Agent: "\u6D3E\u751F\u5B50\u667A\u80FD\u4F53",
|
|
1315
|
+
AskUserQuestion: "\u5411\u7528\u6237\u63D0\u95EE",
|
|
1316
|
+
ReadSkill: "\u8BFB\u53D6\u6280\u80FD",
|
|
1317
|
+
FinishTask: "\u4EFB\u52A1\u5B8C\u6210",
|
|
1318
|
+
ExitPlanMode: "\u63D0\u4EA4\u8BA1\u5212",
|
|
1319
|
+
ListSessions: "\u5217\u51FA\u5386\u53F2\u4F1A\u8BDD",
|
|
1320
|
+
GetSessionHistory: "\u8BFB\u53D6\u4F1A\u8BDD\u5386\u53F2"
|
|
1321
|
+
};
|
|
1322
|
+
function safeParseJson(value) {
|
|
1323
|
+
if (!value) return null;
|
|
1324
|
+
try {
|
|
1325
|
+
return JSON.parse(value);
|
|
1326
|
+
} catch {
|
|
1327
|
+
return null;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
function getStringArgValue(args, key) {
|
|
1331
|
+
const value = args?.[key];
|
|
1332
|
+
return typeof value === "string" ? value.trim() : "";
|
|
1333
|
+
}
|
|
1334
|
+
var SKILL_ENTRY_FILE_NAMES = /* @__PURE__ */ new Set(["skill.md", "command.md"]);
|
|
1335
|
+
var NON_SKILL_DIR_NAMES = /* @__PURE__ */ new Set([".", "..", ".agent", ".agents", ".claude", "skill_data", "skills"]);
|
|
1336
|
+
function getSkillNameFromFilePath(filePath) {
|
|
1337
|
+
if (!filePath) return null;
|
|
1338
|
+
const segments = filePath.split(/[\\/]+/).filter(Boolean);
|
|
1339
|
+
const fileName = segments.pop();
|
|
1340
|
+
if (!fileName || !SKILL_ENTRY_FILE_NAMES.has(fileName.toLowerCase())) return null;
|
|
1341
|
+
const dirName = segments.pop();
|
|
1342
|
+
if (!dirName || NON_SKILL_DIR_NAMES.has(dirName.toLowerCase())) return null;
|
|
1343
|
+
return dirName;
|
|
1344
|
+
}
|
|
1345
|
+
function formatToolName(name) {
|
|
1346
|
+
const trimmed = name.trim();
|
|
1347
|
+
if (!trimmed) return name;
|
|
1348
|
+
const stripped = trimmed.split(":").pop()?.split("/").pop()?.split(".").pop()?.trim() || trimmed;
|
|
1349
|
+
const normalized = stripped.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
1350
|
+
return TOOL_NAME_ALIASES[normalized] ?? stripped;
|
|
1351
|
+
}
|
|
1352
|
+
function getToolDisplayLabel(toolCall) {
|
|
1353
|
+
const normalized = formatToolName(toolCall.name);
|
|
1354
|
+
const args = safeParseJson(toolCall.arguments);
|
|
1355
|
+
const displayName = toolCall.display_name?.trim() ?? "";
|
|
1356
|
+
const baseLabel = displayName || TOOL_DISPLAY_LABELS[normalized] || normalized;
|
|
1357
|
+
const metaDisplayName = getStringArgValue(args, "_meta_display_name");
|
|
1358
|
+
if (metaDisplayName) {
|
|
1359
|
+
return metaDisplayName;
|
|
1360
|
+
}
|
|
1361
|
+
const description = getStringArgValue(args, "description");
|
|
1362
|
+
if (normalized === "BgBash") {
|
|
1363
|
+
return description ? `\u540E\u53F0\u6267\u884C\uFF1A${description}` : "\u540E\u53F0\u6267\u884C\u547D\u4EE4";
|
|
1364
|
+
}
|
|
1365
|
+
if (normalized === "ReadSkill") {
|
|
1366
|
+
const skillName = getStringArgValue(args, "skill") || getStringArgValue(args, "skill_name");
|
|
1367
|
+
return skillName ? `${baseLabel}\u300C${skillName}\u300D` : baseLabel;
|
|
1368
|
+
}
|
|
1369
|
+
if (normalized === "Read") {
|
|
1370
|
+
const skillName = getSkillNameFromFilePath(
|
|
1371
|
+
getStringArgValue(args, "file_path") || getStringArgValue(args, "path")
|
|
1372
|
+
);
|
|
1373
|
+
if (skillName) return `\u8BFB\u53D6\u6280\u80FD\u300C${skillName}\u300D`;
|
|
1374
|
+
}
|
|
1375
|
+
if (normalized === "FinishTask") {
|
|
1376
|
+
const title = getStringArgValue(args, "title");
|
|
1377
|
+
return title ? `${baseLabel}\uFF1A${title}` : baseLabel;
|
|
1378
|
+
}
|
|
1379
|
+
return description || baseLabel;
|
|
1380
|
+
}
|
|
1381
|
+
function getToolTone(status) {
|
|
1382
|
+
if (status === "error" || status === "cancelled") return "red";
|
|
1383
|
+
if (status === "awaiting_answer") return "amber";
|
|
1384
|
+
if (status === "pending") return "blue";
|
|
1385
|
+
return "emerald";
|
|
1386
|
+
}
|
|
1387
|
+
function getToolStatusLabel(status) {
|
|
1388
|
+
if (status === "pending") return "\u8FD0\u884C\u4E2D";
|
|
1389
|
+
if (status === "awaiting_answer") return "\u7B49\u5F85\u56DE\u7B54";
|
|
1390
|
+
if (status === "error") return "\u9519\u8BEF";
|
|
1391
|
+
if (status === "cancelled") return "\u5DF2\u53D6\u6D88";
|
|
1392
|
+
return "\u5B8C\u6210";
|
|
1393
|
+
}
|
|
1394
|
+
function formatToolDuration(ms) {
|
|
1395
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
1396
|
+
const seconds = ms / 1e3;
|
|
1397
|
+
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
1398
|
+
const minutes = Math.floor(seconds / 60);
|
|
1399
|
+
const remainingSeconds = Math.round(seconds % 60);
|
|
1400
|
+
return remainingSeconds > 0 ? `${minutes}m${remainingSeconds}s` : `${minutes}m`;
|
|
1401
|
+
}
|
|
1402
|
+
function formatToolArgs(args) {
|
|
1403
|
+
try {
|
|
1404
|
+
return JSON.stringify(JSON.parse(args), null, 2);
|
|
1405
|
+
} catch {
|
|
1406
|
+
return args;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
var RESULT_PREVIEW_LIMIT = 4e3;
|
|
1410
|
+
function formatToolResult(result) {
|
|
1411
|
+
const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
1412
|
+
if (text == null) return "";
|
|
1413
|
+
if (text.length <= RESULT_PREVIEW_LIMIT) return text;
|
|
1414
|
+
return `${text.slice(0, RESULT_PREVIEW_LIMIT)}
|
|
1415
|
+
\u2026\uFF08\u7ED3\u679C\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\uFF09`;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// src/components/PlanUpdateBlock.tsx
|
|
985
1419
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
onValueChange("");
|
|
1003
|
-
};
|
|
1004
|
-
const handleKeyDown = (event) => {
|
|
1005
|
-
if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
|
|
1006
|
-
event.preventDefault();
|
|
1007
|
-
void handleSend();
|
|
1420
|
+
var PLAN_STEP_STATUSES = /* @__PURE__ */ new Set(["pending", "in_progress", "completed"]);
|
|
1421
|
+
var PLAN_AUTO_COLLAPSE_MS = 5e3;
|
|
1422
|
+
function isPlanUpdateTool(toolCall) {
|
|
1423
|
+
return formatToolName(toolCall.name) === "UpdatePlan";
|
|
1424
|
+
}
|
|
1425
|
+
function getPlanUpdateDisplayState(messages) {
|
|
1426
|
+
let current = null;
|
|
1427
|
+
let latestAttempt = null;
|
|
1428
|
+
let latestAttemptStreaming = false;
|
|
1429
|
+
for (const message of messages) {
|
|
1430
|
+
if ((message.loop_name ?? "root") !== "root") continue;
|
|
1431
|
+
for (const toolCall of message.tool_calls ?? []) {
|
|
1432
|
+
if (!isPlanUpdateTool(toolCall)) continue;
|
|
1433
|
+
latestAttempt = toolCall;
|
|
1434
|
+
latestAttemptStreaming = message.status === "streaming";
|
|
1435
|
+
if (toolCall.status === "done" && parsePlanUpdate(toolCall.arguments)) current = toolCall;
|
|
1008
1436
|
}
|
|
1437
|
+
}
|
|
1438
|
+
return {
|
|
1439
|
+
current,
|
|
1440
|
+
updating: latestAttempt?.status === "pending" && latestAttemptStreaming
|
|
1009
1441
|
};
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
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)]"
|
|
1442
|
+
}
|
|
1443
|
+
function parsePlanUpdate(argumentsJson) {
|
|
1444
|
+
try {
|
|
1445
|
+
const raw = JSON.parse(argumentsJson);
|
|
1446
|
+
if (!raw || typeof raw !== "object") return null;
|
|
1447
|
+
const candidate = raw;
|
|
1448
|
+
if (!Array.isArray(candidate.plan)) return null;
|
|
1449
|
+
const plan = candidate.plan.map((item) => {
|
|
1450
|
+
if (!item || typeof item !== "object") return null;
|
|
1451
|
+
const step = item.step;
|
|
1452
|
+
const status = item.status;
|
|
1453
|
+
if (typeof step !== "string" || !step.trim() || typeof status !== "string" || !PLAN_STEP_STATUSES.has(status)) {
|
|
1454
|
+
return null;
|
|
1026
1455
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1456
|
+
return { step: step.trim(), status };
|
|
1457
|
+
});
|
|
1458
|
+
if (plan.some((item) => item === null)) return null;
|
|
1459
|
+
if (plan.filter((item) => item?.status === "in_progress").length > 1) return null;
|
|
1460
|
+
return { plan };
|
|
1461
|
+
} catch {
|
|
1462
|
+
return null;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
function pickCurrentPlanStep(plan) {
|
|
1466
|
+
return plan.find((item) => item.status === "in_progress") ?? plan.find((item) => item.status === "pending") ?? plan[plan.length - 1] ?? null;
|
|
1467
|
+
}
|
|
1468
|
+
function PlanStepIcon({
|
|
1469
|
+
status,
|
|
1470
|
+
size = 17,
|
|
1471
|
+
running = false
|
|
1472
|
+
}) {
|
|
1473
|
+
if (status === "completed") {
|
|
1474
|
+
return /* @__PURE__ */ jsx4(Check, { size, strokeWidth: 2, className: "shrink-0 text-emerald-500" });
|
|
1475
|
+
}
|
|
1476
|
+
if (status === "in_progress") {
|
|
1477
|
+
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" });
|
|
1478
|
+
}
|
|
1479
|
+
return /* @__PURE__ */ jsx4(Circle, { size, className: "shrink-0 text-[hsl(var(--muted-foreground))]/60" });
|
|
1480
|
+
}
|
|
1481
|
+
function PlanUpdateBlock({
|
|
1482
|
+
toolCall,
|
|
1483
|
+
running = false,
|
|
1484
|
+
autoReveal = false
|
|
1485
|
+
}) {
|
|
1486
|
+
const updateKey = `${toolCall.id}:${toolCall.arguments}`;
|
|
1487
|
+
const revealKey = autoReveal ? updateKey : null;
|
|
1488
|
+
const [collapsed, setCollapsed] = useState4(!autoReveal);
|
|
1489
|
+
const collapseTimerRef = useRef4(null);
|
|
1490
|
+
const data = parsePlanUpdate(toolCall.arguments);
|
|
1491
|
+
useEffect4(() => {
|
|
1492
|
+
if (!revealKey) return;
|
|
1493
|
+
if (collapseTimerRef.current) clearTimeout(collapseTimerRef.current);
|
|
1494
|
+
setCollapsed(false);
|
|
1495
|
+
collapseTimerRef.current = setTimeout(() => {
|
|
1496
|
+
setCollapsed(true);
|
|
1497
|
+
collapseTimerRef.current = null;
|
|
1498
|
+
}, PLAN_AUTO_COLLAPSE_MS);
|
|
1499
|
+
}, [revealKey]);
|
|
1500
|
+
useEffect4(
|
|
1501
|
+
() => () => {
|
|
1502
|
+
if (collapseTimerRef.current) clearTimeout(collapseTimerRef.current);
|
|
1503
|
+
},
|
|
1504
|
+
[]
|
|
1505
|
+
);
|
|
1506
|
+
if (!data) return null;
|
|
1507
|
+
const completed = data.plan.filter((item) => item.status === "completed").length;
|
|
1508
|
+
const currentStep = pickCurrentPlanStep(data.plan);
|
|
1509
|
+
const pausedAtCurrentStep = !running && currentStep?.status === "in_progress";
|
|
1510
|
+
return /* @__PURE__ */ jsxs3("section", { className: "overflow-hidden", children: [
|
|
1511
|
+
/* @__PURE__ */ jsxs3(
|
|
1512
|
+
"button",
|
|
1030
1513
|
{
|
|
1031
1514
|
type: "button",
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1515
|
+
"aria-expanded": !collapsed,
|
|
1516
|
+
onClick: () => {
|
|
1517
|
+
if (collapseTimerRef.current) {
|
|
1518
|
+
clearTimeout(collapseTimerRef.current);
|
|
1519
|
+
collapseTimerRef.current = null;
|
|
1520
|
+
}
|
|
1521
|
+
setCollapsed((value) => !value);
|
|
1522
|
+
},
|
|
1523
|
+
className: cn(
|
|
1524
|
+
"flex w-full items-center gap-2 px-3 py-2 text-left transition-colors hover:bg-[hsl(var(--muted)/0.3)]",
|
|
1525
|
+
!collapsed && "border-b border-[hsl(var(--border))]"
|
|
1526
|
+
),
|
|
1527
|
+
children: [
|
|
1528
|
+
collapsed && currentStep ? /* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 flex-1 items-center gap-1.5 text-xs text-[hsl(var(--foreground))]", children: [
|
|
1529
|
+
/* @__PURE__ */ jsx4(PlanStepIcon, { status: currentStep.status, size: 14, running }),
|
|
1530
|
+
/* @__PURE__ */ jsx4("span", { className: "truncate", children: currentStep.step }),
|
|
1531
|
+
pausedAtCurrentStep ? /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-[11px] text-amber-500", children: "\u5DF2\u6682\u505C" }) : null
|
|
1532
|
+
] }) : /* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 flex-1 items-center gap-1.5 text-[11px] text-[hsl(var(--muted-foreground))]", children: [
|
|
1533
|
+
/* @__PURE__ */ jsx4(ListChecks, { size: 14, className: "shrink-0", "aria-hidden": "true" }),
|
|
1534
|
+
/* @__PURE__ */ jsx4("span", { className: "truncate", children: "\u4EFB\u52A1\u8FDB\u5EA6" }),
|
|
1535
|
+
pausedAtCurrentStep ? /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-amber-500", children: "\u5DF2\u6682\u505C" }) : null
|
|
1536
|
+
] }),
|
|
1537
|
+
/* @__PURE__ */ jsxs3("span", { className: "shrink-0 text-[11px] tabular-nums text-[hsl(var(--muted-foreground))]", children: [
|
|
1538
|
+
completed,
|
|
1539
|
+
"/",
|
|
1540
|
+
data.plan.length
|
|
1541
|
+
] }),
|
|
1542
|
+
/* @__PURE__ */ jsx4(
|
|
1543
|
+
ChevronDown,
|
|
1544
|
+
{
|
|
1545
|
+
size: 14,
|
|
1546
|
+
className: cn(
|
|
1547
|
+
"shrink-0 text-[hsl(var(--muted-foreground))] transition-transform duration-300 ease-out motion-reduce:transition-none",
|
|
1548
|
+
!collapsed && "rotate-180"
|
|
1549
|
+
)
|
|
1550
|
+
}
|
|
1551
|
+
)
|
|
1552
|
+
]
|
|
1038
1553
|
}
|
|
1039
|
-
)
|
|
1040
|
-
|
|
1554
|
+
),
|
|
1555
|
+
/* @__PURE__ */ jsx4(
|
|
1556
|
+
"div",
|
|
1041
1557
|
{
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
className: "flex h-
|
|
1048
|
-
|
|
1558
|
+
"aria-hidden": collapsed,
|
|
1559
|
+
className: cn(
|
|
1560
|
+
"grid transition-[grid-template-rows,opacity] duration-300 ease-out motion-reduce:transition-none",
|
|
1561
|
+
collapsed ? "grid-rows-[0fr] opacity-0" : "grid-rows-[1fr] opacity-100"
|
|
1562
|
+
),
|
|
1563
|
+
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: [
|
|
1564
|
+
/* @__PURE__ */ jsx4("span", { className: "mt-[3px] flex shrink-0", children: /* @__PURE__ */ jsx4(PlanStepIcon, { status: item.status, size: 14, running }) }),
|
|
1565
|
+
/* @__PURE__ */ jsx4(
|
|
1566
|
+
"span",
|
|
1567
|
+
{
|
|
1568
|
+
className: cn(
|
|
1569
|
+
"min-w-0 flex-1 break-words text-[13px] leading-5",
|
|
1570
|
+
item.status === "completed" ? "text-[hsl(var(--muted-foreground))]" : item.status === "in_progress" ? "font-medium text-[hsl(var(--foreground))]" : "text-[hsl(var(--muted-foreground))]"
|
|
1571
|
+
),
|
|
1572
|
+
children: item.step
|
|
1573
|
+
}
|
|
1574
|
+
)
|
|
1575
|
+
] }, `${index}-${item.step}`)) }) })
|
|
1049
1576
|
}
|
|
1050
1577
|
)
|
|
1051
|
-
] })
|
|
1578
|
+
] });
|
|
1579
|
+
}
|
|
1580
|
+
function CurrentPlanPanel({
|
|
1581
|
+
messages,
|
|
1582
|
+
running = false,
|
|
1583
|
+
revealRevision = 0,
|
|
1584
|
+
sessionId,
|
|
1585
|
+
className
|
|
1586
|
+
}) {
|
|
1587
|
+
const { current, updating } = getPlanUpdateDisplayState(messages);
|
|
1588
|
+
const revealBaselinesRef = useRef4(/* @__PURE__ */ new Map([[sessionId, revealRevision]]));
|
|
1589
|
+
const autoReveal = (revealBaselinesRef.current.get(sessionId) ?? 0) !== revealRevision;
|
|
1590
|
+
useEffect4(() => {
|
|
1591
|
+
if (!current) return;
|
|
1592
|
+
revealBaselinesRef.current.set(sessionId, revealRevision);
|
|
1593
|
+
}, [current, revealRevision, sessionId]);
|
|
1594
|
+
if (!current && !updating) return null;
|
|
1595
|
+
return /* @__PURE__ */ jsxs3("div", { className: cn("blade-chat-plan mx-auto w-full max-w-[748px] px-4", className), children: [
|
|
1596
|
+
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: [
|
|
1597
|
+
/* @__PURE__ */ jsx4(LoaderCircle, { size: 14, className: "shrink-0 animate-spin" }),
|
|
1598
|
+
/* @__PURE__ */ jsx4("span", { children: "\u6B63\u5728\u66F4\u65B0\u4EFB\u52A1\u8FDB\u5EA6\u2026" })
|
|
1599
|
+
] }) : null,
|
|
1600
|
+
current ? /* @__PURE__ */ jsx4(
|
|
1601
|
+
PlanUpdateBlock,
|
|
1602
|
+
{
|
|
1603
|
+
toolCall: current,
|
|
1604
|
+
running,
|
|
1605
|
+
autoReveal
|
|
1606
|
+
},
|
|
1607
|
+
sessionId ?? "current-session"
|
|
1608
|
+
) : null
|
|
1609
|
+
] });
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// src/components/ChatSurface.tsx
|
|
1613
|
+
import { chatErrorForDisplay as chatErrorForDisplay2 } from "@blade-hq/agent-client";
|
|
1614
|
+
|
|
1615
|
+
// src/components/ChatInput.tsx
|
|
1616
|
+
import { useEffect as useEffect5, useRef as useRef5, useState as useState5 } from "react";
|
|
1617
|
+
import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1618
|
+
function isImeCompositionKey(event) {
|
|
1619
|
+
return event.isComposing || event.keyCode === 229;
|
|
1620
|
+
}
|
|
1621
|
+
function shouldSubmitChatInput(event, menuOpen) {
|
|
1622
|
+
return event.key === "Enter" && !event.shiftKey && !menuOpen && !isImeCompositionKey(event);
|
|
1623
|
+
}
|
|
1624
|
+
function ChatInput({
|
|
1625
|
+
value,
|
|
1626
|
+
onValueChange,
|
|
1627
|
+
onSend,
|
|
1628
|
+
onAppend,
|
|
1629
|
+
onStop,
|
|
1630
|
+
isStreaming,
|
|
1631
|
+
isStopping = false,
|
|
1632
|
+
placeholder = "\u8F93\u5165\u6D88\u606F\u2026",
|
|
1633
|
+
className,
|
|
1634
|
+
queueKey
|
|
1635
|
+
}) {
|
|
1636
|
+
const trimmed = value.trim();
|
|
1637
|
+
const [sendMode, setSendMode] = useState5("direct");
|
|
1638
|
+
const [promptQueue, setPromptQueue] = useState5([]);
|
|
1639
|
+
const queueSendingRef = useRef5(false);
|
|
1640
|
+
const queueBlockedRef = useRef5(false);
|
|
1641
|
+
const previousQueueKeyRef = useRef5(queueKey);
|
|
1642
|
+
useEffect5(() => {
|
|
1643
|
+
if (previousQueueKeyRef.current === queueKey) return;
|
|
1644
|
+
previousQueueKeyRef.current = queueKey;
|
|
1645
|
+
setPromptQueue([]);
|
|
1646
|
+
queueBlockedRef.current = false;
|
|
1647
|
+
}, [queueKey]);
|
|
1648
|
+
const canSend = trimmed.length > 0 && (!isStreaming || sendMode === "queue");
|
|
1649
|
+
useEffect5(() => {
|
|
1650
|
+
if (isStreaming) {
|
|
1651
|
+
queueBlockedRef.current = false;
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
if (queueBlockedRef.current || promptQueue.length === 0 || queueSendingRef.current) return;
|
|
1655
|
+
const next = promptQueue[0];
|
|
1656
|
+
queueSendingRef.current = true;
|
|
1657
|
+
Promise.resolve().then(() => onSend(next)).then((accepted) => {
|
|
1658
|
+
if (accepted) setPromptQueue((current) => current.slice(1));
|
|
1659
|
+
else queueBlockedRef.current = true;
|
|
1660
|
+
}).finally(() => {
|
|
1661
|
+
queueSendingRef.current = false;
|
|
1662
|
+
});
|
|
1663
|
+
}, [isStreaming, onSend, promptQueue]);
|
|
1664
|
+
const handleSend = async () => {
|
|
1665
|
+
if (!canSend) return;
|
|
1666
|
+
if (isStreaming && sendMode === "queue") {
|
|
1667
|
+
setPromptQueue((current) => [...current, trimmed]);
|
|
1668
|
+
onValueChange("");
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
if (isStreaming && sendMode === "direct") {
|
|
1672
|
+
if (!onAppend) return;
|
|
1673
|
+
onAppend(trimmed);
|
|
1674
|
+
onValueChange("");
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
const accepted = await onSend(trimmed);
|
|
1678
|
+
if (!accepted) return;
|
|
1679
|
+
onValueChange("");
|
|
1680
|
+
};
|
|
1681
|
+
const handleKeyDown = (event) => {
|
|
1682
|
+
if (shouldSubmitChatInput({
|
|
1683
|
+
key: event.key,
|
|
1684
|
+
shiftKey: event.shiftKey,
|
|
1685
|
+
isComposing: event.nativeEvent.isComposing,
|
|
1686
|
+
keyCode: event.nativeEvent.keyCode
|
|
1687
|
+
}, false)) {
|
|
1688
|
+
event.preventDefault();
|
|
1689
|
+
void handleSend();
|
|
1690
|
+
}
|
|
1691
|
+
};
|
|
1692
|
+
return /* @__PURE__ */ jsxs4("div", { className: cn("blade-chat-input border-t border-[hsl(var(--border))] py-3", className), children: [
|
|
1693
|
+
/* @__PURE__ */ jsxs4("div", { className: "mx-auto mb-2 flex max-w-[748px] items-center justify-between px-1 text-xs text-[hsl(var(--muted-foreground))]", children: [
|
|
1694
|
+
/* @__PURE__ */ jsxs4("fieldset", { className: "flex items-center gap-1 rounded-md border border-[hsl(var(--border))] p-0.5", children: [
|
|
1695
|
+
/* @__PURE__ */ jsx5("legend", { className: "sr-only", children: "\u53D1\u9001\u65B9\u5F0F" }),
|
|
1696
|
+
/* @__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" }),
|
|
1697
|
+
/* @__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" })
|
|
1698
|
+
] }),
|
|
1699
|
+
promptQueue.length > 0 ? /* @__PURE__ */ jsxs4("details", { className: "relative", children: [
|
|
1700
|
+
/* @__PURE__ */ jsxs4("summary", { className: "cursor-pointer list-none rounded px-2 py-1 hover:bg-[hsl(var(--accent))]", children: [
|
|
1701
|
+
"\u5F85\u6267\u884C ",
|
|
1702
|
+
promptQueue.length,
|
|
1703
|
+
" \u6761"
|
|
1704
|
+
] }),
|
|
1705
|
+
/* @__PURE__ */ jsx5("div", { className: "absolute bottom-full right-0 z-20 mb-2 w-64 rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--card))] p-2 shadow-lg", children: promptQueue.map((item, index) => /* @__PURE__ */ jsxs4("div", { className: "flex gap-2 border-b border-[hsl(var(--border))] py-2 last:border-0", children: [
|
|
1706
|
+
/* @__PURE__ */ jsx5("span", { className: "min-w-0 flex-1 truncate", children: item }),
|
|
1707
|
+
/* @__PURE__ */ jsx5("button", { type: "button", onClick: () => setPromptQueue((current) => current.filter((_, i) => i !== index)), children: "\u53D6\u6D88" })
|
|
1708
|
+
] }, `${index}-${item}`)) })
|
|
1709
|
+
] }) : null
|
|
1710
|
+
] }),
|
|
1711
|
+
/* @__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: [
|
|
1712
|
+
/* @__PURE__ */ jsx5(
|
|
1713
|
+
"textarea",
|
|
1714
|
+
{
|
|
1715
|
+
value,
|
|
1716
|
+
onChange: (event) => onValueChange(event.target.value),
|
|
1717
|
+
onKeyDown: handleKeyDown,
|
|
1718
|
+
onInput: (event) => {
|
|
1719
|
+
const el = event.currentTarget;
|
|
1720
|
+
el.style.height = "auto";
|
|
1721
|
+
el.style.height = `${Math.min(el.scrollHeight, 192)}px`;
|
|
1722
|
+
},
|
|
1723
|
+
rows: 1,
|
|
1724
|
+
placeholder,
|
|
1725
|
+
"aria-label": "\u804A\u5929\u8F93\u5165",
|
|
1726
|
+
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)]"
|
|
1727
|
+
}
|
|
1728
|
+
),
|
|
1729
|
+
isStreaming ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1730
|
+
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,
|
|
1731
|
+
/* @__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" }) })
|
|
1732
|
+
] }) : /* @__PURE__ */ jsx5(
|
|
1733
|
+
"button",
|
|
1734
|
+
{
|
|
1735
|
+
type: "button",
|
|
1736
|
+
onClick: handleSend,
|
|
1737
|
+
disabled: !canSend,
|
|
1738
|
+
"aria-label": "\u53D1\u9001\u6D88\u606F",
|
|
1739
|
+
title: "\u53D1\u9001\u6D88\u606F",
|
|
1740
|
+
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",
|
|
1741
|
+
children: /* @__PURE__ */ jsx5(ArrowUp, { size: 15 })
|
|
1742
|
+
}
|
|
1743
|
+
)
|
|
1744
|
+
] })
|
|
1745
|
+
] });
|
|
1052
1746
|
}
|
|
1053
1747
|
|
|
1054
1748
|
// src/components/ConnectionBanner.tsx
|
|
1055
|
-
import { useEffect as
|
|
1056
|
-
import { jsx as
|
|
1749
|
+
import { useEffect as useEffect6, useRef as useRef6, useState as useState6 } from "react";
|
|
1750
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1057
1751
|
var CONNECTION_NOTICE_DELAY_MS = 3e3;
|
|
1058
1752
|
var CONNECTION_ERROR_DELAY_MS = 15e3;
|
|
1059
1753
|
function useConnectionNoticePhase(connected) {
|
|
1060
|
-
const [phase, setPhase] =
|
|
1061
|
-
const connectedRef =
|
|
1062
|
-
const timersRef =
|
|
1754
|
+
const [phase, setPhase] = useState6("hidden");
|
|
1755
|
+
const connectedRef = useRef6(connected);
|
|
1756
|
+
const timersRef = useRef6([]);
|
|
1063
1757
|
connectedRef.current = connected;
|
|
1064
|
-
|
|
1758
|
+
useEffect6(() => {
|
|
1065
1759
|
const clearTimers = () => {
|
|
1066
1760
|
for (const timer of timersRef.current) clearTimeout(timer);
|
|
1067
1761
|
timersRef.current = [];
|
|
@@ -1101,14 +1795,14 @@ function useConnectionNoticePhase(connected) {
|
|
|
1101
1795
|
return phase;
|
|
1102
1796
|
}
|
|
1103
1797
|
function ConnectionBanner({ connection, className }) {
|
|
1104
|
-
const hasConnectedRef =
|
|
1798
|
+
const hasConnectedRef = useRef6(connection === "connected" || connection === "reconnecting");
|
|
1105
1799
|
if (connection === "connected") hasConnectedRef.current = true;
|
|
1106
1800
|
const connected = connection === "connected";
|
|
1107
1801
|
const phase = useConnectionNoticePhase(connected);
|
|
1108
1802
|
if (connected || phase === "hidden") return null;
|
|
1109
1803
|
const recovering = phase === "recovering";
|
|
1110
1804
|
const firstConnection = !hasConnectedRef.current;
|
|
1111
|
-
return /* @__PURE__ */
|
|
1805
|
+
return /* @__PURE__ */ jsx6("div", { className: cn("blade-chat-banner bg-[hsl(var(--background))] px-5 pt-3", className), children: /* @__PURE__ */ jsxs5(
|
|
1112
1806
|
"div",
|
|
1113
1807
|
{
|
|
1114
1808
|
className: cn(
|
|
@@ -1116,10 +1810,10 @@ function ConnectionBanner({ connection, className }) {
|
|
|
1116
1810
|
recovering ? "border-amber-500/25 bg-amber-500/10 text-amber-100" : "border-rose-500/25 bg-rose-500/10 text-rose-100"
|
|
1117
1811
|
),
|
|
1118
1812
|
children: [
|
|
1119
|
-
/* @__PURE__ */
|
|
1120
|
-
/* @__PURE__ */
|
|
1121
|
-
/* @__PURE__ */
|
|
1122
|
-
/* @__PURE__ */
|
|
1813
|
+
/* @__PURE__ */ jsx6("span", { className: "mt-0.5 shrink-0", children: recovering ? /* @__PURE__ */ jsx6(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx6(TriangleAlert, { size: 14 }) }),
|
|
1814
|
+
/* @__PURE__ */ jsxs5("div", { className: "min-w-0", children: [
|
|
1815
|
+
/* @__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" }),
|
|
1816
|
+
/* @__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" })
|
|
1123
1817
|
] })
|
|
1124
1818
|
]
|
|
1125
1819
|
}
|
|
@@ -1128,10 +1822,10 @@ function ConnectionBanner({ connection, className }) {
|
|
|
1128
1822
|
|
|
1129
1823
|
// src/components/MessageList.tsx
|
|
1130
1824
|
import { isHiddenInternalMessage } from "@blade-hq/agent-client";
|
|
1131
|
-
import { useCallback as
|
|
1825
|
+
import { useCallback as useCallback7, useEffect as useEffect12, useMemo as useMemo7, useRef as useRef13, useState as useState14 } from "react";
|
|
1132
1826
|
|
|
1133
1827
|
// ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/useStickToBottom.js
|
|
1134
|
-
import { useCallback as
|
|
1828
|
+
import { useCallback as useCallback4, useMemo as useMemo3, useRef as useRef7, useState as useState7 } from "react";
|
|
1135
1829
|
var DEFAULT_SPRING_ANIMATION = {
|
|
1136
1830
|
/**
|
|
1137
1831
|
* A value from 0 to 1, on how much to damp the animation.
|
|
@@ -1168,12 +1862,12 @@ globalThis.document?.addEventListener("click", () => {
|
|
|
1168
1862
|
mouseDown = false;
|
|
1169
1863
|
});
|
|
1170
1864
|
var useStickToBottom = (options = {}) => {
|
|
1171
|
-
const [escapedFromLock, updateEscapedFromLock] =
|
|
1172
|
-
const [isAtBottom, updateIsAtBottom] =
|
|
1173
|
-
const [isNearBottom, setIsNearBottom] =
|
|
1174
|
-
const optionsRef =
|
|
1865
|
+
const [escapedFromLock, updateEscapedFromLock] = useState7(false);
|
|
1866
|
+
const [isAtBottom, updateIsAtBottom] = useState7(options.initial !== false);
|
|
1867
|
+
const [isNearBottom, setIsNearBottom] = useState7(false);
|
|
1868
|
+
const optionsRef = useRef7(null);
|
|
1175
1869
|
optionsRef.current = options;
|
|
1176
|
-
const isSelecting =
|
|
1870
|
+
const isSelecting = useCallback4(() => {
|
|
1177
1871
|
if (!mouseDown) {
|
|
1178
1872
|
return false;
|
|
1179
1873
|
}
|
|
@@ -1184,11 +1878,11 @@ var useStickToBottom = (options = {}) => {
|
|
|
1184
1878
|
const range = selection.getRangeAt(0);
|
|
1185
1879
|
return range.commonAncestorContainer.contains(scrollRef.current) || scrollRef.current?.contains(range.commonAncestorContainer);
|
|
1186
1880
|
}, []);
|
|
1187
|
-
const setIsAtBottom =
|
|
1881
|
+
const setIsAtBottom = useCallback4((isAtBottom2) => {
|
|
1188
1882
|
state.isAtBottom = isAtBottom2;
|
|
1189
1883
|
updateIsAtBottom(isAtBottom2);
|
|
1190
1884
|
}, []);
|
|
1191
|
-
const setEscapedFromLock =
|
|
1885
|
+
const setEscapedFromLock = useCallback4((escapedFromLock2) => {
|
|
1192
1886
|
state.escapedFromLock = escapedFromLock2;
|
|
1193
1887
|
updateEscapedFromLock(escapedFromLock2);
|
|
1194
1888
|
}, []);
|
|
@@ -1245,7 +1939,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
1245
1939
|
}
|
|
1246
1940
|
};
|
|
1247
1941
|
}, []);
|
|
1248
|
-
const scrollToBottom =
|
|
1942
|
+
const scrollToBottom = useCallback4((scrollOptions = {}) => {
|
|
1249
1943
|
if (typeof scrollOptions === "string") {
|
|
1250
1944
|
scrollOptions = { animation: scrollOptions };
|
|
1251
1945
|
}
|
|
@@ -1330,11 +2024,11 @@ var useStickToBottom = (options = {}) => {
|
|
|
1330
2024
|
}
|
|
1331
2025
|
return next();
|
|
1332
2026
|
}, [setIsAtBottom, isSelecting, state]);
|
|
1333
|
-
const stopScroll =
|
|
2027
|
+
const stopScroll = useCallback4(() => {
|
|
1334
2028
|
setEscapedFromLock(true);
|
|
1335
2029
|
setIsAtBottom(false);
|
|
1336
2030
|
}, [setEscapedFromLock, setIsAtBottom]);
|
|
1337
|
-
const handleScroll =
|
|
2031
|
+
const handleScroll = useCallback4(({ target }) => {
|
|
1338
2032
|
if (target !== scrollRef.current) {
|
|
1339
2033
|
return;
|
|
1340
2034
|
}
|
|
@@ -1373,7 +2067,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
1373
2067
|
}
|
|
1374
2068
|
}, 1);
|
|
1375
2069
|
}, [setEscapedFromLock, setIsAtBottom, isSelecting, state]);
|
|
1376
|
-
const handleWheel =
|
|
2070
|
+
const handleWheel = useCallback4(({ target, deltaY }) => {
|
|
1377
2071
|
let element = target;
|
|
1378
2072
|
while (!["scroll", "auto"].includes(getComputedStyle(element).overflow)) {
|
|
1379
2073
|
if (!element.parentElement) {
|
|
@@ -1443,7 +2137,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
1443
2137
|
};
|
|
1444
2138
|
};
|
|
1445
2139
|
function useRefCallback(callback, deps) {
|
|
1446
|
-
const result =
|
|
2140
|
+
const result = useCallback4((ref) => {
|
|
1447
2141
|
result.current = ref;
|
|
1448
2142
|
return callback(ref);
|
|
1449
2143
|
}, deps);
|
|
@@ -1475,11 +2169,11 @@ function mergeAnimations(...animations) {
|
|
|
1475
2169
|
|
|
1476
2170
|
// ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/StickToBottom.js
|
|
1477
2171
|
import * as React from "react";
|
|
1478
|
-
import { createContext as createContext2, useContext as useContext2, useEffect as
|
|
2172
|
+
import { createContext as createContext2, useContext as useContext2, useEffect as useEffect7, useImperativeHandle, useLayoutEffect, useMemo as useMemo4, useRef as useRef8 } from "react";
|
|
1479
2173
|
var StickToBottomContext = createContext2(null);
|
|
1480
|
-
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect :
|
|
2174
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect7;
|
|
1481
2175
|
function StickToBottom({ instance, children, resize, initial, mass, damping, stiffness, targetScrollTop: currentTargetScrollTop, contextRef, ...props }) {
|
|
1482
|
-
const customTargetScrollTop =
|
|
2176
|
+
const customTargetScrollTop = useRef8(null);
|
|
1483
2177
|
const targetScrollTop = React.useCallback((target, elements) => {
|
|
1484
2178
|
const get = context?.targetScrollTop ?? currentTargetScrollTop;
|
|
1485
2179
|
return get?.(target, elements) ?? target;
|
|
@@ -1555,133 +2249,17 @@ function useStickToBottomContext() {
|
|
|
1555
2249
|
}
|
|
1556
2250
|
|
|
1557
2251
|
// src/components/AssistantTurnBlock.tsx
|
|
1558
|
-
import {
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
var TOOL_NAME_ALIASES = {
|
|
1566
|
-
agent: "Agent",
|
|
1567
|
-
ask_user_question: "AskUserQuestion",
|
|
1568
|
-
bash: "Bash",
|
|
1569
|
-
bg_bash: "BgBash",
|
|
1570
|
-
edit: "Edit",
|
|
1571
|
-
exit_plan_mode: "ExitPlanMode",
|
|
1572
|
-
file_edit: "Edit",
|
|
1573
|
-
file_read: "Read",
|
|
1574
|
-
file_write: "Write",
|
|
1575
|
-
finish_task: "FinishTask",
|
|
1576
|
-
glob: "Glob",
|
|
1577
|
-
grep: "Grep",
|
|
1578
|
-
ls: "Ls",
|
|
1579
|
-
read: "Read",
|
|
1580
|
-
read_skill: "ReadSkill",
|
|
1581
|
-
web_fetch: "WebFetch",
|
|
1582
|
-
web_search: "WebSearch",
|
|
1583
|
-
write: "Write"
|
|
1584
|
-
};
|
|
1585
|
-
var TOOL_DISPLAY_LABELS = {
|
|
1586
|
-
Bash: "\u6267\u884C\u547D\u4EE4",
|
|
1587
|
-
BgBash: "\u540E\u53F0\u6267\u884C\u547D\u4EE4",
|
|
1588
|
-
Read: "\u8BFB\u53D6\u6587\u4EF6",
|
|
1589
|
-
Write: "\u5199\u5165\u6587\u4EF6",
|
|
1590
|
-
Edit: "\u7F16\u8F91\u6587\u4EF6",
|
|
1591
|
-
Ls: "\u5217\u51FA\u76EE\u5F55",
|
|
1592
|
-
Glob: "\u5339\u914D\u6587\u4EF6",
|
|
1593
|
-
Grep: "\u641C\u7D22\u6587\u672C",
|
|
1594
|
-
WebSearch: "\u641C\u7D22\u7F51\u9875",
|
|
1595
|
-
WebFetch: "\u6574\u7406\u7F51\u9875\u5185\u5BB9",
|
|
1596
|
-
Agent: "\u6D3E\u751F\u5B50\u667A\u80FD\u4F53",
|
|
1597
|
-
AskUserQuestion: "\u5411\u7528\u6237\u63D0\u95EE",
|
|
1598
|
-
ReadSkill: "\u8BFB\u53D6\u6280\u80FD",
|
|
1599
|
-
FinishTask: "\u4EFB\u52A1\u5B8C\u6210",
|
|
1600
|
-
ExitPlanMode: "\u63D0\u4EA4\u8BA1\u5212",
|
|
1601
|
-
ListSessions: "\u5217\u51FA\u5386\u53F2\u4F1A\u8BDD",
|
|
1602
|
-
GetSessionHistory: "\u8BFB\u53D6\u4F1A\u8BDD\u5386\u53F2"
|
|
1603
|
-
};
|
|
1604
|
-
function safeParseJson(value) {
|
|
1605
|
-
if (!value) return null;
|
|
1606
|
-
try {
|
|
1607
|
-
return JSON.parse(value);
|
|
1608
|
-
} catch {
|
|
1609
|
-
return null;
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
function getStringArgValue(args, key) {
|
|
1613
|
-
const value = args?.[key];
|
|
1614
|
-
return typeof value === "string" ? value.trim() : "";
|
|
1615
|
-
}
|
|
1616
|
-
function formatToolName(name) {
|
|
1617
|
-
const trimmed = name.trim();
|
|
1618
|
-
if (!trimmed) return name;
|
|
1619
|
-
const stripped = trimmed.split(":").pop()?.split("/").pop()?.split(".").pop()?.trim() || trimmed;
|
|
1620
|
-
const normalized = stripped.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
1621
|
-
return TOOL_NAME_ALIASES[normalized] ?? stripped;
|
|
1622
|
-
}
|
|
1623
|
-
function getToolDisplayLabel(toolCall) {
|
|
1624
|
-
const normalized = formatToolName(toolCall.name);
|
|
1625
|
-
const args = safeParseJson(toolCall.arguments);
|
|
1626
|
-
const displayName = toolCall.display_name?.trim() ?? "";
|
|
1627
|
-
const baseLabel = displayName || TOOL_DISPLAY_LABELS[normalized] || normalized;
|
|
1628
|
-
const metaDisplayName = getStringArgValue(args, "_meta_display_name");
|
|
1629
|
-
if (metaDisplayName) {
|
|
1630
|
-
return metaDisplayName;
|
|
1631
|
-
}
|
|
1632
|
-
const description = getStringArgValue(args, "description");
|
|
1633
|
-
if (normalized === "BgBash") {
|
|
1634
|
-
return description ? `\u540E\u53F0\u6267\u884C\uFF1A${description}` : "\u540E\u53F0\u6267\u884C\u547D\u4EE4";
|
|
1635
|
-
}
|
|
1636
|
-
if (normalized === "ReadSkill") {
|
|
1637
|
-
const skillName = getStringArgValue(args, "skill") || getStringArgValue(args, "skill_name");
|
|
1638
|
-
return skillName ? `${baseLabel}\u300C${skillName}\u300D` : baseLabel;
|
|
1639
|
-
}
|
|
1640
|
-
if (normalized === "FinishTask") {
|
|
1641
|
-
const title = getStringArgValue(args, "title");
|
|
1642
|
-
return title ? `${baseLabel}\uFF1A${title}` : baseLabel;
|
|
1643
|
-
}
|
|
1644
|
-
return description || baseLabel;
|
|
1645
|
-
}
|
|
1646
|
-
function getToolTone(status) {
|
|
1647
|
-
if (status === "error" || status === "cancelled") return "red";
|
|
1648
|
-
if (status === "awaiting_answer") return "amber";
|
|
1649
|
-
if (status === "pending") return "blue";
|
|
1650
|
-
return "emerald";
|
|
1651
|
-
}
|
|
1652
|
-
function getToolStatusLabel(status) {
|
|
1653
|
-
if (status === "pending") return "\u8FD0\u884C\u4E2D";
|
|
1654
|
-
if (status === "awaiting_answer") return "\u7B49\u5F85\u56DE\u7B54";
|
|
1655
|
-
if (status === "error") return "\u9519\u8BEF";
|
|
1656
|
-
if (status === "cancelled") return "\u5DF2\u53D6\u6D88";
|
|
1657
|
-
return "\u5B8C\u6210";
|
|
1658
|
-
}
|
|
1659
|
-
function formatToolDuration(ms) {
|
|
1660
|
-
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
1661
|
-
const seconds = ms / 1e3;
|
|
1662
|
-
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
1663
|
-
const minutes = Math.floor(seconds / 60);
|
|
1664
|
-
const remainingSeconds = Math.round(seconds % 60);
|
|
1665
|
-
return remainingSeconds > 0 ? `${minutes}m${remainingSeconds}s` : `${minutes}m`;
|
|
1666
|
-
}
|
|
1667
|
-
function formatToolArgs(args) {
|
|
1668
|
-
try {
|
|
1669
|
-
return JSON.stringify(JSON.parse(args), null, 2);
|
|
1670
|
-
} catch {
|
|
1671
|
-
return args;
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
var RESULT_PREVIEW_LIMIT = 4e3;
|
|
1675
|
-
function formatToolResult(result) {
|
|
1676
|
-
const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
1677
|
-
if (text == null) return "";
|
|
1678
|
-
if (text.length <= RESULT_PREVIEW_LIMIT) return text;
|
|
1679
|
-
return `${text.slice(0, RESULT_PREVIEW_LIMIT)}
|
|
1680
|
-
\u2026\uFF08\u7ED3\u679C\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\uFF09`;
|
|
1681
|
-
}
|
|
2252
|
+
import {
|
|
2253
|
+
getFileParts,
|
|
2254
|
+
getImageParts,
|
|
2255
|
+
getTextContent,
|
|
2256
|
+
normalizeMessageContent
|
|
2257
|
+
} from "@blade-hq/agent-client";
|
|
2258
|
+
import { useEffect as useEffect10, useRef as useRef11, useState as useState12 } from "react";
|
|
1682
2259
|
|
|
1683
2260
|
// src/components/AgentLoopBlock.tsx
|
|
1684
|
-
import {
|
|
2261
|
+
import { useState as useState8 } from "react";
|
|
2262
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1685
2263
|
function parseAgentDescription(argumentsJson) {
|
|
1686
2264
|
try {
|
|
1687
2265
|
const parsed = JSON.parse(argumentsJson);
|
|
@@ -1691,83 +2269,81 @@ function parseAgentDescription(argumentsJson) {
|
|
|
1691
2269
|
}
|
|
1692
2270
|
}
|
|
1693
2271
|
function AgentLoopBlock({ toolCall }) {
|
|
1694
|
-
const [expanded, setExpanded] =
|
|
2272
|
+
const [expanded, setExpanded] = useState8(false);
|
|
1695
2273
|
const description = parseAgentDescription(toolCall.arguments);
|
|
1696
2274
|
const running = toolCall.status === "pending" || toolCall.status === "awaiting_answer";
|
|
1697
2275
|
const failed = toolCall.status === "error" || toolCall.status === "cancelled";
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
2276
|
+
const hasResult = toolCall.result != null;
|
|
2277
|
+
const iconClass = cn(
|
|
2278
|
+
"size-3.5 shrink-0",
|
|
2279
|
+
failed ? "text-red-500" : "text-[hsl(var(--muted-foreground))]"
|
|
2280
|
+
);
|
|
2281
|
+
return /* @__PURE__ */ jsxs6("div", { className: "blade-chat-agent-loop text-xs leading-[22px]", children: [
|
|
2282
|
+
/* @__PURE__ */ jsxs6(
|
|
2283
|
+
"button",
|
|
1701
2284
|
{
|
|
2285
|
+
type: "button",
|
|
2286
|
+
onClick: () => hasResult && setExpanded(!expanded),
|
|
2287
|
+
disabled: !hasResult,
|
|
2288
|
+
"aria-expanded": hasResult ? expanded : void 0,
|
|
2289
|
+
"data-testid": "execution-tool-intent",
|
|
1702
2290
|
className: cn(
|
|
1703
|
-
"
|
|
1704
|
-
|
|
2291
|
+
"flex min-w-0 items-center gap-1 py-1.5 text-left",
|
|
2292
|
+
hasResult && "cursor-pointer hover:text-[hsl(var(--foreground))]",
|
|
2293
|
+
failed ? "text-red-500" : "text-[hsl(var(--muted-foreground))]"
|
|
1705
2294
|
),
|
|
2295
|
+
title: `\u5B50\u4EFB\u52A1\uFF1A${description}`,
|
|
1706
2296
|
children: [
|
|
1707
|
-
/* @__PURE__ */
|
|
1708
|
-
|
|
2297
|
+
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" }),
|
|
2298
|
+
/* @__PURE__ */ jsxs6("span", { className: "min-w-0 truncate", children: [
|
|
2299
|
+
"\u5B50\u4EFB\u52A1\uFF1A",
|
|
2300
|
+
description
|
|
2301
|
+
] }),
|
|
2302
|
+
hasResult ? /* @__PURE__ */ jsx7(
|
|
2303
|
+
ChevronRight,
|
|
1709
2304
|
{
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
className:
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
{
|
|
1718
|
-
size: 11,
|
|
1719
|
-
className: cn(
|
|
1720
|
-
"shrink-0 text-[hsl(var(--muted-foreground))] transition-transform",
|
|
1721
|
-
expanded && "rotate-90"
|
|
1722
|
-
)
|
|
1723
|
-
}
|
|
1724
|
-
),
|
|
1725
|
-
/* @__PURE__ */ jsx6(Bot, { size: 12, className: "shrink-0 text-[hsl(var(--muted-foreground))]" }),
|
|
1726
|
-
/* @__PURE__ */ jsxs5(
|
|
1727
|
-
"span",
|
|
1728
|
-
{
|
|
1729
|
-
className: cn(
|
|
1730
|
-
"flex shrink-0 items-center gap-1 text-[10px]",
|
|
1731
|
-
failed ? "text-[hsl(var(--muted-foreground))]" : running ? "text-blue-300" : "text-[hsl(var(--primary))]"
|
|
1732
|
-
),
|
|
1733
|
-
children: [
|
|
1734
|
-
running ? /* @__PURE__ */ jsx6(LoaderCircle, { size: 11, className: "animate-spin" }) : failed ? /* @__PURE__ */ jsx6(X, { size: 11 }) : /* @__PURE__ */ jsx6(Check, { size: 11 }),
|
|
1735
|
-
/* @__PURE__ */ jsx6("span", { children: running ? "\u6267\u884C\u4E2D" : failed ? "\u5DF2\u7EC8\u6B62" : "\u5B8C\u6210" })
|
|
1736
|
-
]
|
|
1737
|
-
}
|
|
1738
|
-
),
|
|
1739
|
-
/* @__PURE__ */ jsxs5("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: [
|
|
1740
|
-
"\u5B50\u667A\u80FD\u4F53\uFF1A",
|
|
1741
|
-
description
|
|
1742
|
-
] })
|
|
1743
|
-
]
|
|
2305
|
+
size: 14,
|
|
2306
|
+
style: { transitionDuration: "260ms", transitionTimingFunction: "cubic-bezier(0.25, 0.1, 0.25, 1)" },
|
|
2307
|
+
className: cn(
|
|
2308
|
+
"shrink-0 transition-transform",
|
|
2309
|
+
expanded && "rotate-90"
|
|
2310
|
+
),
|
|
2311
|
+
"aria-hidden": "true"
|
|
1744
2312
|
}
|
|
1745
|
-
)
|
|
1746
|
-
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) })
|
|
2313
|
+
) : null
|
|
1747
2314
|
]
|
|
1748
2315
|
}
|
|
1749
2316
|
),
|
|
1750
|
-
expanded &&
|
|
1751
|
-
/* @__PURE__ */ jsx6("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u7ED3\u679C" }),
|
|
1752
|
-
/* @__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) })
|
|
1753
|
-
] })
|
|
2317
|
+
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
|
|
1754
2318
|
] });
|
|
1755
2319
|
}
|
|
1756
2320
|
|
|
1757
2321
|
// src/components/MarkdownContent.tsx
|
|
1758
2322
|
import {
|
|
1759
|
-
useEffect as
|
|
2323
|
+
useEffect as useEffect8,
|
|
1760
2324
|
useMemo as useMemo5,
|
|
1761
|
-
useRef as
|
|
1762
|
-
useState as
|
|
2325
|
+
useRef as useRef9,
|
|
2326
|
+
useState as useState9
|
|
1763
2327
|
} from "react";
|
|
1764
|
-
import { jsx as
|
|
2328
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1765
2329
|
var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
|
|
2330
|
+
function normalizeAdjacentUrlFormatting(value) {
|
|
2331
|
+
const protectedSegments = [];
|
|
2332
|
+
const protectedValue = value.replace(/(`{1,3}[\s\S]*?`{1,3}|\[[^\]]*\]\([^)]*\))/g, (segment) => {
|
|
2333
|
+
const index = protectedSegments.push(segment) - 1;
|
|
2334
|
+
return `blade-url-protected-${index}-marker`;
|
|
2335
|
+
});
|
|
2336
|
+
const normalized = protectedValue.replace(
|
|
2337
|
+
/(\*\*|__|~~|\*|_)(https?:\/\/[^\s<>]+?)\1(?=[\s。,、!?;:,.!?;:]|$)/g,
|
|
2338
|
+
(_, marker, url) => `${marker}[${url}](<${url}>)${marker}`
|
|
2339
|
+
);
|
|
2340
|
+
return normalized.replace(/blade-url-protected-(\d+)-marker/g, (_, index) => protectedSegments[Number(index)]);
|
|
2341
|
+
}
|
|
1766
2342
|
function CodeBlockPre({ children, node: _node, ...props }) {
|
|
1767
|
-
const preRef =
|
|
1768
|
-
const [copied, setCopied] =
|
|
1769
|
-
const [language, setLanguage] =
|
|
1770
|
-
|
|
2343
|
+
const preRef = useRef9(null);
|
|
2344
|
+
const [copied, setCopied] = useState9(false);
|
|
2345
|
+
const [language, setLanguage] = useState9("");
|
|
2346
|
+
useEffect8(() => {
|
|
1771
2347
|
const codeEl = preRef.current?.querySelector("code");
|
|
1772
2348
|
setLanguage(codeEl?.className.match(/language-(\S+)/)?.[1] ?? "");
|
|
1773
2349
|
}, []);
|
|
@@ -1778,10 +2354,10 @@ function CodeBlockPre({ children, node: _node, ...props }) {
|
|
|
1778
2354
|
setTimeout(() => setCopied(false), 2e3);
|
|
1779
2355
|
}
|
|
1780
2356
|
};
|
|
1781
|
-
return /* @__PURE__ */
|
|
1782
|
-
/* @__PURE__ */
|
|
1783
|
-
/* @__PURE__ */
|
|
1784
|
-
/* @__PURE__ */
|
|
2357
|
+
return /* @__PURE__ */ jsxs7("div", { className: "blade-chat-codeblock not-prose my-3 overflow-hidden rounded-xl border border-[hsl(var(--border))]", children: [
|
|
2358
|
+
/* @__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: [
|
|
2359
|
+
/* @__PURE__ */ jsx8("span", { className: "font-mono text-[12px] text-[hsl(var(--muted-foreground))]", children: language || "code" }),
|
|
2360
|
+
/* @__PURE__ */ jsxs7(
|
|
1785
2361
|
"button",
|
|
1786
2362
|
{
|
|
1787
2363
|
type: "button",
|
|
@@ -1791,13 +2367,13 @@ function CodeBlockPre({ children, node: _node, ...props }) {
|
|
|
1791
2367
|
copied ? "text-[hsl(var(--primary))]" : "text-[hsl(var(--muted-foreground))] hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--foreground))]"
|
|
1792
2368
|
),
|
|
1793
2369
|
children: [
|
|
1794
|
-
copied ? /* @__PURE__ */
|
|
1795
|
-
/* @__PURE__ */
|
|
2370
|
+
copied ? /* @__PURE__ */ jsx8(Check, { size: 12 }) : /* @__PURE__ */ jsx8(Copy, { size: 12 }),
|
|
2371
|
+
/* @__PURE__ */ jsx8("span", { children: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236" })
|
|
1796
2372
|
]
|
|
1797
2373
|
}
|
|
1798
2374
|
)
|
|
1799
2375
|
] }),
|
|
1800
|
-
/* @__PURE__ */
|
|
2376
|
+
/* @__PURE__ */ jsx8(
|
|
1801
2377
|
"pre",
|
|
1802
2378
|
{
|
|
1803
2379
|
ref: preRef,
|
|
@@ -1809,7 +2385,7 @@ function CodeBlockPre({ children, node: _node, ...props }) {
|
|
|
1809
2385
|
] });
|
|
1810
2386
|
}
|
|
1811
2387
|
function ExternalAnchor({ node: _node, children, ...props }) {
|
|
1812
|
-
return /* @__PURE__ */
|
|
2388
|
+
return /* @__PURE__ */ jsx8("a", { ...props, target: "_blank", rel: "noopener noreferrer", children });
|
|
1813
2389
|
}
|
|
1814
2390
|
var MARKDOWN_COMPONENTS = {
|
|
1815
2391
|
pre: CodeBlockPre,
|
|
@@ -1817,9 +2393,9 @@ var MARKDOWN_COMPONENTS = {
|
|
|
1817
2393
|
};
|
|
1818
2394
|
function MarkdownContent({ children, className, mode, sessionId }) {
|
|
1819
2395
|
const resolvedChildren = useMemo5(() => {
|
|
1820
|
-
return children.replace(SYSTEM_REMINDER_RE, "");
|
|
2396
|
+
return normalizeAdjacentUrlFormatting(children.replace(SYSTEM_REMINDER_RE, ""));
|
|
1821
2397
|
}, [children]);
|
|
1822
|
-
return /* @__PURE__ */
|
|
2398
|
+
return /* @__PURE__ */ jsx8(
|
|
1823
2399
|
_r,
|
|
1824
2400
|
{
|
|
1825
2401
|
className: cn("blade-chat-markdown break-words", className),
|
|
@@ -1832,17 +2408,46 @@ function MarkdownContent({ children, className, mode, sessionId }) {
|
|
|
1832
2408
|
}
|
|
1833
2409
|
|
|
1834
2410
|
// src/components/Shimmer.tsx
|
|
1835
|
-
import { jsx as
|
|
2411
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
1836
2412
|
function Shimmer({ children = "\u6B63\u5728\u601D\u8003...", className }) {
|
|
1837
|
-
return /* @__PURE__ */
|
|
2413
|
+
return /* @__PURE__ */ jsx9("span", { className: cn("blade-shimmer-text text-sm font-medium", className), children });
|
|
1838
2414
|
}
|
|
1839
2415
|
|
|
1840
2416
|
// src/components/ToolCallBlock.tsx
|
|
1841
|
-
import { useState as
|
|
2417
|
+
import { useState as useState11 } from "react";
|
|
1842
2418
|
|
|
1843
2419
|
// src/components/AskUserQuestionBlock.tsx
|
|
1844
|
-
import { useEffect as
|
|
1845
|
-
import { jsx as
|
|
2420
|
+
import { useEffect as useEffect9, useMemo as useMemo6, useRef as useRef10, useState as useState10 } from "react";
|
|
2421
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2422
|
+
var CUSTOM_TEXTAREA_MAX_HEIGHT = 160;
|
|
2423
|
+
function resizeCustomTextarea(textarea) {
|
|
2424
|
+
textarea.style.height = "auto";
|
|
2425
|
+
textarea.style.height = `${Math.min(textarea.scrollHeight, CUSTOM_TEXTAREA_MAX_HEIGHT)}px`;
|
|
2426
|
+
textarea.style.overflowY = textarea.scrollHeight > CUSTOM_TEXTAREA_MAX_HEIGHT ? "auto" : "hidden";
|
|
2427
|
+
}
|
|
2428
|
+
function useAutoResizeTextarea(value) {
|
|
2429
|
+
const textareaRef = useRef10(null);
|
|
2430
|
+
useEffect9(() => {
|
|
2431
|
+
const textarea = textareaRef.current;
|
|
2432
|
+
if (textarea?.value === value) resizeCustomTextarea(textarea);
|
|
2433
|
+
}, [value]);
|
|
2434
|
+
useEffect9(() => {
|
|
2435
|
+
const textarea = textareaRef.current;
|
|
2436
|
+
if (!textarea || typeof ResizeObserver === "undefined") return;
|
|
2437
|
+
let previousWidth = textarea.clientWidth;
|
|
2438
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
2439
|
+
if (!entry || entry.contentRect.width === previousWidth) return;
|
|
2440
|
+
previousWidth = entry.contentRect.width;
|
|
2441
|
+
resizeCustomTextarea(textarea);
|
|
2442
|
+
});
|
|
2443
|
+
observer.observe(textarea);
|
|
2444
|
+
return () => observer.disconnect();
|
|
2445
|
+
}, []);
|
|
2446
|
+
return textareaRef;
|
|
2447
|
+
}
|
|
2448
|
+
function indentAnswerContinuationLines(answer) {
|
|
2449
|
+
return answer.replaceAll("\n", "\n ");
|
|
2450
|
+
}
|
|
1846
2451
|
function AskUserQuestionBlock({
|
|
1847
2452
|
data,
|
|
1848
2453
|
answered,
|
|
@@ -1851,18 +2456,19 @@ function AskUserQuestionBlock({
|
|
|
1851
2456
|
answerData,
|
|
1852
2457
|
onAnswer
|
|
1853
2458
|
}) {
|
|
1854
|
-
const [selections, setSelections] =
|
|
1855
|
-
const [customTexts, setCustomTexts] =
|
|
1856
|
-
const [usingCustom, setUsingCustom] =
|
|
1857
|
-
const [
|
|
1858
|
-
|
|
2459
|
+
const [selections, setSelections] = useState10(/* @__PURE__ */ new Map());
|
|
2460
|
+
const [customTexts, setCustomTexts] = useState10(/* @__PURE__ */ new Map());
|
|
2461
|
+
const [usingCustom, setUsingCustom] = useState10(/* @__PURE__ */ new Set());
|
|
2462
|
+
const [note, setNote] = useState10("");
|
|
2463
|
+
const [submitted, setSubmitted] = useState10(false);
|
|
2464
|
+
useEffect9(() => {
|
|
1859
2465
|
if (sessionStatus === "failed" || sessionStatus === "interrupted") {
|
|
1860
2466
|
setSubmitted(false);
|
|
1861
2467
|
}
|
|
1862
2468
|
}, [sessionStatus]);
|
|
1863
2469
|
const displayAnswerState = useMemo6(() => {
|
|
1864
2470
|
if (!(answered && answerData)) {
|
|
1865
|
-
return { selections, customTexts, usingCustom };
|
|
2471
|
+
return { selections, customTexts, usingCustom, note };
|
|
1866
2472
|
}
|
|
1867
2473
|
const nextSelections = /* @__PURE__ */ new Map();
|
|
1868
2474
|
const nextCustomTexts = /* @__PURE__ */ new Map();
|
|
@@ -1878,9 +2484,10 @@ function AskUserQuestionBlock({
|
|
|
1878
2484
|
return {
|
|
1879
2485
|
selections: nextSelections,
|
|
1880
2486
|
customTexts: nextCustomTexts,
|
|
1881
|
-
usingCustom: nextUsingCustom
|
|
2487
|
+
usingCustom: nextUsingCustom,
|
|
2488
|
+
note: answerData.note ?? ""
|
|
1882
2489
|
};
|
|
1883
|
-
}, [answerData, answered, customTexts, selections, usingCustom]);
|
|
2490
|
+
}, [answerData, answered, customTexts, note, selections, usingCustom]);
|
|
1884
2491
|
const toggleOption = (qIdx, optIdx, multi) => {
|
|
1885
2492
|
if (answered || submitted) return;
|
|
1886
2493
|
setSelections((prev) => {
|
|
@@ -1928,6 +2535,7 @@ function AskUserQuestionBlock({
|
|
|
1928
2535
|
const allAnswered = data.questions.every((_, i) => getAnswer(i) !== null);
|
|
1929
2536
|
const handleSubmit = () => {
|
|
1930
2537
|
if (answered || submitted || !allAnswered || !onAnswer) return;
|
|
2538
|
+
const trimmedNote = note.trim();
|
|
1931
2539
|
const nextAnswerData = {
|
|
1932
2540
|
selections: Object.fromEntries(
|
|
1933
2541
|
Array.from(selections.entries()).map(([qIdx, optionIndexes]) => [
|
|
@@ -1937,15 +2545,21 @@ function AskUserQuestionBlock({
|
|
|
1937
2545
|
),
|
|
1938
2546
|
custom: Object.fromEntries(
|
|
1939
2547
|
Array.from(usingCustom).map((qIdx) => [qIdx, (customTexts.get(qIdx) ?? "").trim()]).filter(([, text2]) => text2.length > 0)
|
|
1940
|
-
)
|
|
2548
|
+
),
|
|
2549
|
+
...trimmedNote ? { note: trimmedNote } : {}
|
|
1941
2550
|
};
|
|
1942
|
-
const parts = data.questions.map(
|
|
1943
|
-
|
|
1944
|
-
|
|
2551
|
+
const parts = data.questions.map(
|
|
2552
|
+
(q, i) => `- ${q.question} -> ${indentAnswerContinuationLines(getAnswer(i) ?? "")}`
|
|
2553
|
+
);
|
|
2554
|
+
const text = [
|
|
2555
|
+
`\u5173\u4E8E\u9700\u8981\u786E\u8BA4\u7684\u95EE\u9898\uFF0C\u7528\u6237\u7684\u56DE\u7B54\u5982\u4E0B\uFF1A
|
|
2556
|
+
${parts.join("\n")}`,
|
|
2557
|
+
trimmedNote ? `\u8865\u5145\u8BF4\u660E\uFF1A${indentAnswerContinuationLines(trimmedNote)}` : ""
|
|
2558
|
+
].filter(Boolean).join("\n");
|
|
1945
2559
|
setSubmitted(true);
|
|
1946
2560
|
onAnswer(text, toolCallId, nextAnswerData);
|
|
1947
2561
|
};
|
|
1948
|
-
return /* @__PURE__ */
|
|
2562
|
+
return /* @__PURE__ */ jsxs8(
|
|
1949
2563
|
"div",
|
|
1950
2564
|
{
|
|
1951
2565
|
className: cn(
|
|
@@ -1953,12 +2567,12 @@ ${parts.join("\n")}`;
|
|
|
1953
2567
|
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"
|
|
1954
2568
|
),
|
|
1955
2569
|
children: [
|
|
1956
|
-
data.source_loop?.description && /* @__PURE__ */
|
|
2570
|
+
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: [
|
|
1957
2571
|
"\u5B50\u667A\u80FD\u4F53\u300C",
|
|
1958
2572
|
data.source_loop.description,
|
|
1959
2573
|
"\u300D\u5728\u7B49\u5F85\u4F60\u7684\u56DE\u7B54"
|
|
1960
2574
|
] }),
|
|
1961
|
-
data.questions.map((q, qIdx) => /* @__PURE__ */
|
|
2575
|
+
data.questions.map((q, qIdx) => /* @__PURE__ */ jsx10(
|
|
1962
2576
|
QuestionCard,
|
|
1963
2577
|
{
|
|
1964
2578
|
question: q,
|
|
@@ -1973,7 +2587,16 @@ ${parts.join("\n")}`;
|
|
|
1973
2587
|
},
|
|
1974
2588
|
q.question
|
|
1975
2589
|
)),
|
|
1976
|
-
|
|
2590
|
+
/* @__PURE__ */ jsx10(
|
|
2591
|
+
NoteField,
|
|
2592
|
+
{
|
|
2593
|
+
answered,
|
|
2594
|
+
submitted,
|
|
2595
|
+
note: displayAnswerState.note,
|
|
2596
|
+
onChange: setNote
|
|
2597
|
+
}
|
|
2598
|
+
),
|
|
2599
|
+
!answered && !submitted && onAnswer && /* @__PURE__ */ jsx10(
|
|
1977
2600
|
"button",
|
|
1978
2601
|
{
|
|
1979
2602
|
type: "button",
|
|
@@ -1983,14 +2606,14 @@ ${parts.join("\n")}`;
|
|
|
1983
2606
|
children: allAnswered ? "\u786E\u8BA4" : "\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u9009\u9879"
|
|
1984
2607
|
}
|
|
1985
2608
|
),
|
|
1986
|
-
submitted && !answered && /* @__PURE__ */
|
|
2609
|
+
submitted && !answered && /* @__PURE__ */ jsxs8(
|
|
1987
2610
|
"button",
|
|
1988
2611
|
{
|
|
1989
2612
|
type: "button",
|
|
1990
2613
|
disabled: true,
|
|
1991
2614
|
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",
|
|
1992
2615
|
children: [
|
|
1993
|
-
/* @__PURE__ */
|
|
2616
|
+
/* @__PURE__ */ jsx10(LoaderCircle, { size: 14, className: "animate-spin" }),
|
|
1994
2617
|
"\u786E\u8BA4\u4E2D"
|
|
1995
2618
|
]
|
|
1996
2619
|
}
|
|
@@ -2011,30 +2634,31 @@ function QuestionCard({
|
|
|
2011
2634
|
onCustomChange
|
|
2012
2635
|
}) {
|
|
2013
2636
|
const multi = question.multiSelect ?? false;
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2637
|
+
const customTextareaRef = useAutoResizeTextarea(customText);
|
|
2638
|
+
return /* @__PURE__ */ jsxs8("div", { children: [
|
|
2639
|
+
/* @__PURE__ */ jsxs8("div", { className: cn("flex items-start gap-2", answered ? "mb-2" : "mb-3"), children: [
|
|
2640
|
+
/* @__PURE__ */ jsx10(
|
|
2017
2641
|
MessageSquareMore,
|
|
2018
2642
|
{
|
|
2019
2643
|
size: answered ? 12 : 13,
|
|
2020
2644
|
className: "mt-0.5 shrink-0 text-[hsl(var(--primary))]"
|
|
2021
2645
|
}
|
|
2022
2646
|
),
|
|
2023
|
-
/* @__PURE__ */
|
|
2647
|
+
/* @__PURE__ */ jsx10(
|
|
2024
2648
|
"div",
|
|
2025
2649
|
{
|
|
2026
2650
|
className: cn(
|
|
2027
2651
|
"min-w-0 flex-1 font-medium text-[hsl(var(--foreground))]",
|
|
2028
2652
|
answered ? "text-xs" : "text-sm"
|
|
2029
2653
|
),
|
|
2030
|
-
children: /* @__PURE__ */
|
|
2654
|
+
children: /* @__PURE__ */ jsx10(MarkdownContent, { className: "blade-chat-prose", children: question.question })
|
|
2031
2655
|
}
|
|
2032
2656
|
)
|
|
2033
2657
|
] }),
|
|
2034
|
-
/* @__PURE__ */
|
|
2658
|
+
/* @__PURE__ */ jsxs8("div", { className: cn("flex flex-col pl-5", answered ? "gap-1" : "gap-1.5"), children: [
|
|
2035
2659
|
question.options.map((opt, optIdx) => {
|
|
2036
2660
|
const isSel = selected.has(optIdx);
|
|
2037
|
-
return /* @__PURE__ */
|
|
2661
|
+
return /* @__PURE__ */ jsxs8(
|
|
2038
2662
|
"button",
|
|
2039
2663
|
{
|
|
2040
2664
|
type: "button",
|
|
@@ -2048,14 +2672,14 @@ function QuestionCard({
|
|
|
2048
2672
|
answered && "cursor-default opacity-70"
|
|
2049
2673
|
),
|
|
2050
2674
|
children: [
|
|
2051
|
-
multi && /* @__PURE__ */
|
|
2675
|
+
multi && /* @__PURE__ */ jsx10(
|
|
2052
2676
|
"div",
|
|
2053
2677
|
{
|
|
2054
2678
|
className: cn(
|
|
2055
2679
|
"mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded border transition-colors",
|
|
2056
2680
|
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))]"
|
|
2057
2681
|
),
|
|
2058
|
-
children: isSel && /* @__PURE__ */
|
|
2682
|
+
children: isSel && /* @__PURE__ */ jsx10(
|
|
2059
2683
|
Check,
|
|
2060
2684
|
{
|
|
2061
2685
|
size: 9,
|
|
@@ -2064,9 +2688,9 @@ function QuestionCard({
|
|
|
2064
2688
|
)
|
|
2065
2689
|
}
|
|
2066
2690
|
),
|
|
2067
|
-
/* @__PURE__ */
|
|
2068
|
-
/* @__PURE__ */
|
|
2069
|
-
opt.description && /* @__PURE__ */
|
|
2691
|
+
/* @__PURE__ */ jsxs8("div", { className: "min-w-0", children: [
|
|
2692
|
+
/* @__PURE__ */ jsx10("div", { className: cn("font-medium", answered ? "text-xs" : "text-[13px]"), children: opt.label }),
|
|
2693
|
+
opt.description && /* @__PURE__ */ jsx10(
|
|
2070
2694
|
"div",
|
|
2071
2695
|
{
|
|
2072
2696
|
className: cn(
|
|
@@ -2083,29 +2707,30 @@ function QuestionCard({
|
|
|
2083
2707
|
opt.label
|
|
2084
2708
|
);
|
|
2085
2709
|
}),
|
|
2086
|
-
answered && !isCustom ? null : /* @__PURE__ */
|
|
2710
|
+
answered && !isCustom ? null : /* @__PURE__ */ jsxs8(
|
|
2087
2711
|
"div",
|
|
2088
2712
|
{
|
|
2089
2713
|
className: cn(
|
|
2090
|
-
"flex items-
|
|
2714
|
+
"flex items-start gap-2 rounded-lg border transition-all focus-within:ring-2 focus-within:ring-[hsl(var(--ring)/0.35)]",
|
|
2091
2715
|
answered ? "px-2.5 py-1.5" : "px-3 py-2.5",
|
|
2092
2716
|
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))]",
|
|
2093
2717
|
answered && "cursor-default opacity-70"
|
|
2094
2718
|
),
|
|
2095
2719
|
children: [
|
|
2096
|
-
/* @__PURE__ */
|
|
2097
|
-
/* @__PURE__ */
|
|
2098
|
-
"
|
|
2720
|
+
/* @__PURE__ */ jsx10("span", { className: "shrink-0 pt-1 text-xs text-[hsl(var(--muted-foreground))]", children: "\u5176\u4ED6\uFF1A" }),
|
|
2721
|
+
/* @__PURE__ */ jsx10(
|
|
2722
|
+
"textarea",
|
|
2099
2723
|
{
|
|
2100
|
-
|
|
2724
|
+
ref: customTextareaRef,
|
|
2725
|
+
rows: 2,
|
|
2101
2726
|
value: customText,
|
|
2102
|
-
|
|
2727
|
+
readOnly: answered,
|
|
2103
2728
|
onChange: (e) => onCustomChange(qIdx, e.target.value),
|
|
2104
2729
|
onFocus: () => onCustomFocus(qIdx),
|
|
2105
2730
|
"aria-label": "\u81EA\u5B9A\u4E49\u56DE\u7B54",
|
|
2106
2731
|
placeholder: "\u8F93\u5165\u4F60\u7684\u7B54\u6848...",
|
|
2107
2732
|
className: cn(
|
|
2108
|
-
"min-w-0 flex-1 bg-transparent text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground)/0.5)]",
|
|
2733
|
+
"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)]",
|
|
2109
2734
|
answered ? "text-xs" : "text-sm"
|
|
2110
2735
|
)
|
|
2111
2736
|
}
|
|
@@ -2116,6 +2741,49 @@ function QuestionCard({
|
|
|
2116
2741
|
] })
|
|
2117
2742
|
] });
|
|
2118
2743
|
}
|
|
2744
|
+
function NoteField({
|
|
2745
|
+
answered,
|
|
2746
|
+
submitted,
|
|
2747
|
+
note,
|
|
2748
|
+
onChange
|
|
2749
|
+
}) {
|
|
2750
|
+
const textareaRef = useAutoResizeTextarea(note);
|
|
2751
|
+
const readOnly = answered || submitted;
|
|
2752
|
+
if (answered && !note.trim()) return null;
|
|
2753
|
+
return /* @__PURE__ */ jsxs8(
|
|
2754
|
+
"label",
|
|
2755
|
+
{
|
|
2756
|
+
className: cn(
|
|
2757
|
+
"block rounded-lg border transition-all focus-within:ring-2 focus-within:ring-[hsl(var(--ring)/0.35)]",
|
|
2758
|
+
answered ? "px-2.5 py-1.5" : "px-3 py-2.5",
|
|
2759
|
+
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))]",
|
|
2760
|
+
readOnly && "cursor-default opacity-70"
|
|
2761
|
+
),
|
|
2762
|
+
children: [
|
|
2763
|
+
/* @__PURE__ */ jsx10("span", { className: "mb-1.5 block text-xs text-[hsl(var(--muted-foreground))]", children: "\u8865\u5145\u8BF4\u660E\uFF08\u53EF\u9009\uFF09" }),
|
|
2764
|
+
/* @__PURE__ */ jsx10(
|
|
2765
|
+
"textarea",
|
|
2766
|
+
{
|
|
2767
|
+
ref: textareaRef,
|
|
2768
|
+
rows: 2,
|
|
2769
|
+
value: note,
|
|
2770
|
+
readOnly,
|
|
2771
|
+
onChange: (event) => {
|
|
2772
|
+
if (readOnly) return;
|
|
2773
|
+
onChange(event.target.value);
|
|
2774
|
+
},
|
|
2775
|
+
"aria-label": "\u8865\u5145\u8BF4\u660E",
|
|
2776
|
+
placeholder: "\u9009\u5B8C\u8FD8\u53EF\u4EE5\u518D\u8BB2\u4E24\u53E5\uFF0C\u7A7A\u7740\u5C31\u5F53\u6CA1\u6709",
|
|
2777
|
+
className: cn(
|
|
2778
|
+
"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)]",
|
|
2779
|
+
answered ? "text-xs" : "text-sm"
|
|
2780
|
+
)
|
|
2781
|
+
}
|
|
2782
|
+
)
|
|
2783
|
+
]
|
|
2784
|
+
}
|
|
2785
|
+
);
|
|
2786
|
+
}
|
|
2119
2787
|
function parseAskUserQuestion(toolResult) {
|
|
2120
2788
|
if (!toolResult) return null;
|
|
2121
2789
|
try {
|
|
@@ -2138,6 +2806,26 @@ function parseAskUserQuestion(toolResult) {
|
|
|
2138
2806
|
}
|
|
2139
2807
|
return null;
|
|
2140
2808
|
}
|
|
2809
|
+
function parseAskUserQuestionError(toolResult) {
|
|
2810
|
+
if (!toolResult) return null;
|
|
2811
|
+
try {
|
|
2812
|
+
const parsed = JSON.parse(toolResult);
|
|
2813
|
+
let detail = null;
|
|
2814
|
+
if (typeof parsed.error === "string") detail = parsed.error;
|
|
2815
|
+
if (parsed.error && typeof parsed.error === "object") {
|
|
2816
|
+
const message = parsed.error.message;
|
|
2817
|
+
if (typeof message === "string") detail = message;
|
|
2818
|
+
}
|
|
2819
|
+
if (!detail && typeof parsed.message === "string") detail = parsed.message;
|
|
2820
|
+
if (!detail) return null;
|
|
2821
|
+
return {
|
|
2822
|
+
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",
|
|
2823
|
+
detail
|
|
2824
|
+
};
|
|
2825
|
+
} catch {
|
|
2826
|
+
return null;
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2141
2829
|
function normalizeQuestionItem(value) {
|
|
2142
2830
|
if (!value || typeof value !== "object") return null;
|
|
2143
2831
|
const item = value;
|
|
@@ -2162,13 +2850,14 @@ function normalizeOptionItem(value) {
|
|
|
2162
2850
|
}
|
|
2163
2851
|
|
|
2164
2852
|
// src/components/ToolCallBlock.tsx
|
|
2165
|
-
import { Fragment, jsx as
|
|
2853
|
+
import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2166
2854
|
function resolveAskQuestionState({
|
|
2167
2855
|
toolStatus,
|
|
2168
2856
|
hasAnswerData,
|
|
2169
|
-
fallbackAnswered
|
|
2857
|
+
fallbackAnswered,
|
|
2858
|
+
fallbackAwaiting
|
|
2170
2859
|
}) {
|
|
2171
|
-
const awaitingAnswer = !hasAnswerData && toolStatus === "awaiting_answer";
|
|
2860
|
+
const awaitingAnswer = !hasAnswerData && (toolStatus === "awaiting_answer" || toolStatus === "pending" && fallbackAwaiting === true);
|
|
2172
2861
|
return {
|
|
2173
2862
|
awaitingAnswer,
|
|
2174
2863
|
answered: hasAnswerData || !awaitingAnswer && (Boolean(fallbackAnswered) || toolStatus === "done" || toolStatus === "cancelled" || toolStatus === "error")
|
|
@@ -2180,14 +2869,15 @@ function ToolCallBlock({
|
|
|
2180
2869
|
answered,
|
|
2181
2870
|
answerData,
|
|
2182
2871
|
sessionStatus,
|
|
2872
|
+
isActiveQuestion,
|
|
2183
2873
|
renderer
|
|
2184
2874
|
}) {
|
|
2185
|
-
const [expanded, setExpanded] =
|
|
2875
|
+
const [expanded, setExpanded] = useState11(false);
|
|
2186
2876
|
const normalizedName = formatToolName(toolCall.name);
|
|
2187
2877
|
if (renderer) {
|
|
2188
2878
|
const custom = renderer(toolCall);
|
|
2189
2879
|
if (custom !== null && custom !== void 0) {
|
|
2190
|
-
return /* @__PURE__ */
|
|
2880
|
+
return /* @__PURE__ */ jsx11(Fragment2, { children: custom });
|
|
2191
2881
|
}
|
|
2192
2882
|
}
|
|
2193
2883
|
if (normalizedName === "AskUserQuestion") {
|
|
@@ -2195,11 +2885,12 @@ function ToolCallBlock({
|
|
|
2195
2885
|
const questionState = resolveAskQuestionState({
|
|
2196
2886
|
toolStatus: toolCall.status,
|
|
2197
2887
|
hasAnswerData: Boolean(answerData),
|
|
2198
|
-
fallbackAnswered: answered
|
|
2888
|
+
fallbackAnswered: answered,
|
|
2889
|
+
fallbackAwaiting: isActiveQuestion === true && (sessionStatus === "paused" || sessionStatus === "waiting_for_input")
|
|
2199
2890
|
});
|
|
2200
2891
|
const canAnswer = questionState.awaitingAnswer && Boolean(onAnswer);
|
|
2201
2892
|
if (askData) {
|
|
2202
|
-
return /* @__PURE__ */
|
|
2893
|
+
return /* @__PURE__ */ jsx11(
|
|
2203
2894
|
AskUserQuestionBlock,
|
|
2204
2895
|
{
|
|
2205
2896
|
data: askData,
|
|
@@ -2212,24 +2903,31 @@ function ToolCallBlock({
|
|
|
2212
2903
|
);
|
|
2213
2904
|
}
|
|
2214
2905
|
if (toolCall.status === "pending") {
|
|
2215
|
-
return /* @__PURE__ */
|
|
2216
|
-
/* @__PURE__ */
|
|
2217
|
-
/* @__PURE__ */
|
|
2906
|
+
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: [
|
|
2907
|
+
/* @__PURE__ */ jsx11(LoaderCircle, { size: 14, className: "animate-spin" }),
|
|
2908
|
+
/* @__PURE__ */ jsx11("span", { children: "\u6B63\u5728\u51C6\u5907\u95EE\u9898\u2026" })
|
|
2218
2909
|
] });
|
|
2219
2910
|
}
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2911
|
+
const errorDetail = parseAskUserQuestionError(
|
|
2912
|
+
typeof toolCall.result === "string" ? toolCall.result : null
|
|
2913
|
+
);
|
|
2914
|
+
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: [
|
|
2915
|
+
/* @__PURE__ */ jsx11("div", { className: "font-semibold", children: "\u9009\u62E9\u9898\u5185\u5BB9\u6682\u65F6\u65E0\u6CD5\u663E\u793A" }),
|
|
2916
|
+
/* @__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" }),
|
|
2917
|
+
errorDetail?.detail ? /* @__PURE__ */ jsxs9("details", { className: "mt-2 text-xs text-[hsl(var(--muted-foreground))]", children: [
|
|
2918
|
+
/* @__PURE__ */ jsx11("summary", { className: "cursor-pointer", children: "\u67E5\u770B\u5177\u4F53\u539F\u56E0" }),
|
|
2919
|
+
/* @__PURE__ */ jsx11("div", { className: "mt-1 break-words font-mono", children: errorDetail.detail })
|
|
2920
|
+
] }) : null
|
|
2223
2921
|
] });
|
|
2224
2922
|
}
|
|
2225
2923
|
const tone = getToolTone(toolCall.status);
|
|
2226
2924
|
const displayName = getToolDisplayLabel(toolCall);
|
|
2227
2925
|
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))]";
|
|
2228
|
-
const statusIcon = toolCall.status === "pending" ? /* @__PURE__ */
|
|
2926
|
+
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 });
|
|
2229
2927
|
const statusTextClass = tone === "red" ? "text-[hsl(var(--muted-foreground))]" : tone === "amber" ? "text-amber-300" : tone === "blue" ? "text-blue-300" : "text-[hsl(var(--primary))]";
|
|
2230
|
-
return /* @__PURE__ */
|
|
2231
|
-
/* @__PURE__ */
|
|
2232
|
-
/* @__PURE__ */
|
|
2928
|
+
return /* @__PURE__ */ jsxs9("div", { className: "blade-chat-tool ml-4 text-xs", children: [
|
|
2929
|
+
/* @__PURE__ */ jsxs9("div", { className: cn("border-l-[3px] flex items-center gap-2 px-3 py-2", toneClass), children: [
|
|
2930
|
+
/* @__PURE__ */ jsxs9(
|
|
2233
2931
|
"button",
|
|
2234
2932
|
{
|
|
2235
2933
|
type: "button",
|
|
@@ -2237,7 +2935,7 @@ function ToolCallBlock({
|
|
|
2237
2935
|
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",
|
|
2238
2936
|
"aria-expanded": expanded,
|
|
2239
2937
|
children: [
|
|
2240
|
-
/* @__PURE__ */
|
|
2938
|
+
/* @__PURE__ */ jsx11(
|
|
2241
2939
|
ChevronRight,
|
|
2242
2940
|
{
|
|
2243
2941
|
size: 11,
|
|
@@ -2247,24 +2945,24 @@ function ToolCallBlock({
|
|
|
2247
2945
|
)
|
|
2248
2946
|
}
|
|
2249
2947
|
),
|
|
2250
|
-
/* @__PURE__ */
|
|
2948
|
+
/* @__PURE__ */ jsxs9("span", { className: cn("flex shrink-0 items-center gap-1 text-[10px]", statusTextClass), children: [
|
|
2251
2949
|
statusIcon,
|
|
2252
|
-
/* @__PURE__ */
|
|
2950
|
+
/* @__PURE__ */ jsx11("span", { children: getToolStatusLabel(toolCall.status) })
|
|
2253
2951
|
] }),
|
|
2254
|
-
/* @__PURE__ */
|
|
2952
|
+
/* @__PURE__ */ jsx11("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: displayName })
|
|
2255
2953
|
]
|
|
2256
2954
|
}
|
|
2257
2955
|
),
|
|
2258
|
-
typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 && /* @__PURE__ */
|
|
2956
|
+
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) })
|
|
2259
2957
|
] }),
|
|
2260
|
-
expanded && /* @__PURE__ */
|
|
2261
|
-
/* @__PURE__ */
|
|
2262
|
-
/* @__PURE__ */
|
|
2263
|
-
/* @__PURE__ */
|
|
2264
|
-
/* @__PURE__ */
|
|
2265
|
-
toolCall.result != null && /* @__PURE__ */
|
|
2266
|
-
/* @__PURE__ */
|
|
2267
|
-
/* @__PURE__ */
|
|
2958
|
+
expanded && /* @__PURE__ */ jsxs9("div", { className: "blade-chat-tool-detail ml-4 mt-1 rounded-xl bg-[hsl(var(--card))] px-3 py-3", children: [
|
|
2959
|
+
/* @__PURE__ */ jsx11("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u5DE5\u5177" }),
|
|
2960
|
+
/* @__PURE__ */ jsx11("div", { className: "mb-3 font-mono text-[11px] text-[hsl(var(--foreground))]", children: normalizedName }),
|
|
2961
|
+
/* @__PURE__ */ jsx11("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u53C2\u6570" }),
|
|
2962
|
+
/* @__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) }),
|
|
2963
|
+
toolCall.result != null && /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
2964
|
+
/* @__PURE__ */ jsx11("div", { className: "mb-1 mt-3 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u7ED3\u679C" }),
|
|
2965
|
+
/* @__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) })
|
|
2268
2966
|
] })
|
|
2269
2967
|
] })
|
|
2270
2968
|
] });
|
|
@@ -2282,110 +2980,555 @@ function buildAskUserPayload(argumentsJson) {
|
|
|
2282
2980
|
}
|
|
2283
2981
|
|
|
2284
2982
|
// src/components/AssistantTurnBlock.tsx
|
|
2285
|
-
import { jsx as
|
|
2983
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2286
2984
|
function ThinkingBlock({ reasoning, isStreaming }) {
|
|
2287
|
-
const [open, setOpen] =
|
|
2288
|
-
|
|
2289
|
-
|
|
2985
|
+
const [open, setOpen] = useState12(false);
|
|
2986
|
+
if (!isStreaming) return null;
|
|
2987
|
+
return /* @__PURE__ */ jsxs10("div", { className: "blade-chat-thinking text-xs", children: [
|
|
2988
|
+
/* @__PURE__ */ jsxs10(
|
|
2290
2989
|
"button",
|
|
2291
2990
|
{
|
|
2292
2991
|
type: "button",
|
|
2293
2992
|
onClick: () => setOpen(!open),
|
|
2294
2993
|
"aria-expanded": open,
|
|
2295
|
-
className: "inline-flex items-center gap-1
|
|
2994
|
+
className: "group/thinking inline-flex items-center gap-1 text-[hsl(var(--muted-foreground))] transition-colors hover:text-[hsl(var(--foreground))]",
|
|
2296
2995
|
children: [
|
|
2297
|
-
/* @__PURE__ */
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
"\xB7 ",
|
|
2301
|
-
new Intl.NumberFormat("zh-CN").format(reasoning.length),
|
|
2302
|
-
" \u5B57"
|
|
2303
|
-
] }),
|
|
2304
|
-
/* @__PURE__ */ jsx11(
|
|
2305
|
-
ChevronDown,
|
|
2996
|
+
/* @__PURE__ */ jsx12(Shimmer, { className: "text-xs", children: "\u6B63\u5728\u601D\u8003" }),
|
|
2997
|
+
/* @__PURE__ */ jsx12(
|
|
2998
|
+
ChevronRight,
|
|
2306
2999
|
{
|
|
2307
|
-
size:
|
|
2308
|
-
className: cn(
|
|
3000
|
+
size: 14,
|
|
3001
|
+
className: cn(
|
|
3002
|
+
"shrink-0 opacity-0 transition-[opacity,transform] group-hover/thinking:opacity-100",
|
|
3003
|
+
open && "rotate-90 opacity-100"
|
|
3004
|
+
)
|
|
2309
3005
|
}
|
|
2310
3006
|
)
|
|
2311
3007
|
]
|
|
2312
3008
|
}
|
|
2313
3009
|
),
|
|
2314
|
-
open
|
|
3010
|
+
open ? /* @__PURE__ */ jsx12("div", { className: "mt-1.5 whitespace-pre-wrap text-xs leading-[22px] text-[hsl(var(--muted-foreground))]", children: reasoning }) : null
|
|
2315
3011
|
] });
|
|
2316
3012
|
}
|
|
2317
3013
|
function getMessageText(message) {
|
|
2318
3014
|
return getTextContent(normalizeMessageContent(message.content)).trim();
|
|
2319
3015
|
}
|
|
2320
|
-
function
|
|
2321
|
-
|
|
2322
|
-
|
|
3016
|
+
function hasRenderableMessageContent(message) {
|
|
3017
|
+
return Boolean(getMessageText(message)) || getImageParts(message.content).length > 0 || getFileParts(message.content).length > 0;
|
|
3018
|
+
}
|
|
3019
|
+
function getLastContentMessage(messages) {
|
|
3020
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
3021
|
+
if (hasRenderableMessageContent(messages[index])) return messages[index];
|
|
3022
|
+
}
|
|
3023
|
+
return null;
|
|
3024
|
+
}
|
|
3025
|
+
function getOrderedMessageParts(message, toolCalls) {
|
|
3026
|
+
const blocks = message.blocks ?? [];
|
|
3027
|
+
if (!blocks.some((block) => block.type === "text") || !blocks.some((block) => block.type === "tool_use")) return [];
|
|
3028
|
+
const toolsById = new Map(toolCalls.map((toolCall) => [toolCall.id, toolCall]));
|
|
3029
|
+
const seenToolIds = /* @__PURE__ */ new Set();
|
|
3030
|
+
const parts = [];
|
|
3031
|
+
for (const [index, block] of blocks.entries()) {
|
|
3032
|
+
if (block.type === "text" && block.content != null && block.content !== "") {
|
|
3033
|
+
const content = Array.isArray(block.content) ? block.content : String(block.content);
|
|
3034
|
+
parts.push({ type: "text", key: `text-${index}`, content });
|
|
3035
|
+
}
|
|
3036
|
+
if (block.type !== "tool_use" || !block.tool_call_id) continue;
|
|
3037
|
+
const toolCall = toolsById.get(block.tool_call_id);
|
|
3038
|
+
if (!toolCall) continue;
|
|
3039
|
+
seenToolIds.add(toolCall.id);
|
|
3040
|
+
const previous = parts[parts.length - 1];
|
|
3041
|
+
if (previous?.type === "tools") previous.toolCalls.push(toolCall);
|
|
3042
|
+
else parts.push({ type: "tools", key: `tools-${index}`, toolCalls: [toolCall] });
|
|
3043
|
+
}
|
|
3044
|
+
const missingTools = toolCalls.filter((toolCall) => !seenToolIds.has(toolCall.id));
|
|
3045
|
+
if (seenToolIds.size === 0) return [];
|
|
3046
|
+
if (missingTools.length > 0) {
|
|
3047
|
+
parts.push({ type: "tools", key: "tools-missing", toolCalls: missingTools });
|
|
3048
|
+
}
|
|
3049
|
+
return parts;
|
|
3050
|
+
}
|
|
3051
|
+
function findLatestReasoningMessageIndex(messages) {
|
|
3052
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
3053
|
+
if (messages[index].reasoning) return index;
|
|
3054
|
+
}
|
|
3055
|
+
return -1;
|
|
3056
|
+
}
|
|
3057
|
+
function resolveTurnDisplayMode({
|
|
3058
|
+
isStreaming: _isStreaming,
|
|
3059
|
+
displayMode
|
|
3060
|
+
}) {
|
|
3061
|
+
return displayMode;
|
|
3062
|
+
}
|
|
3063
|
+
function formatExecutionDuration(durationMs) {
|
|
3064
|
+
const totalSeconds = Math.max(0, Math.round(durationMs / 1e3));
|
|
3065
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
3066
|
+
const seconds = totalSeconds % 60;
|
|
3067
|
+
return minutes > 0 ? `${minutes}\u5206${seconds}\u79D2` : `${seconds}\u79D2`;
|
|
3068
|
+
}
|
|
3069
|
+
function getExecutionDurationMs({
|
|
3070
|
+
messages,
|
|
3071
|
+
isStreaming,
|
|
3072
|
+
now = Date.now()
|
|
3073
|
+
}) {
|
|
3074
|
+
const knownDuration = messages.reduce(
|
|
3075
|
+
(total, message) => {
|
|
3076
|
+
if (typeof message.duration_ms === "number" && message.duration_ms > 0) {
|
|
3077
|
+
return total + message.duration_ms;
|
|
3078
|
+
}
|
|
3079
|
+
return total + (message.tool_calls ?? []).reduce(
|
|
3080
|
+
(toolTotal, toolCall) => toolTotal + (typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 ? toolCall.duration_ms : 0),
|
|
3081
|
+
0
|
|
3082
|
+
);
|
|
3083
|
+
},
|
|
3084
|
+
0
|
|
3085
|
+
);
|
|
3086
|
+
if (!isStreaming) return knownDuration;
|
|
3087
|
+
const startedAt = messages.map((message) => message.timestamp ? Date.parse(message.timestamp) : Number.NaN).filter((value) => Number.isFinite(value)).sort((a, b) => a - b)[0];
|
|
3088
|
+
if (startedAt === void 0) return knownDuration;
|
|
3089
|
+
return Math.max(knownDuration, now - startedAt);
|
|
3090
|
+
}
|
|
3091
|
+
function findLastExceptionalEvent(messages) {
|
|
3092
|
+
for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex -= 1) {
|
|
3093
|
+
const messageStatus = messages[messageIndex].status;
|
|
3094
|
+
if (messageStatus === "failed") return { messageIndex, status: "error" };
|
|
3095
|
+
if (messageStatus === "interrupted") return { messageIndex, status: "cancelled" };
|
|
3096
|
+
const toolCalls = messages[messageIndex].tool_calls ?? [];
|
|
3097
|
+
for (let toolIndex = toolCalls.length - 1; toolIndex >= 0; toolIndex -= 1) {
|
|
3098
|
+
const status = toolCalls[toolIndex].status;
|
|
3099
|
+
if (status === "error" || status === "cancelled") {
|
|
3100
|
+
return { messageIndex, status };
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
return null;
|
|
3105
|
+
}
|
|
3106
|
+
function executionSummaryLabel({
|
|
3107
|
+
messages,
|
|
3108
|
+
isStreaming,
|
|
3109
|
+
durationMs,
|
|
3110
|
+
sessionStatus,
|
|
3111
|
+
askAnswers
|
|
3112
|
+
}) {
|
|
3113
|
+
if (isStreaming) {
|
|
3114
|
+
return durationMs > 0 ? `\u6B63\u5728\u6267\u884C ${formatExecutionDuration(durationMs)}` : "\u6B63\u5728\u6267\u884C";
|
|
3115
|
+
}
|
|
3116
|
+
if (sessionStatus === "waiting_for_input" && messages.some(
|
|
3117
|
+
(message) => (message.tool_calls ?? []).some(
|
|
3118
|
+
(toolCall) => formatToolName(toolCall.name) === "AskUserQuestion" && toolCall.status === "awaiting_answer" && !askAnswers?.[toolCall.id]
|
|
3119
|
+
)
|
|
3120
|
+
)) {
|
|
3121
|
+
return "\u7B49\u5F85\u8F93\u5165";
|
|
3122
|
+
}
|
|
3123
|
+
const completedLabel = durationMs > 0 ? `\u6267\u884C\u5B8C\u6210 ${formatExecutionDuration(durationMs)}` : "\u6267\u884C\u5B8C\u6210";
|
|
3124
|
+
const lastExceptionalEvent = findLastExceptionalEvent(messages);
|
|
3125
|
+
if (lastExceptionalEvent) {
|
|
3126
|
+
const recovered = messages.slice(lastExceptionalEvent.messageIndex + 1).some(hasRenderableMessageContent);
|
|
3127
|
+
if (lastExceptionalEvent.status === "error") {
|
|
3128
|
+
return recovered ? `${completedLabel} \xB7 \u90E8\u5206\u6B65\u9AA4\u5931\u8D25` : "\u6267\u884C\u5931\u8D25";
|
|
3129
|
+
}
|
|
3130
|
+
return recovered ? `${completedLabel} \xB7 \u90E8\u5206\u6B65\u9AA4\u672A\u5B8C\u6210` : "\u6267\u884C\u5DF2\u4E2D\u65AD";
|
|
3131
|
+
}
|
|
3132
|
+
return completedLabel;
|
|
3133
|
+
}
|
|
3134
|
+
function businessToolDisplayName(toolCall) {
|
|
3135
|
+
const displayName = toolCall.display_name?.trim() ?? "";
|
|
3136
|
+
if (!displayName) return "";
|
|
3137
|
+
const rawName = toolCall.name.trim();
|
|
3138
|
+
return displayName !== rawName && formatToolName(displayName) !== formatToolName(rawName) ? displayName : "";
|
|
3139
|
+
}
|
|
3140
|
+
function executionToolTypeLabel(toolCall) {
|
|
3141
|
+
switch (formatToolName(toolCall.name)) {
|
|
3142
|
+
case "WebSearch":
|
|
3143
|
+
case "WebFetch":
|
|
3144
|
+
return "\u7F51\u7EDC\u68C0\u7D22";
|
|
3145
|
+
case "Bash":
|
|
3146
|
+
case "BgBash":
|
|
3147
|
+
return "\u547D\u4EE4\u6267\u884C";
|
|
3148
|
+
case "Read":
|
|
3149
|
+
case "ReadSkill":
|
|
3150
|
+
return "\u5185\u5BB9\u8BFB\u53D6";
|
|
3151
|
+
case "Write":
|
|
3152
|
+
case "Edit":
|
|
3153
|
+
case "MultiEdit":
|
|
3154
|
+
return "\u6587\u4EF6\u5904\u7406";
|
|
3155
|
+
case "Grep":
|
|
3156
|
+
case "Glob":
|
|
3157
|
+
return "\u5185\u5BB9\u641C\u7D22";
|
|
3158
|
+
case "Agent":
|
|
3159
|
+
return "\u5B50\u4EFB\u52A1";
|
|
3160
|
+
case "search_skills":
|
|
3161
|
+
return "\u6280\u80FD\u68C0\u7D22";
|
|
3162
|
+
case "get_skill_content":
|
|
3163
|
+
return "\u8BFB\u53D6\u6280\u80FD";
|
|
3164
|
+
case "run_skill_tool":
|
|
3165
|
+
return "\u6267\u884C\u6280\u80FD";
|
|
3166
|
+
default:
|
|
3167
|
+
return businessToolDisplayName(toolCall) || "\u6267\u884C\u6B65\u9AA4";
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
function executionToolIntent(toolCall) {
|
|
3171
|
+
const normalizedName = formatToolName(toolCall.name);
|
|
3172
|
+
let args = null;
|
|
3173
|
+
try {
|
|
3174
|
+
const parsed = JSON.parse(toolCall.arguments);
|
|
3175
|
+
args = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
3176
|
+
} catch {
|
|
3177
|
+
args = null;
|
|
3178
|
+
}
|
|
3179
|
+
const getString = (key) => {
|
|
3180
|
+
const value = args?.[key];
|
|
3181
|
+
return typeof value === "string" ? value.trim() : "";
|
|
3182
|
+
};
|
|
3183
|
+
const explicitIntent = getString("description") || getString("_meta_display_name") || getString("display_name") || "";
|
|
3184
|
+
if (explicitIntent) return explicitIntent;
|
|
3185
|
+
if (normalizedName === "search_skills") return getString("query");
|
|
3186
|
+
if (normalizedName === "get_skill_content" || normalizedName === "ReadSkill") {
|
|
3187
|
+
return getString("skill_name") || getString("skill");
|
|
3188
|
+
}
|
|
3189
|
+
if (normalizedName === "FinishTask") return getString("title");
|
|
3190
|
+
return "";
|
|
3191
|
+
}
|
|
3192
|
+
function ExecutionToolRow({ toolCall }) {
|
|
3193
|
+
const normalizedName = formatToolName(toolCall.name);
|
|
3194
|
+
const typeLabel = executionToolTypeLabel(toolCall);
|
|
3195
|
+
const intent = executionToolIntent(toolCall);
|
|
3196
|
+
const label = intent ? `${typeLabel}\uFF1A${intent}` : typeLabel;
|
|
3197
|
+
const failed = toolCall.status === "error" || toolCall.status === "cancelled";
|
|
3198
|
+
const iconClass = cn(
|
|
3199
|
+
"size-3.5 shrink-0",
|
|
3200
|
+
failed ? "text-red-500" : "text-[hsl(var(--muted-foreground))]"
|
|
3201
|
+
);
|
|
3202
|
+
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" });
|
|
3203
|
+
const rowClassName = cn(
|
|
3204
|
+
"flex min-w-0 items-center gap-1 py-1.5 text-xs leading-[22px]",
|
|
3205
|
+
failed ? "text-red-500" : "text-[hsl(var(--muted-foreground))]"
|
|
3206
|
+
);
|
|
3207
|
+
return /* @__PURE__ */ jsxs10("div", { "data-testid": "execution-tool-intent", className: rowClassName, title: label, children: [
|
|
3208
|
+
icon,
|
|
3209
|
+
/* @__PURE__ */ jsx12("span", { className: "min-w-0 truncate", children: label })
|
|
3210
|
+
] });
|
|
3211
|
+
}
|
|
3212
|
+
function AssistantTurnBlock({
|
|
3213
|
+
messages,
|
|
3214
|
+
isStreaming = false,
|
|
3215
|
+
askAnswers,
|
|
3216
|
+
onAnswer,
|
|
3217
|
+
sessionStatus,
|
|
3218
|
+
toolCallRenderer,
|
|
3219
|
+
hidePlanUpdateTools = false,
|
|
3220
|
+
sessionId
|
|
3221
|
+
}) {
|
|
3222
|
+
const shouldHideToolCall = (message, toolCall) => {
|
|
3223
|
+
if (!hidePlanUpdateTools || !isPlanUpdateTool(toolCall) || parsePlanUpdate(toolCall.arguments) === null) {
|
|
3224
|
+
return false;
|
|
3225
|
+
}
|
|
3226
|
+
return toolCall.status === "done" || toolCall.status === "pending" && message.status === "streaming";
|
|
3227
|
+
};
|
|
3228
|
+
const hasInterrupted = messages.some((message) => message.status === "interrupted");
|
|
3229
|
+
const hasFailedWithoutContent = messages.some(
|
|
3230
|
+
(message) => message.status === "failed" && !hasRenderableMessageContent(message)
|
|
3231
|
+
);
|
|
3232
|
+
const finalMessage = getLastContentMessage(messages);
|
|
3233
|
+
const turnToolCalls = messages.flatMap((message) => message.tool_calls ?? []);
|
|
3234
|
+
const finalOrderedParts = finalMessage ? getOrderedMessageParts(
|
|
3235
|
+
finalMessage,
|
|
3236
|
+
(finalMessage.tool_calls ?? []).filter(
|
|
3237
|
+
(toolCall) => formatToolName(toolCall.name) !== "AskUserQuestion" && !shouldHideToolCall(finalMessage, toolCall)
|
|
3238
|
+
)
|
|
3239
|
+
) : [];
|
|
3240
|
+
const hasExecutionProcess = messages.some(
|
|
3241
|
+
(message) => message.reasoning || (message.tool_calls ?? []).some((toolCall) => !shouldHideToolCall(message, toolCall))
|
|
3242
|
+
);
|
|
3243
|
+
const latestReasoningIndex = isStreaming ? findLatestReasoningMessageIndex(messages) : -1;
|
|
3244
|
+
const hasActionableToolCall = messages.some(
|
|
3245
|
+
(message) => message.status === "failed" || message.status === "interrupted" || (message.tool_calls ?? []).some(
|
|
3246
|
+
(toolCall) => !shouldHideToolCall(message, toolCall) && (toolCall.status === "error" || toolCall.status === "cancelled")
|
|
3247
|
+
)
|
|
3248
|
+
);
|
|
3249
|
+
const questionToolCalls = messages.flatMap(
|
|
3250
|
+
(message) => (message.tool_calls ?? []).filter(
|
|
3251
|
+
(toolCall) => formatToolName(toolCall.name) === "AskUserQuestion"
|
|
3252
|
+
)
|
|
3253
|
+
);
|
|
3254
|
+
const activeQuestionId = questionToolCalls.filter((toolCall) => toolCall.status === "pending").at(-1)?.id;
|
|
3255
|
+
const [displayMode, setDisplayMode] = useState12(
|
|
3256
|
+
() => isStreaming || hasActionableToolCall ? "detail" : "compact"
|
|
3257
|
+
);
|
|
3258
|
+
const userSelectedDisplayModeRef = useRef11(false);
|
|
3259
|
+
const wasStreamingRef = useRef11(isStreaming);
|
|
3260
|
+
useEffect10(() => {
|
|
3261
|
+
if (wasStreamingRef.current && !isStreaming && !userSelectedDisplayModeRef.current) {
|
|
3262
|
+
setDisplayMode(hasActionableToolCall ? "detail" : "compact");
|
|
3263
|
+
}
|
|
3264
|
+
wasStreamingRef.current = isStreaming;
|
|
3265
|
+
}, [hasActionableToolCall, isStreaming]);
|
|
3266
|
+
const effectiveMode = resolveTurnDisplayMode({ isStreaming, displayMode });
|
|
3267
|
+
const executionDurationMs = getExecutionDurationMs({ messages, isStreaming });
|
|
3268
|
+
const [clock, setClock] = useState12(() => Date.now());
|
|
3269
|
+
const hasLiveStartTime = messages.some(
|
|
3270
|
+
(message) => message.timestamp != null && Number.isFinite(Date.parse(message.timestamp))
|
|
3271
|
+
);
|
|
3272
|
+
useEffect10(() => {
|
|
3273
|
+
if (!isStreaming || !hasLiveStartTime) return;
|
|
3274
|
+
const timer = window.setInterval(() => setClock(Date.now()), 1e3);
|
|
3275
|
+
return () => window.clearInterval(timer);
|
|
3276
|
+
}, [hasLiveStartTime, isStreaming]);
|
|
3277
|
+
const liveExecutionDurationMs = isStreaming ? getExecutionDurationMs({ messages, isStreaming, now: clock }) : executionDurationMs;
|
|
3278
|
+
const memoryRefs = collectMemoryRefs(messages);
|
|
3279
|
+
if (!hasExecutionProcess) {
|
|
3280
|
+
return /* @__PURE__ */ jsxs10(
|
|
3281
|
+
"div",
|
|
3282
|
+
{
|
|
3283
|
+
"aria-busy": isStreaming || void 0,
|
|
3284
|
+
className: "blade-chat-assistant-turn flex flex-col gap-3",
|
|
3285
|
+
children: [
|
|
3286
|
+
memoryRefs.length > 0 ? /* @__PURE__ */ jsx12(MemoryRefsHint, { refs: memoryRefs }) : null,
|
|
3287
|
+
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" }),
|
|
3288
|
+
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" }),
|
|
3289
|
+
messages.map((message, index) => {
|
|
3290
|
+
return hasRenderableMessageContent(message) ? /* @__PURE__ */ jsx12(
|
|
3291
|
+
"div",
|
|
3292
|
+
{
|
|
3293
|
+
className: "flex flex-col gap-3",
|
|
3294
|
+
children: /* @__PURE__ */ jsx12(
|
|
3295
|
+
AssistantMessageContent,
|
|
3296
|
+
{
|
|
3297
|
+
message,
|
|
3298
|
+
sessionId,
|
|
3299
|
+
streaming: isStreaming && index === messages.length - 1
|
|
3300
|
+
}
|
|
3301
|
+
)
|
|
3302
|
+
},
|
|
3303
|
+
message.entry_id ?? `${message.timestamp ?? "assistant"}-${index}`
|
|
3304
|
+
) : null;
|
|
3305
|
+
})
|
|
3306
|
+
]
|
|
3307
|
+
}
|
|
3308
|
+
);
|
|
3309
|
+
}
|
|
3310
|
+
return /* @__PURE__ */ jsxs10(
|
|
3311
|
+
"div",
|
|
3312
|
+
{
|
|
3313
|
+
"aria-busy": isStreaming || void 0,
|
|
3314
|
+
className: "blade-chat-assistant-turn flex flex-col gap-3",
|
|
3315
|
+
children: [
|
|
3316
|
+
memoryRefs.length > 0 ? /* @__PURE__ */ jsx12(MemoryRefsHint, { refs: memoryRefs }) : null,
|
|
3317
|
+
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" }),
|
|
3318
|
+
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" }),
|
|
3319
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex w-full items-start gap-2.5", children: [
|
|
3320
|
+
/* @__PURE__ */ jsx12(
|
|
3321
|
+
"span",
|
|
3322
|
+
{
|
|
3323
|
+
className: "grid size-[30px] shrink-0 place-items-center rounded-full bg-[hsl(var(--muted)/0.55)] text-[hsl(var(--foreground))]",
|
|
3324
|
+
"aria-hidden": "true",
|
|
3325
|
+
children: /* @__PURE__ */ jsx12(Bot, { size: 16 })
|
|
3326
|
+
}
|
|
3327
|
+
),
|
|
3328
|
+
/* @__PURE__ */ jsxs10("div", { className: "min-w-0 flex-1 pt-0.5", children: [
|
|
3329
|
+
/* @__PURE__ */ jsxs10(
|
|
3330
|
+
"button",
|
|
3331
|
+
{
|
|
3332
|
+
type: "button",
|
|
3333
|
+
onClick: () => {
|
|
3334
|
+
userSelectedDisplayModeRef.current = true;
|
|
3335
|
+
setDisplayMode(displayMode === "detail" ? "compact" : "detail");
|
|
3336
|
+
},
|
|
3337
|
+
"aria-expanded": effectiveMode === "detail",
|
|
3338
|
+
"aria-label": effectiveMode === "detail" ? "\u6536\u8D77\u6267\u884C\u8FC7\u7A0B" : "\u5C55\u5F00\u6267\u884C\u8FC7\u7A0B",
|
|
3339
|
+
"data-testid": "assistant-execution-summary",
|
|
3340
|
+
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",
|
|
3341
|
+
children: [
|
|
3342
|
+
/* @__PURE__ */ jsx12("span", { className: "min-w-0 truncate", children: executionSummaryLabel({
|
|
3343
|
+
messages,
|
|
3344
|
+
isStreaming,
|
|
3345
|
+
durationMs: liveExecutionDurationMs,
|
|
3346
|
+
sessionStatus,
|
|
3347
|
+
askAnswers
|
|
3348
|
+
}) }),
|
|
3349
|
+
/* @__PURE__ */ jsx12(
|
|
3350
|
+
ChevronRight,
|
|
3351
|
+
{
|
|
3352
|
+
size: 14,
|
|
3353
|
+
style: { transitionDuration: "260ms", transitionTimingFunction: "cubic-bezier(0.25, 0.1, 0.25, 1)" },
|
|
3354
|
+
className: cn(
|
|
3355
|
+
"shrink-0 transition-transform",
|
|
3356
|
+
effectiveMode === "detail" && "rotate-90"
|
|
3357
|
+
),
|
|
3358
|
+
"aria-hidden": "true"
|
|
3359
|
+
}
|
|
3360
|
+
)
|
|
3361
|
+
]
|
|
3362
|
+
}
|
|
3363
|
+
),
|
|
3364
|
+
/* @__PURE__ */ jsx12("div", { className: "mt-3 h-px w-full bg-[hsl(var(--border)/0.75)]" })
|
|
3365
|
+
] })
|
|
3366
|
+
] }),
|
|
3367
|
+
effectiveMode === "detail" ? /* @__PURE__ */ jsx12("div", { className: "ml-10 flex flex-col gap-3 pt-1", children: messages.map((message, index) => {
|
|
3368
|
+
const isLast = index === messages.length - 1;
|
|
3369
|
+
const streamingThis = isStreaming && isLast;
|
|
3370
|
+
const text = getMessageText(message);
|
|
3371
|
+
const toolCalls = (message.tool_calls ?? []).filter(
|
|
3372
|
+
(toolCall) => formatToolName(toolCall.name) !== "AskUserQuestion" && !shouldHideToolCall(message, toolCall)
|
|
3373
|
+
);
|
|
3374
|
+
const orderedParts = getOrderedMessageParts(message, toolCalls);
|
|
3375
|
+
const showReasoning = !!message.reasoning && isStreaming && index === latestReasoningIndex;
|
|
3376
|
+
return /* @__PURE__ */ jsxs10(
|
|
3377
|
+
"div",
|
|
3378
|
+
{
|
|
3379
|
+
className: "flex flex-col gap-3",
|
|
3380
|
+
children: [
|
|
3381
|
+
showReasoning && message.reasoning ? /* @__PURE__ */ jsx12(ThinkingBlock, { reasoning: message.reasoning, isStreaming: streamingThis && !text }) : null,
|
|
3382
|
+
orderedParts.length > 0 ? orderedParts.map(
|
|
3383
|
+
(part) => part.type === "text" ? /* @__PURE__ */ jsx12(
|
|
3384
|
+
AssistantMessageContent,
|
|
3385
|
+
{
|
|
3386
|
+
message: { ...message, content: part.content, tool_calls: turnToolCalls },
|
|
3387
|
+
sessionId,
|
|
3388
|
+
streaming: streamingThis,
|
|
3389
|
+
compact: true
|
|
3390
|
+
},
|
|
3391
|
+
part.key
|
|
3392
|
+
) : /* @__PURE__ */ jsx12("div", { className: "flex flex-col gap-0.5", children: part.toolCalls.map((toolCall) => {
|
|
3393
|
+
const custom = toolCallRenderer?.(toolCall);
|
|
3394
|
+
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);
|
|
3395
|
+
}) }, part.key)
|
|
3396
|
+
) : hasRenderableMessageContent(message) && message !== finalMessage ? /* @__PURE__ */ jsx12(
|
|
3397
|
+
AssistantMessageContent,
|
|
3398
|
+
{
|
|
3399
|
+
message,
|
|
3400
|
+
sessionId,
|
|
3401
|
+
streaming: streamingThis,
|
|
3402
|
+
compact: true
|
|
3403
|
+
}
|
|
3404
|
+
) : null,
|
|
3405
|
+
orderedParts.length === 0 && toolCalls.length > 0 ? /* @__PURE__ */ jsx12("div", { className: "flex flex-col gap-0.5", children: toolCalls.map((toolCall) => {
|
|
3406
|
+
const custom = toolCallRenderer?.(toolCall);
|
|
3407
|
+
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);
|
|
3408
|
+
}) }) : null
|
|
3409
|
+
]
|
|
3410
|
+
},
|
|
3411
|
+
message.entry_id ?? `${message.timestamp ?? "assistant"}-${index}`
|
|
3412
|
+
);
|
|
3413
|
+
}) }) : null,
|
|
3414
|
+
finalMessage && (effectiveMode === "compact" || finalOrderedParts.length === 0) ? /* @__PURE__ */ jsx12("div", { className: "ml-10", children: /* @__PURE__ */ jsx12(
|
|
3415
|
+
AssistantMessageContent,
|
|
3416
|
+
{
|
|
3417
|
+
message: finalMessage,
|
|
3418
|
+
sessionId,
|
|
3419
|
+
streaming: isStreaming && finalMessage === messages[messages.length - 1]
|
|
3420
|
+
}
|
|
3421
|
+
) }) : null,
|
|
3422
|
+
questionToolCalls.map((toolCall) => /* @__PURE__ */ jsx12(
|
|
3423
|
+
ToolCallBlock,
|
|
3424
|
+
{
|
|
3425
|
+
toolCall,
|
|
3426
|
+
answerData: askAnswers?.[toolCall.id],
|
|
3427
|
+
onAnswer,
|
|
3428
|
+
answered: sessionStatus !== "waiting_for_input",
|
|
3429
|
+
sessionStatus,
|
|
3430
|
+
isActiveQuestion: toolCall.id === activeQuestionId,
|
|
3431
|
+
renderer: toolCallRenderer
|
|
3432
|
+
},
|
|
3433
|
+
toolCall.id
|
|
3434
|
+
))
|
|
3435
|
+
]
|
|
3436
|
+
}
|
|
3437
|
+
);
|
|
3438
|
+
}
|
|
3439
|
+
function collectMemoryRefs(messages) {
|
|
3440
|
+
const refs = /* @__PURE__ */ new Map();
|
|
3441
|
+
for (const message of messages) {
|
|
3442
|
+
for (const ref of message.memory_refs ?? []) if (!refs.has(ref.id)) refs.set(ref.id, ref);
|
|
2323
3443
|
}
|
|
2324
|
-
return
|
|
3444
|
+
return [...refs.values()];
|
|
2325
3445
|
}
|
|
2326
|
-
function
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
3446
|
+
function MemoryRefsHint({ refs }) {
|
|
3447
|
+
const [expanded, setExpanded] = useState12(false);
|
|
3448
|
+
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";
|
|
3449
|
+
return /* @__PURE__ */ jsxs10("div", { className: "blade-chat-memory-refs ml-1 w-full max-w-[680px]", children: [
|
|
3450
|
+
/* @__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: [
|
|
3451
|
+
/* @__PURE__ */ jsx12(BookOpen, { size: 12 }),
|
|
3452
|
+
/* @__PURE__ */ jsxs10("span", { children: [
|
|
3453
|
+
label,
|
|
3454
|
+
"\uFF08",
|
|
3455
|
+
refs.length,
|
|
3456
|
+
"\uFF09"
|
|
3457
|
+
] }),
|
|
3458
|
+
/* @__PURE__ */ jsx12(ChevronRight, { size: 10, className: cn("transition-transform", expanded && "rotate-90") })
|
|
3459
|
+
] }),
|
|
3460
|
+
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: [
|
|
3461
|
+
/* @__PURE__ */ jsx12("p", { className: "line-clamp-2 break-words leading-5", children: ref.content_preview }),
|
|
3462
|
+
ref.skill_name ? /* @__PURE__ */ jsx12("span", { className: "mt-1 inline-flex text-[10px] text-[hsl(var(--primary))]", children: ref.skill_name }) : null
|
|
3463
|
+
] }, ref.id)) }) : null
|
|
3464
|
+
] });
|
|
3465
|
+
}
|
|
3466
|
+
function AssistantMessageContent({
|
|
3467
|
+
message,
|
|
3468
|
+
sessionId,
|
|
3469
|
+
streaming,
|
|
3470
|
+
compact = false
|
|
2334
3471
|
}) {
|
|
2335
|
-
const
|
|
2336
|
-
const
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
const
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
"div",
|
|
3472
|
+
const text = getMessageText(message);
|
|
3473
|
+
const imageParts = getImageParts(message.content);
|
|
3474
|
+
const fileParts = getFileParts(message.content);
|
|
3475
|
+
const failed = message.status === "failed";
|
|
3476
|
+
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;
|
|
3477
|
+
const textContent = text ? /* @__PURE__ */ jsx12(
|
|
3478
|
+
"div",
|
|
3479
|
+
{
|
|
3480
|
+
className: cn(
|
|
3481
|
+
"blade-chat-assistant-text",
|
|
3482
|
+
compact ? "text-xs leading-[22px] text-[hsl(var(--foreground))]" : "text-[15px] leading-8 text-[hsl(var(--foreground))]"
|
|
3483
|
+
),
|
|
3484
|
+
children: /* @__PURE__ */ jsx12(
|
|
3485
|
+
MarkdownContent,
|
|
2350
3486
|
{
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
3487
|
+
mode: streaming ? "streaming" : "static",
|
|
3488
|
+
className: "blade-chat-prose",
|
|
3489
|
+
sessionId,
|
|
3490
|
+
children: text
|
|
3491
|
+
}
|
|
3492
|
+
)
|
|
3493
|
+
}
|
|
3494
|
+
) : null;
|
|
3495
|
+
if (imageParts.length === 0 && fileParts.length === 0) {
|
|
3496
|
+
if (!failed) return textContent;
|
|
3497
|
+
return failedBadge || textContent ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-2", children: [
|
|
3498
|
+
failedBadge,
|
|
3499
|
+
textContent
|
|
3500
|
+
] }) : null;
|
|
3501
|
+
}
|
|
3502
|
+
return /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-3", children: [
|
|
3503
|
+
failedBadge,
|
|
3504
|
+
imageParts.length > 0 ? /* @__PURE__ */ jsx12("div", { className: "grid gap-2", children: imageParts.map((part) => /* @__PURE__ */ jsx12(
|
|
3505
|
+
"img",
|
|
3506
|
+
{
|
|
3507
|
+
src: part.image_url.url,
|
|
3508
|
+
alt: "\u6D88\u606F\u9644\u4EF6",
|
|
3509
|
+
className: "max-h-72 rounded-xl border border-[hsl(var(--border))] object-cover"
|
|
3510
|
+
},
|
|
3511
|
+
part.image_url.url
|
|
3512
|
+
)) }) : null,
|
|
3513
|
+
fileParts.length > 0 ? /* @__PURE__ */ jsx12("div", { className: "flex flex-wrap gap-1.5", children: fileParts.map((part) => /* @__PURE__ */ jsxs10(
|
|
3514
|
+
"div",
|
|
3515
|
+
{
|
|
3516
|
+
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))]",
|
|
3517
|
+
title: part.name,
|
|
3518
|
+
children: [
|
|
3519
|
+
/* @__PURE__ */ jsx12(FileText, { size: 12, className: "shrink-0" }),
|
|
3520
|
+
/* @__PURE__ */ jsx12("span", { className: "max-w-56 truncate", children: part.name })
|
|
3521
|
+
]
|
|
3522
|
+
},
|
|
3523
|
+
`${part.name}-${part.data.slice(0, 32)}`
|
|
3524
|
+
)) }) : null,
|
|
3525
|
+
textContent
|
|
2383
3526
|
] });
|
|
2384
3527
|
}
|
|
2385
3528
|
|
|
2386
3529
|
// src/components/RenderErrorBoundary.tsx
|
|
2387
3530
|
import { Component } from "react";
|
|
2388
|
-
import { jsx as
|
|
3531
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2389
3532
|
function getFirstComponentName(componentStack) {
|
|
2390
3533
|
const match = componentStack.match(/\n\s+at\s+([^\s(]+)/);
|
|
2391
3534
|
return match?.[1] ?? null;
|
|
@@ -2418,26 +3561,26 @@ var RenderErrorBoundary = class extends Component {
|
|
|
2418
3561
|
return children;
|
|
2419
3562
|
}
|
|
2420
3563
|
const componentName = getFirstComponentName(componentStack);
|
|
2421
|
-
return /* @__PURE__ */
|
|
2422
|
-
/* @__PURE__ */
|
|
2423
|
-
/* @__PURE__ */
|
|
2424
|
-
/* @__PURE__ */
|
|
3564
|
+
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: [
|
|
3565
|
+
/* @__PURE__ */ jsx13(TriangleAlert, { className: "mt-0.5 h-4 w-4 shrink-0 text-amber-300" }),
|
|
3566
|
+
/* @__PURE__ */ jsxs11("div", { className: "min-w-0 flex-1", children: [
|
|
3567
|
+
/* @__PURE__ */ jsxs11("div", { className: "font-medium", children: [
|
|
2425
3568
|
label,
|
|
2426
3569
|
"\u6E32\u67D3\u5931\u8D25"
|
|
2427
3570
|
] }),
|
|
2428
|
-
/* @__PURE__ */
|
|
3571
|
+
/* @__PURE__ */ jsxs11("div", { className: "mt-1 break-words text-xs leading-5 text-amber-100/75", children: [
|
|
2429
3572
|
componentName ? `\u7EC4\u4EF6\uFF1A${componentName}\u3002` : null,
|
|
2430
3573
|
error.message || "\u53D1\u751F\u4E86\u672A\u9884\u671F\u7684\u6E32\u67D3\u9519\u8BEF\u3002"
|
|
2431
3574
|
] }),
|
|
2432
|
-
details ? /* @__PURE__ */
|
|
3575
|
+
details ? /* @__PURE__ */ jsx13("div", { className: "mt-1 truncate text-xs text-amber-100/55", children: details }) : null
|
|
2433
3576
|
] })
|
|
2434
3577
|
] }) });
|
|
2435
3578
|
}
|
|
2436
3579
|
};
|
|
2437
3580
|
|
|
2438
3581
|
// src/components/PostChatFollowupBlock.tsx
|
|
2439
|
-
import { useCallback as
|
|
2440
|
-
import { Fragment as
|
|
3582
|
+
import { useCallback as useCallback6, useEffect as useEffect11, useRef as useRef12, useState as useState13 } from "react";
|
|
3583
|
+
import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2441
3584
|
function emitInteraction(callback, event) {
|
|
2442
3585
|
try {
|
|
2443
3586
|
callback?.(event);
|
|
@@ -2447,9 +3590,6 @@ function emitInteraction(callback, event) {
|
|
|
2447
3590
|
function basename(path) {
|
|
2448
3591
|
return path.split(/[\\/]/).filter(Boolean).pop() || path;
|
|
2449
3592
|
}
|
|
2450
|
-
function isVideo(path) {
|
|
2451
|
-
return /\.(?:mp4|mov|webm|mkv|avi|m4v)$/i.test(path);
|
|
2452
|
-
}
|
|
2453
3593
|
function ArtifactCard({
|
|
2454
3594
|
artifact,
|
|
2455
3595
|
sessionId,
|
|
@@ -2459,10 +3599,10 @@ function ArtifactCard({
|
|
|
2459
3599
|
onArtifactOpened
|
|
2460
3600
|
}) {
|
|
2461
3601
|
const client = useBladeClient();
|
|
2462
|
-
const [downloading, setDownloading] =
|
|
3602
|
+
const [downloading, setDownloading] = useState13(false);
|
|
2463
3603
|
const name = artifact.label || basename(artifact.target);
|
|
2464
3604
|
if (artifact.kind === "link") {
|
|
2465
|
-
return /* @__PURE__ */
|
|
3605
|
+
return /* @__PURE__ */ jsxs12(
|
|
2466
3606
|
"a",
|
|
2467
3607
|
{
|
|
2468
3608
|
href: artifact.target,
|
|
@@ -2473,51 +3613,55 @@ function ArtifactCard({
|
|
|
2473
3613
|
${artifact.target}`,
|
|
2474
3614
|
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))]",
|
|
2475
3615
|
children: [
|
|
2476
|
-
/* @__PURE__ */
|
|
2477
|
-
/* @__PURE__ */
|
|
2478
|
-
/* @__PURE__ */
|
|
3616
|
+
/* @__PURE__ */ jsx14(Globe, { size: 15, className: "shrink-0 text-[hsl(var(--primary))]" }),
|
|
3617
|
+
/* @__PURE__ */ jsx14("span", { className: "min-w-0 flex-1 truncate font-medium", children: name }),
|
|
3618
|
+
/* @__PURE__ */ jsx14(ArrowUpRight, { size: 13, className: "absolute right-2 opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100" })
|
|
2479
3619
|
]
|
|
2480
3620
|
}
|
|
2481
3621
|
);
|
|
2482
3622
|
}
|
|
2483
|
-
const
|
|
2484
|
-
|
|
2485
|
-
|
|
3623
|
+
const fileName = basename(artifact.target);
|
|
3624
|
+
const downloadUrl = sessionId ? client.buildAuthedUrl(
|
|
3625
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/files/${encodeURIComponent(artifact.target)}`
|
|
3626
|
+
) : void 0;
|
|
3627
|
+
const handleDownload = async (event) => {
|
|
3628
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
3629
|
+
event.preventDefault();
|
|
3630
|
+
if (!sessionId || downloading) return;
|
|
3631
|
+
setDownloading(true);
|
|
3632
|
+
emitInteraction(onInteraction, {
|
|
3633
|
+
type: "artifact_download_started",
|
|
3634
|
+
sessionId,
|
|
3635
|
+
assistantEntryId,
|
|
3636
|
+
artifactIndex,
|
|
3637
|
+
artifactKind: "file"
|
|
3638
|
+
});
|
|
3639
|
+
try {
|
|
3640
|
+
await client.sessions.downloadFile(sessionId, artifact.target, fileName);
|
|
3641
|
+
emitInteraction(onInteraction, {
|
|
3642
|
+
type: "artifact_download_succeeded",
|
|
3643
|
+
sessionId,
|
|
3644
|
+
assistantEntryId,
|
|
3645
|
+
artifactIndex,
|
|
3646
|
+
artifactKind: "file"
|
|
3647
|
+
});
|
|
3648
|
+
} catch {
|
|
3649
|
+
} finally {
|
|
3650
|
+
setDownloading(false);
|
|
3651
|
+
}
|
|
3652
|
+
};
|
|
3653
|
+
return /* @__PURE__ */ jsx14(
|
|
3654
|
+
"a",
|
|
2486
3655
|
{
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
onClick:
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
artifactIndex,
|
|
2497
|
-
artifactKind: "file"
|
|
2498
|
-
});
|
|
2499
|
-
try {
|
|
2500
|
-
await client.sessions.downloadFile(sessionId, artifact.target, basename(artifact.target));
|
|
2501
|
-
emitInteraction(onInteraction, {
|
|
2502
|
-
type: "artifact_download_succeeded",
|
|
2503
|
-
sessionId,
|
|
2504
|
-
assistantEntryId,
|
|
2505
|
-
artifactIndex,
|
|
2506
|
-
artifactKind: "file"
|
|
2507
|
-
});
|
|
2508
|
-
} catch {
|
|
2509
|
-
} finally {
|
|
2510
|
-
setDownloading(false);
|
|
2511
|
-
}
|
|
2512
|
-
},
|
|
2513
|
-
title: name,
|
|
2514
|
-
"aria-label": `\u4E0B\u8F7D ${name}`,
|
|
2515
|
-
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",
|
|
2516
|
-
children: [
|
|
2517
|
-
/* @__PURE__ */ jsx13(Icon2, { size: 15, className: "shrink-0 text-[hsl(var(--primary))]" }),
|
|
2518
|
-
/* @__PURE__ */ jsx13("span", { className: "min-w-0 flex-1 truncate font-medium", children: name }),
|
|
2519
|
-
/* @__PURE__ */ jsx13(Download, { size: 13, className: "absolute right-2 opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100" })
|
|
2520
|
-
]
|
|
3656
|
+
href: downloadUrl,
|
|
3657
|
+
download: fileName,
|
|
3658
|
+
onClick: handleDownload,
|
|
3659
|
+
title: fileName,
|
|
3660
|
+
"aria-label": `\u4E0B\u8F7D\u6587\u4EF6\uFF1A${fileName}`,
|
|
3661
|
+
"aria-disabled": !sessionId || void 0,
|
|
3662
|
+
"aria-busy": downloading || void 0,
|
|
3663
|
+
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",
|
|
3664
|
+
children: fileName
|
|
2521
3665
|
}
|
|
2522
3666
|
);
|
|
2523
3667
|
}
|
|
@@ -2534,17 +3678,17 @@ function feedbackReasonLabel(reason) {
|
|
|
2534
3678
|
}
|
|
2535
3679
|
function HistoricalResultFeedback({ feedback }) {
|
|
2536
3680
|
const label = feedbackReasonLabel(feedback.reason);
|
|
2537
|
-
return /* @__PURE__ */
|
|
3681
|
+
return /* @__PURE__ */ jsxs12(
|
|
2538
3682
|
"section",
|
|
2539
3683
|
{
|
|
2540
3684
|
"aria-label": "\u5386\u53F2\u7ED3\u679C\u53CD\u9988",
|
|
2541
3685
|
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))]",
|
|
2542
3686
|
children: [
|
|
2543
|
-
/* @__PURE__ */
|
|
3687
|
+
/* @__PURE__ */ jsxs12("span", { children: [
|
|
2544
3688
|
"\u4F60\u5BF9\u6B64\u8F6E\u7ED3\u679C\u7684\u8BC4\u4EF7\uFF1A",
|
|
2545
3689
|
feedback.helpful ? "\u6709\u5E2E\u52A9" : "\u6CA1\u5E2E\u52A9"
|
|
2546
3690
|
] }),
|
|
2547
|
-
label ? /* @__PURE__ */
|
|
3691
|
+
label ? /* @__PURE__ */ jsxs12("span", { children: [
|
|
2548
3692
|
" \xB7 ",
|
|
2549
3693
|
label
|
|
2550
3694
|
] }) : null
|
|
@@ -2561,15 +3705,15 @@ function ResultFeedback({
|
|
|
2561
3705
|
onFeedbackSaved
|
|
2562
3706
|
}) {
|
|
2563
3707
|
const client = useBladeClient();
|
|
2564
|
-
const [saved, setSaved] =
|
|
2565
|
-
const [helpful, setHelpful] =
|
|
2566
|
-
const [reason, setReason] =
|
|
2567
|
-
const [saving, setSaving] =
|
|
2568
|
-
const [saveError, setSaveError] =
|
|
2569
|
-
const reportedShown =
|
|
2570
|
-
const latestChoice =
|
|
3708
|
+
const [saved, setSaved] = useState13(savedFeedback ?? null);
|
|
3709
|
+
const [helpful, setHelpful] = useState13(savedFeedback?.helpful ?? null);
|
|
3710
|
+
const [reason, setReason] = useState13(savedFeedback?.reason ?? null);
|
|
3711
|
+
const [saving, setSaving] = useState13(false);
|
|
3712
|
+
const [saveError, setSaveError] = useState13(false);
|
|
3713
|
+
const reportedShown = useRef12(false);
|
|
3714
|
+
const latestChoice = useRef12(null);
|
|
2571
3715
|
const eligible = followup.feedback_eligible === true && Boolean(sessionId) && !isViewer;
|
|
2572
|
-
|
|
3716
|
+
useEffect11(() => {
|
|
2573
3717
|
if (!eligible || reportedShown.current) return;
|
|
2574
3718
|
reportedShown.current = true;
|
|
2575
3719
|
emitInteraction(onInteraction, {
|
|
@@ -2578,13 +3722,13 @@ function ResultFeedback({
|
|
|
2578
3722
|
assistantEntryId: followup.assistant_entry_id
|
|
2579
3723
|
});
|
|
2580
3724
|
}, [eligible, followup.assistant_entry_id, onInteraction, sessionId]);
|
|
2581
|
-
|
|
3725
|
+
useEffect11(() => {
|
|
2582
3726
|
if (!savedFeedback || latestChoice.current) return;
|
|
2583
3727
|
setSaved(savedFeedback);
|
|
2584
3728
|
setHelpful(savedFeedback.helpful);
|
|
2585
3729
|
setReason(savedFeedback.reason);
|
|
2586
3730
|
}, [savedFeedback]);
|
|
2587
|
-
const submit =
|
|
3731
|
+
const submit = useCallback6(
|
|
2588
3732
|
async (nextHelpful, nextReason) => {
|
|
2589
3733
|
if (!sessionId) return;
|
|
2590
3734
|
const choice = { helpful: nextHelpful, reason: nextReason };
|
|
@@ -2619,15 +3763,15 @@ function ResultFeedback({
|
|
|
2619
3763
|
[client, followup.assistant_entry_id, onFeedbackSaved, onInteraction, sessionId]
|
|
2620
3764
|
);
|
|
2621
3765
|
if (!eligible) return null;
|
|
2622
|
-
return /* @__PURE__ */
|
|
3766
|
+
return /* @__PURE__ */ jsxs12(
|
|
2623
3767
|
"section",
|
|
2624
3768
|
{
|
|
2625
3769
|
"aria-label": "\u7ED3\u679C\u53CD\u9988",
|
|
2626
3770
|
className: "flex flex-col gap-2 border-t border-[hsl(var(--border))] pt-3",
|
|
2627
3771
|
children: [
|
|
2628
|
-
/* @__PURE__ */
|
|
2629
|
-
/* @__PURE__ */
|
|
2630
|
-
/* @__PURE__ */
|
|
3772
|
+
/* @__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" }),
|
|
3773
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap gap-1.5", children: [
|
|
3774
|
+
/* @__PURE__ */ jsx14(
|
|
2631
3775
|
"button",
|
|
2632
3776
|
{
|
|
2633
3777
|
type: "button",
|
|
@@ -2638,7 +3782,7 @@ function ResultFeedback({
|
|
|
2638
3782
|
children: "\u6709\u5E2E\u52A9"
|
|
2639
3783
|
}
|
|
2640
3784
|
),
|
|
2641
|
-
/* @__PURE__ */
|
|
3785
|
+
/* @__PURE__ */ jsx14(
|
|
2642
3786
|
"button",
|
|
2643
3787
|
{
|
|
2644
3788
|
type: "button",
|
|
@@ -2650,7 +3794,7 @@ function ResultFeedback({
|
|
|
2650
3794
|
}
|
|
2651
3795
|
)
|
|
2652
3796
|
] }),
|
|
2653
|
-
helpful === false ? /* @__PURE__ */
|
|
3797
|
+
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(
|
|
2654
3798
|
"button",
|
|
2655
3799
|
{
|
|
2656
3800
|
type: "button",
|
|
@@ -2662,9 +3806,9 @@ function ResultFeedback({
|
|
|
2662
3806
|
},
|
|
2663
3807
|
item.value
|
|
2664
3808
|
)) }) : null,
|
|
2665
|
-
saveError ? /* @__PURE__ */
|
|
2666
|
-
/* @__PURE__ */
|
|
2667
|
-
/* @__PURE__ */
|
|
3809
|
+
saveError ? /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2 text-xs text-[hsl(var(--destructive))]", children: [
|
|
3810
|
+
/* @__PURE__ */ jsx14("span", { children: "\u53CD\u9988\u6682\u672A\u4FDD\u5B58\uFF0C\u53EF\u91CD\u8BD5" }),
|
|
3811
|
+
/* @__PURE__ */ jsx14(
|
|
2668
3812
|
"button",
|
|
2669
3813
|
{
|
|
2670
3814
|
type: "button",
|
|
@@ -2676,7 +3820,7 @@ function ResultFeedback({
|
|
|
2676
3820
|
children: "\u91CD\u8BD5"
|
|
2677
3821
|
}
|
|
2678
3822
|
)
|
|
2679
|
-
] }) : saved ? /* @__PURE__ */
|
|
3823
|
+
] }) : saved ? /* @__PURE__ */ jsx14("div", { className: "text-[11px] text-[hsl(var(--muted-foreground))]", children: "\u5DF2\u4FDD\u5B58\uFF0C\u53EF\u968F\u65F6\u4FEE\u6539" }) : null
|
|
2680
3824
|
]
|
|
2681
3825
|
}
|
|
2682
3826
|
);
|
|
@@ -2690,14 +3834,14 @@ function PostChatFollowupBlock({
|
|
|
2690
3834
|
savedFeedback,
|
|
2691
3835
|
onFeedbackSaved
|
|
2692
3836
|
}) {
|
|
2693
|
-
const [expanded, setExpanded] =
|
|
2694
|
-
const adopted =
|
|
2695
|
-
const reportedSuggestions =
|
|
2696
|
-
const reportedArtifacts =
|
|
2697
|
-
const openedArtifacts =
|
|
3837
|
+
const [expanded, setExpanded] = useState13(false);
|
|
3838
|
+
const adopted = useRef12(/* @__PURE__ */ new Set());
|
|
3839
|
+
const reportedSuggestions = useRef12(false);
|
|
3840
|
+
const reportedArtifacts = useRef12(/* @__PURE__ */ new Set());
|
|
3841
|
+
const openedArtifacts = useRef12(/* @__PURE__ */ new Set());
|
|
2698
3842
|
const artifacts = followup.final_artifacts ?? [];
|
|
2699
3843
|
const visibleArtifacts = expanded ? artifacts : artifacts.slice(0, 3);
|
|
2700
|
-
|
|
3844
|
+
useEffect11(() => {
|
|
2701
3845
|
if (!reportedSuggestions.current && followup.suggestions.length > 0) {
|
|
2702
3846
|
reportedSuggestions.current = true;
|
|
2703
3847
|
emitInteraction(onInteraction, {
|
|
@@ -2727,7 +3871,7 @@ function PostChatFollowupBlock({
|
|
|
2727
3871
|
sessionId,
|
|
2728
3872
|
visibleArtifacts
|
|
2729
3873
|
]);
|
|
2730
|
-
const reportArtifactOpened =
|
|
3874
|
+
const reportArtifactOpened = useCallback6(
|
|
2731
3875
|
(artifactIndex, artifactKind) => {
|
|
2732
3876
|
if (openedArtifacts.current.has(artifactIndex)) return;
|
|
2733
3877
|
openedArtifacts.current.add(artifactIndex);
|
|
@@ -2743,15 +3887,15 @@ function PostChatFollowupBlock({
|
|
|
2743
3887
|
);
|
|
2744
3888
|
if (!followup.recaption && artifacts.length === 0 && followup.suggestions.length === 0 && !followup.feedback_eligible)
|
|
2745
3889
|
return null;
|
|
2746
|
-
return /* @__PURE__ */
|
|
2747
|
-
followup.recaption || artifacts.length > 0 ? /* @__PURE__ */
|
|
2748
|
-
/* @__PURE__ */
|
|
2749
|
-
/* @__PURE__ */
|
|
3890
|
+
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: [
|
|
3891
|
+
followup.recaption || artifacts.length > 0 ? /* @__PURE__ */ jsxs12("section", { "aria-label": "\u672C\u8F6E\u5C0F\u7ED3", className: "flex flex-col gap-2", children: [
|
|
3892
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5 text-xs font-medium text-[hsl(var(--muted-foreground))]", children: [
|
|
3893
|
+
/* @__PURE__ */ jsx14(Sparkles, { size: 14 }),
|
|
2750
3894
|
"\u672C\u8F6E\u5C0F\u7ED3"
|
|
2751
3895
|
] }),
|
|
2752
|
-
followup.recaption ? /* @__PURE__ */
|
|
2753
|
-
artifacts.length > 0 ? /* @__PURE__ */
|
|
2754
|
-
/* @__PURE__ */
|
|
3896
|
+
followup.recaption ? /* @__PURE__ */ jsx14("p", { className: "text-[13px] leading-5", children: followup.recaption }) : null,
|
|
3897
|
+
artifacts.length > 0 ? /* @__PURE__ */ jsxs12(Fragment3, { children: [
|
|
3898
|
+
/* @__PURE__ */ jsx14("div", { className: "grid max-w-full grid-cols-3 gap-1.5", children: visibleArtifacts.map((artifact, artifactIndex) => /* @__PURE__ */ jsx14(
|
|
2755
3899
|
ArtifactCard,
|
|
2756
3900
|
{
|
|
2757
3901
|
artifact,
|
|
@@ -2763,7 +3907,7 @@ function PostChatFollowupBlock({
|
|
|
2763
3907
|
},
|
|
2764
3908
|
`${artifact.kind}:${artifactIndex}`
|
|
2765
3909
|
)) }),
|
|
2766
|
-
artifacts.length > 3 ? /* @__PURE__ */
|
|
3910
|
+
artifacts.length > 3 ? /* @__PURE__ */ jsxs12(
|
|
2767
3911
|
"button",
|
|
2768
3912
|
{
|
|
2769
3913
|
type: "button",
|
|
@@ -2772,15 +3916,15 @@ function PostChatFollowupBlock({
|
|
|
2772
3916
|
className: "flex w-fit items-center gap-0.5 text-[11px] text-[hsl(var(--muted-foreground))]",
|
|
2773
3917
|
children: [
|
|
2774
3918
|
expanded ? "\u6536\u8D77" : `\u5C55\u5F00 ${artifacts.length - 3} \u4E2A`,
|
|
2775
|
-
/* @__PURE__ */
|
|
3919
|
+
/* @__PURE__ */ jsx14(ChevronDown, { size: 13, className: expanded ? "rotate-180" : void 0 })
|
|
2776
3920
|
]
|
|
2777
3921
|
}
|
|
2778
3922
|
) : null
|
|
2779
3923
|
] }) : null
|
|
2780
3924
|
] }) : null,
|
|
2781
|
-
followup.suggestions.length > 0 ? /* @__PURE__ */
|
|
2782
|
-
/* @__PURE__ */
|
|
2783
|
-
followup.suggestions.map((suggestion, suggestionIndex) => /* @__PURE__ */
|
|
3925
|
+
followup.suggestions.length > 0 ? /* @__PURE__ */ jsxs12("section", { "aria-label": "\u4E0B\u4E00\u6B65\u5EFA\u8BAE", className: "flex flex-col gap-1.5", children: [
|
|
3926
|
+
/* @__PURE__ */ jsx14("div", { className: "text-xs font-medium text-[hsl(var(--muted-foreground))]", children: "\u4E0B\u4E00\u6B65\u53EF\u4EE5" }),
|
|
3927
|
+
followup.suggestions.map((suggestion, suggestionIndex) => /* @__PURE__ */ jsxs12(
|
|
2784
3928
|
"button",
|
|
2785
3929
|
{
|
|
2786
3930
|
type: "button",
|
|
@@ -2799,14 +3943,14 @@ function PostChatFollowupBlock({
|
|
|
2799
3943
|
},
|
|
2800
3944
|
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",
|
|
2801
3945
|
children: [
|
|
2802
|
-
/* @__PURE__ */
|
|
2803
|
-
/* @__PURE__ */
|
|
3946
|
+
/* @__PURE__ */ jsx14("span", { children: suggestion }),
|
|
3947
|
+
/* @__PURE__ */ jsx14(ArrowRight, { size: 14, className: "ml-auto shrink-0" })
|
|
2804
3948
|
]
|
|
2805
3949
|
},
|
|
2806
3950
|
suggestion
|
|
2807
3951
|
))
|
|
2808
3952
|
] }) : null,
|
|
2809
|
-
/* @__PURE__ */
|
|
3953
|
+
/* @__PURE__ */ jsx14(
|
|
2810
3954
|
ResultFeedback,
|
|
2811
3955
|
{
|
|
2812
3956
|
followup,
|
|
@@ -2821,8 +3965,91 @@ function PostChatFollowupBlock({
|
|
|
2821
3965
|
}
|
|
2822
3966
|
|
|
2823
3967
|
// src/components/UserMessageBubble.tsx
|
|
2824
|
-
import {
|
|
2825
|
-
|
|
3968
|
+
import {
|
|
3969
|
+
chatErrorForDisplay,
|
|
3970
|
+
getFileParts as getFileParts2,
|
|
3971
|
+
getImageParts as getImageParts2,
|
|
3972
|
+
getTextContent as getTextContent2
|
|
3973
|
+
} from "@blade-hq/agent-client";
|
|
3974
|
+
|
|
3975
|
+
// src/lib/whatif-prompt.ts
|
|
3976
|
+
var HEADER_RE = /^以下消息和 step 产物标记为 deprecated_by_rerun,请基于最新用户假设从 step(\d+) 开始完整重新推演,不要复用旧结论。$/;
|
|
3977
|
+
var QUOTE_HEADER_RE = /^\[步骤(\d+)\s*·\s*(.+?)\]$/;
|
|
3978
|
+
var USER_INPUT_TAG = "[\u7528\u6237\u8F93\u5165]";
|
|
3979
|
+
function parseWhatIfPrompt(text) {
|
|
3980
|
+
const lines = text.replace(/\r\n/g, "\n").trimEnd().split("\n");
|
|
3981
|
+
const headerMatch = lines[0]?.match(HEADER_RE);
|
|
3982
|
+
if (!headerMatch) return null;
|
|
3983
|
+
const userTagIdx = lines.indexOf(USER_INPUT_TAG);
|
|
3984
|
+
const hasUserTag = userTagIdx >= 0;
|
|
3985
|
+
const quoteBlockEndExclusive = hasUserTag ? userTagIdx : lines.length;
|
|
3986
|
+
const quoteHeaderIdxs = [];
|
|
3987
|
+
let quoteBlockFound = false;
|
|
3988
|
+
for (let i = 1; i < quoteBlockEndExclusive; i++) {
|
|
3989
|
+
if (!quoteBlockFound && lines[i].trim() === "[\u5F15\u7528]") {
|
|
3990
|
+
quoteBlockFound = true;
|
|
3991
|
+
} else if (quoteBlockFound && QUOTE_HEADER_RE.test(lines[i])) {
|
|
3992
|
+
quoteHeaderIdxs.push(i);
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3995
|
+
let legacyUserTextStart = -1;
|
|
3996
|
+
if (!hasUserTag && quoteHeaderIdxs.length > 0) {
|
|
3997
|
+
const lastSnapshotStart = quoteHeaderIdxs.at(-1) + 1;
|
|
3998
|
+
let i = lines.length - 1;
|
|
3999
|
+
while (i >= lastSnapshotStart && lines[i].trim() === "") i -= 1;
|
|
4000
|
+
while (i >= lastSnapshotStart && lines[i].trim() !== "") i -= 1;
|
|
4001
|
+
if (i >= lastSnapshotStart) legacyUserTextStart = i + 1;
|
|
4002
|
+
}
|
|
4003
|
+
const quotes = quoteHeaderIdxs.map((headerIdx, index) => {
|
|
4004
|
+
const match = lines[headerIdx].match(QUOTE_HEADER_RE);
|
|
4005
|
+
const nextHeader = quoteHeaderIdxs[index + 1];
|
|
4006
|
+
const end = nextHeader ?? (legacyUserTextStart >= 0 ? legacyUserTextStart : quoteBlockEndExclusive);
|
|
4007
|
+
const snapshotLines = lines.slice(headerIdx + 1, end);
|
|
4008
|
+
while (snapshotLines.at(-1)?.trim() === "") snapshotLines.pop();
|
|
4009
|
+
return {
|
|
4010
|
+
stepNumber: Number.parseInt(match[1], 10),
|
|
4011
|
+
label: match[2].trim(),
|
|
4012
|
+
snapshot: snapshotLines.join("\n")
|
|
4013
|
+
};
|
|
4014
|
+
});
|
|
4015
|
+
const userTextStart = hasUserTag ? userTagIdx + 1 : legacyUserTextStart;
|
|
4016
|
+
const userLines = userTextStart >= 0 ? lines.slice(userTextStart) : [];
|
|
4017
|
+
while (userLines[0]?.trim() === "") userLines.shift();
|
|
4018
|
+
while (userLines.at(-1)?.trim() === "") userLines.pop();
|
|
4019
|
+
const fromStep = Number.parseInt(headerMatch[1], 10);
|
|
4020
|
+
return {
|
|
4021
|
+
fromStep: Number.isFinite(fromStep) ? fromStep : null,
|
|
4022
|
+
quotes,
|
|
4023
|
+
userText: userLines.join("\n")
|
|
4024
|
+
};
|
|
4025
|
+
}
|
|
4026
|
+
|
|
4027
|
+
// src/components/WhatIfUserBubble.tsx
|
|
4028
|
+
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
4029
|
+
function WhatIfUserBubble({ parsed, onQuoteClick }) {
|
|
4030
|
+
const { fromStep, quotes, userText } = parsed;
|
|
4031
|
+
return /* @__PURE__ */ jsxs13("div", { className: "flex flex-col items-end gap-2", children: [
|
|
4032
|
+
/* @__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: [
|
|
4033
|
+
/* @__PURE__ */ jsx15(RefreshCcw, { size: 10 }),
|
|
4034
|
+
/* @__PURE__ */ jsx15("span", { children: fromStep != null ? `\u91CD\u8DD1\u81EA step ${fromStep}` : "\u91CD\u8DD1" })
|
|
4035
|
+
] }),
|
|
4036
|
+
quotes.length > 0 && /* @__PURE__ */ jsx15("div", { className: "flex max-w-[min(72vw,42rem)] flex-col items-stretch gap-2", children: quotes.map((quote, index) => {
|
|
4037
|
+
const clickable = quote.stepNumber != null && !!onQuoteClick;
|
|
4038
|
+
const label = quote.stepNumber != null ? `\u6B65\u9AA4${quote.stepNumber} \xB7 ${quote.label}` : quote.label;
|
|
4039
|
+
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: [
|
|
4040
|
+
/* @__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: [
|
|
4041
|
+
/* @__PURE__ */ jsx15("span", { children: "\u21B3" }),
|
|
4042
|
+
/* @__PURE__ */ jsx15("span", { className: "truncate", children: label })
|
|
4043
|
+
] }),
|
|
4044
|
+
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
|
|
4045
|
+
] }, `${quote.stepNumber ?? "x"}-${index}`);
|
|
4046
|
+
}) }),
|
|
4047
|
+
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 }) })
|
|
4048
|
+
] });
|
|
4049
|
+
}
|
|
4050
|
+
|
|
4051
|
+
// src/components/UserMessageBubble.tsx
|
|
4052
|
+
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2826
4053
|
function isUserMessage(message) {
|
|
2827
4054
|
return message.role === "user";
|
|
2828
4055
|
}
|
|
@@ -2832,10 +4059,14 @@ function isErrorMessage(message) {
|
|
|
2832
4059
|
var isSending = (message) => message.status === "streaming";
|
|
2833
4060
|
function UserMessageBubble({ message, className }) {
|
|
2834
4061
|
const text = getTextContent2(message.content).trim();
|
|
2835
|
-
const fileParts =
|
|
2836
|
-
const imageParts =
|
|
2837
|
-
|
|
2838
|
-
|
|
4062
|
+
const fileParts = getFileParts2(message.content);
|
|
4063
|
+
const imageParts = getImageParts2(message.content);
|
|
4064
|
+
const whatifParsed = text && imageParts.length === 0 && fileParts.length === 0 ? parseWhatIfPrompt(text) : null;
|
|
4065
|
+
if (whatifParsed) {
|
|
4066
|
+
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 }) }) });
|
|
4067
|
+
}
|
|
4068
|
+
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: [
|
|
4069
|
+
imageParts.length > 0 && /* @__PURE__ */ jsx16("div", { className: "grid gap-2", children: imageParts.map((part) => /* @__PURE__ */ jsx16(
|
|
2839
4070
|
"img",
|
|
2840
4071
|
{
|
|
2841
4072
|
src: part.image_url.url,
|
|
@@ -2844,21 +4075,21 @@ function UserMessageBubble({ message, className }) {
|
|
|
2844
4075
|
},
|
|
2845
4076
|
part.image_url.url
|
|
2846
4077
|
)) }),
|
|
2847
|
-
fileParts.length > 0 && /* @__PURE__ */
|
|
4078
|
+
fileParts.length > 0 && /* @__PURE__ */ jsx16("div", { className: "flex flex-col items-end gap-1.5", children: fileParts.map((part) => /* @__PURE__ */ jsxs14(
|
|
2848
4079
|
"div",
|
|
2849
4080
|
{
|
|
2850
4081
|
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))]",
|
|
2851
4082
|
children: [
|
|
2852
|
-
/* @__PURE__ */
|
|
2853
|
-
/* @__PURE__ */
|
|
4083
|
+
/* @__PURE__ */ jsx16(FileText, { size: 12, className: "shrink-0" }),
|
|
4084
|
+
/* @__PURE__ */ jsx16("span", { className: "max-w-56 truncate", title: part.name, children: part.name })
|
|
2854
4085
|
]
|
|
2855
4086
|
},
|
|
2856
4087
|
`${part.name}-${part.data.length}`
|
|
2857
4088
|
)) }),
|
|
2858
|
-
text && /* @__PURE__ */
|
|
2859
|
-
text && isSending(message) && /* @__PURE__ */
|
|
2860
|
-
/* @__PURE__ */
|
|
2861
|
-
/* @__PURE__ */
|
|
4089
|
+
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 }) }),
|
|
4090
|
+
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: [
|
|
4091
|
+
/* @__PURE__ */ jsx16(LoaderCircle, { size: 11, className: "animate-spin", "aria-hidden": "true" }),
|
|
4092
|
+
/* @__PURE__ */ jsx16("span", { children: "\u53D1\u9001\u4E2D" })
|
|
2862
4093
|
] })
|
|
2863
4094
|
] }) });
|
|
2864
4095
|
}
|
|
@@ -2866,12 +4097,12 @@ function ErrorMessageBlock({
|
|
|
2866
4097
|
message,
|
|
2867
4098
|
className
|
|
2868
4099
|
}) {
|
|
2869
|
-
const text = getTextContent2(message.content);
|
|
2870
|
-
return /* @__PURE__ */
|
|
4100
|
+
const text = chatErrorForDisplay(getTextContent2(message.content));
|
|
4101
|
+
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 }) });
|
|
2871
4102
|
}
|
|
2872
4103
|
|
|
2873
4104
|
// src/components/MessageList.tsx
|
|
2874
|
-
import { jsx as
|
|
4105
|
+
import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2875
4106
|
function parseModeChange(message) {
|
|
2876
4107
|
if (message.kind !== "mode_change" || typeof message.content !== "string") {
|
|
2877
4108
|
return null;
|
|
@@ -2912,6 +4143,7 @@ function MessageList({
|
|
|
2912
4143
|
askAnswers,
|
|
2913
4144
|
onAnswer,
|
|
2914
4145
|
toolCallRenderer,
|
|
4146
|
+
hidePlanUpdateTools = false,
|
|
2915
4147
|
emptyState,
|
|
2916
4148
|
className,
|
|
2917
4149
|
sessionId,
|
|
@@ -2920,10 +4152,22 @@ function MessageList({
|
|
|
2920
4152
|
resultFeedbackByEntry = /* @__PURE__ */ new Map(),
|
|
2921
4153
|
onResultFeedbackSaved
|
|
2922
4154
|
}) {
|
|
4155
|
+
const visibleRootMessages = messages.filter((message) => {
|
|
4156
|
+
if ((message.loop_name ?? "root") !== "root") return false;
|
|
4157
|
+
if (isHiddenInternalMessage(message)) return false;
|
|
4158
|
+
if (message.kind === "context") return false;
|
|
4159
|
+
return message.role !== "tool" || getPlanningDividerKind(message) !== null;
|
|
4160
|
+
});
|
|
4161
|
+
const userMessages = visibleRootMessages.filter((message) => isUserMessage(message));
|
|
4162
|
+
const latestUserMessage = userMessages.at(-1);
|
|
4163
|
+
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() : "";
|
|
4164
|
+
const latestPromptPreview = latestPromptText.length > 80 ? `${latestPromptText.slice(0, 80)}\u2026` : latestPromptText;
|
|
4165
|
+
const shouldPinLatestUser = latestUserMessage != null && (latestUserMessage.entry_id == null || latestUserMessage.entry_id.startsWith("local-user-"));
|
|
2923
4166
|
const renderBlocks = useMemo7(() => {
|
|
2924
4167
|
const visible = messages.filter((message) => {
|
|
2925
4168
|
if ((message.loop_name ?? "root") !== "root") return false;
|
|
2926
4169
|
if (isHiddenInternalMessage(message)) return false;
|
|
4170
|
+
if (message.kind === "context") return false;
|
|
2927
4171
|
if (message.kind === "compaction") return true;
|
|
2928
4172
|
return message.role !== "tool" || getPlanningDividerKind(message) !== null;
|
|
2929
4173
|
});
|
|
@@ -2993,98 +4237,164 @@ function MessageList({
|
|
|
2993
4237
|
}
|
|
2994
4238
|
return blocks;
|
|
2995
4239
|
}, [messages, isStreaming]);
|
|
2996
|
-
return /* @__PURE__ */
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
4240
|
+
return /* @__PURE__ */ jsxs15("div", { className: cn("blade-chat-messages relative min-h-0 flex-1", className), children: [
|
|
4241
|
+
isStreaming ? /* @__PURE__ */ jsx17("output", { className: "sr-only", children: "\u6B63\u5728\u751F\u6210\u56DE\u590D" }) : null,
|
|
4242
|
+
/* @__PURE__ */ jsxs15(
|
|
4243
|
+
StickToBottom,
|
|
4244
|
+
{
|
|
4245
|
+
className: "h-full overflow-y-hidden",
|
|
4246
|
+
initial: "instant",
|
|
4247
|
+
resize: "instant",
|
|
4248
|
+
children: [
|
|
4249
|
+
/* @__PURE__ */ jsx17(StickToBottom.Content, { className: "blade-chat-messages-scroll", children: /* @__PURE__ */ jsxs15("div", { className: "blade-chat-messages-content mx-auto max-w-[748px]", children: [
|
|
4250
|
+
isStreaming && latestUserMessage && latestPromptPreview ? /* @__PURE__ */ jsx17(
|
|
4251
|
+
"button",
|
|
4252
|
+
{
|
|
4253
|
+
type: "button",
|
|
4254
|
+
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",
|
|
4255
|
+
onClick: () => Array.from(document.querySelectorAll("[data-entry-id]")).find((el) => el.dataset.entryId === latestUserMessage.entry_id)?.scrollIntoView({ behavior: "smooth", block: "start" }),
|
|
4256
|
+
"aria-label": "\u8DF3\u8F6C\u5230\u5F53\u524D\u63D0\u95EE",
|
|
4257
|
+
children: latestPromptPreview
|
|
4258
|
+
}
|
|
4259
|
+
) : null,
|
|
4260
|
+
/* @__PURE__ */ jsxs15("div", { className: "flex min-w-0 flex-col", children: [
|
|
4261
|
+
renderBlocks.length === 0 ? emptyState ?? /* @__PURE__ */ jsxs15("div", { className: "blade-chat-empty", children: [
|
|
4262
|
+
/* @__PURE__ */ jsx17(MessageSquare, { size: 40, strokeWidth: 1.5 }),
|
|
4263
|
+
/* @__PURE__ */ jsx17("span", { className: "text-base font-medium", children: "\u5F00\u59CB\u5BF9\u8BDD" }),
|
|
4264
|
+
/* @__PURE__ */ jsx17("span", { className: "text-sm opacity-60", children: "\u5728\u4E0B\u65B9\u8F93\u5165\u6D88\u606F\u5F00\u59CB\u804A\u5929" })
|
|
4265
|
+
] }) : renderBlocks.map((block) => {
|
|
4266
|
+
if (block.type === "message") {
|
|
4267
|
+
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);
|
|
4268
|
+
}
|
|
4269
|
+
if (block.type === "assistant_turn") {
|
|
4270
|
+
const blockFeedback = block.messages.map(
|
|
4271
|
+
(message) => message.entry_id ? resultFeedbackByEntry.get(message.entry_id) : void 0
|
|
4272
|
+
).find((feedback) => feedback != null);
|
|
4273
|
+
const hasActiveFollowup = Boolean(
|
|
4274
|
+
postChatFollowup && block.messages.some(
|
|
4275
|
+
(message) => message.entry_id === postChatFollowup.assistant_entry_id
|
|
4276
|
+
)
|
|
4277
|
+
);
|
|
4278
|
+
return /* @__PURE__ */ jsx17("div", { "data-entry-id": block.messages[0]?.entry_id, children: /* @__PURE__ */ jsxs15(
|
|
4279
|
+
RenderErrorBoundary,
|
|
4280
|
+
{
|
|
4281
|
+
label: "\u52A9\u624B\u6D88\u606F",
|
|
4282
|
+
details: block.key,
|
|
4283
|
+
resetKey: getMessageResetSignature(block.messages),
|
|
4284
|
+
children: [
|
|
4285
|
+
/* @__PURE__ */ jsx17(
|
|
4286
|
+
AssistantTurnBlock,
|
|
4287
|
+
{
|
|
4288
|
+
messages: block.messages,
|
|
4289
|
+
isStreaming: block.isStreaming,
|
|
4290
|
+
askAnswers,
|
|
4291
|
+
onAnswer,
|
|
4292
|
+
sessionStatus,
|
|
4293
|
+
toolCallRenderer,
|
|
4294
|
+
hidePlanUpdateTools,
|
|
4295
|
+
sessionId
|
|
4296
|
+
}
|
|
4297
|
+
),
|
|
4298
|
+
blockFeedback && !hasActiveFollowup ? /* @__PURE__ */ jsx17(HistoricalResultFeedback, { feedback: blockFeedback }) : null,
|
|
4299
|
+
hasActiveFollowup && postChatFollowup ? /* @__PURE__ */ jsx17(
|
|
4300
|
+
PostChatFollowupBlock,
|
|
4301
|
+
{
|
|
4302
|
+
followup: postChatFollowup,
|
|
4303
|
+
sessionId,
|
|
4304
|
+
onSuggestion,
|
|
4305
|
+
isViewer,
|
|
4306
|
+
onInteraction: onFollowupInteraction,
|
|
4307
|
+
savedFeedback: blockFeedback,
|
|
4308
|
+
onFeedbackSaved: onResultFeedbackSaved
|
|
4309
|
+
}
|
|
4310
|
+
) : null
|
|
4311
|
+
]
|
|
4312
|
+
}
|
|
4313
|
+
) }, block.key);
|
|
4314
|
+
}
|
|
4315
|
+
if (block.type === "compaction") {
|
|
4316
|
+
return /* @__PURE__ */ jsxs15(
|
|
4317
|
+
"div",
|
|
4318
|
+
{
|
|
4319
|
+
className: "flex items-center gap-2 text-xs text-[hsl(var(--muted-foreground))]",
|
|
4320
|
+
children: [
|
|
4321
|
+
/* @__PURE__ */ jsx17(Layers, { size: 12 }),
|
|
4322
|
+
/* @__PURE__ */ jsx17("span", { children: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29" })
|
|
4323
|
+
]
|
|
4324
|
+
},
|
|
4325
|
+
block.key
|
|
4326
|
+
);
|
|
4327
|
+
}
|
|
4328
|
+
return /* @__PURE__ */ jsx17(PlanningDivider, { kind: block.kind }, block.key);
|
|
4329
|
+
}),
|
|
4330
|
+
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
|
|
4331
|
+
] })
|
|
4332
|
+
] }) }),
|
|
4333
|
+
/* @__PURE__ */ jsx17(
|
|
4334
|
+
PinLatestUserMessage,
|
|
3054
4335
|
{
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
/* @__PURE__ */ jsx15("span", { children: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29" })
|
|
3059
|
-
]
|
|
4336
|
+
userMessageCount: userMessages.length,
|
|
4337
|
+
shouldPinLatestUser,
|
|
4338
|
+
targetKey: latestUserMessage?.render_id ?? latestUserMessage?.entry_id ?? (latestUserMessage ? `user:${userMessages.length}` : null)
|
|
3060
4339
|
},
|
|
3061
|
-
|
|
3062
|
-
)
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
}
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
/* @__PURE__ */ jsx15(ScrollToBottomButton, {})
|
|
3070
|
-
] }) });
|
|
4340
|
+
sessionId ?? "no-session"
|
|
4341
|
+
),
|
|
4342
|
+
/* @__PURE__ */ jsx17(ScrollToBottomButton, {})
|
|
4343
|
+
]
|
|
4344
|
+
},
|
|
4345
|
+
sessionId ?? "no-session"
|
|
4346
|
+
)
|
|
4347
|
+
] });
|
|
3071
4348
|
}
|
|
3072
|
-
function
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
4349
|
+
function PinLatestUserMessage({
|
|
4350
|
+
userMessageCount,
|
|
4351
|
+
shouldPinLatestUser,
|
|
4352
|
+
targetKey
|
|
4353
|
+
}) {
|
|
4354
|
+
const { contentRef, scrollRef, scrollToBottom, stopScroll } = useStickToBottomContext();
|
|
4355
|
+
const previousCountRef = useRef13(userMessageCount);
|
|
4356
|
+
const spacerHeightRef = useRef13(0);
|
|
4357
|
+
const getScrollElement = useCallback7(() => scrollRef.current, [scrollRef]);
|
|
4358
|
+
const getContentElement = useCallback7(() => contentRef.current, [contentRef]);
|
|
4359
|
+
const getTargetElement = useCallback7(() => {
|
|
4360
|
+
const rows = contentRef.current?.querySelectorAll(".blade-chat-user-row");
|
|
4361
|
+
return rows?.item((rows?.length ?? 0) - 1) ?? null;
|
|
4362
|
+
}, [contentRef]);
|
|
4363
|
+
const getSpacerHeight = useCallback7(() => spacerHeightRef.current, []);
|
|
4364
|
+
const setSpacerHeight = useCallback7(
|
|
4365
|
+
(height) => {
|
|
4366
|
+
spacerHeightRef.current = height;
|
|
4367
|
+
const content = contentRef.current;
|
|
4368
|
+
if (!content) return;
|
|
4369
|
+
if (height > 0) content.style.setProperty("--blade-chat-pin-spacer", `${height}px`);
|
|
4370
|
+
else content.style.removeProperty("--blade-chat-pin-spacer");
|
|
4371
|
+
},
|
|
4372
|
+
[contentRef]
|
|
4373
|
+
);
|
|
4374
|
+
useMessagePin({
|
|
4375
|
+
targetKey,
|
|
4376
|
+
pinTarget: shouldPinLatestUser,
|
|
4377
|
+
getScrollElement,
|
|
4378
|
+
getContentElement,
|
|
4379
|
+
getTargetElement,
|
|
4380
|
+
getSpacerHeight,
|
|
4381
|
+
setSpacerHeight,
|
|
4382
|
+
stopAutoScroll: stopScroll,
|
|
4383
|
+
scrollToBottom
|
|
4384
|
+
});
|
|
4385
|
+
useEffect12(() => {
|
|
4386
|
+
if (userMessageCount > previousCountRef.current && !shouldPinLatestUser) {
|
|
3077
4387
|
scrollToBottom("instant");
|
|
3078
4388
|
}
|
|
3079
4389
|
previousCountRef.current = userMessageCount;
|
|
3080
|
-
}, [
|
|
4390
|
+
}, [scrollToBottom, shouldPinLatestUser, userMessageCount]);
|
|
3081
4391
|
return null;
|
|
3082
4392
|
}
|
|
3083
4393
|
function ScrollToBottomButton() {
|
|
3084
4394
|
const { isAtBottom, scrollToBottom } = useStickToBottomContext();
|
|
3085
|
-
const [visible, setVisible] =
|
|
3086
|
-
const hideTimerRef =
|
|
3087
|
-
|
|
4395
|
+
const [visible, setVisible] = useState14(false);
|
|
4396
|
+
const hideTimerRef = useRef13(null);
|
|
4397
|
+
useEffect12(() => {
|
|
3088
4398
|
if (isAtBottom) {
|
|
3089
4399
|
if (!hideTimerRef.current) {
|
|
3090
4400
|
hideTimerRef.current = setTimeout(() => {
|
|
@@ -3106,7 +4416,7 @@ function ScrollToBottomButton() {
|
|
|
3106
4416
|
}
|
|
3107
4417
|
};
|
|
3108
4418
|
}, [isAtBottom]);
|
|
3109
|
-
const handleClick =
|
|
4419
|
+
const handleClick = useCallback7(() => {
|
|
3110
4420
|
if (hideTimerRef.current) {
|
|
3111
4421
|
clearTimeout(hideTimerRef.current);
|
|
3112
4422
|
hideTimerRef.current = null;
|
|
@@ -3115,7 +4425,7 @@ function ScrollToBottomButton() {
|
|
|
3115
4425
|
scrollToBottom();
|
|
3116
4426
|
}, [scrollToBottom]);
|
|
3117
4427
|
if (!visible) return null;
|
|
3118
|
-
return /* @__PURE__ */
|
|
4428
|
+
return /* @__PURE__ */ jsxs15(
|
|
3119
4429
|
"button",
|
|
3120
4430
|
{
|
|
3121
4431
|
type: "button",
|
|
@@ -3123,25 +4433,25 @@ function ScrollToBottomButton() {
|
|
|
3123
4433
|
"aria-label": "\u6EDA\u52A8\u5230\u5E95\u90E8",
|
|
3124
4434
|
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))]",
|
|
3125
4435
|
children: [
|
|
3126
|
-
/* @__PURE__ */
|
|
3127
|
-
/* @__PURE__ */
|
|
4436
|
+
/* @__PURE__ */ jsx17(ChevronDown, { size: 14 }),
|
|
4437
|
+
/* @__PURE__ */ jsx17("span", { className: "blade-chat-scroll-bottom-label", children: "\u6EDA\u52A8\u5230\u5E95\u90E8" })
|
|
3128
4438
|
]
|
|
3129
4439
|
}
|
|
3130
4440
|
);
|
|
3131
4441
|
}
|
|
3132
4442
|
function PlanningDivider({ kind }) {
|
|
3133
|
-
return /* @__PURE__ */
|
|
3134
|
-
/* @__PURE__ */
|
|
3135
|
-
/* @__PURE__ */
|
|
3136
|
-
/* @__PURE__ */
|
|
3137
|
-
/* @__PURE__ */
|
|
4443
|
+
return /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-3 py-1", children: [
|
|
4444
|
+
/* @__PURE__ */ jsx17("div", { className: "h-px flex-1 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent" }),
|
|
4445
|
+
/* @__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: [
|
|
4446
|
+
/* @__PURE__ */ jsx17(Lightbulb, { size: 12 }),
|
|
4447
|
+
/* @__PURE__ */ jsx17("span", { children: kind === "enter" ? "\u8FDB\u5165\u89C4\u5212\u6A21\u5F0F" : "\u89C4\u5212\u5B8C\u6210" })
|
|
3138
4448
|
] }),
|
|
3139
|
-
/* @__PURE__ */
|
|
4449
|
+
/* @__PURE__ */ jsx17("div", { className: "h-px flex-1 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent" })
|
|
3140
4450
|
] });
|
|
3141
4451
|
}
|
|
3142
4452
|
|
|
3143
4453
|
// src/components/ChatSurface.tsx
|
|
3144
|
-
import { jsx as
|
|
4454
|
+
import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
3145
4455
|
function themeAttr(theme) {
|
|
3146
4456
|
return theme === "dark" ? "dark" : void 0;
|
|
3147
4457
|
}
|
|
@@ -3161,6 +4471,7 @@ function ChatSurface({
|
|
|
3161
4471
|
onInputChange,
|
|
3162
4472
|
onSuggestion,
|
|
3163
4473
|
onSend,
|
|
4474
|
+
onAppend,
|
|
3164
4475
|
onStop,
|
|
3165
4476
|
sessionStatus,
|
|
3166
4477
|
askAnswers,
|
|
@@ -3171,9 +4482,11 @@ function ChatSurface({
|
|
|
3171
4482
|
onResultFeedbackSaved,
|
|
3172
4483
|
onFollowupInteraction,
|
|
3173
4484
|
beforeInput,
|
|
4485
|
+
showPlanUpdates = false,
|
|
4486
|
+
planRevealRevision = 0,
|
|
3174
4487
|
banner
|
|
3175
4488
|
}) {
|
|
3176
|
-
return /* @__PURE__ */
|
|
4489
|
+
return /* @__PURE__ */ jsxs16(
|
|
3177
4490
|
"div",
|
|
3178
4491
|
{
|
|
3179
4492
|
"data-theme": themeAttr(theme),
|
|
@@ -3182,14 +4495,14 @@ function ChatSurface({
|
|
|
3182
4495
|
classNames?.root
|
|
3183
4496
|
),
|
|
3184
4497
|
children: [
|
|
3185
|
-
/* @__PURE__ */
|
|
4498
|
+
/* @__PURE__ */ jsx18(ConnectionBanner, { connection, className: classNames?.banner }),
|
|
3186
4499
|
banner,
|
|
3187
|
-
errorMessage && /* @__PURE__ */
|
|
3188
|
-
/* @__PURE__ */
|
|
3189
|
-
/* @__PURE__ */
|
|
4500
|
+
errorMessage && /* @__PURE__ */ jsxs16("div", { className: "blade-chat-error-bar flex items-start gap-2 border-b px-4 py-3 text-sm", children: [
|
|
4501
|
+
/* @__PURE__ */ jsx18(CircleAlert, { size: 16, className: "mt-0.5 shrink-0" }),
|
|
4502
|
+
/* @__PURE__ */ jsx18("span", { className: "min-w-0 whitespace-pre-wrap break-words [overflow-wrap:anywhere]", children: chatErrorForDisplay2(errorMessage) })
|
|
3190
4503
|
] }),
|
|
3191
4504
|
slots?.header,
|
|
3192
|
-
/* @__PURE__ */
|
|
4505
|
+
/* @__PURE__ */ jsx18(
|
|
3193
4506
|
MessageList,
|
|
3194
4507
|
{
|
|
3195
4508
|
messages,
|
|
@@ -3200,6 +4513,7 @@ function ChatSurface({
|
|
|
3200
4513
|
askAnswers,
|
|
3201
4514
|
onAnswer,
|
|
3202
4515
|
toolCallRenderer: renderers?.toolCall,
|
|
4516
|
+
hidePlanUpdateTools: showPlanUpdates,
|
|
3203
4517
|
emptyState: slots?.emptyState,
|
|
3204
4518
|
className: classNames?.messageList,
|
|
3205
4519
|
sessionId,
|
|
@@ -3209,17 +4523,29 @@ function ChatSurface({
|
|
|
3209
4523
|
onFollowupInteraction
|
|
3210
4524
|
}
|
|
3211
4525
|
),
|
|
4526
|
+
showPlanUpdates ? /* @__PURE__ */ jsx18(
|
|
4527
|
+
CurrentPlanPanel,
|
|
4528
|
+
{
|
|
4529
|
+
messages,
|
|
4530
|
+
running: isStreaming,
|
|
4531
|
+
revealRevision: planRevealRevision,
|
|
4532
|
+
sessionId,
|
|
4533
|
+
className: "border-t border-[hsl(var(--border))]"
|
|
4534
|
+
}
|
|
4535
|
+
) : null,
|
|
3212
4536
|
beforeInput,
|
|
3213
|
-
/* @__PURE__ */
|
|
4537
|
+
/* @__PURE__ */ jsx18(
|
|
3214
4538
|
ChatInput,
|
|
3215
4539
|
{
|
|
3216
4540
|
value: inputText,
|
|
3217
4541
|
onValueChange: onInputChange,
|
|
3218
4542
|
onSend,
|
|
4543
|
+
onAppend,
|
|
3219
4544
|
onStop,
|
|
3220
4545
|
isStreaming,
|
|
3221
4546
|
isStopping,
|
|
3222
4547
|
placeholder,
|
|
4548
|
+
queueKey: sessionId,
|
|
3223
4549
|
className: classNames?.chatInput
|
|
3224
4550
|
}
|
|
3225
4551
|
),
|
|
@@ -3230,13 +4556,13 @@ function ChatSurface({
|
|
|
3230
4556
|
}
|
|
3231
4557
|
|
|
3232
4558
|
// src/components/AgentChat.tsx
|
|
3233
|
-
import { Fragment as
|
|
4559
|
+
import { Fragment as Fragment4, jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
3234
4560
|
function isUnauthorizedError(error) {
|
|
3235
4561
|
return error instanceof BladeApiError && error.status === 401;
|
|
3236
4562
|
}
|
|
3237
4563
|
function LoginCard({ client, onLoggedIn }) {
|
|
3238
|
-
const [loggingIn, setLoggingIn] =
|
|
3239
|
-
const [loginError, setLoginError] =
|
|
4564
|
+
const [loggingIn, setLoggingIn] = useState15(false);
|
|
4565
|
+
const [loginError, setLoginError] = useState15(null);
|
|
3240
4566
|
const handleLogin = async () => {
|
|
3241
4567
|
setLoggingIn(true);
|
|
3242
4568
|
setLoginError(null);
|
|
@@ -3249,11 +4575,11 @@ function LoginCard({ client, onLoggedIn }) {
|
|
|
3249
4575
|
setLoggingIn(false);
|
|
3250
4576
|
}
|
|
3251
4577
|
};
|
|
3252
|
-
return /* @__PURE__ */
|
|
3253
|
-
/* @__PURE__ */
|
|
3254
|
-
/* @__PURE__ */
|
|
3255
|
-
/* @__PURE__ */
|
|
3256
|
-
/* @__PURE__ */
|
|
4578
|
+
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: [
|
|
4579
|
+
/* @__PURE__ */ jsx19(LockKeyhole, { size: 28, className: "text-[hsl(var(--muted-foreground))]" }),
|
|
4580
|
+
/* @__PURE__ */ jsx19("div", { className: "text-base font-medium text-[hsl(var(--foreground))]", children: "\u9700\u8981\u767B\u5F55\u540E\u4F7F\u7528" }),
|
|
4581
|
+
/* @__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" }),
|
|
4582
|
+
/* @__PURE__ */ jsx19(
|
|
3257
4583
|
"button",
|
|
3258
4584
|
{
|
|
3259
4585
|
type: "button",
|
|
@@ -3263,20 +4589,20 @@ function LoginCard({ client, onLoggedIn }) {
|
|
|
3263
4589
|
children: loggingIn ? "\u767B\u5F55\u4E2D\u2026" : "\u767B\u5F55"
|
|
3264
4590
|
}
|
|
3265
4591
|
),
|
|
3266
|
-
loginError && /* @__PURE__ */
|
|
4592
|
+
loginError && /* @__PURE__ */ jsx19("div", { className: "text-xs text-[hsl(var(--destructive))]", children: loginError })
|
|
3267
4593
|
] }) });
|
|
3268
4594
|
}
|
|
3269
4595
|
function AgentChat(props) {
|
|
3270
4596
|
const client = useBladeClient();
|
|
3271
|
-
const [attempt, setAttempt] =
|
|
3272
|
-
const [needLogin, setNeedLogin] =
|
|
4597
|
+
const [attempt, setAttempt] = useState15(0);
|
|
4598
|
+
const [needLogin, setNeedLogin] = useState15(() => !client.hasToken());
|
|
3273
4599
|
if (needLogin) {
|
|
3274
|
-
return /* @__PURE__ */
|
|
4600
|
+
return /* @__PURE__ */ jsx19(
|
|
3275
4601
|
"div",
|
|
3276
4602
|
{
|
|
3277
4603
|
"data-theme": themeAttr(props.theme),
|
|
3278
4604
|
className: cn("blade-chat flex min-h-0 flex-1 flex-col", props.classNames?.root),
|
|
3279
|
-
children: /* @__PURE__ */
|
|
4605
|
+
children: /* @__PURE__ */ jsx19(
|
|
3280
4606
|
LoginCard,
|
|
3281
4607
|
{
|
|
3282
4608
|
client,
|
|
@@ -3289,7 +4615,7 @@ function AgentChat(props) {
|
|
|
3289
4615
|
}
|
|
3290
4616
|
);
|
|
3291
4617
|
}
|
|
3292
|
-
return /* @__PURE__ */
|
|
4618
|
+
return /* @__PURE__ */ jsx19(ChatSessionView, { ...props, onUnauthorized: () => setNeedLogin(true) }, attempt);
|
|
3293
4619
|
}
|
|
3294
4620
|
function ChatSessionView({
|
|
3295
4621
|
sessionId,
|
|
@@ -3306,17 +4632,33 @@ function ChatSessionView({
|
|
|
3306
4632
|
onUnauthorized
|
|
3307
4633
|
}) {
|
|
3308
4634
|
const client = useBladeClient();
|
|
4635
|
+
const [planRevealRevisions, setPlanRevealRevisions] = useState15(
|
|
4636
|
+
() => /* @__PURE__ */ new Map()
|
|
4637
|
+
);
|
|
4638
|
+
const handleSessionConnected = useCallback8((connectedSession) => {
|
|
4639
|
+
return connectedSession.on("toolResult", ({ toolCall, turn, source }) => {
|
|
4640
|
+
if (source === "reconnect_replay" || (turn.loop_id || "root") !== "root" || toolCall.status !== "done" || !isPlanUpdateTool(toolCall) || !parsePlanUpdate(toolCall.arguments)) {
|
|
4641
|
+
return;
|
|
4642
|
+
}
|
|
4643
|
+
setPlanRevealRevisions((current) => {
|
|
4644
|
+
const next = new Map(current);
|
|
4645
|
+
next.set(connectedSession.sessionId, (current.get(connectedSession.sessionId) ?? 0) + 1);
|
|
4646
|
+
return next;
|
|
4647
|
+
});
|
|
4648
|
+
});
|
|
4649
|
+
}, []);
|
|
3309
4650
|
const { session, state, error } = useAgentSession(sessionId, {
|
|
3310
4651
|
createOptions,
|
|
3311
|
-
onSessionCreated
|
|
4652
|
+
onSessionCreated,
|
|
4653
|
+
onSessionConnected: handleSessionConnected
|
|
3312
4654
|
});
|
|
3313
4655
|
const replay = useReplay(session);
|
|
3314
|
-
const [stopRequested, setStopRequested] =
|
|
3315
|
-
const [inputText, setInputText] =
|
|
3316
|
-
const [resultFeedback, setResultFeedback] =
|
|
4656
|
+
const [stopRequested, setStopRequested] = useState15(false);
|
|
4657
|
+
const [inputText, setInputText] = useState15("");
|
|
4658
|
+
const [resultFeedback, setResultFeedback] = useState15([]);
|
|
3317
4659
|
const resolvedSessionId = session?.sessionId;
|
|
3318
4660
|
const isViewer = state?.viewerRole === "viewer";
|
|
3319
|
-
|
|
4661
|
+
useEffect13(() => {
|
|
3320
4662
|
setResultFeedback([]);
|
|
3321
4663
|
if (!resolvedSessionId || isViewer) return;
|
|
3322
4664
|
let cancelled = false;
|
|
@@ -3345,18 +4687,18 @@ function ChatSessionView({
|
|
|
3345
4687
|
() => new Map(resultFeedback.map((item) => [item.assistant_entry_id, item])),
|
|
3346
4688
|
[resultFeedback]
|
|
3347
4689
|
);
|
|
3348
|
-
const handleResultFeedbackSaved =
|
|
4690
|
+
const handleResultFeedbackSaved = useCallback8((saved) => {
|
|
3349
4691
|
setResultFeedback((current) => [
|
|
3350
4692
|
...current.filter((item) => item.assistant_entry_id !== saved.assistant_entry_id),
|
|
3351
4693
|
saved
|
|
3352
4694
|
]);
|
|
3353
4695
|
}, []);
|
|
3354
|
-
|
|
4696
|
+
useEffect13(() => {
|
|
3355
4697
|
if (session) {
|
|
3356
4698
|
onSessionReady?.(session);
|
|
3357
4699
|
}
|
|
3358
4700
|
}, [session, onSessionReady]);
|
|
3359
|
-
|
|
4701
|
+
useEffect13(() => {
|
|
3360
4702
|
if (!session) return;
|
|
3361
4703
|
const offAttach = session.on("attachRequested", ({ label, content }) => {
|
|
3362
4704
|
setInputText((prev) => `${prev ? `${prev}
|
|
@@ -3372,12 +4714,12 @@ ${content}`);
|
|
|
3372
4714
|
offInsert();
|
|
3373
4715
|
};
|
|
3374
4716
|
}, [session]);
|
|
3375
|
-
|
|
4717
|
+
useEffect13(() => {
|
|
3376
4718
|
if (isUnauthorizedError(error)) {
|
|
3377
4719
|
onUnauthorized();
|
|
3378
4720
|
}
|
|
3379
4721
|
}, [error, onUnauthorized]);
|
|
3380
|
-
|
|
4722
|
+
useEffect13(() => {
|
|
3381
4723
|
if (!session || !commands) return;
|
|
3382
4724
|
const unsubscribes = Object.entries(commands).map(
|
|
3383
4725
|
([action, handler]) => session.onCommand(action, (payload) => handler(payload))
|
|
@@ -3387,6 +4729,7 @@ ${content}`);
|
|
|
3387
4729
|
};
|
|
3388
4730
|
}, [session, commands]);
|
|
3389
4731
|
const isStreaming = state?.isStreaming ?? false;
|
|
4732
|
+
const planRevealRevision = resolvedSessionId ? planRevealRevisions.get(resolvedSessionId) ?? 0 : 0;
|
|
3390
4733
|
const isStopping = stopRequested && isStreaming;
|
|
3391
4734
|
const connectError = error && !isUnauthorizedError(error) ? error.message || "\u8FDE\u63A5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" : null;
|
|
3392
4735
|
const errorMessage = connectError ?? state?.errorMessage ?? replay.error?.message ?? null;
|
|
@@ -3398,7 +4741,7 @@ ${content}`);
|
|
|
3398
4741
|
setStopRequested(true);
|
|
3399
4742
|
void session?.stop();
|
|
3400
4743
|
};
|
|
3401
|
-
return /* @__PURE__ */
|
|
4744
|
+
return /* @__PURE__ */ jsx19(
|
|
3402
4745
|
ChatSurface,
|
|
3403
4746
|
{
|
|
3404
4747
|
theme,
|
|
@@ -3408,8 +4751,8 @@ ${content}`);
|
|
|
3408
4751
|
slots,
|
|
3409
4752
|
placeholder,
|
|
3410
4753
|
connection: state?.connection ?? "connecting",
|
|
3411
|
-
banner: /* @__PURE__ */
|
|
3412
|
-
/* @__PURE__ */
|
|
4754
|
+
banner: /* @__PURE__ */ jsxs17(Fragment4, { children: [
|
|
4755
|
+
/* @__PURE__ */ jsx19(
|
|
3413
4756
|
ReplayBar,
|
|
3414
4757
|
{
|
|
3415
4758
|
isReplay: replay.isReplay,
|
|
@@ -3419,7 +4762,7 @@ ${content}`);
|
|
|
3419
4762
|
onExit: () => void replay.exitToAutonomous()
|
|
3420
4763
|
}
|
|
3421
4764
|
),
|
|
3422
|
-
/* @__PURE__ */
|
|
4765
|
+
/* @__PURE__ */ jsx19(ReplayMismatchPrompt, { mismatch: replay.mismatch })
|
|
3423
4766
|
] }),
|
|
3424
4767
|
errorMessage,
|
|
3425
4768
|
messages: state?.messages ?? [],
|
|
@@ -3427,11 +4770,16 @@ ${content}`);
|
|
|
3427
4770
|
resultFeedbackByEntry,
|
|
3428
4771
|
onResultFeedbackSaved: handleResultFeedbackSaved,
|
|
3429
4772
|
isStreaming,
|
|
4773
|
+
showPlanUpdates: true,
|
|
4774
|
+
planRevealRevision,
|
|
3430
4775
|
isStopping,
|
|
3431
4776
|
inputText,
|
|
3432
4777
|
onInputChange: setInputText,
|
|
3433
4778
|
onSuggestion: setInputText,
|
|
3434
4779
|
onSend: handleSend,
|
|
4780
|
+
onAppend: (text) => {
|
|
4781
|
+
void session?.send(text, { mode: state?.mode ?? void 0 });
|
|
4782
|
+
},
|
|
3435
4783
|
onStop: handleStop,
|
|
3436
4784
|
sessionStatus: state?.status ?? void 0,
|
|
3437
4785
|
askAnswers: state?.askAnswers,
|
|
@@ -3448,11 +4796,11 @@ ${content}`);
|
|
|
3448
4796
|
}
|
|
3449
4797
|
|
|
3450
4798
|
// src/components/LlmChat.tsx
|
|
3451
|
-
import { useEffect as
|
|
4799
|
+
import { useEffect as useEffect14, useMemo as useMemo9, useState as useState17 } from "react";
|
|
3452
4800
|
|
|
3453
4801
|
// src/components/LlmAdvancedSettings.tsx
|
|
3454
|
-
import { useState as
|
|
3455
|
-
import { jsx as
|
|
4802
|
+
import { useState as useState16 } from "react";
|
|
4803
|
+
import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
3456
4804
|
var FIELDS = [
|
|
3457
4805
|
{ id: "baseURL", label: "\u6A21\u578B\u670D\u52A1\u5730\u5740", placeholder: "http://\u5185\u7F51\u5730\u5740/v1" },
|
|
3458
4806
|
{ id: "model", label: "\u6A21\u578B", placeholder: "\u6A21\u578B\u540D\u79F0" },
|
|
@@ -3498,13 +4846,13 @@ function writeOverride(settings, baseURL, override) {
|
|
|
3498
4846
|
}
|
|
3499
4847
|
function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
3500
4848
|
const normalized = normalizeAdvanced(settings);
|
|
3501
|
-
const [open, setOpen] =
|
|
3502
|
-
const [draft, setDraft] =
|
|
4849
|
+
const [open, setOpen] = useState16(false);
|
|
4850
|
+
const [draft, setDraft] = useState16(override);
|
|
3503
4851
|
if (!normalized) return null;
|
|
3504
4852
|
const fields = FIELDS.filter((field) => normalized[field.id]);
|
|
3505
4853
|
const dirty = Object.keys(override).length > 0;
|
|
3506
|
-
return /* @__PURE__ */
|
|
3507
|
-
/* @__PURE__ */
|
|
4854
|
+
return /* @__PURE__ */ jsxs18("div", { className: "blade-chat-advanced border-t border-[hsl(var(--border))] px-4 py-2 text-xs", children: [
|
|
4855
|
+
/* @__PURE__ */ jsxs18(
|
|
3508
4856
|
"button",
|
|
3509
4857
|
{
|
|
3510
4858
|
type: "button",
|
|
@@ -3514,16 +4862,16 @@ function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
|
3514
4862
|
},
|
|
3515
4863
|
className: "flex items-center gap-1.5 text-[hsl(var(--muted-foreground))] transition-colors hover:text-[hsl(var(--foreground))]",
|
|
3516
4864
|
children: [
|
|
3517
|
-
/* @__PURE__ */
|
|
4865
|
+
/* @__PURE__ */ jsx20(Settings2, { size: 13 }),
|
|
3518
4866
|
"\u9AD8\u7EA7\u8BBE\u7F6E",
|
|
3519
|
-
dirty && /* @__PURE__ */
|
|
4867
|
+
dirty && /* @__PURE__ */ jsx20("span", { className: "text-[hsl(var(--primary))]", children: "\uFF08\u5DF2\u81EA\u5B9A\u4E49\uFF09" })
|
|
3520
4868
|
]
|
|
3521
4869
|
}
|
|
3522
4870
|
),
|
|
3523
|
-
open && /* @__PURE__ */
|
|
3524
|
-
fields.map((field) => /* @__PURE__ */
|
|
3525
|
-
/* @__PURE__ */
|
|
3526
|
-
/* @__PURE__ */
|
|
4871
|
+
open && /* @__PURE__ */ jsxs18("div", { className: "mt-2 flex flex-col gap-2", children: [
|
|
4872
|
+
fields.map((field) => /* @__PURE__ */ jsxs18("label", { className: "flex flex-col gap-1", children: [
|
|
4873
|
+
/* @__PURE__ */ jsx20("span", { className: "text-[hsl(var(--muted-foreground))]", children: field.label }),
|
|
4874
|
+
/* @__PURE__ */ jsx20(
|
|
3527
4875
|
"input",
|
|
3528
4876
|
{
|
|
3529
4877
|
type: field.secret ? "password" : "text",
|
|
@@ -3534,9 +4882,9 @@ function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
|
3534
4882
|
}
|
|
3535
4883
|
)
|
|
3536
4884
|
] }, field.id)),
|
|
3537
|
-
normalized.apiKey && /* @__PURE__ */
|
|
3538
|
-
/* @__PURE__ */
|
|
3539
|
-
/* @__PURE__ */
|
|
4885
|
+
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" }),
|
|
4886
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex gap-2", children: [
|
|
4887
|
+
/* @__PURE__ */ jsx20(
|
|
3540
4888
|
"button",
|
|
3541
4889
|
{
|
|
3542
4890
|
type: "button",
|
|
@@ -3551,7 +4899,7 @@ function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
|
3551
4899
|
children: "\u4FDD\u5B58"
|
|
3552
4900
|
}
|
|
3553
4901
|
),
|
|
3554
|
-
/* @__PURE__ */
|
|
4902
|
+
/* @__PURE__ */ jsx20(
|
|
3555
4903
|
"button",
|
|
3556
4904
|
{
|
|
3557
4905
|
type: "button",
|
|
@@ -3570,7 +4918,7 @@ function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
|
3570
4918
|
}
|
|
3571
4919
|
|
|
3572
4920
|
// src/components/LlmChat.tsx
|
|
3573
|
-
import { jsx as
|
|
4921
|
+
import { jsx as jsx21 } from "react/jsx-runtime";
|
|
3574
4922
|
function LlmChat({
|
|
3575
4923
|
classNames,
|
|
3576
4924
|
renderers,
|
|
@@ -3582,11 +4930,11 @@ function LlmChat({
|
|
|
3582
4930
|
onOverrideChange,
|
|
3583
4931
|
...options
|
|
3584
4932
|
}) {
|
|
3585
|
-
const [override, setOverride] =
|
|
4933
|
+
const [override, setOverride] = useState17(() => readOverride(advanced, options.baseURL));
|
|
3586
4934
|
const effective = { ...options, ...override };
|
|
3587
4935
|
const { messages, isStreaming, error, send, stop, reset } = useLlmChat(effective);
|
|
3588
|
-
const [inputText, setInputText] =
|
|
3589
|
-
const [stopRequested, setStopRequested] =
|
|
4936
|
+
const [inputText, setInputText] = useState17("");
|
|
4937
|
+
const [stopRequested, setStopRequested] = useState17(false);
|
|
3590
4938
|
const handle = useMemo9(
|
|
3591
4939
|
() => ({
|
|
3592
4940
|
insertText: (text) => setInputText((prev) => prev ? `${prev}
|
|
@@ -3596,10 +4944,10 @@ ${text}` : text),
|
|
|
3596
4944
|
}),
|
|
3597
4945
|
[send, reset]
|
|
3598
4946
|
);
|
|
3599
|
-
|
|
4947
|
+
useEffect14(() => {
|
|
3600
4948
|
onReady?.(handle);
|
|
3601
4949
|
}, [handle, onReady]);
|
|
3602
|
-
return /* @__PURE__ */
|
|
4950
|
+
return /* @__PURE__ */ jsx21(
|
|
3603
4951
|
ChatSurface,
|
|
3604
4952
|
{
|
|
3605
4953
|
theme,
|
|
@@ -3624,7 +4972,7 @@ ${text}` : text),
|
|
|
3624
4972
|
setStopRequested(true);
|
|
3625
4973
|
stop();
|
|
3626
4974
|
},
|
|
3627
|
-
beforeInput: advanced ? /* @__PURE__ */
|
|
4975
|
+
beforeInput: advanced ? /* @__PURE__ */ jsx21(
|
|
3628
4976
|
LlmAdvancedSettingsBar,
|
|
3629
4977
|
{
|
|
3630
4978
|
settings: advanced,
|
|
@@ -3642,14 +4990,14 @@ ${text}` : text),
|
|
|
3642
4990
|
}
|
|
3643
4991
|
|
|
3644
4992
|
// src/components/ChatView.tsx
|
|
3645
|
-
import { jsx as
|
|
4993
|
+
import { jsx as jsx22 } from "react/jsx-runtime";
|
|
3646
4994
|
function ChatView(props) {
|
|
3647
4995
|
const { mode, llm, onLlmReady, ...rest } = props;
|
|
3648
4996
|
if (mode === "llm") {
|
|
3649
4997
|
if (!llm) {
|
|
3650
4998
|
throw new Error('ChatView: mode="llm" \u9700\u8981\u540C\u65F6\u4F20 llm={{ baseURL, model }}');
|
|
3651
4999
|
}
|
|
3652
|
-
return /* @__PURE__ */
|
|
5000
|
+
return /* @__PURE__ */ jsx22(
|
|
3653
5001
|
LlmChat,
|
|
3654
5002
|
{
|
|
3655
5003
|
...llm,
|
|
@@ -3662,7 +5010,232 @@ function ChatView(props) {
|
|
|
3662
5010
|
}
|
|
3663
5011
|
);
|
|
3664
5012
|
}
|
|
3665
|
-
return /* @__PURE__ */
|
|
5013
|
+
return /* @__PURE__ */ jsx22(AgentChat, { ...rest });
|
|
5014
|
+
}
|
|
5015
|
+
|
|
5016
|
+
// src/components/ContextCard.tsx
|
|
5017
|
+
import {
|
|
5018
|
+
getContextDisplayState,
|
|
5019
|
+
getContextGroupDisplayState
|
|
5020
|
+
} from "@blade-hq/agent-client";
|
|
5021
|
+
import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
5022
|
+
function ContextCard({ context, className }) {
|
|
5023
|
+
const display = getContextDisplayState(context);
|
|
5024
|
+
return /* @__PURE__ */ jsxs19(
|
|
5025
|
+
"details",
|
|
5026
|
+
{
|
|
5027
|
+
className: `blade-chat-context-card group/context-card rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] text-sm ${className ?? ""}`,
|
|
5028
|
+
children: [
|
|
5029
|
+
/* @__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: [
|
|
5030
|
+
/* @__PURE__ */ jsx23(
|
|
5031
|
+
Layers,
|
|
5032
|
+
{
|
|
5033
|
+
size: 15,
|
|
5034
|
+
className: "blade-chat-context-icon shrink-0 text-[hsl(var(--muted-foreground))]",
|
|
5035
|
+
"aria-hidden": "true"
|
|
5036
|
+
}
|
|
5037
|
+
),
|
|
5038
|
+
/* @__PURE__ */ jsxs19("span", { className: "blade-chat-context-copy min-w-0 flex-1", children: [
|
|
5039
|
+
/* @__PURE__ */ jsx23("span", { className: "blade-chat-context-title block font-medium text-[hsl(var(--foreground))]", children: display.title }),
|
|
5040
|
+
/* @__PURE__ */ jsx23("span", { className: "blade-chat-context-status block truncate text-xs text-[hsl(var(--muted-foreground))]", children: display.summary })
|
|
5041
|
+
] }),
|
|
5042
|
+
/* @__PURE__ */ jsx23(
|
|
5043
|
+
ChevronDown,
|
|
5044
|
+
{
|
|
5045
|
+
size: 14,
|
|
5046
|
+
className: "blade-chat-context-chevron shrink-0 text-[hsl(var(--muted-foreground))] transition-transform group-open/context-card:rotate-180",
|
|
5047
|
+
"aria-hidden": "true"
|
|
5048
|
+
}
|
|
5049
|
+
)
|
|
5050
|
+
] }),
|
|
5051
|
+
/* @__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 })
|
|
5052
|
+
]
|
|
5053
|
+
}
|
|
5054
|
+
);
|
|
5055
|
+
}
|
|
5056
|
+
function ContextGroupCard({ contexts, className }) {
|
|
5057
|
+
if (contexts.length === 0) return null;
|
|
5058
|
+
const single = contexts.length === 1 ? getContextDisplayState(contexts[0]) : null;
|
|
5059
|
+
const group = single ? null : getContextGroupDisplayState(contexts);
|
|
5060
|
+
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: [
|
|
5061
|
+
/* @__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: [
|
|
5062
|
+
/* @__PURE__ */ jsx23(
|
|
5063
|
+
Layers,
|
|
5064
|
+
{
|
|
5065
|
+
size: 15,
|
|
5066
|
+
className: "blade-chat-context-icon shrink-0 text-[hsl(var(--muted-foreground))]",
|
|
5067
|
+
"aria-hidden": "true"
|
|
5068
|
+
}
|
|
5069
|
+
),
|
|
5070
|
+
/* @__PURE__ */ jsxs19("span", { className: "blade-chat-context-copy min-w-0 flex-1", children: [
|
|
5071
|
+
/* @__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` }),
|
|
5072
|
+
/* @__PURE__ */ jsx23("span", { className: "blade-chat-context-status block truncate text-xs text-[hsl(var(--muted-foreground))]", children: single ? single.summary : group?.summary })
|
|
5073
|
+
] }),
|
|
5074
|
+
/* @__PURE__ */ jsx23(
|
|
5075
|
+
ChevronDown,
|
|
5076
|
+
{
|
|
5077
|
+
size: 14,
|
|
5078
|
+
className: "blade-chat-context-chevron shrink-0 text-[hsl(var(--muted-foreground))] transition-transform group-open/context-group:rotate-180",
|
|
5079
|
+
"aria-hidden": "true"
|
|
5080
|
+
}
|
|
5081
|
+
)
|
|
5082
|
+
] }),
|
|
5083
|
+
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(
|
|
5084
|
+
ContextCard,
|
|
5085
|
+
{
|
|
5086
|
+
context
|
|
5087
|
+
},
|
|
5088
|
+
`${context.context_kind}:${context.context_key}`
|
|
5089
|
+
)) })
|
|
5090
|
+
] });
|
|
5091
|
+
}
|
|
5092
|
+
|
|
5093
|
+
// src/components/SessionMemoryToggle.tsx
|
|
5094
|
+
import { useCallback as useCallback9, useEffect as useEffect15, useRef as useRef14, useState as useState18, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
5095
|
+
import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
5096
|
+
var saveStates = /* @__PURE__ */ new WeakMap();
|
|
5097
|
+
function getSaveState(client, sessionId) {
|
|
5098
|
+
let clientStates = saveStates.get(client);
|
|
5099
|
+
if (!clientStates) {
|
|
5100
|
+
clientStates = /* @__PURE__ */ new Map();
|
|
5101
|
+
saveStates.set(client, clientStates);
|
|
5102
|
+
}
|
|
5103
|
+
let state = clientStates.get(sessionId);
|
|
5104
|
+
if (!state) {
|
|
5105
|
+
state = { saving: false, listeners: /* @__PURE__ */ new Set() };
|
|
5106
|
+
clientStates.set(sessionId, state);
|
|
5107
|
+
}
|
|
5108
|
+
return state;
|
|
5109
|
+
}
|
|
5110
|
+
function notify(state) {
|
|
5111
|
+
for (const listener of state.listeners) listener();
|
|
5112
|
+
}
|
|
5113
|
+
function cleanupSaveState(client, sessionId, state) {
|
|
5114
|
+
if (state.saving || state.listeners.size > 0) return;
|
|
5115
|
+
const clientStates = saveStates.get(client);
|
|
5116
|
+
if (clientStates?.get(sessionId) === state) clientStates.delete(sessionId);
|
|
5117
|
+
}
|
|
5118
|
+
function SessionMemoryToggle({
|
|
5119
|
+
sessionId,
|
|
5120
|
+
enabled,
|
|
5121
|
+
client: clientProp,
|
|
5122
|
+
disabled = false,
|
|
5123
|
+
label = "\u5F53\u524D\u4F1A\u8BDD\u4F7F\u7528\u8BB0\u5FC6",
|
|
5124
|
+
className,
|
|
5125
|
+
labelClassName,
|
|
5126
|
+
inputClassName,
|
|
5127
|
+
onSaved,
|
|
5128
|
+
onError
|
|
5129
|
+
}) {
|
|
5130
|
+
const contextClient = useOptionalBladeClient();
|
|
5131
|
+
const client = clientProp ?? contextClient;
|
|
5132
|
+
if (!client) {
|
|
5133
|
+
throw new Error("SessionMemoryToggle \u5FC5\u987B\u5728 <BladeProvider> \u5185\u4F7F\u7528\u6216\u663E\u5F0F\u4F20\u5165 client");
|
|
5134
|
+
}
|
|
5135
|
+
const saveState = getSaveState(client, sessionId);
|
|
5136
|
+
const subscribe = useCallback9(
|
|
5137
|
+
(listener) => {
|
|
5138
|
+
saveState.listeners.add(listener);
|
|
5139
|
+
return () => {
|
|
5140
|
+
saveState.listeners.delete(listener);
|
|
5141
|
+
cleanupSaveState(client, sessionId, saveState);
|
|
5142
|
+
};
|
|
5143
|
+
},
|
|
5144
|
+
[client, saveState, sessionId]
|
|
5145
|
+
);
|
|
5146
|
+
const getSaving = useCallback9(() => saveState.saving, [saveState]);
|
|
5147
|
+
const saving = useSyncExternalStore2(
|
|
5148
|
+
subscribe,
|
|
5149
|
+
getSaving,
|
|
5150
|
+
getSaving
|
|
5151
|
+
);
|
|
5152
|
+
const [draftEnabled, setDraftEnabled] = useState18(enabled);
|
|
5153
|
+
const activeSessionIdRef = useRef14(sessionId);
|
|
5154
|
+
activeSessionIdRef.current = sessionId;
|
|
5155
|
+
useEffect15(() => {
|
|
5156
|
+
setDraftEnabled(enabled);
|
|
5157
|
+
}, [enabled, sessionId]);
|
|
5158
|
+
const update = useCallback9(
|
|
5159
|
+
(nextEnabled) => {
|
|
5160
|
+
const currentSaveState = getSaveState(client, sessionId);
|
|
5161
|
+
if (currentSaveState.saving) return;
|
|
5162
|
+
currentSaveState.saving = true;
|
|
5163
|
+
notify(currentSaveState);
|
|
5164
|
+
setDraftEnabled(nextEnabled);
|
|
5165
|
+
void client.sessions.updateSessionMemory(sessionId, nextEnabled).then(
|
|
5166
|
+
(updated) => {
|
|
5167
|
+
if (activeSessionIdRef.current === sessionId) {
|
|
5168
|
+
setDraftEnabled(updated.memory_enabled);
|
|
5169
|
+
}
|
|
5170
|
+
onSaved?.(sessionId, updated.memory_enabled);
|
|
5171
|
+
},
|
|
5172
|
+
(error) => {
|
|
5173
|
+
if (activeSessionIdRef.current === sessionId) setDraftEnabled(enabled);
|
|
5174
|
+
onError?.(error);
|
|
5175
|
+
}
|
|
5176
|
+
).finally(() => {
|
|
5177
|
+
currentSaveState.saving = false;
|
|
5178
|
+
notify(currentSaveState);
|
|
5179
|
+
cleanupSaveState(client, sessionId, currentSaveState);
|
|
5180
|
+
});
|
|
5181
|
+
},
|
|
5182
|
+
[client, enabled, onError, onSaved, sessionId]
|
|
5183
|
+
);
|
|
5184
|
+
return /* @__PURE__ */ jsxs20("label", { className: cn("flex items-center justify-between", className), children: [
|
|
5185
|
+
/* @__PURE__ */ jsx24("span", { className: labelClassName, children: label }),
|
|
5186
|
+
/* @__PURE__ */ jsx24(
|
|
5187
|
+
"input",
|
|
5188
|
+
{
|
|
5189
|
+
type: "checkbox",
|
|
5190
|
+
checked: draftEnabled,
|
|
5191
|
+
onChange: (event) => update(event.target.checked),
|
|
5192
|
+
disabled: disabled || saving,
|
|
5193
|
+
className: inputClassName
|
|
5194
|
+
}
|
|
5195
|
+
)
|
|
5196
|
+
] });
|
|
5197
|
+
}
|
|
5198
|
+
|
|
5199
|
+
// src/lib/agent-computer-command.ts
|
|
5200
|
+
var COMPUTER_LAUNCH_COMMAND_PATTERN = /(?:^|[\n;&|(]\s*)computer\s+launch(?:\s|$)/;
|
|
5201
|
+
function isAgentComputerCommand(command) {
|
|
5202
|
+
return COMPUTER_LAUNCH_COMMAND_PATTERN.test(command);
|
|
5203
|
+
}
|
|
5204
|
+
function isAgentComputerToolCall(argumentsJson) {
|
|
5205
|
+
if (!argumentsJson) return false;
|
|
5206
|
+
let command;
|
|
5207
|
+
try {
|
|
5208
|
+
const parsed = JSON.parse(argumentsJson);
|
|
5209
|
+
if (typeof parsed !== "object" || parsed === null) return false;
|
|
5210
|
+
command = parsed.command;
|
|
5211
|
+
} catch {
|
|
5212
|
+
return false;
|
|
5213
|
+
}
|
|
5214
|
+
return typeof command === "string" && isAgentComputerCommand(command);
|
|
5215
|
+
}
|
|
5216
|
+
var LAUNCH_SUCCESS_MARKER = "\u5DF2\u542F\u52A8 ";
|
|
5217
|
+
function resultContainsLaunchSuccessMarker(result, depth = 0) {
|
|
5218
|
+
if (depth > 2) return false;
|
|
5219
|
+
if (typeof result === "string") {
|
|
5220
|
+
if (result.includes(LAUNCH_SUCCESS_MARKER)) return true;
|
|
5221
|
+
try {
|
|
5222
|
+
return resultContainsLaunchSuccessMarker(JSON.parse(result), depth + 1);
|
|
5223
|
+
} catch {
|
|
5224
|
+
return false;
|
|
5225
|
+
}
|
|
5226
|
+
}
|
|
5227
|
+
if (typeof result === "object" && result !== null) {
|
|
5228
|
+
for (const value of Object.values(result)) {
|
|
5229
|
+
if (typeof value === "string" && value.includes(LAUNCH_SUCCESS_MARKER)) return true;
|
|
5230
|
+
}
|
|
5231
|
+
}
|
|
5232
|
+
return false;
|
|
5233
|
+
}
|
|
5234
|
+
function classifyAgentComputerLaunchOutcome(toolCall) {
|
|
5235
|
+
if (toolCall.status === "error" || toolCall.status === "cancelled") return "failed";
|
|
5236
|
+
if (toolCall.status !== "done") return "pending";
|
|
5237
|
+
if (toolCall.result === void 0 || toolCall.result === null) return "unknown";
|
|
5238
|
+
return resultContainsLaunchSuccessMarker(toolCall.result) ? "succeeded" : "failed";
|
|
3666
5239
|
}
|
|
3667
5240
|
|
|
3668
5241
|
// src/index.ts
|
|
@@ -3671,13 +5244,32 @@ export {
|
|
|
3671
5244
|
AgentChat,
|
|
3672
5245
|
BladeProvider,
|
|
3673
5246
|
ChatView,
|
|
5247
|
+
ContextCard,
|
|
5248
|
+
ContextGroupCard,
|
|
5249
|
+
CurrentPlanPanel,
|
|
3674
5250
|
LlmChat,
|
|
3675
5251
|
MarkdownContent,
|
|
5252
|
+
MemoryRefsHint,
|
|
5253
|
+
PLAN_AUTO_COLLAPSE_MS,
|
|
5254
|
+
PlanUpdateBlock,
|
|
3676
5255
|
ReplayBar,
|
|
3677
5256
|
ReplayMismatchPrompt,
|
|
5257
|
+
SessionMemoryToggle,
|
|
5258
|
+
WhatIfUserBubble,
|
|
5259
|
+
classifyAgentComputerLaunchOutcome,
|
|
5260
|
+
collectMemoryRefs,
|
|
5261
|
+
getPlanUpdateDisplayState,
|
|
5262
|
+
isAgentComputerCommand,
|
|
5263
|
+
isAgentComputerToolCall,
|
|
5264
|
+
isPlanUpdateTool,
|
|
5265
|
+
normalizeAdjacentUrlFormatting,
|
|
5266
|
+
parsePlanUpdate,
|
|
5267
|
+
parseWhatIfPrompt,
|
|
5268
|
+
pickCurrentPlanStep,
|
|
3678
5269
|
useAgentSession,
|
|
3679
5270
|
useBladeClient,
|
|
3680
5271
|
useLlmChat,
|
|
5272
|
+
useMessagePin,
|
|
3681
5273
|
useReplay
|
|
3682
5274
|
};
|
|
3683
5275
|
/*! Bundled license information:
|
|
@@ -3689,29 +5281,35 @@ lucide-react/dist/esm/createLucideIcon.js:
|
|
|
3689
5281
|
lucide-react/dist/esm/icons/arrow-right.js:
|
|
3690
5282
|
lucide-react/dist/esm/icons/arrow-up-right.js:
|
|
3691
5283
|
lucide-react/dist/esm/icons/arrow-up.js:
|
|
5284
|
+
lucide-react/dist/esm/icons/book-open.js:
|
|
3692
5285
|
lucide-react/dist/esm/icons/bot.js:
|
|
3693
|
-
lucide-react/dist/esm/icons/brain.js:
|
|
3694
5286
|
lucide-react/dist/esm/icons/check.js:
|
|
3695
5287
|
lucide-react/dist/esm/icons/chevron-down.js:
|
|
3696
5288
|
lucide-react/dist/esm/icons/chevron-right.js:
|
|
3697
5289
|
lucide-react/dist/esm/icons/circle-alert.js:
|
|
5290
|
+
lucide-react/dist/esm/icons/circle-dot.js:
|
|
5291
|
+
lucide-react/dist/esm/icons/circle.js:
|
|
3698
5292
|
lucide-react/dist/esm/icons/copy.js:
|
|
3699
|
-
lucide-react/dist/esm/icons/
|
|
5293
|
+
lucide-react/dist/esm/icons/earth.js:
|
|
5294
|
+
lucide-react/dist/esm/icons/file-pen-line.js:
|
|
3700
5295
|
lucide-react/dist/esm/icons/file-text.js:
|
|
3701
|
-
lucide-react/dist/esm/icons/file.js:
|
|
3702
|
-
lucide-react/dist/esm/icons/film.js:
|
|
3703
5296
|
lucide-react/dist/esm/icons/globe.js:
|
|
3704
5297
|
lucide-react/dist/esm/icons/layers.js:
|
|
3705
5298
|
lucide-react/dist/esm/icons/lightbulb.js:
|
|
5299
|
+
lucide-react/dist/esm/icons/list-checks.js:
|
|
3706
5300
|
lucide-react/dist/esm/icons/loader-circle.js:
|
|
3707
5301
|
lucide-react/dist/esm/icons/lock-keyhole.js:
|
|
3708
5302
|
lucide-react/dist/esm/icons/message-square-more.js:
|
|
3709
5303
|
lucide-react/dist/esm/icons/message-square.js:
|
|
3710
5304
|
lucide-react/dist/esm/icons/play.js:
|
|
5305
|
+
lucide-react/dist/esm/icons/refresh-ccw.js:
|
|
5306
|
+
lucide-react/dist/esm/icons/search.js:
|
|
3711
5307
|
lucide-react/dist/esm/icons/settings-2.js:
|
|
3712
5308
|
lucide-react/dist/esm/icons/sparkles.js:
|
|
3713
5309
|
lucide-react/dist/esm/icons/square.js:
|
|
5310
|
+
lucide-react/dist/esm/icons/terminal.js:
|
|
3714
5311
|
lucide-react/dist/esm/icons/triangle-alert.js:
|
|
5312
|
+
lucide-react/dist/esm/icons/wrench.js:
|
|
3715
5313
|
lucide-react/dist/esm/icons/x.js:
|
|
3716
5314
|
lucide-react/dist/esm/lucide-react.js:
|
|
3717
5315
|
(**
|