@blade-hq/agent-react 2610.0.0-beta.5 → 2610.0.0-beta.50
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 +91 -3
- package/dist/components/AgentChat.d.ts +3 -0
- package/dist/components/AgentLoopBlock.d.ts +1 -4
- package/dist/components/AskUserQuestionBlock.d.ts +9 -0
- package/dist/components/AssistantTurnBlock.d.ts +20 -6
- package/dist/components/ChatInput.d.ts +4 -1
- package/dist/components/ChatSurface.d.ts +12 -1
- package/dist/components/ConnectionBanner.d.ts +1 -1
- package/dist/components/ContextCard.d.ts +20 -0
- package/dist/components/MarkdownContent.d.ts +2 -0
- package/dist/components/MessageList.d.ts +8 -1
- package/dist/components/PlanUpdateBlock.d.ts +31 -0
- package/dist/components/SessionMemoryToggle.d.ts +19 -0
- package/dist/components/SessionPluginSelector.d.ts +10 -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 +29 -0
- package/dist/hooks/use-agent-session.d.ts +5 -0
- package/dist/hooks/use-message-pin.d.ts +28 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +2690 -838
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-computer-command.d.ts +34 -0
- package/dist/lib/utils.d.ts +3 -1
- package/dist/lib/whatif-prompt.d.ts +18 -0
- package/dist/style.css +91 -1
- package/dist/style.full.css +92 -2
- package/package.json +2 -2
- package/public-api.md +614 -9
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,187 @@ 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
|
+
isStreaming = true,
|
|
621
|
+
layoutKey,
|
|
622
|
+
getScrollElement,
|
|
623
|
+
getContentElement,
|
|
624
|
+
getTargetElement,
|
|
625
|
+
getTargetScrollTop,
|
|
626
|
+
getSpacerHeight,
|
|
627
|
+
setSpacerHeight,
|
|
628
|
+
stopAutoScroll,
|
|
629
|
+
// 不再内部调用(见下面 reposition 里的说明),但这是公开的 SDK hook 参数,
|
|
630
|
+
// 删掉会让已经传这个 prop 的调用方在 TS 严格类型下编译不过——留着接口
|
|
631
|
+
// 不动,只是这里不再用它。
|
|
632
|
+
// biome-ignore lint/correctness/noUnusedVariables: 保留公开 API 形状
|
|
633
|
+
scrollToBottom,
|
|
634
|
+
margin = DEFAULT_MARGIN_PX
|
|
635
|
+
}) {
|
|
636
|
+
const pinActiveRef = useRef3(false);
|
|
637
|
+
const frameRef = useRef3(null);
|
|
638
|
+
const retryTimeoutRef = useRef3(null);
|
|
639
|
+
const manualScrollCheckTimeoutRef = useRef3(null);
|
|
640
|
+
const repositionPendingRef = useRef3(false);
|
|
641
|
+
const pinnedTargetScrollTopRef = useRef3(null);
|
|
642
|
+
const appliedTargetKeyRef = useRef3(null);
|
|
643
|
+
const initialObservedTargetKeyRef = useRef3(pinTarget ? null : targetKey);
|
|
644
|
+
const observedTargetKeyRef = useRef3(initialObservedTargetKeyRef.current);
|
|
645
|
+
const setSpacerHeightRef = useRef3(setSpacerHeight);
|
|
646
|
+
setSpacerHeightRef.current = setSpacerHeight;
|
|
647
|
+
const release = useCallback3(() => {
|
|
648
|
+
if (!pinActiveRef.current) return;
|
|
649
|
+
pinActiveRef.current = false;
|
|
650
|
+
repositionPendingRef.current = false;
|
|
651
|
+
pinnedTargetScrollTopRef.current = null;
|
|
652
|
+
if (frameRef.current != null) cancelAnimationFrame(frameRef.current);
|
|
653
|
+
frameRef.current = null;
|
|
654
|
+
if (manualScrollCheckTimeoutRef.current != null) {
|
|
655
|
+
clearTimeout(manualScrollCheckTimeoutRef.current);
|
|
656
|
+
manualScrollCheckTimeoutRef.current = null;
|
|
657
|
+
}
|
|
658
|
+
setSpacerHeightRef.current(0);
|
|
659
|
+
}, []);
|
|
660
|
+
const reposition = useCallback3(
|
|
661
|
+
() => {
|
|
662
|
+
const scroll = getScrollElement();
|
|
663
|
+
const target = getTargetElement();
|
|
664
|
+
if (!scroll || !pinActiveRef.current) return;
|
|
665
|
+
const targetScrollTop = target ? Math.max(
|
|
666
|
+
0,
|
|
667
|
+
scroll.scrollTop + target.getBoundingClientRect().top - scroll.getBoundingClientRect().top - margin
|
|
668
|
+
) : getTargetScrollTop?.(scroll);
|
|
669
|
+
if (targetScrollTop == null) return;
|
|
670
|
+
const previousSpacerHeight = getSpacerHeight();
|
|
671
|
+
const baseScrollHeight = scroll.scrollHeight - previousSpacerHeight;
|
|
672
|
+
const nextSpacerHeight = Math.max(
|
|
673
|
+
0,
|
|
674
|
+
targetScrollTop + scroll.clientHeight - baseScrollHeight
|
|
675
|
+
);
|
|
676
|
+
setSpacerHeightRef.current(nextSpacerHeight);
|
|
677
|
+
pinnedTargetScrollTopRef.current = targetScrollTop;
|
|
678
|
+
stopAutoScroll();
|
|
679
|
+
scroll.scrollTop = targetScrollTop;
|
|
680
|
+
if (nextSpacerHeight === 0 && previousSpacerHeight > 0) {
|
|
681
|
+
pinActiveRef.current = false;
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
[
|
|
685
|
+
getScrollElement,
|
|
686
|
+
getSpacerHeight,
|
|
687
|
+
getTargetElement,
|
|
688
|
+
getTargetScrollTop,
|
|
689
|
+
margin,
|
|
690
|
+
stopAutoScroll
|
|
691
|
+
]
|
|
692
|
+
);
|
|
693
|
+
const scheduleReposition = useCallback3(
|
|
694
|
+
() => {
|
|
695
|
+
if (!pinActiveRef.current) return;
|
|
696
|
+
repositionPendingRef.current = true;
|
|
697
|
+
if (frameRef.current != null) return;
|
|
698
|
+
frameRef.current = requestAnimationFrame(() => {
|
|
699
|
+
frameRef.current = null;
|
|
700
|
+
const shouldReposition = repositionPendingRef.current;
|
|
701
|
+
repositionPendingRef.current = false;
|
|
702
|
+
if (shouldReposition) reposition();
|
|
703
|
+
});
|
|
704
|
+
},
|
|
705
|
+
[reposition]
|
|
706
|
+
);
|
|
707
|
+
useEffect3(() => {
|
|
708
|
+
if (!targetKey) {
|
|
709
|
+
release();
|
|
710
|
+
appliedTargetKeyRef.current = null;
|
|
711
|
+
observedTargetKeyRef.current = null;
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
if (!pinTarget) {
|
|
715
|
+
if (pinActiveRef.current && appliedTargetKeyRef.current != null && (appliedTargetKeyRef.current !== targetKey || !isStreaming)) {
|
|
716
|
+
release();
|
|
717
|
+
}
|
|
718
|
+
observedTargetKeyRef.current = targetKey;
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
if (appliedTargetKeyRef.current === targetKey) {
|
|
722
|
+
observedTargetKeyRef.current = targetKey;
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (observedTargetKeyRef.current === targetKey) return;
|
|
726
|
+
appliedTargetKeyRef.current = targetKey;
|
|
727
|
+
observedTargetKeyRef.current = targetKey;
|
|
728
|
+
pinActiveRef.current = true;
|
|
729
|
+
stopAutoScroll();
|
|
730
|
+
scheduleReposition();
|
|
731
|
+
if (retryTimeoutRef.current != null) clearTimeout(retryTimeoutRef.current);
|
|
732
|
+
retryTimeoutRef.current = window.setTimeout(() => {
|
|
733
|
+
retryTimeoutRef.current = null;
|
|
734
|
+
scheduleReposition();
|
|
735
|
+
}, 80);
|
|
736
|
+
}, [isStreaming, pinTarget, release, scheduleReposition, stopAutoScroll, targetKey]);
|
|
737
|
+
useEffect3(() => {
|
|
738
|
+
if (layoutKey !== void 0) scheduleReposition();
|
|
739
|
+
}, [layoutKey, scheduleReposition]);
|
|
740
|
+
useEffect3(() => {
|
|
741
|
+
const scroll = getScrollElement();
|
|
742
|
+
if (!scroll) return;
|
|
743
|
+
const content = getContentElement?.();
|
|
744
|
+
const observer = new ResizeObserver(scheduleReposition);
|
|
745
|
+
observer.observe(scroll);
|
|
746
|
+
if (content && content !== scroll) observer.observe(content);
|
|
747
|
+
window.addEventListener("resize", scheduleReposition);
|
|
748
|
+
window.visualViewport?.addEventListener("resize", scheduleReposition);
|
|
749
|
+
const handleScroll = () => {
|
|
750
|
+
if (!pinActiveRef.current) return;
|
|
751
|
+
if (manualScrollCheckTimeoutRef.current != null) {
|
|
752
|
+
clearTimeout(manualScrollCheckTimeoutRef.current);
|
|
753
|
+
}
|
|
754
|
+
manualScrollCheckTimeoutRef.current = window.setTimeout(() => {
|
|
755
|
+
manualScrollCheckTimeoutRef.current = null;
|
|
756
|
+
if (!pinActiveRef.current || repositionPendingRef.current) return;
|
|
757
|
+
const targetScrollTop = pinnedTargetScrollTopRef.current;
|
|
758
|
+
if (targetScrollTop != null && Math.abs(scroll.scrollTop - targetScrollTop) > MANUAL_SCROLL_TOLERANCE_PX) {
|
|
759
|
+
release();
|
|
760
|
+
}
|
|
761
|
+
}, 0);
|
|
762
|
+
};
|
|
763
|
+
scroll.addEventListener("scroll", handleScroll, { passive: true });
|
|
764
|
+
return () => {
|
|
765
|
+
observer.disconnect();
|
|
766
|
+
window.removeEventListener("resize", scheduleReposition);
|
|
767
|
+
window.visualViewport?.removeEventListener("resize", scheduleReposition);
|
|
768
|
+
scroll.removeEventListener("scroll", handleScroll);
|
|
769
|
+
};
|
|
770
|
+
}, [getContentElement, getScrollElement, release, scheduleReposition]);
|
|
771
|
+
useEffect3(
|
|
772
|
+
() => () => {
|
|
773
|
+
if (frameRef.current != null) cancelAnimationFrame(frameRef.current);
|
|
774
|
+
frameRef.current = null;
|
|
775
|
+
if (retryTimeoutRef.current != null) clearTimeout(retryTimeoutRef.current);
|
|
776
|
+
retryTimeoutRef.current = null;
|
|
777
|
+
if (manualScrollCheckTimeoutRef.current != null) {
|
|
778
|
+
clearTimeout(manualScrollCheckTimeoutRef.current);
|
|
779
|
+
}
|
|
780
|
+
manualScrollCheckTimeoutRef.current = null;
|
|
781
|
+
repositionPendingRef.current = false;
|
|
782
|
+
pinnedTargetScrollTopRef.current = null;
|
|
783
|
+
appliedTargetKeyRef.current = null;
|
|
784
|
+
observedTargetKeyRef.current = initialObservedTargetKeyRef.current;
|
|
785
|
+
pinActiveRef.current = false;
|
|
786
|
+
setSpacerHeightRef.current(0);
|
|
787
|
+
},
|
|
788
|
+
[]
|
|
789
|
+
);
|
|
790
|
+
const isActive = useCallback3(() => pinActiveRef.current, []);
|
|
791
|
+
return { release, isActive };
|
|
792
|
+
}
|
|
793
|
+
|
|
547
794
|
// src/components/AgentChat.tsx
|
|
548
795
|
import { BladeApiError, latestPostChatFollowup } from "@blade-hq/agent-client";
|
|
549
796
|
|
|
@@ -636,6 +883,18 @@ var ArrowUp = createLucideIcon("ArrowUp", [
|
|
|
636
883
|
["path", { d: "M12 19V5", key: "x0mq9r" }]
|
|
637
884
|
]);
|
|
638
885
|
|
|
886
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/book-open.js
|
|
887
|
+
var BookOpen = createLucideIcon("BookOpen", [
|
|
888
|
+
["path", { d: "M12 7v14", key: "1akyts" }],
|
|
889
|
+
[
|
|
890
|
+
"path",
|
|
891
|
+
{
|
|
892
|
+
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",
|
|
893
|
+
key: "ruj8y"
|
|
894
|
+
}
|
|
895
|
+
]
|
|
896
|
+
]);
|
|
897
|
+
|
|
639
898
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/bot.js
|
|
640
899
|
var Bot = createLucideIcon("Bot", [
|
|
641
900
|
["path", { d: "M12 8V4H8", key: "hb8ula" }],
|
|
@@ -646,31 +905,6 @@ var Bot = createLucideIcon("Bot", [
|
|
|
646
905
|
["path", { d: "M9 13v2", key: "rq6x2g" }]
|
|
647
906
|
]);
|
|
648
907
|
|
|
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
908
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/check.js
|
|
675
909
|
var Check = createLucideIcon("Check", [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]]);
|
|
676
910
|
|
|
@@ -691,17 +925,54 @@ var CircleAlert = createLucideIcon("CircleAlert", [
|
|
|
691
925
|
["line", { x1: "12", x2: "12.01", y1: "16", y2: "16", key: "4dfq90" }]
|
|
692
926
|
]);
|
|
693
927
|
|
|
928
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/circle-dot.js
|
|
929
|
+
var CircleDot = createLucideIcon("CircleDot", [
|
|
930
|
+
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
|
|
931
|
+
["circle", { cx: "12", cy: "12", r: "1", key: "41hilf" }]
|
|
932
|
+
]);
|
|
933
|
+
|
|
934
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/circle.js
|
|
935
|
+
var Circle = createLucideIcon("Circle", [
|
|
936
|
+
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]
|
|
937
|
+
]);
|
|
938
|
+
|
|
694
939
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/copy.js
|
|
695
940
|
var Copy = createLucideIcon("Copy", [
|
|
696
941
|
["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }],
|
|
697
942
|
["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
943
|
]);
|
|
699
944
|
|
|
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
|
-
|
|
945
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/earth.js
|
|
946
|
+
var Earth = createLucideIcon("Earth", [
|
|
947
|
+
["path", { d: "M21.54 15H17a2 2 0 0 0-2 2v4.54", key: "1djwo0" }],
|
|
948
|
+
[
|
|
949
|
+
"path",
|
|
950
|
+
{
|
|
951
|
+
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",
|
|
952
|
+
key: "1tzkfa"
|
|
953
|
+
}
|
|
954
|
+
],
|
|
955
|
+
["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" }],
|
|
956
|
+
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]
|
|
957
|
+
]);
|
|
958
|
+
|
|
959
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/file-pen-line.js
|
|
960
|
+
var FilePenLine = createLucideIcon("FilePenLine", [
|
|
961
|
+
[
|
|
962
|
+
"path",
|
|
963
|
+
{
|
|
964
|
+
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",
|
|
965
|
+
key: "142zxg"
|
|
966
|
+
}
|
|
967
|
+
],
|
|
968
|
+
[
|
|
969
|
+
"path",
|
|
970
|
+
{
|
|
971
|
+
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",
|
|
972
|
+
key: "2t3380"
|
|
973
|
+
}
|
|
974
|
+
],
|
|
975
|
+
["path", { d: "M8 18h1", key: "13wk12" }]
|
|
705
976
|
]);
|
|
706
977
|
|
|
707
978
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/file-text.js
|
|
@@ -713,24 +984,6 @@ var FileText = createLucideIcon("FileText", [
|
|
|
713
984
|
["path", { d: "M16 17H8", key: "z1uh3a" }]
|
|
714
985
|
]);
|
|
715
986
|
|
|
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
987
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/globe.js
|
|
735
988
|
var Globe = createLucideIcon("Globe", [
|
|
736
989
|
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
|
|
@@ -776,6 +1029,15 @@ var Lightbulb = createLucideIcon("Lightbulb", [
|
|
|
776
1029
|
["path", { d: "M10 22h4", key: "ceow96" }]
|
|
777
1030
|
]);
|
|
778
1031
|
|
|
1032
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/list-checks.js
|
|
1033
|
+
var ListChecks = createLucideIcon("ListChecks", [
|
|
1034
|
+
["path", { d: "m3 17 2 2 4-4", key: "1jhpwq" }],
|
|
1035
|
+
["path", { d: "m3 7 2 2 4-4", key: "1obspn" }],
|
|
1036
|
+
["path", { d: "M13 6h8", key: "15sg57" }],
|
|
1037
|
+
["path", { d: "M13 12h8", key: "h98zly" }],
|
|
1038
|
+
["path", { d: "M13 18h8", key: "oe0vm4" }]
|
|
1039
|
+
]);
|
|
1040
|
+
|
|
779
1041
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/loader-circle.js
|
|
780
1042
|
var LoaderCircle = createLucideIcon("LoaderCircle", [
|
|
781
1043
|
["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]
|
|
@@ -806,6 +1068,20 @@ var Play = createLucideIcon("Play", [
|
|
|
806
1068
|
["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]
|
|
807
1069
|
]);
|
|
808
1070
|
|
|
1071
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/refresh-ccw.js
|
|
1072
|
+
var RefreshCcw = createLucideIcon("RefreshCcw", [
|
|
1073
|
+
["path", { d: "M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8", key: "14sxne" }],
|
|
1074
|
+
["path", { d: "M3 3v5h5", key: "1xhq8a" }],
|
|
1075
|
+
["path", { d: "M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16", key: "1hlbsb" }],
|
|
1076
|
+
["path", { d: "M16 16h5v5", key: "ccwih5" }]
|
|
1077
|
+
]);
|
|
1078
|
+
|
|
1079
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/search.js
|
|
1080
|
+
var Search = createLucideIcon("Search", [
|
|
1081
|
+
["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }],
|
|
1082
|
+
["path", { d: "m21 21-4.3-4.3", key: "1qie3q" }]
|
|
1083
|
+
]);
|
|
1084
|
+
|
|
809
1085
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/settings-2.js
|
|
810
1086
|
var Settings2 = createLucideIcon("Settings2", [
|
|
811
1087
|
["path", { d: "M20 7h-9", key: "3s1dr2" }],
|
|
@@ -834,6 +1110,12 @@ var Square = createLucideIcon("Square", [
|
|
|
834
1110
|
["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }]
|
|
835
1111
|
]);
|
|
836
1112
|
|
|
1113
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/terminal.js
|
|
1114
|
+
var Terminal = createLucideIcon("Terminal", [
|
|
1115
|
+
["polyline", { points: "4 17 10 11 4 5", key: "akl6gq" }],
|
|
1116
|
+
["line", { x1: "12", x2: "20", y1: "19", y2: "19", key: "q2wloq" }]
|
|
1117
|
+
]);
|
|
1118
|
+
|
|
837
1119
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/triangle-alert.js
|
|
838
1120
|
var TriangleAlert = createLucideIcon("TriangleAlert", [
|
|
839
1121
|
[
|
|
@@ -847,6 +1129,17 @@ var TriangleAlert = createLucideIcon("TriangleAlert", [
|
|
|
847
1129
|
["path", { d: "M12 17h.01", key: "p32p05" }]
|
|
848
1130
|
]);
|
|
849
1131
|
|
|
1132
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/wrench.js
|
|
1133
|
+
var Wrench = createLucideIcon("Wrench", [
|
|
1134
|
+
[
|
|
1135
|
+
"path",
|
|
1136
|
+
{
|
|
1137
|
+
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",
|
|
1138
|
+
key: "cbrjhi"
|
|
1139
|
+
}
|
|
1140
|
+
]
|
|
1141
|
+
]);
|
|
1142
|
+
|
|
850
1143
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/x.js
|
|
851
1144
|
var X = createLucideIcon("X", [
|
|
852
1145
|
["path", { d: "M18 6 6 18", key: "1bl5f8" }],
|
|
@@ -854,23 +1147,177 @@ var X = createLucideIcon("X", [
|
|
|
854
1147
|
]);
|
|
855
1148
|
|
|
856
1149
|
// src/components/AgentChat.tsx
|
|
857
|
-
import { useCallback as
|
|
1150
|
+
import { useCallback as useCallback8, useEffect as useEffect13, useMemo as useMemo8, useRef as useRef14, useState as useState16 } from "react";
|
|
1151
|
+
|
|
1152
|
+
// src/components/SessionPluginSelector.tsx
|
|
1153
|
+
import { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
|
|
1154
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
1155
|
+
function SessionPluginSelector(props) {
|
|
1156
|
+
return /* @__PURE__ */ jsx2(PluginSelectorState, { ...props }, props.sessionId);
|
|
1157
|
+
}
|
|
1158
|
+
function PluginSelectorState({ client, sessionId, className, side = "bottom", onChange }) {
|
|
1159
|
+
const [open, setOpen] = useState4(false);
|
|
1160
|
+
const [plugins, setPlugins] = useState4([]);
|
|
1161
|
+
const [loading, setLoading] = useState4(false);
|
|
1162
|
+
const [busy, setBusy] = useState4(false);
|
|
1163
|
+
const [listError, setListError] = useState4(false);
|
|
1164
|
+
const [mutationError, setMutationError] = useState4(false);
|
|
1165
|
+
const alive = useRef4(true);
|
|
1166
|
+
const generation = useRef4(0);
|
|
1167
|
+
const mutationGenerations = useRef4(/* @__PURE__ */ new Map());
|
|
1168
|
+
const desiredActivations = useRef4(/* @__PURE__ */ new Map());
|
|
1169
|
+
const pendingMutations = useRef4(/* @__PURE__ */ new Map());
|
|
1170
|
+
const lifetime = useRef4(0);
|
|
1171
|
+
const request = useRef4(null);
|
|
1172
|
+
useEffect4(() => {
|
|
1173
|
+
alive.current = true;
|
|
1174
|
+
setPlugins([]);
|
|
1175
|
+
setOpen(false);
|
|
1176
|
+
setLoading(false);
|
|
1177
|
+
setBusy(false);
|
|
1178
|
+
setListError(false);
|
|
1179
|
+
setMutationError(false);
|
|
1180
|
+
desiredActivations.current.clear();
|
|
1181
|
+
mutationGenerations.current.clear();
|
|
1182
|
+
pendingMutations.current.clear();
|
|
1183
|
+
return () => {
|
|
1184
|
+
alive.current = false;
|
|
1185
|
+
lifetime.current++;
|
|
1186
|
+
generation.current++;
|
|
1187
|
+
request.current?.abort();
|
|
1188
|
+
};
|
|
1189
|
+
}, [client]);
|
|
1190
|
+
const load = async () => {
|
|
1191
|
+
if (!alive.current) return;
|
|
1192
|
+
request.current?.abort();
|
|
1193
|
+
const controller = new AbortController();
|
|
1194
|
+
request.current = controller;
|
|
1195
|
+
const current = ++generation.current;
|
|
1196
|
+
setLoading(true);
|
|
1197
|
+
setListError(false);
|
|
1198
|
+
try {
|
|
1199
|
+
const result = await client.sessions.listSessionPlugins(sessionId, { signal: controller.signal });
|
|
1200
|
+
if (alive.current && current === generation.current) {
|
|
1201
|
+
setPlugins(result.plugins.map((item) => {
|
|
1202
|
+
const pending = pendingMutations.current.has(item.name);
|
|
1203
|
+
const desired = desiredActivations.current.get(item.name);
|
|
1204
|
+
return pending && desired !== void 0 ? { ...item, active: desired } : item;
|
|
1205
|
+
}));
|
|
1206
|
+
}
|
|
1207
|
+
} catch {
|
|
1208
|
+
if (alive.current && current === generation.current && !controller.signal.aborted) setListError(true);
|
|
1209
|
+
} finally {
|
|
1210
|
+
if (alive.current && current === generation.current) setLoading(false);
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
const select = async (name, active) => {
|
|
1214
|
+
desiredActivations.current.set(name, active);
|
|
1215
|
+
const currentLifetime = lifetime.current;
|
|
1216
|
+
const currentMutation = (mutationGenerations.current.get(name) ?? 0) + 1;
|
|
1217
|
+
mutationGenerations.current.set(name, currentMutation);
|
|
1218
|
+
pendingMutations.current.set(name, currentMutation);
|
|
1219
|
+
request.current?.abort();
|
|
1220
|
+
generation.current++;
|
|
1221
|
+
setLoading(false);
|
|
1222
|
+
setBusy(true);
|
|
1223
|
+
setMutationError(false);
|
|
1224
|
+
setPlugins((current) => current.map((item) => item.name === name ? { ...item, active } : item));
|
|
1225
|
+
try {
|
|
1226
|
+
const { plugin } = await client.sessions.setSessionPluginActivation(sessionId, name, active);
|
|
1227
|
+
if (!alive.current || currentLifetime !== lifetime.current) return;
|
|
1228
|
+
if (currentMutation !== mutationGenerations.current.get(name)) {
|
|
1229
|
+
const desired = desiredActivations.current.get(name);
|
|
1230
|
+
if (desired !== void 0 && desired !== plugin.active) void select(name, desired);
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
pendingMutations.current.delete(name);
|
|
1234
|
+
setBusy(pendingMutations.current.size > 0);
|
|
1235
|
+
setPlugins((current) => current.map((item) => item.name === plugin.name ? { ...item, ...plugin } : item));
|
|
1236
|
+
onChange?.(plugin);
|
|
1237
|
+
await load();
|
|
1238
|
+
} catch {
|
|
1239
|
+
if (alive.current && currentLifetime === lifetime.current && currentMutation === mutationGenerations.current.get(name)) {
|
|
1240
|
+
pendingMutations.current.delete(name);
|
|
1241
|
+
setBusy(pendingMutations.current.size > 0);
|
|
1242
|
+
setMutationError(true);
|
|
1243
|
+
void load();
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
};
|
|
1247
|
+
return /* @__PURE__ */ jsxs("div", { className: `relative text-xs ${className ?? ""}`, children: [
|
|
1248
|
+
/* @__PURE__ */ jsx2(
|
|
1249
|
+
"button",
|
|
1250
|
+
{
|
|
1251
|
+
type: "button",
|
|
1252
|
+
disabled: !sessionId,
|
|
1253
|
+
"aria-expanded": open,
|
|
1254
|
+
onClick: () => {
|
|
1255
|
+
setOpen(!open);
|
|
1256
|
+
if (!open) void load();
|
|
1257
|
+
},
|
|
1258
|
+
className: "rounded-full border border-[hsl(var(--border))] px-2.5 py-1",
|
|
1259
|
+
children: "\u4F1A\u8BDD\u63D2\u4EF6"
|
|
1260
|
+
}
|
|
1261
|
+
),
|
|
1262
|
+
open ? /* @__PURE__ */ jsxs("div", { className: `absolute left-0 z-40 my-2 max-h-72 w-72 max-w-[calc(100vw-2rem)] overflow-y-auto rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--popover))] p-3 text-[hsl(var(--popover-foreground))] shadow-lg ${side === "top" ? "bottom-full" : "top-full"}`, children: [
|
|
1263
|
+
loading ? /* @__PURE__ */ jsx2("div", { children: "\u6B63\u5728\u83B7\u53D6\u63D2\u4EF6\u2026" }) : null,
|
|
1264
|
+
listError ? /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => void load(), children: "\u63D2\u4EF6\u5217\u8868\u6682\u4E0D\u53EF\u7528\uFF0C\u70B9\u51FB\u91CD\u8BD5" }) : null,
|
|
1265
|
+
!loading && !listError && !plugins.length ? /* @__PURE__ */ jsx2("div", { children: "\u5C1A\u672A\u5B89\u88C5\u63D2\u4EF6" }) : null,
|
|
1266
|
+
plugins.map((plugin) => /* @__PURE__ */ jsxs("div", { className: "py-1", children: [
|
|
1267
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-2", children: [
|
|
1268
|
+
/* @__PURE__ */ jsx2("span", { className: "min-w-0 truncate", children: plugin.name }),
|
|
1269
|
+
/* @__PURE__ */ jsx2(
|
|
1270
|
+
"input",
|
|
1271
|
+
{
|
|
1272
|
+
type: "checkbox",
|
|
1273
|
+
checked: plugin.active,
|
|
1274
|
+
disabled: busy && !plugin.active || !plugin.installed && !plugin.active,
|
|
1275
|
+
onChange: (event) => void select(plugin.name, event.target.checked),
|
|
1276
|
+
"aria-label": `${plugin.name} \u4F1A\u8BDD\u6FC0\u6D3B`
|
|
1277
|
+
}
|
|
1278
|
+
)
|
|
1279
|
+
] }),
|
|
1280
|
+
plugin.active && plugin.status !== "valid" ? /* @__PURE__ */ jsxs("div", { className: "text-[hsl(var(--muted-foreground))]", children: [
|
|
1281
|
+
plugin.reason || "\u63D2\u4EF6\u6682\u4E0D\u53EF\u7528",
|
|
1282
|
+
/* @__PURE__ */ jsx2("button", { type: "button", disabled: busy, onClick: () => void select(plugin.name, true), className: "ml-2 underline", children: "\u91CD\u8BD5" })
|
|
1283
|
+
] }) : null
|
|
1284
|
+
] }, plugin.name)),
|
|
1285
|
+
mutationError ? /* @__PURE__ */ jsx2("div", { children: "\u64CD\u4F5C\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u8BD5" }) : null
|
|
1286
|
+
] }) : null
|
|
1287
|
+
] });
|
|
1288
|
+
}
|
|
858
1289
|
|
|
859
1290
|
// src/lib/utils.ts
|
|
860
1291
|
function cn(...inputs) {
|
|
861
1292
|
return clsx(inputs);
|
|
862
1293
|
}
|
|
863
1294
|
async function copyToClipboard(text) {
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1295
|
+
const clipboard = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
|
|
1296
|
+
if (clipboard && typeof clipboard.writeText === "function") {
|
|
1297
|
+
try {
|
|
1298
|
+
await clipboard.writeText(text);
|
|
1299
|
+
return true;
|
|
1300
|
+
} catch {
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
if (typeof document === "undefined" || typeof document.execCommand !== "function") {
|
|
868
1304
|
return false;
|
|
869
1305
|
}
|
|
1306
|
+
const textarea = document.createElement("textarea");
|
|
1307
|
+
textarea.value = text;
|
|
1308
|
+
textarea.style.position = "fixed";
|
|
1309
|
+
textarea.style.opacity = "0";
|
|
1310
|
+
document.body.appendChild(textarea);
|
|
1311
|
+
textarea.select();
|
|
1312
|
+
try {
|
|
1313
|
+
return document.execCommand("copy");
|
|
1314
|
+
} finally {
|
|
1315
|
+
document.body.removeChild(textarea);
|
|
1316
|
+
}
|
|
870
1317
|
}
|
|
871
1318
|
|
|
872
1319
|
// src/components/ReplayBar.tsx
|
|
873
|
-
import { jsx as
|
|
1320
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
874
1321
|
var SPEED_OPTIONS = [1, 2, 5];
|
|
875
1322
|
function ReplayBar({
|
|
876
1323
|
isReplay,
|
|
@@ -881,7 +1328,7 @@ function ReplayBar({
|
|
|
881
1328
|
className
|
|
882
1329
|
}) {
|
|
883
1330
|
if (!isReplay) return null;
|
|
884
|
-
return /* @__PURE__ */
|
|
1331
|
+
return /* @__PURE__ */ jsxs2(
|
|
885
1332
|
"div",
|
|
886
1333
|
{
|
|
887
1334
|
className: cn(
|
|
@@ -889,13 +1336,13 @@ function ReplayBar({
|
|
|
889
1336
|
className
|
|
890
1337
|
),
|
|
891
1338
|
children: [
|
|
892
|
-
/* @__PURE__ */
|
|
893
|
-
/* @__PURE__ */
|
|
1339
|
+
/* @__PURE__ */ jsxs2("span", { className: "inline-flex items-center gap-1.5 font-medium text-[hsl(var(--foreground))]", children: [
|
|
1340
|
+
/* @__PURE__ */ jsx3(Play, { size: 13 }),
|
|
894
1341
|
"\u56DE\u653E\u6A21\u5F0F"
|
|
895
1342
|
] }),
|
|
896
|
-
/* @__PURE__ */
|
|
897
|
-
/* @__PURE__ */
|
|
898
|
-
/* @__PURE__ */
|
|
1343
|
+
/* @__PURE__ */ jsx3("span", { className: "text-[hsl(var(--muted-foreground))]", children: "\u6B63\u5728\u91CD\u73B0\u4E4B\u524D\u7684\u5BF9\u8BDD" }),
|
|
1344
|
+
/* @__PURE__ */ jsxs2("div", { className: "ml-auto flex items-center gap-2", children: [
|
|
1345
|
+
/* @__PURE__ */ jsx3("div", { className: "flex items-center gap-0.5 rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--card))] p-0.5", children: SPEED_OPTIONS.map((option) => /* @__PURE__ */ jsxs2(
|
|
899
1346
|
"button",
|
|
900
1347
|
{
|
|
901
1348
|
type: "button",
|
|
@@ -914,7 +1361,7 @@ function ReplayBar({
|
|
|
914
1361
|
},
|
|
915
1362
|
option
|
|
916
1363
|
)) }),
|
|
917
|
-
/* @__PURE__ */
|
|
1364
|
+
/* @__PURE__ */ jsx3(
|
|
918
1365
|
"button",
|
|
919
1366
|
{
|
|
920
1367
|
type: "button",
|
|
@@ -934,10 +1381,10 @@ function ReplayBar({
|
|
|
934
1381
|
}
|
|
935
1382
|
|
|
936
1383
|
// src/components/ReplayMismatchPrompt.tsx
|
|
937
|
-
import { jsx as
|
|
1384
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
938
1385
|
function ReplayMismatchPrompt({ mismatch, className }) {
|
|
939
1386
|
if (!mismatch) return null;
|
|
940
|
-
return /* @__PURE__ */
|
|
1387
|
+
return /* @__PURE__ */ jsxs3(
|
|
941
1388
|
"div",
|
|
942
1389
|
{
|
|
943
1390
|
className: cn(
|
|
@@ -945,19 +1392,19 @@ function ReplayMismatchPrompt({ mismatch, className }) {
|
|
|
945
1392
|
className
|
|
946
1393
|
),
|
|
947
1394
|
children: [
|
|
948
|
-
/* @__PURE__ */
|
|
949
|
-
/* @__PURE__ */
|
|
950
|
-
/* @__PURE__ */
|
|
951
|
-
/* @__PURE__ */
|
|
952
|
-
/* @__PURE__ */
|
|
1395
|
+
/* @__PURE__ */ jsx4("div", { className: "font-medium text-[hsl(var(--foreground))]", children: "\u8FD9\u53E5\u8BDD\u548C\u4E4B\u524D\u5F55\u5236\u7684\u4E0D\u4E00\u6837" }),
|
|
1396
|
+
/* @__PURE__ */ jsxs3("dl", { className: "mt-2 space-y-1 text-xs text-[hsl(var(--muted-foreground))]", children: [
|
|
1397
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex gap-2", children: [
|
|
1398
|
+
/* @__PURE__ */ jsx4("dt", { className: "shrink-0", children: "\u5F55\u5236\u7684\u662F" }),
|
|
1399
|
+
/* @__PURE__ */ jsx4("dd", { className: "min-w-0 break-words text-[hsl(var(--foreground))]", children: mismatch.expectedMessage || "\uFF08\u7A7A\uFF09" })
|
|
953
1400
|
] }),
|
|
954
|
-
/* @__PURE__ */
|
|
955
|
-
/* @__PURE__ */
|
|
956
|
-
/* @__PURE__ */
|
|
1401
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex gap-2", children: [
|
|
1402
|
+
/* @__PURE__ */ jsx4("dt", { className: "shrink-0", children: "\u4F60\u8F93\u5165\u7684" }),
|
|
1403
|
+
/* @__PURE__ */ jsx4("dd", { className: "min-w-0 break-words text-[hsl(var(--foreground))]", children: mismatch.actualMessage || "\uFF08\u7A7A\uFF09" })
|
|
957
1404
|
] })
|
|
958
1405
|
] }),
|
|
959
|
-
/* @__PURE__ */
|
|
960
|
-
/* @__PURE__ */
|
|
1406
|
+
/* @__PURE__ */ jsxs3("div", { className: "mt-3 flex flex-wrap gap-2", children: [
|
|
1407
|
+
/* @__PURE__ */ jsx4(
|
|
961
1408
|
"button",
|
|
962
1409
|
{
|
|
963
1410
|
type: "button",
|
|
@@ -966,7 +1413,7 @@ function ReplayMismatchPrompt({ mismatch, className }) {
|
|
|
966
1413
|
children: "\u6309\u5F55\u5236\u5185\u5BB9\u7EE7\u7EED"
|
|
967
1414
|
}
|
|
968
1415
|
),
|
|
969
|
-
/* @__PURE__ */
|
|
1416
|
+
/* @__PURE__ */ jsx4(
|
|
970
1417
|
"button",
|
|
971
1418
|
{
|
|
972
1419
|
type: "button",
|
|
@@ -981,110 +1428,527 @@ function ReplayMismatchPrompt({ mismatch, className }) {
|
|
|
981
1428
|
);
|
|
982
1429
|
}
|
|
983
1430
|
|
|
984
|
-
// src/components/
|
|
985
|
-
import {
|
|
986
|
-
function ChatInput({
|
|
987
|
-
value,
|
|
988
|
-
onValueChange,
|
|
989
|
-
onSend,
|
|
990
|
-
onStop,
|
|
991
|
-
isStreaming,
|
|
992
|
-
isStopping = false,
|
|
993
|
-
placeholder = "\u8F93\u5165\u6D88\u606F\u2026",
|
|
994
|
-
className
|
|
995
|
-
}) {
|
|
996
|
-
const trimmed = value.trim();
|
|
997
|
-
const canSend = trimmed.length > 0 && !isStreaming;
|
|
998
|
-
const handleSend = async () => {
|
|
999
|
-
if (!canSend) return;
|
|
1000
|
-
const accepted = await onSend(trimmed);
|
|
1001
|
-
if (!accepted) return;
|
|
1002
|
-
onValueChange("");
|
|
1003
|
-
};
|
|
1004
|
-
const handleKeyDown = (event) => {
|
|
1005
|
-
if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
|
|
1006
|
-
event.preventDefault();
|
|
1007
|
-
void handleSend();
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
return /* @__PURE__ */ jsx4("div", { className: cn("blade-chat-input border-t border-[hsl(var(--border))] py-3", className), children: /* @__PURE__ */ jsxs3("div", { className: "blade-chat-input-inner mx-auto flex max-w-[748px] items-end gap-2 rounded-2xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-3 py-2", children: [
|
|
1011
|
-
/* @__PURE__ */ jsx4(
|
|
1012
|
-
"textarea",
|
|
1013
|
-
{
|
|
1014
|
-
value,
|
|
1015
|
-
onChange: (event) => onValueChange(event.target.value),
|
|
1016
|
-
onKeyDown: handleKeyDown,
|
|
1017
|
-
onInput: (event) => {
|
|
1018
|
-
const el = event.currentTarget;
|
|
1019
|
-
el.style.height = "auto";
|
|
1020
|
-
el.style.height = `${Math.min(el.scrollHeight, 192)}px`;
|
|
1021
|
-
},
|
|
1022
|
-
rows: 1,
|
|
1023
|
-
placeholder,
|
|
1024
|
-
"aria-label": "\u804A\u5929\u8F93\u5165",
|
|
1025
|
-
className: "blade-chat-textarea max-h-48 min-h-[28px] flex-1 resize-none bg-transparent py-1 text-sm leading-6 text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground)/0.6)]"
|
|
1026
|
-
}
|
|
1027
|
-
),
|
|
1028
|
-
isStreaming ? /* @__PURE__ */ jsx4(
|
|
1029
|
-
"button",
|
|
1030
|
-
{
|
|
1031
|
-
type: "button",
|
|
1032
|
-
onClick: onStop,
|
|
1033
|
-
disabled: isStopping,
|
|
1034
|
-
"aria-label": isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D",
|
|
1035
|
-
title: isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D",
|
|
1036
|
-
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--muted))] text-[hsl(var(--foreground))] transition-opacity hover:opacity-90 disabled:opacity-60",
|
|
1037
|
-
children: isStopping ? /* @__PURE__ */ jsx4(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx4(Square, { size: 12, fill: "currentColor" })
|
|
1038
|
-
}
|
|
1039
|
-
) : /* @__PURE__ */ jsx4(
|
|
1040
|
-
"button",
|
|
1041
|
-
{
|
|
1042
|
-
type: "button",
|
|
1043
|
-
onClick: handleSend,
|
|
1044
|
-
disabled: !canSend,
|
|
1045
|
-
"aria-label": "\u53D1\u9001\u6D88\u606F",
|
|
1046
|
-
title: "\u53D1\u9001\u6D88\u606F",
|
|
1047
|
-
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",
|
|
1048
|
-
children: /* @__PURE__ */ jsx4(ArrowUp, { size: 15 })
|
|
1049
|
-
}
|
|
1050
|
-
)
|
|
1051
|
-
] }) });
|
|
1052
|
-
}
|
|
1431
|
+
// src/components/PlanUpdateBlock.tsx
|
|
1432
|
+
import { useEffect as useEffect5, useRef as useRef5, useState as useState5 } from "react";
|
|
1053
1433
|
|
|
1054
|
-
// src/components/
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1434
|
+
// src/components/display-utils.ts
|
|
1435
|
+
var TOOL_NAME_ALIASES = {
|
|
1436
|
+
agent: "Agent",
|
|
1437
|
+
ask_user_question: "AskUserQuestion",
|
|
1438
|
+
bash: "Bash",
|
|
1439
|
+
bg_bash: "BgBash",
|
|
1440
|
+
edit: "Edit",
|
|
1441
|
+
exit_plan_mode: "ExitPlanMode",
|
|
1442
|
+
file_edit: "Edit",
|
|
1443
|
+
file_read: "Read",
|
|
1444
|
+
file_write: "Write",
|
|
1445
|
+
finish_task: "FinishTask",
|
|
1446
|
+
glob: "Glob",
|
|
1447
|
+
grep: "Grep",
|
|
1448
|
+
kb_search: "KbSearch",
|
|
1449
|
+
ls: "Ls",
|
|
1450
|
+
multi_edit: "MultiEdit",
|
|
1451
|
+
read: "Read",
|
|
1452
|
+
read_skill: "ReadSkill",
|
|
1453
|
+
update_plan: "UpdatePlan",
|
|
1454
|
+
web_fetch: "WebFetch",
|
|
1455
|
+
web_search: "WebSearch",
|
|
1456
|
+
write: "Write"
|
|
1457
|
+
};
|
|
1458
|
+
var TOOL_DISPLAY_LABELS = {
|
|
1459
|
+
Bash: "\u6267\u884C\u547D\u4EE4",
|
|
1460
|
+
BgBash: "\u540E\u53F0\u6267\u884C\u547D\u4EE4",
|
|
1461
|
+
Read: "\u8BFB\u53D6\u6587\u4EF6",
|
|
1462
|
+
Write: "\u5199\u5165\u6587\u4EF6",
|
|
1463
|
+
Edit: "\u7F16\u8F91\u6587\u4EF6",
|
|
1464
|
+
MultiEdit: "\u7F16\u8F91\u6587\u4EF6",
|
|
1465
|
+
Ls: "\u5217\u51FA\u76EE\u5F55",
|
|
1466
|
+
Glob: "\u5339\u914D\u6587\u4EF6",
|
|
1467
|
+
Grep: "\u641C\u7D22\u6587\u672C",
|
|
1468
|
+
KbSearch: "\u68C0\u7D22\u77E5\u8BC6\u5E93",
|
|
1469
|
+
WebSearch: "\u641C\u7D22\u7F51\u9875",
|
|
1470
|
+
WebFetch: "\u6574\u7406\u7F51\u9875\u5185\u5BB9",
|
|
1471
|
+
Agent: "\u6D3E\u751F\u5B50\u667A\u80FD\u4F53",
|
|
1472
|
+
AskUserQuestion: "\u5411\u7528\u6237\u63D0\u95EE",
|
|
1473
|
+
ReadSkill: "\u8BFB\u53D6\u6280\u80FD",
|
|
1474
|
+
FinishTask: "\u4EFB\u52A1\u5B8C\u6210",
|
|
1475
|
+
ExitPlanMode: "\u63D0\u4EA4\u8BA1\u5212",
|
|
1476
|
+
ListSessions: "\u5217\u51FA\u5386\u53F2\u4F1A\u8BDD",
|
|
1477
|
+
GetSessionHistory: "\u8BFB\u53D6\u4F1A\u8BDD\u5386\u53F2"
|
|
1478
|
+
};
|
|
1479
|
+
function safeParseJson(value) {
|
|
1480
|
+
if (!value) return null;
|
|
1481
|
+
try {
|
|
1482
|
+
return JSON.parse(value);
|
|
1483
|
+
} catch {
|
|
1058
1484
|
return null;
|
|
1059
1485
|
}
|
|
1060
|
-
const reconnecting = connection === "reconnecting";
|
|
1061
|
-
return /* @__PURE__ */ jsx5("div", { className: cn("blade-chat-banner bg-[hsl(var(--background))] px-5 pt-3", className), children: /* @__PURE__ */ jsxs4(
|
|
1062
|
-
"div",
|
|
1063
|
-
{
|
|
1064
|
-
className: cn(
|
|
1065
|
-
"mx-auto flex max-w-3xl items-start gap-3 rounded-2xl border px-4 py-3",
|
|
1066
|
-
reconnecting ? "border-amber-500/25 bg-amber-500/10 text-amber-100" : "border-rose-500/25 bg-rose-500/10 text-rose-100"
|
|
1067
|
-
),
|
|
1068
|
-
children: [
|
|
1069
|
-
/* @__PURE__ */ jsx5("span", { className: "mt-0.5 shrink-0", children: reconnecting ? /* @__PURE__ */ jsx5(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx5(TriangleAlert, { size: 14 }) }),
|
|
1070
|
-
/* @__PURE__ */ jsxs4("div", { className: "min-w-0", children: [
|
|
1071
|
-
/* @__PURE__ */ jsx5("div", { className: "text-sm font-medium", children: reconnecting ? "\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF0C\u6B63\u5728\u91CD\u8FDE\u2026" : "\u8FDE\u63A5\u5DF2\u65AD\u5F00" }),
|
|
1072
|
-
/* @__PURE__ */ jsx5("div", { className: "text-xs opacity-80", children: "\u6D88\u606F\u540C\u6B65\u53EF\u80FD\u4F1A\u5EF6\u8FDF\uFF0C\u7CFB\u7EDF\u4F1A\u7EE7\u7EED\u81EA\u52A8\u91CD\u8BD5" })
|
|
1073
|
-
] })
|
|
1074
|
-
]
|
|
1075
|
-
}
|
|
1076
|
-
) });
|
|
1077
1486
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1487
|
+
function getStringArgValue(args, key) {
|
|
1488
|
+
const value = args?.[key];
|
|
1489
|
+
return typeof value === "string" ? value.trim() : "";
|
|
1490
|
+
}
|
|
1491
|
+
var SKILL_ENTRY_FILE_NAMES = /* @__PURE__ */ new Set(["skill.md", "command.md"]);
|
|
1492
|
+
var NON_SKILL_DIR_NAMES = /* @__PURE__ */ new Set([".", "..", ".agent", ".agents", ".claude", "skill_data", "skills"]);
|
|
1493
|
+
function getSkillNameFromFilePath(filePath) {
|
|
1494
|
+
if (!filePath) return null;
|
|
1495
|
+
const segments = filePath.split(/[\\/]+/).filter(Boolean);
|
|
1496
|
+
const fileName = segments.pop();
|
|
1497
|
+
if (!fileName || !SKILL_ENTRY_FILE_NAMES.has(fileName.toLowerCase())) return null;
|
|
1498
|
+
const dirName = segments.pop();
|
|
1499
|
+
if (!dirName || NON_SKILL_DIR_NAMES.has(dirName.toLowerCase())) return null;
|
|
1500
|
+
return dirName;
|
|
1501
|
+
}
|
|
1502
|
+
function formatToolName(name) {
|
|
1503
|
+
const trimmed = name.trim();
|
|
1504
|
+
if (!trimmed) return name;
|
|
1505
|
+
const stripped = trimmed.split(":").pop()?.split("/").pop()?.split(".").pop()?.trim() || trimmed;
|
|
1506
|
+
const normalized = stripped.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
1507
|
+
return TOOL_NAME_ALIASES[normalized] ?? stripped;
|
|
1508
|
+
}
|
|
1509
|
+
function getToolDisplayLabel(toolCall) {
|
|
1510
|
+
const normalized = formatToolName(toolCall.name);
|
|
1511
|
+
const args = safeParseJson(toolCall.arguments);
|
|
1512
|
+
const displayName = toolCall.display_name?.trim() ?? "";
|
|
1513
|
+
const baseLabel = displayName || TOOL_DISPLAY_LABELS[normalized] || normalized;
|
|
1514
|
+
const metaDisplayName = getStringArgValue(args, "_meta_display_name");
|
|
1515
|
+
if (metaDisplayName) {
|
|
1516
|
+
return metaDisplayName;
|
|
1517
|
+
}
|
|
1518
|
+
const description = getStringArgValue(args, "description");
|
|
1519
|
+
if (normalized === "BgBash") {
|
|
1520
|
+
return description ? `\u540E\u53F0\u6267\u884C\uFF1A${description}` : "\u540E\u53F0\u6267\u884C\u547D\u4EE4";
|
|
1521
|
+
}
|
|
1522
|
+
if (normalized === "ReadSkill") {
|
|
1523
|
+
const skillName = getStringArgValue(args, "skill") || getStringArgValue(args, "skill_name");
|
|
1524
|
+
return skillName ? `${baseLabel}\u300C${skillName}\u300D` : baseLabel;
|
|
1525
|
+
}
|
|
1526
|
+
if (normalized === "Read") {
|
|
1527
|
+
const skillName = getSkillNameFromFilePath(
|
|
1528
|
+
getStringArgValue(args, "file_path") || getStringArgValue(args, "path")
|
|
1529
|
+
);
|
|
1530
|
+
if (skillName) return `\u8BFB\u53D6\u6280\u80FD\u300C${skillName}\u300D`;
|
|
1531
|
+
}
|
|
1532
|
+
if (normalized === "FinishTask") {
|
|
1533
|
+
const title = getStringArgValue(args, "title");
|
|
1534
|
+
return title ? `${baseLabel}\uFF1A${title}` : baseLabel;
|
|
1535
|
+
}
|
|
1536
|
+
return description || baseLabel;
|
|
1537
|
+
}
|
|
1538
|
+
function getToolTone(status) {
|
|
1539
|
+
if (status === "error" || status === "cancelled") return "red";
|
|
1540
|
+
if (status === "awaiting_answer") return "amber";
|
|
1541
|
+
if (status === "pending") return "blue";
|
|
1542
|
+
return "emerald";
|
|
1543
|
+
}
|
|
1544
|
+
function getToolStatusLabel(status) {
|
|
1545
|
+
if (status === "pending") return "\u8FD0\u884C\u4E2D";
|
|
1546
|
+
if (status === "awaiting_answer") return "\u7B49\u5F85\u56DE\u7B54";
|
|
1547
|
+
if (status === "error") return "\u9519\u8BEF";
|
|
1548
|
+
if (status === "cancelled") return "\u5DF2\u53D6\u6D88";
|
|
1549
|
+
return "\u5B8C\u6210";
|
|
1550
|
+
}
|
|
1551
|
+
function formatToolDuration(ms) {
|
|
1552
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
1553
|
+
const seconds = ms / 1e3;
|
|
1554
|
+
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
1555
|
+
const minutes = Math.floor(seconds / 60);
|
|
1556
|
+
const remainingSeconds = Math.round(seconds % 60);
|
|
1557
|
+
return remainingSeconds > 0 ? `${minutes}m${remainingSeconds}s` : `${minutes}m`;
|
|
1558
|
+
}
|
|
1559
|
+
function formatToolArgs(args) {
|
|
1560
|
+
try {
|
|
1561
|
+
return JSON.stringify(JSON.parse(args), null, 2);
|
|
1562
|
+
} catch {
|
|
1563
|
+
return args;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
var RESULT_PREVIEW_LIMIT = 4e3;
|
|
1567
|
+
function formatToolResult(result) {
|
|
1568
|
+
const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
1569
|
+
if (text == null) return "";
|
|
1570
|
+
if (text.length <= RESULT_PREVIEW_LIMIT) return text;
|
|
1571
|
+
return `${text.slice(0, RESULT_PREVIEW_LIMIT)}
|
|
1572
|
+
\u2026\uFF08\u7ED3\u679C\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\uFF09`;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
// src/components/PlanUpdateBlock.tsx
|
|
1576
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1577
|
+
var PLAN_STEP_STATUSES = /* @__PURE__ */ new Set(["pending", "in_progress", "completed"]);
|
|
1578
|
+
var PLAN_AUTO_COLLAPSE_MS = 5e3;
|
|
1579
|
+
function isPlanUpdateTool(toolCall) {
|
|
1580
|
+
return formatToolName(toolCall.name) === "UpdatePlan";
|
|
1581
|
+
}
|
|
1582
|
+
function getPlanUpdateDisplayState(messages) {
|
|
1583
|
+
let current = null;
|
|
1584
|
+
let latestAttempt = null;
|
|
1585
|
+
let latestAttemptStreaming = false;
|
|
1586
|
+
for (const message of messages) {
|
|
1587
|
+
if ((message.loop_name ?? "root") !== "root") continue;
|
|
1588
|
+
for (const toolCall of message.tool_calls ?? []) {
|
|
1589
|
+
if (!isPlanUpdateTool(toolCall)) continue;
|
|
1590
|
+
latestAttempt = toolCall;
|
|
1591
|
+
latestAttemptStreaming = message.status === "streaming";
|
|
1592
|
+
if (toolCall.status === "done" && parsePlanUpdate(toolCall.arguments)) current = toolCall;
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
return {
|
|
1596
|
+
current,
|
|
1597
|
+
updating: latestAttempt?.status === "pending" && latestAttemptStreaming
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
function parsePlanUpdate(argumentsJson) {
|
|
1601
|
+
try {
|
|
1602
|
+
const raw = JSON.parse(argumentsJson);
|
|
1603
|
+
if (!raw || typeof raw !== "object") return null;
|
|
1604
|
+
const candidate = raw;
|
|
1605
|
+
if (!Array.isArray(candidate.plan)) return null;
|
|
1606
|
+
const plan = candidate.plan.map((item) => {
|
|
1607
|
+
if (!item || typeof item !== "object") return null;
|
|
1608
|
+
const step = item.step;
|
|
1609
|
+
const status = item.status;
|
|
1610
|
+
if (typeof step !== "string" || !step.trim() || typeof status !== "string" || !PLAN_STEP_STATUSES.has(status)) {
|
|
1611
|
+
return null;
|
|
1612
|
+
}
|
|
1613
|
+
return { step: step.trim(), status };
|
|
1614
|
+
});
|
|
1615
|
+
if (plan.some((item) => item === null)) return null;
|
|
1616
|
+
if (plan.filter((item) => item?.status === "in_progress").length > 1) return null;
|
|
1617
|
+
return { plan };
|
|
1618
|
+
} catch {
|
|
1619
|
+
return null;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
function pickCurrentPlanStep(plan) {
|
|
1623
|
+
return plan.find((item) => item.status === "in_progress") ?? plan.find((item) => item.status === "pending") ?? plan[plan.length - 1] ?? null;
|
|
1624
|
+
}
|
|
1625
|
+
function PlanStepIcon({
|
|
1626
|
+
status,
|
|
1627
|
+
size = 17,
|
|
1628
|
+
running = false
|
|
1629
|
+
}) {
|
|
1630
|
+
if (status === "completed") {
|
|
1631
|
+
return /* @__PURE__ */ jsx5(Check, { size, strokeWidth: 2, className: "shrink-0 text-emerald-500" });
|
|
1632
|
+
}
|
|
1633
|
+
if (status === "in_progress") {
|
|
1634
|
+
return running ? /* @__PURE__ */ jsx5(LoaderCircle, { size, className: "shrink-0 animate-spin text-[hsl(var(--muted-foreground))]" }) : /* @__PURE__ */ jsx5(CircleDot, { size, className: "shrink-0 text-amber-500" });
|
|
1635
|
+
}
|
|
1636
|
+
return /* @__PURE__ */ jsx5(Circle, { size, className: "shrink-0 text-[hsl(var(--muted-foreground))]/60" });
|
|
1637
|
+
}
|
|
1638
|
+
function PlanUpdateBlock({
|
|
1639
|
+
toolCall,
|
|
1640
|
+
running = false,
|
|
1641
|
+
autoReveal = false
|
|
1642
|
+
}) {
|
|
1643
|
+
const updateKey = `${toolCall.id}:${toolCall.arguments}`;
|
|
1644
|
+
const revealKey = autoReveal ? updateKey : null;
|
|
1645
|
+
const [collapsed, setCollapsed] = useState5(!autoReveal);
|
|
1646
|
+
const collapseTimerRef = useRef5(null);
|
|
1647
|
+
const data = parsePlanUpdate(toolCall.arguments);
|
|
1648
|
+
useEffect5(() => {
|
|
1649
|
+
if (!revealKey) return;
|
|
1650
|
+
if (collapseTimerRef.current) clearTimeout(collapseTimerRef.current);
|
|
1651
|
+
setCollapsed(false);
|
|
1652
|
+
collapseTimerRef.current = setTimeout(() => {
|
|
1653
|
+
setCollapsed(true);
|
|
1654
|
+
collapseTimerRef.current = null;
|
|
1655
|
+
}, PLAN_AUTO_COLLAPSE_MS);
|
|
1656
|
+
}, [revealKey]);
|
|
1657
|
+
useEffect5(
|
|
1658
|
+
() => () => {
|
|
1659
|
+
if (collapseTimerRef.current) clearTimeout(collapseTimerRef.current);
|
|
1660
|
+
},
|
|
1661
|
+
[]
|
|
1662
|
+
);
|
|
1663
|
+
if (!data) return null;
|
|
1664
|
+
const completed = data.plan.filter((item) => item.status === "completed").length;
|
|
1665
|
+
const currentStep = pickCurrentPlanStep(data.plan);
|
|
1666
|
+
const pausedAtCurrentStep = !running && currentStep?.status === "in_progress";
|
|
1667
|
+
return /* @__PURE__ */ jsxs4("section", { className: "overflow-hidden", children: [
|
|
1668
|
+
/* @__PURE__ */ jsxs4(
|
|
1669
|
+
"button",
|
|
1670
|
+
{
|
|
1671
|
+
type: "button",
|
|
1672
|
+
"aria-expanded": !collapsed,
|
|
1673
|
+
onClick: () => {
|
|
1674
|
+
if (collapseTimerRef.current) {
|
|
1675
|
+
clearTimeout(collapseTimerRef.current);
|
|
1676
|
+
collapseTimerRef.current = null;
|
|
1677
|
+
}
|
|
1678
|
+
setCollapsed((value) => !value);
|
|
1679
|
+
},
|
|
1680
|
+
className: cn(
|
|
1681
|
+
"flex w-full items-center gap-2 px-3 py-2 text-left transition-colors hover:bg-[hsl(var(--muted)/0.3)]",
|
|
1682
|
+
!collapsed && "border-b border-[hsl(var(--border))]"
|
|
1683
|
+
),
|
|
1684
|
+
children: [
|
|
1685
|
+
collapsed && currentStep ? /* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 flex-1 items-center gap-1.5 text-xs text-[hsl(var(--foreground))]", children: [
|
|
1686
|
+
/* @__PURE__ */ jsx5(PlanStepIcon, { status: currentStep.status, size: 14, running }),
|
|
1687
|
+
/* @__PURE__ */ jsx5("span", { className: "truncate", children: currentStep.step }),
|
|
1688
|
+
pausedAtCurrentStep ? /* @__PURE__ */ jsx5("span", { className: "shrink-0 text-[11px] text-amber-500", children: "\u5DF2\u6682\u505C" }) : null
|
|
1689
|
+
] }) : /* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 flex-1 items-center gap-1.5 text-[11px] text-[hsl(var(--muted-foreground))]", children: [
|
|
1690
|
+
/* @__PURE__ */ jsx5(ListChecks, { size: 14, className: "shrink-0", "aria-hidden": "true" }),
|
|
1691
|
+
/* @__PURE__ */ jsx5("span", { className: "truncate", children: "\u4EFB\u52A1\u8FDB\u5EA6" }),
|
|
1692
|
+
pausedAtCurrentStep ? /* @__PURE__ */ jsx5("span", { className: "shrink-0 text-amber-500", children: "\u5DF2\u6682\u505C" }) : null
|
|
1693
|
+
] }),
|
|
1694
|
+
/* @__PURE__ */ jsxs4("span", { className: "shrink-0 text-[11px] tabular-nums text-[hsl(var(--muted-foreground))]", children: [
|
|
1695
|
+
completed,
|
|
1696
|
+
"/",
|
|
1697
|
+
data.plan.length
|
|
1698
|
+
] }),
|
|
1699
|
+
/* @__PURE__ */ jsx5(
|
|
1700
|
+
ChevronDown,
|
|
1701
|
+
{
|
|
1702
|
+
size: 14,
|
|
1703
|
+
className: cn(
|
|
1704
|
+
"shrink-0 text-[hsl(var(--muted-foreground))] transition-transform duration-300 ease-out motion-reduce:transition-none",
|
|
1705
|
+
!collapsed && "rotate-180"
|
|
1706
|
+
)
|
|
1707
|
+
}
|
|
1708
|
+
)
|
|
1709
|
+
]
|
|
1710
|
+
}
|
|
1711
|
+
),
|
|
1712
|
+
/* @__PURE__ */ jsx5(
|
|
1713
|
+
"div",
|
|
1714
|
+
{
|
|
1715
|
+
"aria-hidden": collapsed,
|
|
1716
|
+
className: cn(
|
|
1717
|
+
"grid transition-[grid-template-rows,opacity] duration-300 ease-out motion-reduce:transition-none",
|
|
1718
|
+
collapsed ? "grid-rows-[0fr] opacity-0" : "grid-rows-[1fr] opacity-100"
|
|
1719
|
+
),
|
|
1720
|
+
children: /* @__PURE__ */ jsx5("div", { className: "min-h-0 overflow-hidden", children: /* @__PURE__ */ jsx5("div", { className: "flex max-h-40 flex-col gap-0.5 overflow-y-auto px-3 py-2", children: data.plan.length === 0 ? /* @__PURE__ */ jsx5("span", { className: "text-xs text-[hsl(var(--muted-foreground))]", children: "\u6682\u65E0\u4EFB\u52A1\u6B65\u9AA4" }) : data.plan.map((item, index) => /* @__PURE__ */ jsxs4("div", { className: "flex items-start gap-2 py-0.5", children: [
|
|
1721
|
+
/* @__PURE__ */ jsx5("span", { className: "mt-[3px] flex shrink-0", children: /* @__PURE__ */ jsx5(PlanStepIcon, { status: item.status, size: 14, running }) }),
|
|
1722
|
+
/* @__PURE__ */ jsx5(
|
|
1723
|
+
"span",
|
|
1724
|
+
{
|
|
1725
|
+
className: cn(
|
|
1726
|
+
"min-w-0 flex-1 break-words text-[13px] leading-5",
|
|
1727
|
+
item.status === "completed" ? "text-[hsl(var(--muted-foreground))]" : item.status === "in_progress" ? "font-medium text-[hsl(var(--foreground))]" : "text-[hsl(var(--muted-foreground))]"
|
|
1728
|
+
),
|
|
1729
|
+
children: item.step
|
|
1730
|
+
}
|
|
1731
|
+
)
|
|
1732
|
+
] }, `${index}-${item.step}`)) }) })
|
|
1733
|
+
}
|
|
1734
|
+
)
|
|
1735
|
+
] });
|
|
1736
|
+
}
|
|
1737
|
+
function CurrentPlanPanel({
|
|
1738
|
+
messages,
|
|
1739
|
+
running = false,
|
|
1740
|
+
revealRevision = 0,
|
|
1741
|
+
sessionId,
|
|
1742
|
+
className
|
|
1743
|
+
}) {
|
|
1744
|
+
const { current, updating } = getPlanUpdateDisplayState(messages);
|
|
1745
|
+
const revealBaselinesRef = useRef5(/* @__PURE__ */ new Map([[sessionId, revealRevision]]));
|
|
1746
|
+
const autoReveal = (revealBaselinesRef.current.get(sessionId) ?? 0) !== revealRevision;
|
|
1747
|
+
useEffect5(() => {
|
|
1748
|
+
if (!current) return;
|
|
1749
|
+
revealBaselinesRef.current.set(sessionId, revealRevision);
|
|
1750
|
+
}, [current, revealRevision, sessionId]);
|
|
1751
|
+
if (!current && !updating) return null;
|
|
1752
|
+
return /* @__PURE__ */ jsxs4("div", { className: cn("blade-chat-plan mx-auto w-full max-w-[748px] px-4", className), children: [
|
|
1753
|
+
updating ? /* @__PURE__ */ jsxs4("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: [
|
|
1754
|
+
/* @__PURE__ */ jsx5(LoaderCircle, { size: 14, className: "shrink-0 animate-spin" }),
|
|
1755
|
+
/* @__PURE__ */ jsx5("span", { children: "\u6B63\u5728\u66F4\u65B0\u4EFB\u52A1\u8FDB\u5EA6\u2026" })
|
|
1756
|
+
] }) : null,
|
|
1757
|
+
current ? /* @__PURE__ */ jsx5(
|
|
1758
|
+
PlanUpdateBlock,
|
|
1759
|
+
{
|
|
1760
|
+
toolCall: current,
|
|
1761
|
+
running,
|
|
1762
|
+
autoReveal
|
|
1763
|
+
},
|
|
1764
|
+
sessionId ?? "current-session"
|
|
1765
|
+
) : null
|
|
1766
|
+
] });
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// src/components/ChatSurface.tsx
|
|
1770
|
+
import { chatErrorForDisplay as chatErrorForDisplay2 } from "@blade-hq/agent-client";
|
|
1771
|
+
|
|
1772
|
+
// src/components/ChatInput.tsx
|
|
1773
|
+
import { useState as useState6 } from "react";
|
|
1774
|
+
import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1775
|
+
function isImeCompositionKey(event) {
|
|
1776
|
+
return event.isComposing || event.keyCode === 229;
|
|
1777
|
+
}
|
|
1778
|
+
function shouldSubmitChatInput(event, menuOpen) {
|
|
1779
|
+
return event.key === "Enter" && !event.shiftKey && !menuOpen && !isImeCompositionKey(event);
|
|
1780
|
+
}
|
|
1781
|
+
function ChatInput({
|
|
1782
|
+
value,
|
|
1783
|
+
onValueChange,
|
|
1784
|
+
onSend,
|
|
1785
|
+
onAppend,
|
|
1786
|
+
onStop,
|
|
1787
|
+
isStreaming,
|
|
1788
|
+
isStopping = false,
|
|
1789
|
+
placeholder = "\u8F93\u5165\u6D88\u606F\u2026",
|
|
1790
|
+
className,
|
|
1791
|
+
queueKey
|
|
1792
|
+
}) {
|
|
1793
|
+
const trimmed = value.trim();
|
|
1794
|
+
const [sendMode, setSendMode] = useState6("queue");
|
|
1795
|
+
void queueKey;
|
|
1796
|
+
const canSend = trimmed.length > 0 && (!isStreaming || !isStopping && sendMode === "queue" && !!onAppend);
|
|
1797
|
+
const handleSend = async () => {
|
|
1798
|
+
if (!canSend) return;
|
|
1799
|
+
if (isStreaming && sendMode === "queue") {
|
|
1800
|
+
if (onAppend) onAppend(trimmed);
|
|
1801
|
+
onValueChange("");
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
if (isStreaming && sendMode === "direct") {
|
|
1805
|
+
if (!onAppend) return;
|
|
1806
|
+
onAppend(trimmed);
|
|
1807
|
+
onValueChange("");
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1810
|
+
const accepted = await onSend(trimmed);
|
|
1811
|
+
if (!accepted) return;
|
|
1812
|
+
onValueChange("");
|
|
1813
|
+
};
|
|
1814
|
+
const handleKeyDown = (event) => {
|
|
1815
|
+
if (shouldSubmitChatInput({
|
|
1816
|
+
key: event.key,
|
|
1817
|
+
shiftKey: event.shiftKey,
|
|
1818
|
+
isComposing: event.nativeEvent.isComposing,
|
|
1819
|
+
keyCode: event.nativeEvent.keyCode
|
|
1820
|
+
}, false)) {
|
|
1821
|
+
event.preventDefault();
|
|
1822
|
+
void handleSend();
|
|
1823
|
+
}
|
|
1824
|
+
};
|
|
1825
|
+
return /* @__PURE__ */ jsxs5("div", { className: cn("blade-chat-input border-t border-[hsl(var(--border))] py-3", className), children: [
|
|
1826
|
+
/* @__PURE__ */ jsx6("div", { className: "mx-auto mb-2 flex max-w-[748px] items-center justify-between px-1 text-xs text-[hsl(var(--muted-foreground))]", children: /* @__PURE__ */ jsxs5("fieldset", { className: "flex items-center gap-1 rounded-md border border-[hsl(var(--border))] p-0.5", children: [
|
|
1827
|
+
/* @__PURE__ */ jsx6("legend", { className: "sr-only", children: "\u53D1\u9001\u65B9\u5F0F" }),
|
|
1828
|
+
/* @__PURE__ */ jsx6("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" }),
|
|
1829
|
+
/* @__PURE__ */ jsx6("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" })
|
|
1830
|
+
] }) }),
|
|
1831
|
+
/* @__PURE__ */ jsxs5("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: [
|
|
1832
|
+
/* @__PURE__ */ jsx6(
|
|
1833
|
+
"textarea",
|
|
1834
|
+
{
|
|
1835
|
+
value,
|
|
1836
|
+
onChange: (event) => onValueChange(event.target.value),
|
|
1837
|
+
onKeyDown: handleKeyDown,
|
|
1838
|
+
onInput: (event) => {
|
|
1839
|
+
const el = event.currentTarget;
|
|
1840
|
+
el.style.height = "auto";
|
|
1841
|
+
el.style.height = `${Math.min(el.scrollHeight, 192)}px`;
|
|
1842
|
+
},
|
|
1843
|
+
rows: 1,
|
|
1844
|
+
placeholder,
|
|
1845
|
+
"aria-label": "\u804A\u5929\u8F93\u5165",
|
|
1846
|
+
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)]"
|
|
1847
|
+
}
|
|
1848
|
+
),
|
|
1849
|
+
isStreaming ? /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
1850
|
+
sendMode === "queue" ? /* @__PURE__ */ jsx6("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__ */ jsx6(ArrowUp, { size: 15 }) }) : null,
|
|
1851
|
+
/* @__PURE__ */ jsx6("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__ */ jsx6(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx6(Square, { size: 12, fill: "currentColor" }) })
|
|
1852
|
+
] }) : /* @__PURE__ */ jsx6(
|
|
1853
|
+
"button",
|
|
1854
|
+
{
|
|
1855
|
+
type: "button",
|
|
1856
|
+
onClick: handleSend,
|
|
1857
|
+
disabled: !canSend,
|
|
1858
|
+
"aria-label": "\u53D1\u9001\u6D88\u606F",
|
|
1859
|
+
title: "\u53D1\u9001\u6D88\u606F",
|
|
1860
|
+
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",
|
|
1861
|
+
children: /* @__PURE__ */ jsx6(ArrowUp, { size: 15 })
|
|
1862
|
+
}
|
|
1863
|
+
)
|
|
1864
|
+
] })
|
|
1865
|
+
] });
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
// src/components/ConnectionBanner.tsx
|
|
1869
|
+
import { useEffect as useEffect6, useRef as useRef6, useState as useState7 } from "react";
|
|
1870
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1871
|
+
var CONNECTION_NOTICE_DELAY_MS = 3e3;
|
|
1872
|
+
var CONNECTION_ERROR_DELAY_MS = 15e3;
|
|
1873
|
+
function useConnectionNoticePhase(connected) {
|
|
1874
|
+
const [phase, setPhase] = useState7("hidden");
|
|
1875
|
+
const connectedRef = useRef6(connected);
|
|
1876
|
+
const timersRef = useRef6([]);
|
|
1877
|
+
connectedRef.current = connected;
|
|
1878
|
+
useEffect6(() => {
|
|
1879
|
+
const clearTimers = () => {
|
|
1880
|
+
for (const timer of timersRef.current) clearTimeout(timer);
|
|
1881
|
+
timersRef.current = [];
|
|
1882
|
+
};
|
|
1883
|
+
const startGracePeriod = () => {
|
|
1884
|
+
clearTimers();
|
|
1885
|
+
setPhase("hidden");
|
|
1886
|
+
timersRef.current = [
|
|
1887
|
+
setTimeout(() => setPhase("recovering"), CONNECTION_NOTICE_DELAY_MS),
|
|
1888
|
+
setTimeout(() => setPhase("failed"), CONNECTION_ERROR_DELAY_MS)
|
|
1889
|
+
];
|
|
1890
|
+
};
|
|
1891
|
+
if (connected) {
|
|
1892
|
+
clearTimers();
|
|
1893
|
+
setPhase("hidden");
|
|
1894
|
+
} else {
|
|
1895
|
+
startGracePeriod();
|
|
1896
|
+
}
|
|
1897
|
+
const handleForeground = () => {
|
|
1898
|
+
if (!connectedRef.current) startGracePeriod();
|
|
1899
|
+
};
|
|
1900
|
+
const handleVisibilityChange = () => {
|
|
1901
|
+
if (document.visibilityState === "visible") handleForeground();
|
|
1902
|
+
};
|
|
1903
|
+
window.addEventListener("blade:app-active", handleForeground);
|
|
1904
|
+
window.addEventListener("focus", handleForeground);
|
|
1905
|
+
window.addEventListener("pageshow", handleForeground);
|
|
1906
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
1907
|
+
return () => {
|
|
1908
|
+
clearTimers();
|
|
1909
|
+
window.removeEventListener("blade:app-active", handleForeground);
|
|
1910
|
+
window.removeEventListener("focus", handleForeground);
|
|
1911
|
+
window.removeEventListener("pageshow", handleForeground);
|
|
1912
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
1913
|
+
};
|
|
1914
|
+
}, [connected]);
|
|
1915
|
+
return phase;
|
|
1916
|
+
}
|
|
1917
|
+
function ConnectionBanner({ connection, className }) {
|
|
1918
|
+
const hasConnectedRef = useRef6(connection === "connected" || connection === "reconnecting");
|
|
1919
|
+
if (connection === "connected") hasConnectedRef.current = true;
|
|
1920
|
+
const connected = connection === "connected";
|
|
1921
|
+
const phase = useConnectionNoticePhase(connected);
|
|
1922
|
+
if (connected || phase === "hidden") return null;
|
|
1923
|
+
const recovering = phase === "recovering";
|
|
1924
|
+
const firstConnection = !hasConnectedRef.current;
|
|
1925
|
+
return /* @__PURE__ */ jsx7("div", { className: cn("blade-chat-banner bg-[hsl(var(--background))] px-5 pt-3", className), children: /* @__PURE__ */ jsxs6(
|
|
1926
|
+
"div",
|
|
1927
|
+
{
|
|
1928
|
+
className: cn(
|
|
1929
|
+
"mx-auto flex max-w-3xl items-start gap-3 rounded-2xl border px-4 py-3",
|
|
1930
|
+
recovering ? "border-amber-500/25 bg-amber-500/10 text-amber-100" : "border-rose-500/25 bg-rose-500/10 text-rose-100"
|
|
1931
|
+
),
|
|
1932
|
+
children: [
|
|
1933
|
+
/* @__PURE__ */ jsx7("span", { className: "mt-0.5 shrink-0", children: recovering ? /* @__PURE__ */ jsx7(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx7(TriangleAlert, { size: 14 }) }),
|
|
1934
|
+
/* @__PURE__ */ jsxs6("div", { className: "min-w-0", children: [
|
|
1935
|
+
/* @__PURE__ */ jsx7("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" }),
|
|
1936
|
+
/* @__PURE__ */ jsx7("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" })
|
|
1937
|
+
] })
|
|
1938
|
+
]
|
|
1939
|
+
}
|
|
1940
|
+
) });
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
// src/components/MessageList.tsx
|
|
1944
|
+
import { isHiddenInternalMessage } from "@blade-hq/agent-client";
|
|
1945
|
+
import { useCallback as useCallback7, useEffect as useEffect12, useMemo as useMemo7, useRef as useRef13, useState as useState15 } from "react";
|
|
1946
|
+
|
|
1947
|
+
// ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/useStickToBottom.js
|
|
1948
|
+
import { useCallback as useCallback4, useMemo as useMemo3, useRef as useRef7, useState as useState8 } from "react";
|
|
1949
|
+
var DEFAULT_SPRING_ANIMATION = {
|
|
1950
|
+
/**
|
|
1951
|
+
* A value from 0 to 1, on how much to damp the animation.
|
|
1088
1952
|
* 0 means no damping, 1 means full damping.
|
|
1089
1953
|
*
|
|
1090
1954
|
* @default 0.7
|
|
@@ -1118,12 +1982,12 @@ globalThis.document?.addEventListener("click", () => {
|
|
|
1118
1982
|
mouseDown = false;
|
|
1119
1983
|
});
|
|
1120
1984
|
var useStickToBottom = (options = {}) => {
|
|
1121
|
-
const [escapedFromLock, updateEscapedFromLock] =
|
|
1122
|
-
const [isAtBottom, updateIsAtBottom] =
|
|
1123
|
-
const [isNearBottom, setIsNearBottom] =
|
|
1124
|
-
const optionsRef =
|
|
1985
|
+
const [escapedFromLock, updateEscapedFromLock] = useState8(false);
|
|
1986
|
+
const [isAtBottom, updateIsAtBottom] = useState8(options.initial !== false);
|
|
1987
|
+
const [isNearBottom, setIsNearBottom] = useState8(false);
|
|
1988
|
+
const optionsRef = useRef7(null);
|
|
1125
1989
|
optionsRef.current = options;
|
|
1126
|
-
const isSelecting =
|
|
1990
|
+
const isSelecting = useCallback4(() => {
|
|
1127
1991
|
if (!mouseDown) {
|
|
1128
1992
|
return false;
|
|
1129
1993
|
}
|
|
@@ -1134,11 +1998,11 @@ var useStickToBottom = (options = {}) => {
|
|
|
1134
1998
|
const range = selection.getRangeAt(0);
|
|
1135
1999
|
return range.commonAncestorContainer.contains(scrollRef.current) || scrollRef.current?.contains(range.commonAncestorContainer);
|
|
1136
2000
|
}, []);
|
|
1137
|
-
const setIsAtBottom =
|
|
2001
|
+
const setIsAtBottom = useCallback4((isAtBottom2) => {
|
|
1138
2002
|
state.isAtBottom = isAtBottom2;
|
|
1139
2003
|
updateIsAtBottom(isAtBottom2);
|
|
1140
2004
|
}, []);
|
|
1141
|
-
const setEscapedFromLock =
|
|
2005
|
+
const setEscapedFromLock = useCallback4((escapedFromLock2) => {
|
|
1142
2006
|
state.escapedFromLock = escapedFromLock2;
|
|
1143
2007
|
updateEscapedFromLock(escapedFromLock2);
|
|
1144
2008
|
}, []);
|
|
@@ -1195,7 +2059,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
1195
2059
|
}
|
|
1196
2060
|
};
|
|
1197
2061
|
}, []);
|
|
1198
|
-
const scrollToBottom =
|
|
2062
|
+
const scrollToBottom = useCallback4((scrollOptions = {}) => {
|
|
1199
2063
|
if (typeof scrollOptions === "string") {
|
|
1200
2064
|
scrollOptions = { animation: scrollOptions };
|
|
1201
2065
|
}
|
|
@@ -1280,11 +2144,11 @@ var useStickToBottom = (options = {}) => {
|
|
|
1280
2144
|
}
|
|
1281
2145
|
return next();
|
|
1282
2146
|
}, [setIsAtBottom, isSelecting, state]);
|
|
1283
|
-
const stopScroll =
|
|
2147
|
+
const stopScroll = useCallback4(() => {
|
|
1284
2148
|
setEscapedFromLock(true);
|
|
1285
2149
|
setIsAtBottom(false);
|
|
1286
2150
|
}, [setEscapedFromLock, setIsAtBottom]);
|
|
1287
|
-
const handleScroll =
|
|
2151
|
+
const handleScroll = useCallback4(({ target }) => {
|
|
1288
2152
|
if (target !== scrollRef.current) {
|
|
1289
2153
|
return;
|
|
1290
2154
|
}
|
|
@@ -1323,7 +2187,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
1323
2187
|
}
|
|
1324
2188
|
}, 1);
|
|
1325
2189
|
}, [setEscapedFromLock, setIsAtBottom, isSelecting, state]);
|
|
1326
|
-
const handleWheel =
|
|
2190
|
+
const handleWheel = useCallback4(({ target, deltaY }) => {
|
|
1327
2191
|
let element = target;
|
|
1328
2192
|
while (!["scroll", "auto"].includes(getComputedStyle(element).overflow)) {
|
|
1329
2193
|
if (!element.parentElement) {
|
|
@@ -1393,7 +2257,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
1393
2257
|
};
|
|
1394
2258
|
};
|
|
1395
2259
|
function useRefCallback(callback, deps) {
|
|
1396
|
-
const result =
|
|
2260
|
+
const result = useCallback4((ref) => {
|
|
1397
2261
|
result.current = ref;
|
|
1398
2262
|
return callback(ref);
|
|
1399
2263
|
}, deps);
|
|
@@ -1425,11 +2289,11 @@ function mergeAnimations(...animations) {
|
|
|
1425
2289
|
|
|
1426
2290
|
// ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/StickToBottom.js
|
|
1427
2291
|
import * as React from "react";
|
|
1428
|
-
import { createContext as createContext2, useContext as useContext2, useEffect as
|
|
2292
|
+
import { createContext as createContext2, useContext as useContext2, useEffect as useEffect7, useImperativeHandle, useLayoutEffect, useMemo as useMemo4, useRef as useRef8 } from "react";
|
|
1429
2293
|
var StickToBottomContext = createContext2(null);
|
|
1430
|
-
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect :
|
|
2294
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect7;
|
|
1431
2295
|
function StickToBottom({ instance, children, resize, initial, mass, damping, stiffness, targetScrollTop: currentTargetScrollTop, contextRef, ...props }) {
|
|
1432
|
-
const customTargetScrollTop =
|
|
2296
|
+
const customTargetScrollTop = useRef8(null);
|
|
1433
2297
|
const targetScrollTop = React.useCallback((target, elements) => {
|
|
1434
2298
|
const get = context?.targetScrollTop ?? currentTargetScrollTop;
|
|
1435
2299
|
return get?.(target, elements) ?? target;
|
|
@@ -1469,169 +2333,53 @@ function StickToBottom({ instance, children, resize, initial, mass, damping, sti
|
|
|
1469
2333
|
useImperativeHandle(contextRef, () => context, [context]);
|
|
1470
2334
|
useIsomorphicLayoutEffect(() => {
|
|
1471
2335
|
if (!scrollRef.current) {
|
|
1472
|
-
return;
|
|
1473
|
-
}
|
|
1474
|
-
if (getComputedStyle(scrollRef.current).overflow === "visible") {
|
|
1475
|
-
scrollRef.current.style.overflow = "auto";
|
|
1476
|
-
}
|
|
1477
|
-
}, []);
|
|
1478
|
-
return React.createElement(
|
|
1479
|
-
StickToBottomContext.Provider,
|
|
1480
|
-
{ value: context },
|
|
1481
|
-
React.createElement("div", { ...props }, typeof children === "function" ? children(context) : children)
|
|
1482
|
-
);
|
|
1483
|
-
}
|
|
1484
|
-
(function(StickToBottom2) {
|
|
1485
|
-
function Content({ children, scrollClassName, ...props }) {
|
|
1486
|
-
const context = useStickToBottomContext();
|
|
1487
|
-
return React.createElement(
|
|
1488
|
-
"div",
|
|
1489
|
-
{ ref: context.scrollRef, style: {
|
|
1490
|
-
height: "100%",
|
|
1491
|
-
width: "100%",
|
|
1492
|
-
scrollbarGutter: "stable both-edges"
|
|
1493
|
-
}, className: scrollClassName },
|
|
1494
|
-
React.createElement("div", { ...props, ref: context.contentRef }, typeof children === "function" ? children(context) : children)
|
|
1495
|
-
);
|
|
1496
|
-
}
|
|
1497
|
-
StickToBottom2.Content = Content;
|
|
1498
|
-
})(StickToBottom || (StickToBottom = {}));
|
|
1499
|
-
function useStickToBottomContext() {
|
|
1500
|
-
const context = useContext2(StickToBottomContext);
|
|
1501
|
-
if (!context) {
|
|
1502
|
-
throw new Error("use-stick-to-bottom component context must be used within a StickToBottom component");
|
|
1503
|
-
}
|
|
1504
|
-
return context;
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
// src/components/AssistantTurnBlock.tsx
|
|
1508
|
-
import { getTextContent, normalizeMessageContent } from "@blade-hq/agent-client";
|
|
1509
|
-
import { useState as useState9 } from "react";
|
|
1510
|
-
|
|
1511
|
-
// src/components/AgentLoopBlock.tsx
|
|
1512
|
-
import { useState as useState5 } from "react";
|
|
1513
|
-
|
|
1514
|
-
// src/components/display-utils.ts
|
|
1515
|
-
var TOOL_NAME_ALIASES = {
|
|
1516
|
-
agent: "Agent",
|
|
1517
|
-
ask_user_question: "AskUserQuestion",
|
|
1518
|
-
bash: "Bash",
|
|
1519
|
-
bg_bash: "BgBash",
|
|
1520
|
-
edit: "Edit",
|
|
1521
|
-
exit_plan_mode: "ExitPlanMode",
|
|
1522
|
-
file_edit: "Edit",
|
|
1523
|
-
file_read: "Read",
|
|
1524
|
-
file_write: "Write",
|
|
1525
|
-
finish_task: "FinishTask",
|
|
1526
|
-
glob: "Glob",
|
|
1527
|
-
grep: "Grep",
|
|
1528
|
-
ls: "Ls",
|
|
1529
|
-
read: "Read",
|
|
1530
|
-
read_skill: "ReadSkill",
|
|
1531
|
-
web_fetch: "WebFetch",
|
|
1532
|
-
web_search: "WebSearch",
|
|
1533
|
-
write: "Write"
|
|
1534
|
-
};
|
|
1535
|
-
var TOOL_DISPLAY_LABELS = {
|
|
1536
|
-
Bash: "\u6267\u884C\u547D\u4EE4",
|
|
1537
|
-
BgBash: "\u540E\u53F0\u6267\u884C\u547D\u4EE4",
|
|
1538
|
-
Read: "\u8BFB\u53D6\u6587\u4EF6",
|
|
1539
|
-
Write: "\u5199\u5165\u6587\u4EF6",
|
|
1540
|
-
Edit: "\u7F16\u8F91\u6587\u4EF6",
|
|
1541
|
-
Ls: "\u5217\u51FA\u76EE\u5F55",
|
|
1542
|
-
Glob: "\u5339\u914D\u6587\u4EF6",
|
|
1543
|
-
Grep: "\u641C\u7D22\u6587\u672C",
|
|
1544
|
-
WebSearch: "\u641C\u7D22\u7F51\u9875",
|
|
1545
|
-
WebFetch: "\u6574\u7406\u7F51\u9875\u5185\u5BB9",
|
|
1546
|
-
Agent: "\u6D3E\u751F\u5B50\u667A\u80FD\u4F53",
|
|
1547
|
-
AskUserQuestion: "\u5411\u7528\u6237\u63D0\u95EE",
|
|
1548
|
-
ReadSkill: "\u8BFB\u53D6\u6280\u80FD",
|
|
1549
|
-
FinishTask: "\u4EFB\u52A1\u5B8C\u6210",
|
|
1550
|
-
ExitPlanMode: "\u63D0\u4EA4\u8BA1\u5212",
|
|
1551
|
-
ListSessions: "\u5217\u51FA\u5386\u53F2\u4F1A\u8BDD",
|
|
1552
|
-
GetSessionHistory: "\u8BFB\u53D6\u4F1A\u8BDD\u5386\u53F2"
|
|
1553
|
-
};
|
|
1554
|
-
function safeParseJson(value) {
|
|
1555
|
-
if (!value) return null;
|
|
1556
|
-
try {
|
|
1557
|
-
return JSON.parse(value);
|
|
1558
|
-
} catch {
|
|
1559
|
-
return null;
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
function getStringArgValue(args, key) {
|
|
1563
|
-
const value = args?.[key];
|
|
1564
|
-
return typeof value === "string" ? value.trim() : "";
|
|
1565
|
-
}
|
|
1566
|
-
function formatToolName(name) {
|
|
1567
|
-
const trimmed = name.trim();
|
|
1568
|
-
if (!trimmed) return name;
|
|
1569
|
-
const stripped = trimmed.split(":").pop()?.split("/").pop()?.split(".").pop()?.trim() || trimmed;
|
|
1570
|
-
const normalized = stripped.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
1571
|
-
return TOOL_NAME_ALIASES[normalized] ?? stripped;
|
|
1572
|
-
}
|
|
1573
|
-
function getToolDisplayLabel(toolCall) {
|
|
1574
|
-
const normalized = formatToolName(toolCall.name);
|
|
1575
|
-
const args = safeParseJson(toolCall.arguments);
|
|
1576
|
-
const displayName = toolCall.display_name?.trim() ?? "";
|
|
1577
|
-
const baseLabel = displayName || TOOL_DISPLAY_LABELS[normalized] || normalized;
|
|
1578
|
-
const metaDisplayName = getStringArgValue(args, "_meta_display_name");
|
|
1579
|
-
if (metaDisplayName) {
|
|
1580
|
-
return metaDisplayName;
|
|
1581
|
-
}
|
|
1582
|
-
const description = getStringArgValue(args, "description");
|
|
1583
|
-
if (normalized === "BgBash") {
|
|
1584
|
-
return description ? `\u540E\u53F0\u6267\u884C\uFF1A${description}` : "\u540E\u53F0\u6267\u884C\u547D\u4EE4";
|
|
1585
|
-
}
|
|
1586
|
-
if (normalized === "ReadSkill") {
|
|
1587
|
-
const skillName = getStringArgValue(args, "skill") || getStringArgValue(args, "skill_name");
|
|
1588
|
-
return skillName ? `${baseLabel}\u300C${skillName}\u300D` : baseLabel;
|
|
1589
|
-
}
|
|
1590
|
-
if (normalized === "FinishTask") {
|
|
1591
|
-
const title = getStringArgValue(args, "title");
|
|
1592
|
-
return title ? `${baseLabel}\uFF1A${title}` : baseLabel;
|
|
1593
|
-
}
|
|
1594
|
-
return description || baseLabel;
|
|
1595
|
-
}
|
|
1596
|
-
function getToolTone(status) {
|
|
1597
|
-
if (status === "error" || status === "cancelled") return "red";
|
|
1598
|
-
if (status === "awaiting_answer") return "amber";
|
|
1599
|
-
if (status === "pending") return "blue";
|
|
1600
|
-
return "emerald";
|
|
1601
|
-
}
|
|
1602
|
-
function getToolStatusLabel(status) {
|
|
1603
|
-
if (status === "pending") return "\u8FD0\u884C\u4E2D";
|
|
1604
|
-
if (status === "awaiting_answer") return "\u7B49\u5F85\u56DE\u7B54";
|
|
1605
|
-
if (status === "error") return "\u9519\u8BEF";
|
|
1606
|
-
if (status === "cancelled") return "\u5DF2\u53D6\u6D88";
|
|
1607
|
-
return "\u5B8C\u6210";
|
|
1608
|
-
}
|
|
1609
|
-
function formatToolDuration(ms) {
|
|
1610
|
-
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
1611
|
-
const seconds = ms / 1e3;
|
|
1612
|
-
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
1613
|
-
const minutes = Math.floor(seconds / 60);
|
|
1614
|
-
const remainingSeconds = Math.round(seconds % 60);
|
|
1615
|
-
return remainingSeconds > 0 ? `${minutes}m${remainingSeconds}s` : `${minutes}m`;
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
if (getComputedStyle(scrollRef.current).overflow === "visible") {
|
|
2339
|
+
scrollRef.current.style.overflow = "auto";
|
|
2340
|
+
}
|
|
2341
|
+
}, []);
|
|
2342
|
+
return React.createElement(
|
|
2343
|
+
StickToBottomContext.Provider,
|
|
2344
|
+
{ value: context },
|
|
2345
|
+
React.createElement("div", { ...props }, typeof children === "function" ? children(context) : children)
|
|
2346
|
+
);
|
|
1616
2347
|
}
|
|
1617
|
-
function
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
2348
|
+
(function(StickToBottom2) {
|
|
2349
|
+
function Content({ children, scrollClassName, ...props }) {
|
|
2350
|
+
const context = useStickToBottomContext();
|
|
2351
|
+
return React.createElement(
|
|
2352
|
+
"div",
|
|
2353
|
+
{ ref: context.scrollRef, style: {
|
|
2354
|
+
height: "100%",
|
|
2355
|
+
width: "100%",
|
|
2356
|
+
scrollbarGutter: "stable both-edges"
|
|
2357
|
+
}, className: scrollClassName },
|
|
2358
|
+
React.createElement("div", { ...props, ref: context.contentRef }, typeof children === "function" ? children(context) : children)
|
|
2359
|
+
);
|
|
1622
2360
|
}
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
function
|
|
1626
|
-
const
|
|
1627
|
-
if (
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
2361
|
+
StickToBottom2.Content = Content;
|
|
2362
|
+
})(StickToBottom || (StickToBottom = {}));
|
|
2363
|
+
function useStickToBottomContext() {
|
|
2364
|
+
const context = useContext2(StickToBottomContext);
|
|
2365
|
+
if (!context) {
|
|
2366
|
+
throw new Error("use-stick-to-bottom component context must be used within a StickToBottom component");
|
|
2367
|
+
}
|
|
2368
|
+
return context;
|
|
1631
2369
|
}
|
|
1632
2370
|
|
|
2371
|
+
// src/components/AssistantTurnBlock.tsx
|
|
2372
|
+
import {
|
|
2373
|
+
getFileParts,
|
|
2374
|
+
getImageParts,
|
|
2375
|
+
getTextContent,
|
|
2376
|
+
normalizeMessageContent
|
|
2377
|
+
} from "@blade-hq/agent-client";
|
|
2378
|
+
import { useEffect as useEffect10, useRef as useRef11, useState as useState13 } from "react";
|
|
2379
|
+
|
|
1633
2380
|
// src/components/AgentLoopBlock.tsx
|
|
1634
|
-
import {
|
|
2381
|
+
import { useState as useState9 } from "react";
|
|
2382
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1635
2383
|
function parseAgentDescription(argumentsJson) {
|
|
1636
2384
|
try {
|
|
1637
2385
|
const parsed = JSON.parse(argumentsJson);
|
|
@@ -1641,83 +2389,81 @@ function parseAgentDescription(argumentsJson) {
|
|
|
1641
2389
|
}
|
|
1642
2390
|
}
|
|
1643
2391
|
function AgentLoopBlock({ toolCall }) {
|
|
1644
|
-
const [expanded, setExpanded] =
|
|
2392
|
+
const [expanded, setExpanded] = useState9(false);
|
|
1645
2393
|
const description = parseAgentDescription(toolCall.arguments);
|
|
1646
2394
|
const running = toolCall.status === "pending" || toolCall.status === "awaiting_answer";
|
|
1647
2395
|
const failed = toolCall.status === "error" || toolCall.status === "cancelled";
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
2396
|
+
const hasResult = toolCall.result != null;
|
|
2397
|
+
const iconClass = cn(
|
|
2398
|
+
"size-3.5 shrink-0",
|
|
2399
|
+
failed ? "text-red-500" : "text-[hsl(var(--muted-foreground))]"
|
|
2400
|
+
);
|
|
2401
|
+
return /* @__PURE__ */ jsxs7("div", { className: "blade-chat-agent-loop text-xs leading-[22px]", children: [
|
|
2402
|
+
/* @__PURE__ */ jsxs7(
|
|
2403
|
+
"button",
|
|
1651
2404
|
{
|
|
2405
|
+
type: "button",
|
|
2406
|
+
onClick: () => hasResult && setExpanded(!expanded),
|
|
2407
|
+
disabled: !hasResult,
|
|
2408
|
+
"aria-expanded": hasResult ? expanded : void 0,
|
|
2409
|
+
"data-testid": "execution-tool-intent",
|
|
1652
2410
|
className: cn(
|
|
1653
|
-
"
|
|
1654
|
-
|
|
2411
|
+
"flex min-w-0 items-center gap-1 py-1.5 text-left",
|
|
2412
|
+
hasResult && "cursor-pointer hover:text-[hsl(var(--foreground))]",
|
|
2413
|
+
failed ? "text-red-500" : "text-[hsl(var(--muted-foreground))]"
|
|
1655
2414
|
),
|
|
2415
|
+
title: `\u5B50\u4EFB\u52A1\uFF1A${description}`,
|
|
1656
2416
|
children: [
|
|
1657
|
-
/* @__PURE__ */
|
|
1658
|
-
|
|
2417
|
+
running ? /* @__PURE__ */ jsx8(LoaderCircle, { className: cn(iconClass, "animate-spin"), "aria-hidden": "true" }) : toolCall.status === "error" ? /* @__PURE__ */ jsx8(CircleAlert, { className: iconClass, "aria-hidden": "true" }) : toolCall.status === "cancelled" ? /* @__PURE__ */ jsx8(X, { className: iconClass, "aria-hidden": "true" }) : /* @__PURE__ */ jsx8(Bot, { className: iconClass, "aria-hidden": "true" }),
|
|
2418
|
+
/* @__PURE__ */ jsxs7("span", { className: "min-w-0 truncate", children: [
|
|
2419
|
+
"\u5B50\u4EFB\u52A1\uFF1A",
|
|
2420
|
+
description
|
|
2421
|
+
] }),
|
|
2422
|
+
hasResult ? /* @__PURE__ */ jsx8(
|
|
2423
|
+
ChevronRight,
|
|
1659
2424
|
{
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
className:
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
{
|
|
1668
|
-
size: 11,
|
|
1669
|
-
className: cn(
|
|
1670
|
-
"shrink-0 text-[hsl(var(--muted-foreground))] transition-transform",
|
|
1671
|
-
expanded && "rotate-90"
|
|
1672
|
-
)
|
|
1673
|
-
}
|
|
1674
|
-
),
|
|
1675
|
-
/* @__PURE__ */ jsx6(Bot, { size: 12, className: "shrink-0 text-[hsl(var(--muted-foreground))]" }),
|
|
1676
|
-
/* @__PURE__ */ jsxs5(
|
|
1677
|
-
"span",
|
|
1678
|
-
{
|
|
1679
|
-
className: cn(
|
|
1680
|
-
"flex shrink-0 items-center gap-1 text-[10px]",
|
|
1681
|
-
failed ? "text-[hsl(var(--muted-foreground))]" : running ? "text-blue-300" : "text-[hsl(var(--primary))]"
|
|
1682
|
-
),
|
|
1683
|
-
children: [
|
|
1684
|
-
running ? /* @__PURE__ */ jsx6(LoaderCircle, { size: 11, className: "animate-spin" }) : failed ? /* @__PURE__ */ jsx6(X, { size: 11 }) : /* @__PURE__ */ jsx6(Check, { size: 11 }),
|
|
1685
|
-
/* @__PURE__ */ jsx6("span", { children: running ? "\u6267\u884C\u4E2D" : failed ? "\u5DF2\u7EC8\u6B62" : "\u5B8C\u6210" })
|
|
1686
|
-
]
|
|
1687
|
-
}
|
|
1688
|
-
),
|
|
1689
|
-
/* @__PURE__ */ jsxs5("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: [
|
|
1690
|
-
"\u5B50\u667A\u80FD\u4F53\uFF1A",
|
|
1691
|
-
description
|
|
1692
|
-
] })
|
|
1693
|
-
]
|
|
2425
|
+
size: 14,
|
|
2426
|
+
style: { transitionDuration: "260ms", transitionTimingFunction: "cubic-bezier(0.25, 0.1, 0.25, 1)" },
|
|
2427
|
+
className: cn(
|
|
2428
|
+
"shrink-0 transition-transform",
|
|
2429
|
+
expanded && "rotate-90"
|
|
2430
|
+
),
|
|
2431
|
+
"aria-hidden": "true"
|
|
1694
2432
|
}
|
|
1695
|
-
)
|
|
1696
|
-
typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 && /* @__PURE__ */ jsx6("span", { className: "shrink-0 font-mono text-[10px] text-[hsl(var(--muted-foreground))]", children: formatToolDuration(toolCall.duration_ms) })
|
|
2433
|
+
) : null
|
|
1697
2434
|
]
|
|
1698
2435
|
}
|
|
1699
2436
|
),
|
|
1700
|
-
expanded &&
|
|
1701
|
-
/* @__PURE__ */ jsx6("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u7ED3\u679C" }),
|
|
1702
|
-
/* @__PURE__ */ jsx6("pre", { className: "max-h-[400px] overflow-auto whitespace-pre-wrap rounded-md bg-[hsl(var(--muted))] p-2 font-mono text-[11px] text-[hsl(var(--foreground))]", children: formatToolResult(toolCall.result) })
|
|
1703
|
-
] })
|
|
2437
|
+
expanded && hasResult ? /* @__PURE__ */ jsx8("div", { className: "ml-[18px] mt-1.5 max-h-[400px] overflow-auto whitespace-pre-wrap text-xs leading-[22px] text-[hsl(var(--muted-foreground))]", children: formatToolResult(toolCall.result) }) : null
|
|
1704
2438
|
] });
|
|
1705
2439
|
}
|
|
1706
2440
|
|
|
1707
2441
|
// src/components/MarkdownContent.tsx
|
|
1708
2442
|
import {
|
|
1709
|
-
useEffect as
|
|
2443
|
+
useEffect as useEffect8,
|
|
1710
2444
|
useMemo as useMemo5,
|
|
1711
|
-
useRef as
|
|
1712
|
-
useState as
|
|
2445
|
+
useRef as useRef9,
|
|
2446
|
+
useState as useState10
|
|
1713
2447
|
} from "react";
|
|
1714
|
-
import { jsx as
|
|
2448
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1715
2449
|
var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
|
|
2450
|
+
function normalizeAdjacentUrlFormatting(value) {
|
|
2451
|
+
const protectedSegments = [];
|
|
2452
|
+
const protectedValue = value.replace(/(`{1,3}[\s\S]*?`{1,3}|\[[^\]]*\]\([^)]*\))/g, (segment) => {
|
|
2453
|
+
const index = protectedSegments.push(segment) - 1;
|
|
2454
|
+
return `blade-url-protected-${index}-marker`;
|
|
2455
|
+
});
|
|
2456
|
+
const normalized = protectedValue.replace(
|
|
2457
|
+
/(\*\*|__|~~|\*|_)(https?:\/\/[^\s<>]+?)\1(?=[\s。,、!?;:,.!?;:]|$)/g,
|
|
2458
|
+
(_, marker, url) => `${marker}[${url}](<${url}>)${marker}`
|
|
2459
|
+
);
|
|
2460
|
+
return normalized.replace(/blade-url-protected-(\d+)-marker/g, (_, index) => protectedSegments[Number(index)]);
|
|
2461
|
+
}
|
|
1716
2462
|
function CodeBlockPre({ children, node: _node, ...props }) {
|
|
1717
|
-
const preRef =
|
|
1718
|
-
const [copied, setCopied] =
|
|
1719
|
-
const [language, setLanguage] =
|
|
1720
|
-
|
|
2463
|
+
const preRef = useRef9(null);
|
|
2464
|
+
const [copied, setCopied] = useState10(false);
|
|
2465
|
+
const [language, setLanguage] = useState10("");
|
|
2466
|
+
useEffect8(() => {
|
|
1721
2467
|
const codeEl = preRef.current?.querySelector("code");
|
|
1722
2468
|
setLanguage(codeEl?.className.match(/language-(\S+)/)?.[1] ?? "");
|
|
1723
2469
|
}, []);
|
|
@@ -1728,10 +2474,10 @@ function CodeBlockPre({ children, node: _node, ...props }) {
|
|
|
1728
2474
|
setTimeout(() => setCopied(false), 2e3);
|
|
1729
2475
|
}
|
|
1730
2476
|
};
|
|
1731
|
-
return /* @__PURE__ */
|
|
1732
|
-
/* @__PURE__ */
|
|
1733
|
-
/* @__PURE__ */
|
|
1734
|
-
/* @__PURE__ */
|
|
2477
|
+
return /* @__PURE__ */ jsxs8("div", { className: "blade-chat-codeblock not-prose my-3 overflow-hidden rounded-xl border border-[hsl(var(--border))]", children: [
|
|
2478
|
+
/* @__PURE__ */ jsxs8("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: [
|
|
2479
|
+
/* @__PURE__ */ jsx9("span", { className: "font-mono text-[12px] text-[hsl(var(--muted-foreground))]", children: language || "code" }),
|
|
2480
|
+
/* @__PURE__ */ jsxs8(
|
|
1735
2481
|
"button",
|
|
1736
2482
|
{
|
|
1737
2483
|
type: "button",
|
|
@@ -1741,13 +2487,13 @@ function CodeBlockPre({ children, node: _node, ...props }) {
|
|
|
1741
2487
|
copied ? "text-[hsl(var(--primary))]" : "text-[hsl(var(--muted-foreground))] hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--foreground))]"
|
|
1742
2488
|
),
|
|
1743
2489
|
children: [
|
|
1744
|
-
copied ? /* @__PURE__ */
|
|
1745
|
-
/* @__PURE__ */
|
|
2490
|
+
copied ? /* @__PURE__ */ jsx9(Check, { size: 12 }) : /* @__PURE__ */ jsx9(Copy, { size: 12 }),
|
|
2491
|
+
/* @__PURE__ */ jsx9("span", { children: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236" })
|
|
1746
2492
|
]
|
|
1747
2493
|
}
|
|
1748
2494
|
)
|
|
1749
2495
|
] }),
|
|
1750
|
-
/* @__PURE__ */
|
|
2496
|
+
/* @__PURE__ */ jsx9(
|
|
1751
2497
|
"pre",
|
|
1752
2498
|
{
|
|
1753
2499
|
ref: preRef,
|
|
@@ -1759,7 +2505,7 @@ function CodeBlockPre({ children, node: _node, ...props }) {
|
|
|
1759
2505
|
] });
|
|
1760
2506
|
}
|
|
1761
2507
|
function ExternalAnchor({ node: _node, children, ...props }) {
|
|
1762
|
-
return /* @__PURE__ */
|
|
2508
|
+
return /* @__PURE__ */ jsx9("a", { ...props, target: "_blank", rel: "noopener noreferrer", children });
|
|
1763
2509
|
}
|
|
1764
2510
|
var MARKDOWN_COMPONENTS = {
|
|
1765
2511
|
pre: CodeBlockPre,
|
|
@@ -1767,9 +2513,9 @@ var MARKDOWN_COMPONENTS = {
|
|
|
1767
2513
|
};
|
|
1768
2514
|
function MarkdownContent({ children, className, mode, sessionId }) {
|
|
1769
2515
|
const resolvedChildren = useMemo5(() => {
|
|
1770
|
-
return children.replace(SYSTEM_REMINDER_RE, "");
|
|
2516
|
+
return normalizeAdjacentUrlFormatting(children.replace(SYSTEM_REMINDER_RE, ""));
|
|
1771
2517
|
}, [children]);
|
|
1772
|
-
return /* @__PURE__ */
|
|
2518
|
+
return /* @__PURE__ */ jsx9(
|
|
1773
2519
|
_r,
|
|
1774
2520
|
{
|
|
1775
2521
|
className: cn("blade-chat-markdown break-words", className),
|
|
@@ -1782,17 +2528,46 @@ function MarkdownContent({ children, className, mode, sessionId }) {
|
|
|
1782
2528
|
}
|
|
1783
2529
|
|
|
1784
2530
|
// src/components/Shimmer.tsx
|
|
1785
|
-
import { jsx as
|
|
2531
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
1786
2532
|
function Shimmer({ children = "\u6B63\u5728\u601D\u8003...", className }) {
|
|
1787
|
-
return /* @__PURE__ */
|
|
2533
|
+
return /* @__PURE__ */ jsx10("span", { className: cn("blade-shimmer-text text-sm font-medium", className), children });
|
|
1788
2534
|
}
|
|
1789
2535
|
|
|
1790
2536
|
// src/components/ToolCallBlock.tsx
|
|
1791
|
-
import { useState as
|
|
2537
|
+
import { useState as useState12 } from "react";
|
|
1792
2538
|
|
|
1793
2539
|
// src/components/AskUserQuestionBlock.tsx
|
|
1794
|
-
import { useEffect as
|
|
1795
|
-
import { jsx as
|
|
2540
|
+
import { useEffect as useEffect9, useMemo as useMemo6, useRef as useRef10, useState as useState11 } from "react";
|
|
2541
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2542
|
+
var CUSTOM_TEXTAREA_MAX_HEIGHT = 160;
|
|
2543
|
+
function resizeCustomTextarea(textarea) {
|
|
2544
|
+
textarea.style.height = "auto";
|
|
2545
|
+
textarea.style.height = `${Math.min(textarea.scrollHeight, CUSTOM_TEXTAREA_MAX_HEIGHT)}px`;
|
|
2546
|
+
textarea.style.overflowY = textarea.scrollHeight > CUSTOM_TEXTAREA_MAX_HEIGHT ? "auto" : "hidden";
|
|
2547
|
+
}
|
|
2548
|
+
function useAutoResizeTextarea(value) {
|
|
2549
|
+
const textareaRef = useRef10(null);
|
|
2550
|
+
useEffect9(() => {
|
|
2551
|
+
const textarea = textareaRef.current;
|
|
2552
|
+
if (textarea?.value === value) resizeCustomTextarea(textarea);
|
|
2553
|
+
}, [value]);
|
|
2554
|
+
useEffect9(() => {
|
|
2555
|
+
const textarea = textareaRef.current;
|
|
2556
|
+
if (!textarea || typeof ResizeObserver === "undefined") return;
|
|
2557
|
+
let previousWidth = textarea.clientWidth;
|
|
2558
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
2559
|
+
if (!entry || entry.contentRect.width === previousWidth) return;
|
|
2560
|
+
previousWidth = entry.contentRect.width;
|
|
2561
|
+
resizeCustomTextarea(textarea);
|
|
2562
|
+
});
|
|
2563
|
+
observer.observe(textarea);
|
|
2564
|
+
return () => observer.disconnect();
|
|
2565
|
+
}, []);
|
|
2566
|
+
return textareaRef;
|
|
2567
|
+
}
|
|
2568
|
+
function indentAnswerContinuationLines(answer) {
|
|
2569
|
+
return answer.replaceAll("\n", "\n ");
|
|
2570
|
+
}
|
|
1796
2571
|
function AskUserQuestionBlock({
|
|
1797
2572
|
data,
|
|
1798
2573
|
answered,
|
|
@@ -1801,18 +2576,19 @@ function AskUserQuestionBlock({
|
|
|
1801
2576
|
answerData,
|
|
1802
2577
|
onAnswer
|
|
1803
2578
|
}) {
|
|
1804
|
-
const [selections, setSelections] =
|
|
1805
|
-
const [customTexts, setCustomTexts] =
|
|
1806
|
-
const [usingCustom, setUsingCustom] =
|
|
1807
|
-
const [
|
|
1808
|
-
|
|
2579
|
+
const [selections, setSelections] = useState11(/* @__PURE__ */ new Map());
|
|
2580
|
+
const [customTexts, setCustomTexts] = useState11(/* @__PURE__ */ new Map());
|
|
2581
|
+
const [usingCustom, setUsingCustom] = useState11(/* @__PURE__ */ new Set());
|
|
2582
|
+
const [note, setNote] = useState11("");
|
|
2583
|
+
const [submitted, setSubmitted] = useState11(false);
|
|
2584
|
+
useEffect9(() => {
|
|
1809
2585
|
if (sessionStatus === "failed" || sessionStatus === "interrupted") {
|
|
1810
2586
|
setSubmitted(false);
|
|
1811
2587
|
}
|
|
1812
2588
|
}, [sessionStatus]);
|
|
1813
2589
|
const displayAnswerState = useMemo6(() => {
|
|
1814
2590
|
if (!(answered && answerData)) {
|
|
1815
|
-
return { selections, customTexts, usingCustom };
|
|
2591
|
+
return { selections, customTexts, usingCustom, note };
|
|
1816
2592
|
}
|
|
1817
2593
|
const nextSelections = /* @__PURE__ */ new Map();
|
|
1818
2594
|
const nextCustomTexts = /* @__PURE__ */ new Map();
|
|
@@ -1828,9 +2604,10 @@ function AskUserQuestionBlock({
|
|
|
1828
2604
|
return {
|
|
1829
2605
|
selections: nextSelections,
|
|
1830
2606
|
customTexts: nextCustomTexts,
|
|
1831
|
-
usingCustom: nextUsingCustom
|
|
2607
|
+
usingCustom: nextUsingCustom,
|
|
2608
|
+
note: answerData.note ?? ""
|
|
1832
2609
|
};
|
|
1833
|
-
}, [answerData, answered, customTexts, selections, usingCustom]);
|
|
2610
|
+
}, [answerData, answered, customTexts, note, selections, usingCustom]);
|
|
1834
2611
|
const toggleOption = (qIdx, optIdx, multi) => {
|
|
1835
2612
|
if (answered || submitted) return;
|
|
1836
2613
|
setSelections((prev) => {
|
|
@@ -1878,6 +2655,7 @@ function AskUserQuestionBlock({
|
|
|
1878
2655
|
const allAnswered = data.questions.every((_, i) => getAnswer(i) !== null);
|
|
1879
2656
|
const handleSubmit = () => {
|
|
1880
2657
|
if (answered || submitted || !allAnswered || !onAnswer) return;
|
|
2658
|
+
const trimmedNote = note.trim();
|
|
1881
2659
|
const nextAnswerData = {
|
|
1882
2660
|
selections: Object.fromEntries(
|
|
1883
2661
|
Array.from(selections.entries()).map(([qIdx, optionIndexes]) => [
|
|
@@ -1887,15 +2665,21 @@ function AskUserQuestionBlock({
|
|
|
1887
2665
|
),
|
|
1888
2666
|
custom: Object.fromEntries(
|
|
1889
2667
|
Array.from(usingCustom).map((qIdx) => [qIdx, (customTexts.get(qIdx) ?? "").trim()]).filter(([, text2]) => text2.length > 0)
|
|
1890
|
-
)
|
|
2668
|
+
),
|
|
2669
|
+
...trimmedNote ? { note: trimmedNote } : {}
|
|
1891
2670
|
};
|
|
1892
|
-
const parts = data.questions.map(
|
|
1893
|
-
|
|
1894
|
-
|
|
2671
|
+
const parts = data.questions.map(
|
|
2672
|
+
(q, i) => `- ${q.question} -> ${indentAnswerContinuationLines(getAnswer(i) ?? "")}`
|
|
2673
|
+
);
|
|
2674
|
+
const text = [
|
|
2675
|
+
`\u5173\u4E8E\u9700\u8981\u786E\u8BA4\u7684\u95EE\u9898\uFF0C\u7528\u6237\u7684\u56DE\u7B54\u5982\u4E0B\uFF1A
|
|
2676
|
+
${parts.join("\n")}`,
|
|
2677
|
+
trimmedNote ? `\u8865\u5145\u8BF4\u660E\uFF1A${indentAnswerContinuationLines(trimmedNote)}` : ""
|
|
2678
|
+
].filter(Boolean).join("\n");
|
|
1895
2679
|
setSubmitted(true);
|
|
1896
2680
|
onAnswer(text, toolCallId, nextAnswerData);
|
|
1897
2681
|
};
|
|
1898
|
-
return /* @__PURE__ */
|
|
2682
|
+
return /* @__PURE__ */ jsxs9(
|
|
1899
2683
|
"div",
|
|
1900
2684
|
{
|
|
1901
2685
|
className: cn(
|
|
@@ -1903,12 +2687,12 @@ ${parts.join("\n")}`;
|
|
|
1903
2687
|
answered ? "max-w-2xl space-y-3 p-3 text-xs text-[hsl(var(--muted-foreground))] opacity-80" : "max-w-lg space-y-5 p-4 text-sm"
|
|
1904
2688
|
),
|
|
1905
2689
|
children: [
|
|
1906
|
-
data.source_loop?.description && /* @__PURE__ */
|
|
2690
|
+
data.source_loop?.description && /* @__PURE__ */ jsxs9("div", { className: "rounded-lg bg-[hsl(var(--muted)/0.35)] px-3 py-2 text-xs text-[hsl(var(--muted-foreground))]", children: [
|
|
1907
2691
|
"\u5B50\u667A\u80FD\u4F53\u300C",
|
|
1908
2692
|
data.source_loop.description,
|
|
1909
2693
|
"\u300D\u5728\u7B49\u5F85\u4F60\u7684\u56DE\u7B54"
|
|
1910
2694
|
] }),
|
|
1911
|
-
data.questions.map((q, qIdx) => /* @__PURE__ */
|
|
2695
|
+
data.questions.map((q, qIdx) => /* @__PURE__ */ jsx11(
|
|
1912
2696
|
QuestionCard,
|
|
1913
2697
|
{
|
|
1914
2698
|
question: q,
|
|
@@ -1923,7 +2707,16 @@ ${parts.join("\n")}`;
|
|
|
1923
2707
|
},
|
|
1924
2708
|
q.question
|
|
1925
2709
|
)),
|
|
1926
|
-
|
|
2710
|
+
/* @__PURE__ */ jsx11(
|
|
2711
|
+
NoteField,
|
|
2712
|
+
{
|
|
2713
|
+
answered,
|
|
2714
|
+
submitted,
|
|
2715
|
+
note: displayAnswerState.note,
|
|
2716
|
+
onChange: setNote
|
|
2717
|
+
}
|
|
2718
|
+
),
|
|
2719
|
+
!answered && !submitted && onAnswer && /* @__PURE__ */ jsx11(
|
|
1927
2720
|
"button",
|
|
1928
2721
|
{
|
|
1929
2722
|
type: "button",
|
|
@@ -1933,14 +2726,14 @@ ${parts.join("\n")}`;
|
|
|
1933
2726
|
children: allAnswered ? "\u786E\u8BA4" : "\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u9009\u9879"
|
|
1934
2727
|
}
|
|
1935
2728
|
),
|
|
1936
|
-
submitted && !answered && /* @__PURE__ */
|
|
2729
|
+
submitted && !answered && /* @__PURE__ */ jsxs9(
|
|
1937
2730
|
"button",
|
|
1938
2731
|
{
|
|
1939
2732
|
type: "button",
|
|
1940
2733
|
disabled: true,
|
|
1941
2734
|
className: "flex w-full items-center justify-center gap-2 rounded-lg bg-[hsl(var(--primary))] px-4 py-2 text-xs font-semibold text-[hsl(var(--primary-foreground))] opacity-80",
|
|
1942
2735
|
children: [
|
|
1943
|
-
/* @__PURE__ */
|
|
2736
|
+
/* @__PURE__ */ jsx11(LoaderCircle, { size: 14, className: "animate-spin" }),
|
|
1944
2737
|
"\u786E\u8BA4\u4E2D"
|
|
1945
2738
|
]
|
|
1946
2739
|
}
|
|
@@ -1961,30 +2754,31 @@ function QuestionCard({
|
|
|
1961
2754
|
onCustomChange
|
|
1962
2755
|
}) {
|
|
1963
2756
|
const multi = question.multiSelect ?? false;
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
2757
|
+
const customTextareaRef = useAutoResizeTextarea(customText);
|
|
2758
|
+
return /* @__PURE__ */ jsxs9("div", { children: [
|
|
2759
|
+
/* @__PURE__ */ jsxs9("div", { className: cn("flex items-start gap-2", answered ? "mb-2" : "mb-3"), children: [
|
|
2760
|
+
/* @__PURE__ */ jsx11(
|
|
1967
2761
|
MessageSquareMore,
|
|
1968
2762
|
{
|
|
1969
2763
|
size: answered ? 12 : 13,
|
|
1970
2764
|
className: "mt-0.5 shrink-0 text-[hsl(var(--primary))]"
|
|
1971
2765
|
}
|
|
1972
2766
|
),
|
|
1973
|
-
/* @__PURE__ */
|
|
2767
|
+
/* @__PURE__ */ jsx11(
|
|
1974
2768
|
"div",
|
|
1975
2769
|
{
|
|
1976
2770
|
className: cn(
|
|
1977
2771
|
"min-w-0 flex-1 font-medium text-[hsl(var(--foreground))]",
|
|
1978
2772
|
answered ? "text-xs" : "text-sm"
|
|
1979
2773
|
),
|
|
1980
|
-
children: /* @__PURE__ */
|
|
2774
|
+
children: /* @__PURE__ */ jsx11(MarkdownContent, { className: "blade-chat-prose", children: question.question })
|
|
1981
2775
|
}
|
|
1982
2776
|
)
|
|
1983
2777
|
] }),
|
|
1984
|
-
/* @__PURE__ */
|
|
2778
|
+
/* @__PURE__ */ jsxs9("div", { className: cn("flex flex-col pl-5", answered ? "gap-1" : "gap-1.5"), children: [
|
|
1985
2779
|
question.options.map((opt, optIdx) => {
|
|
1986
2780
|
const isSel = selected.has(optIdx);
|
|
1987
|
-
return /* @__PURE__ */
|
|
2781
|
+
return /* @__PURE__ */ jsxs9(
|
|
1988
2782
|
"button",
|
|
1989
2783
|
{
|
|
1990
2784
|
type: "button",
|
|
@@ -1998,14 +2792,14 @@ function QuestionCard({
|
|
|
1998
2792
|
answered && "cursor-default opacity-70"
|
|
1999
2793
|
),
|
|
2000
2794
|
children: [
|
|
2001
|
-
multi && /* @__PURE__ */
|
|
2795
|
+
multi && /* @__PURE__ */ jsx11(
|
|
2002
2796
|
"div",
|
|
2003
2797
|
{
|
|
2004
2798
|
className: cn(
|
|
2005
2799
|
"mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded border transition-colors",
|
|
2006
2800
|
isSel && !answered ? "border-[hsl(var(--primary-foreground)/0.6)] bg-[hsl(var(--primary-foreground)/0.2)]" : isSel ? "border-[hsl(var(--primary)/0.45)] bg-[hsl(var(--primary)/0.12)]" : "border-[hsl(var(--border))]"
|
|
2007
2801
|
),
|
|
2008
|
-
children: isSel && /* @__PURE__ */
|
|
2802
|
+
children: isSel && /* @__PURE__ */ jsx11(
|
|
2009
2803
|
Check,
|
|
2010
2804
|
{
|
|
2011
2805
|
size: 9,
|
|
@@ -2014,9 +2808,9 @@ function QuestionCard({
|
|
|
2014
2808
|
)
|
|
2015
2809
|
}
|
|
2016
2810
|
),
|
|
2017
|
-
/* @__PURE__ */
|
|
2018
|
-
/* @__PURE__ */
|
|
2019
|
-
opt.description && /* @__PURE__ */
|
|
2811
|
+
/* @__PURE__ */ jsxs9("div", { className: "min-w-0", children: [
|
|
2812
|
+
/* @__PURE__ */ jsx11("div", { className: cn("font-medium", answered ? "text-xs" : "text-[13px]"), children: opt.label }),
|
|
2813
|
+
opt.description && /* @__PURE__ */ jsx11(
|
|
2020
2814
|
"div",
|
|
2021
2815
|
{
|
|
2022
2816
|
className: cn(
|
|
@@ -2033,29 +2827,30 @@ function QuestionCard({
|
|
|
2033
2827
|
opt.label
|
|
2034
2828
|
);
|
|
2035
2829
|
}),
|
|
2036
|
-
answered && !isCustom ? null : /* @__PURE__ */
|
|
2830
|
+
answered && !isCustom ? null : /* @__PURE__ */ jsxs9(
|
|
2037
2831
|
"div",
|
|
2038
2832
|
{
|
|
2039
2833
|
className: cn(
|
|
2040
|
-
"flex items-
|
|
2834
|
+
"flex items-start gap-2 rounded-lg border transition-all focus-within:ring-2 focus-within:ring-[hsl(var(--ring)/0.35)]",
|
|
2041
2835
|
answered ? "px-2.5 py-1.5" : "px-3 py-2.5",
|
|
2042
2836
|
isCustom ? "border-[hsl(var(--ring)/0.6)] bg-[hsl(var(--accent))]" : "border-[hsl(var(--border))] hover:border-[hsl(var(--ring)/0.3)] hover:bg-[hsl(var(--accent))]",
|
|
2043
2837
|
answered && "cursor-default opacity-70"
|
|
2044
2838
|
),
|
|
2045
2839
|
children: [
|
|
2046
|
-
/* @__PURE__ */
|
|
2047
|
-
/* @__PURE__ */
|
|
2048
|
-
"
|
|
2840
|
+
/* @__PURE__ */ jsx11("span", { className: "shrink-0 pt-1 text-xs text-[hsl(var(--muted-foreground))]", children: "\u5176\u4ED6\uFF1A" }),
|
|
2841
|
+
/* @__PURE__ */ jsx11(
|
|
2842
|
+
"textarea",
|
|
2049
2843
|
{
|
|
2050
|
-
|
|
2844
|
+
ref: customTextareaRef,
|
|
2845
|
+
rows: 2,
|
|
2051
2846
|
value: customText,
|
|
2052
|
-
|
|
2847
|
+
readOnly: answered,
|
|
2053
2848
|
onChange: (e) => onCustomChange(qIdx, e.target.value),
|
|
2054
2849
|
onFocus: () => onCustomFocus(qIdx),
|
|
2055
2850
|
"aria-label": "\u81EA\u5B9A\u4E49\u56DE\u7B54",
|
|
2056
2851
|
placeholder: "\u8F93\u5165\u4F60\u7684\u7B54\u6848...",
|
|
2057
2852
|
className: cn(
|
|
2058
|
-
"min-w-0 flex-1 bg-transparent text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground)/0.5)]",
|
|
2853
|
+
"min-h-10 min-w-0 flex-1 resize-none bg-transparent leading-5 text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground)/0.5)]",
|
|
2059
2854
|
answered ? "text-xs" : "text-sm"
|
|
2060
2855
|
)
|
|
2061
2856
|
}
|
|
@@ -2066,6 +2861,49 @@ function QuestionCard({
|
|
|
2066
2861
|
] })
|
|
2067
2862
|
] });
|
|
2068
2863
|
}
|
|
2864
|
+
function NoteField({
|
|
2865
|
+
answered,
|
|
2866
|
+
submitted,
|
|
2867
|
+
note,
|
|
2868
|
+
onChange
|
|
2869
|
+
}) {
|
|
2870
|
+
const textareaRef = useAutoResizeTextarea(note);
|
|
2871
|
+
const readOnly = answered || submitted;
|
|
2872
|
+
if (answered && !note.trim()) return null;
|
|
2873
|
+
return /* @__PURE__ */ jsxs9(
|
|
2874
|
+
"label",
|
|
2875
|
+
{
|
|
2876
|
+
className: cn(
|
|
2877
|
+
"block rounded-lg border transition-all focus-within:ring-2 focus-within:ring-[hsl(var(--ring)/0.35)]",
|
|
2878
|
+
answered ? "px-2.5 py-1.5" : "px-3 py-2.5",
|
|
2879
|
+
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))]",
|
|
2880
|
+
readOnly && "cursor-default opacity-70"
|
|
2881
|
+
),
|
|
2882
|
+
children: [
|
|
2883
|
+
/* @__PURE__ */ jsx11("span", { className: "mb-1.5 block text-xs text-[hsl(var(--muted-foreground))]", children: "\u8865\u5145\u8BF4\u660E\uFF08\u53EF\u9009\uFF09" }),
|
|
2884
|
+
/* @__PURE__ */ jsx11(
|
|
2885
|
+
"textarea",
|
|
2886
|
+
{
|
|
2887
|
+
ref: textareaRef,
|
|
2888
|
+
rows: 2,
|
|
2889
|
+
value: note,
|
|
2890
|
+
readOnly,
|
|
2891
|
+
onChange: (event) => {
|
|
2892
|
+
if (readOnly) return;
|
|
2893
|
+
onChange(event.target.value);
|
|
2894
|
+
},
|
|
2895
|
+
"aria-label": "\u8865\u5145\u8BF4\u660E",
|
|
2896
|
+
placeholder: "\u9009\u5B8C\u8FD8\u53EF\u4EE5\u518D\u8BB2\u4E24\u53E5\uFF0C\u7A7A\u7740\u5C31\u5F53\u6CA1\u6709",
|
|
2897
|
+
className: cn(
|
|
2898
|
+
"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)]",
|
|
2899
|
+
answered ? "text-xs" : "text-sm"
|
|
2900
|
+
)
|
|
2901
|
+
}
|
|
2902
|
+
)
|
|
2903
|
+
]
|
|
2904
|
+
}
|
|
2905
|
+
);
|
|
2906
|
+
}
|
|
2069
2907
|
function parseAskUserQuestion(toolResult) {
|
|
2070
2908
|
if (!toolResult) return null;
|
|
2071
2909
|
try {
|
|
@@ -2088,6 +2926,26 @@ function parseAskUserQuestion(toolResult) {
|
|
|
2088
2926
|
}
|
|
2089
2927
|
return null;
|
|
2090
2928
|
}
|
|
2929
|
+
function parseAskUserQuestionError(toolResult) {
|
|
2930
|
+
if (!toolResult) return null;
|
|
2931
|
+
try {
|
|
2932
|
+
const parsed = JSON.parse(toolResult);
|
|
2933
|
+
let detail = null;
|
|
2934
|
+
if (typeof parsed.error === "string") detail = parsed.error;
|
|
2935
|
+
if (parsed.error && typeof parsed.error === "object") {
|
|
2936
|
+
const message = parsed.error.message;
|
|
2937
|
+
if (typeof message === "string") detail = message;
|
|
2938
|
+
}
|
|
2939
|
+
if (!detail && typeof parsed.message === "string") detail = parsed.message;
|
|
2940
|
+
if (!detail) return null;
|
|
2941
|
+
return {
|
|
2942
|
+
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",
|
|
2943
|
+
detail
|
|
2944
|
+
};
|
|
2945
|
+
} catch {
|
|
2946
|
+
return null;
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2091
2949
|
function normalizeQuestionItem(value) {
|
|
2092
2950
|
if (!value || typeof value !== "object") return null;
|
|
2093
2951
|
const item = value;
|
|
@@ -2112,13 +2970,14 @@ function normalizeOptionItem(value) {
|
|
|
2112
2970
|
}
|
|
2113
2971
|
|
|
2114
2972
|
// src/components/ToolCallBlock.tsx
|
|
2115
|
-
import { Fragment, jsx as
|
|
2973
|
+
import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2116
2974
|
function resolveAskQuestionState({
|
|
2117
2975
|
toolStatus,
|
|
2118
2976
|
hasAnswerData,
|
|
2119
|
-
fallbackAnswered
|
|
2977
|
+
fallbackAnswered,
|
|
2978
|
+
fallbackAwaiting
|
|
2120
2979
|
}) {
|
|
2121
|
-
const awaitingAnswer = !hasAnswerData && toolStatus === "awaiting_answer";
|
|
2980
|
+
const awaitingAnswer = !hasAnswerData && (toolStatus === "awaiting_answer" || toolStatus === "pending" && fallbackAwaiting === true);
|
|
2122
2981
|
return {
|
|
2123
2982
|
awaitingAnswer,
|
|
2124
2983
|
answered: hasAnswerData || !awaitingAnswer && (Boolean(fallbackAnswered) || toolStatus === "done" || toolStatus === "cancelled" || toolStatus === "error")
|
|
@@ -2130,14 +2989,15 @@ function ToolCallBlock({
|
|
|
2130
2989
|
answered,
|
|
2131
2990
|
answerData,
|
|
2132
2991
|
sessionStatus,
|
|
2992
|
+
isActiveQuestion,
|
|
2133
2993
|
renderer
|
|
2134
2994
|
}) {
|
|
2135
|
-
const [expanded, setExpanded] =
|
|
2995
|
+
const [expanded, setExpanded] = useState12(false);
|
|
2136
2996
|
const normalizedName = formatToolName(toolCall.name);
|
|
2137
2997
|
if (renderer) {
|
|
2138
2998
|
const custom = renderer(toolCall);
|
|
2139
2999
|
if (custom !== null && custom !== void 0) {
|
|
2140
|
-
return /* @__PURE__ */
|
|
3000
|
+
return /* @__PURE__ */ jsx12(Fragment2, { children: custom });
|
|
2141
3001
|
}
|
|
2142
3002
|
}
|
|
2143
3003
|
if (normalizedName === "AskUserQuestion") {
|
|
@@ -2145,11 +3005,12 @@ function ToolCallBlock({
|
|
|
2145
3005
|
const questionState = resolveAskQuestionState({
|
|
2146
3006
|
toolStatus: toolCall.status,
|
|
2147
3007
|
hasAnswerData: Boolean(answerData),
|
|
2148
|
-
fallbackAnswered: answered
|
|
3008
|
+
fallbackAnswered: answered,
|
|
3009
|
+
fallbackAwaiting: isActiveQuestion === true && (sessionStatus === "paused" || sessionStatus === "waiting_for_input")
|
|
2149
3010
|
});
|
|
2150
3011
|
const canAnswer = questionState.awaitingAnswer && Boolean(onAnswer);
|
|
2151
3012
|
if (askData) {
|
|
2152
|
-
return /* @__PURE__ */
|
|
3013
|
+
return /* @__PURE__ */ jsx12(
|
|
2153
3014
|
AskUserQuestionBlock,
|
|
2154
3015
|
{
|
|
2155
3016
|
data: askData,
|
|
@@ -2162,24 +3023,31 @@ function ToolCallBlock({
|
|
|
2162
3023
|
);
|
|
2163
3024
|
}
|
|
2164
3025
|
if (toolCall.status === "pending") {
|
|
2165
|
-
return /* @__PURE__ */
|
|
2166
|
-
/* @__PURE__ */
|
|
2167
|
-
/* @__PURE__ */
|
|
3026
|
+
return /* @__PURE__ */ jsxs10("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: [
|
|
3027
|
+
/* @__PURE__ */ jsx12(LoaderCircle, { size: 14, className: "animate-spin" }),
|
|
3028
|
+
/* @__PURE__ */ jsx12("span", { children: "\u6B63\u5728\u51C6\u5907\u95EE\u9898\u2026" })
|
|
2168
3029
|
] });
|
|
2169
3030
|
}
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
3031
|
+
const errorDetail = parseAskUserQuestionError(
|
|
3032
|
+
typeof toolCall.result === "string" ? toolCall.result : null
|
|
3033
|
+
);
|
|
3034
|
+
return /* @__PURE__ */ jsxs10("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: [
|
|
3035
|
+
/* @__PURE__ */ jsx12("div", { className: "font-semibold", children: "\u9009\u62E9\u9898\u5185\u5BB9\u6682\u65F6\u65E0\u6CD5\u663E\u793A" }),
|
|
3036
|
+
/* @__PURE__ */ jsx12("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" }),
|
|
3037
|
+
errorDetail?.detail ? /* @__PURE__ */ jsxs10("details", { className: "mt-2 text-xs text-[hsl(var(--muted-foreground))]", children: [
|
|
3038
|
+
/* @__PURE__ */ jsx12("summary", { className: "cursor-pointer", children: "\u67E5\u770B\u5177\u4F53\u539F\u56E0" }),
|
|
3039
|
+
/* @__PURE__ */ jsx12("div", { className: "mt-1 break-words font-mono", children: errorDetail.detail })
|
|
3040
|
+
] }) : null
|
|
2173
3041
|
] });
|
|
2174
3042
|
}
|
|
2175
3043
|
const tone = getToolTone(toolCall.status);
|
|
2176
3044
|
const displayName = getToolDisplayLabel(toolCall);
|
|
2177
3045
|
const toneClass = tone === "red" ? "border-l-[hsl(var(--muted-foreground)/0.5)]" : tone === "amber" ? "border-l-amber-400" : tone === "blue" ? "border-l-blue-500" : "border-l-[hsl(var(--primary))]";
|
|
2178
|
-
const statusIcon = toolCall.status === "pending" ? /* @__PURE__ */
|
|
3046
|
+
const statusIcon = toolCall.status === "pending" ? /* @__PURE__ */ jsx12(LoaderCircle, { size: 11, className: "animate-spin" }) : toolCall.status === "awaiting_answer" ? /* @__PURE__ */ jsx12(MessageSquareMore, { size: 11 }) : toolCall.status === "cancelled" || toolCall.status === "error" ? /* @__PURE__ */ jsx12(X, { size: 11 }) : /* @__PURE__ */ jsx12(Check, { size: 11 });
|
|
2179
3047
|
const statusTextClass = tone === "red" ? "text-[hsl(var(--muted-foreground))]" : tone === "amber" ? "text-amber-300" : tone === "blue" ? "text-blue-300" : "text-[hsl(var(--primary))]";
|
|
2180
|
-
return /* @__PURE__ */
|
|
2181
|
-
/* @__PURE__ */
|
|
2182
|
-
/* @__PURE__ */
|
|
3048
|
+
return /* @__PURE__ */ jsxs10("div", { className: "blade-chat-tool ml-4 text-xs", children: [
|
|
3049
|
+
/* @__PURE__ */ jsxs10("div", { className: cn("border-l-[3px] flex items-center gap-2 px-3 py-2", toneClass), children: [
|
|
3050
|
+
/* @__PURE__ */ jsxs10(
|
|
2183
3051
|
"button",
|
|
2184
3052
|
{
|
|
2185
3053
|
type: "button",
|
|
@@ -2187,7 +3055,7 @@ function ToolCallBlock({
|
|
|
2187
3055
|
className: "flex min-w-0 flex-1 items-center gap-2 text-left transition-colors hover:bg-white/3 focus-visible:ring-1 focus-visible:ring-[hsl(var(--ring))] focus:outline-none",
|
|
2188
3056
|
"aria-expanded": expanded,
|
|
2189
3057
|
children: [
|
|
2190
|
-
/* @__PURE__ */
|
|
3058
|
+
/* @__PURE__ */ jsx12(
|
|
2191
3059
|
ChevronRight,
|
|
2192
3060
|
{
|
|
2193
3061
|
size: 11,
|
|
@@ -2197,24 +3065,24 @@ function ToolCallBlock({
|
|
|
2197
3065
|
)
|
|
2198
3066
|
}
|
|
2199
3067
|
),
|
|
2200
|
-
/* @__PURE__ */
|
|
3068
|
+
/* @__PURE__ */ jsxs10("span", { className: cn("flex shrink-0 items-center gap-1 text-[10px]", statusTextClass), children: [
|
|
2201
3069
|
statusIcon,
|
|
2202
|
-
/* @__PURE__ */
|
|
3070
|
+
/* @__PURE__ */ jsx12("span", { children: getToolStatusLabel(toolCall.status) })
|
|
2203
3071
|
] }),
|
|
2204
|
-
/* @__PURE__ */
|
|
3072
|
+
/* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: displayName })
|
|
2205
3073
|
]
|
|
2206
3074
|
}
|
|
2207
3075
|
),
|
|
2208
|
-
typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 && /* @__PURE__ */
|
|
3076
|
+
typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 && /* @__PURE__ */ jsx12("span", { className: "shrink-0 font-mono text-[10px] text-[hsl(var(--muted-foreground))]", children: formatToolDuration(toolCall.duration_ms) })
|
|
2209
3077
|
] }),
|
|
2210
|
-
expanded && /* @__PURE__ */
|
|
2211
|
-
/* @__PURE__ */
|
|
2212
|
-
/* @__PURE__ */
|
|
2213
|
-
/* @__PURE__ */
|
|
2214
|
-
/* @__PURE__ */
|
|
2215
|
-
toolCall.result != null && /* @__PURE__ */
|
|
2216
|
-
/* @__PURE__ */
|
|
2217
|
-
/* @__PURE__ */
|
|
3078
|
+
expanded && /* @__PURE__ */ jsxs10("div", { className: "blade-chat-tool-detail ml-4 mt-1 rounded-xl bg-[hsl(var(--card))] px-3 py-3", children: [
|
|
3079
|
+
/* @__PURE__ */ jsx12("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u5DE5\u5177" }),
|
|
3080
|
+
/* @__PURE__ */ jsx12("div", { className: "mb-3 font-mono text-[11px] text-[hsl(var(--foreground))]", children: normalizedName }),
|
|
3081
|
+
/* @__PURE__ */ jsx12("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u53C2\u6570" }),
|
|
3082
|
+
/* @__PURE__ */ jsx12("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) }),
|
|
3083
|
+
toolCall.result != null && /* @__PURE__ */ jsxs10(Fragment2, { children: [
|
|
3084
|
+
/* @__PURE__ */ jsx12("div", { className: "mb-1 mt-3 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u7ED3\u679C" }),
|
|
3085
|
+
/* @__PURE__ */ jsx12("pre", { className: "max-h-[400px] overflow-auto whitespace-pre-wrap rounded-md bg-[hsl(var(--muted))] p-2 font-mono text-[11px] text-[hsl(var(--foreground))]", children: formatToolResult(toolCall.result) })
|
|
2218
3086
|
] })
|
|
2219
3087
|
] })
|
|
2220
3088
|
] });
|
|
@@ -2232,47 +3100,235 @@ function buildAskUserPayload(argumentsJson) {
|
|
|
2232
3100
|
}
|
|
2233
3101
|
|
|
2234
3102
|
// src/components/AssistantTurnBlock.tsx
|
|
2235
|
-
import { jsx as
|
|
3103
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2236
3104
|
function ThinkingBlock({ reasoning, isStreaming }) {
|
|
2237
|
-
const [open, setOpen] =
|
|
2238
|
-
|
|
2239
|
-
|
|
3105
|
+
const [open, setOpen] = useState13(false);
|
|
3106
|
+
if (!isStreaming) return null;
|
|
3107
|
+
return /* @__PURE__ */ jsxs11("div", { className: "blade-chat-thinking text-xs", children: [
|
|
3108
|
+
/* @__PURE__ */ jsxs11(
|
|
2240
3109
|
"button",
|
|
2241
3110
|
{
|
|
2242
3111
|
type: "button",
|
|
2243
3112
|
onClick: () => setOpen(!open),
|
|
2244
3113
|
"aria-expanded": open,
|
|
2245
|
-
className: "inline-flex items-center gap-1
|
|
3114
|
+
className: "group/thinking inline-flex items-center gap-1 text-[hsl(var(--muted-foreground))] transition-colors hover:text-[hsl(var(--foreground))]",
|
|
2246
3115
|
children: [
|
|
2247
|
-
/* @__PURE__ */
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
"\xB7 ",
|
|
2251
|
-
new Intl.NumberFormat("zh-CN").format(reasoning.length),
|
|
2252
|
-
" \u5B57"
|
|
2253
|
-
] }),
|
|
2254
|
-
/* @__PURE__ */ jsx11(
|
|
2255
|
-
ChevronDown,
|
|
3116
|
+
/* @__PURE__ */ jsx13(Shimmer, { className: "text-xs", children: "\u6B63\u5728\u601D\u8003" }),
|
|
3117
|
+
/* @__PURE__ */ jsx13(
|
|
3118
|
+
ChevronRight,
|
|
2256
3119
|
{
|
|
2257
|
-
size:
|
|
2258
|
-
className: cn(
|
|
3120
|
+
size: 14,
|
|
3121
|
+
className: cn(
|
|
3122
|
+
"shrink-0 opacity-0 transition-[opacity,transform] group-hover/thinking:opacity-100",
|
|
3123
|
+
open && "rotate-90 opacity-100"
|
|
3124
|
+
)
|
|
2259
3125
|
}
|
|
2260
3126
|
)
|
|
2261
3127
|
]
|
|
2262
3128
|
}
|
|
2263
3129
|
),
|
|
2264
|
-
open
|
|
3130
|
+
open ? /* @__PURE__ */ jsx13("div", { className: "mt-1.5 whitespace-pre-wrap text-xs leading-[22px] text-[hsl(var(--muted-foreground))]", children: reasoning }) : null
|
|
2265
3131
|
] });
|
|
2266
3132
|
}
|
|
2267
3133
|
function getMessageText(message) {
|
|
2268
3134
|
return getTextContent(normalizeMessageContent(message.content)).trim();
|
|
2269
3135
|
}
|
|
3136
|
+
function hasRenderableMessageContent(message) {
|
|
3137
|
+
return Boolean(getMessageText(message)) || getImageParts(message.content).length > 0 || getFileParts(message.content).length > 0;
|
|
3138
|
+
}
|
|
3139
|
+
function getLastContentMessage(messages) {
|
|
3140
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
3141
|
+
if (hasRenderableMessageContent(messages[index])) return messages[index];
|
|
3142
|
+
}
|
|
3143
|
+
return null;
|
|
3144
|
+
}
|
|
3145
|
+
function getOrderedMessageParts(message, toolCalls) {
|
|
3146
|
+
const blocks = message.blocks ?? [];
|
|
3147
|
+
if (!blocks.some((block) => block.type === "text") || !blocks.some((block) => block.type === "tool_use")) return [];
|
|
3148
|
+
const toolsById = new Map(toolCalls.map((toolCall) => [toolCall.id, toolCall]));
|
|
3149
|
+
const seenToolIds = /* @__PURE__ */ new Set();
|
|
3150
|
+
const parts = [];
|
|
3151
|
+
for (const [index, block] of blocks.entries()) {
|
|
3152
|
+
if (block.type === "text" && block.content != null && block.content !== "") {
|
|
3153
|
+
const content = Array.isArray(block.content) ? block.content : String(block.content);
|
|
3154
|
+
parts.push({ type: "text", key: `text-${index}`, content });
|
|
3155
|
+
}
|
|
3156
|
+
if (block.type !== "tool_use" || !block.tool_call_id) continue;
|
|
3157
|
+
const toolCall = toolsById.get(block.tool_call_id);
|
|
3158
|
+
if (!toolCall) continue;
|
|
3159
|
+
seenToolIds.add(toolCall.id);
|
|
3160
|
+
const previous = parts[parts.length - 1];
|
|
3161
|
+
if (previous?.type === "tools") previous.toolCalls.push(toolCall);
|
|
3162
|
+
else parts.push({ type: "tools", key: `tools-${index}`, toolCalls: [toolCall] });
|
|
3163
|
+
}
|
|
3164
|
+
const missingTools = toolCalls.filter((toolCall) => !seenToolIds.has(toolCall.id));
|
|
3165
|
+
if (seenToolIds.size === 0) return [];
|
|
3166
|
+
if (missingTools.length > 0) {
|
|
3167
|
+
parts.push({ type: "tools", key: "tools-missing", toolCalls: missingTools });
|
|
3168
|
+
}
|
|
3169
|
+
return parts;
|
|
3170
|
+
}
|
|
2270
3171
|
function findLatestReasoningMessageIndex(messages) {
|
|
2271
3172
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
2272
3173
|
if (messages[index].reasoning) return index;
|
|
2273
3174
|
}
|
|
2274
3175
|
return -1;
|
|
2275
3176
|
}
|
|
3177
|
+
function resolveTurnDisplayMode({
|
|
3178
|
+
isStreaming: _isStreaming,
|
|
3179
|
+
displayMode
|
|
3180
|
+
}) {
|
|
3181
|
+
return displayMode;
|
|
3182
|
+
}
|
|
3183
|
+
function formatExecutionDuration(durationMs) {
|
|
3184
|
+
const totalSeconds = Math.max(0, Math.round(durationMs / 1e3));
|
|
3185
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
3186
|
+
const seconds = totalSeconds % 60;
|
|
3187
|
+
return minutes > 0 ? `${minutes}\u5206${seconds}\u79D2` : `${seconds}\u79D2`;
|
|
3188
|
+
}
|
|
3189
|
+
function getExecutionDurationMs({
|
|
3190
|
+
messages,
|
|
3191
|
+
isStreaming,
|
|
3192
|
+
now = Date.now()
|
|
3193
|
+
}) {
|
|
3194
|
+
const knownDuration = messages.reduce(
|
|
3195
|
+
(total, message) => {
|
|
3196
|
+
if (typeof message.duration_ms === "number" && message.duration_ms > 0) {
|
|
3197
|
+
return total + message.duration_ms;
|
|
3198
|
+
}
|
|
3199
|
+
return total + (message.tool_calls ?? []).reduce(
|
|
3200
|
+
(toolTotal, toolCall) => toolTotal + (typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 ? toolCall.duration_ms : 0),
|
|
3201
|
+
0
|
|
3202
|
+
);
|
|
3203
|
+
},
|
|
3204
|
+
0
|
|
3205
|
+
);
|
|
3206
|
+
if (!isStreaming) return knownDuration;
|
|
3207
|
+
const startedAt = messages.map((message) => message.timestamp ? Date.parse(message.timestamp) : Number.NaN).filter((value) => Number.isFinite(value)).sort((a, b) => a - b)[0];
|
|
3208
|
+
if (startedAt === void 0) return knownDuration;
|
|
3209
|
+
return Math.max(knownDuration, now - startedAt);
|
|
3210
|
+
}
|
|
3211
|
+
function findLastExceptionalEvent(messages) {
|
|
3212
|
+
for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex -= 1) {
|
|
3213
|
+
const messageStatus = messages[messageIndex].status;
|
|
3214
|
+
if (messageStatus === "failed") return { messageIndex, status: "error" };
|
|
3215
|
+
if (messageStatus === "interrupted") return { messageIndex, status: "cancelled" };
|
|
3216
|
+
const toolCalls = messages[messageIndex].tool_calls ?? [];
|
|
3217
|
+
for (let toolIndex = toolCalls.length - 1; toolIndex >= 0; toolIndex -= 1) {
|
|
3218
|
+
const status = toolCalls[toolIndex].status;
|
|
3219
|
+
if (status === "error" || status === "cancelled") {
|
|
3220
|
+
return { messageIndex, status };
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
return null;
|
|
3225
|
+
}
|
|
3226
|
+
function executionSummaryLabel({
|
|
3227
|
+
messages,
|
|
3228
|
+
isStreaming,
|
|
3229
|
+
durationMs,
|
|
3230
|
+
sessionStatus,
|
|
3231
|
+
askAnswers
|
|
3232
|
+
}) {
|
|
3233
|
+
if (isStreaming) {
|
|
3234
|
+
return durationMs > 0 ? `\u6B63\u5728\u6267\u884C ${formatExecutionDuration(durationMs)}` : "\u6B63\u5728\u6267\u884C";
|
|
3235
|
+
}
|
|
3236
|
+
if (sessionStatus === "waiting_for_input" && messages.some(
|
|
3237
|
+
(message) => (message.tool_calls ?? []).some(
|
|
3238
|
+
(toolCall) => formatToolName(toolCall.name) === "AskUserQuestion" && toolCall.status === "awaiting_answer" && !askAnswers?.[toolCall.id]
|
|
3239
|
+
)
|
|
3240
|
+
)) {
|
|
3241
|
+
return "\u7B49\u5F85\u8F93\u5165";
|
|
3242
|
+
}
|
|
3243
|
+
const completedLabel = durationMs > 0 ? `\u6267\u884C\u5B8C\u6210 ${formatExecutionDuration(durationMs)}` : "\u6267\u884C\u5B8C\u6210";
|
|
3244
|
+
const lastExceptionalEvent = findLastExceptionalEvent(messages);
|
|
3245
|
+
if (lastExceptionalEvent) {
|
|
3246
|
+
const recovered = messages.slice(lastExceptionalEvent.messageIndex + 1).some(hasRenderableMessageContent);
|
|
3247
|
+
if (lastExceptionalEvent.status === "error") {
|
|
3248
|
+
return recovered ? `${completedLabel} \xB7 \u90E8\u5206\u6B65\u9AA4\u5931\u8D25` : "\u6267\u884C\u5931\u8D25";
|
|
3249
|
+
}
|
|
3250
|
+
return recovered ? `${completedLabel} \xB7 \u90E8\u5206\u6B65\u9AA4\u672A\u5B8C\u6210` : "\u6267\u884C\u5DF2\u4E2D\u65AD";
|
|
3251
|
+
}
|
|
3252
|
+
return completedLabel;
|
|
3253
|
+
}
|
|
3254
|
+
function businessToolDisplayName(toolCall) {
|
|
3255
|
+
const displayName = toolCall.display_name?.trim() ?? "";
|
|
3256
|
+
if (!displayName) return "";
|
|
3257
|
+
const rawName = toolCall.name.trim();
|
|
3258
|
+
return displayName !== rawName && formatToolName(displayName) !== formatToolName(rawName) ? displayName : "";
|
|
3259
|
+
}
|
|
3260
|
+
function executionToolTypeLabel(toolCall) {
|
|
3261
|
+
switch (formatToolName(toolCall.name)) {
|
|
3262
|
+
case "WebSearch":
|
|
3263
|
+
case "WebFetch":
|
|
3264
|
+
return "\u7F51\u7EDC\u68C0\u7D22";
|
|
3265
|
+
case "Bash":
|
|
3266
|
+
case "BgBash":
|
|
3267
|
+
return "\u547D\u4EE4\u6267\u884C";
|
|
3268
|
+
case "Read":
|
|
3269
|
+
case "ReadSkill":
|
|
3270
|
+
return "\u5185\u5BB9\u8BFB\u53D6";
|
|
3271
|
+
case "Write":
|
|
3272
|
+
case "Edit":
|
|
3273
|
+
case "MultiEdit":
|
|
3274
|
+
return "\u6587\u4EF6\u5904\u7406";
|
|
3275
|
+
case "Grep":
|
|
3276
|
+
case "Glob":
|
|
3277
|
+
return "\u5185\u5BB9\u641C\u7D22";
|
|
3278
|
+
case "Agent":
|
|
3279
|
+
return "\u5B50\u4EFB\u52A1";
|
|
3280
|
+
case "search_skills":
|
|
3281
|
+
return "\u6280\u80FD\u68C0\u7D22";
|
|
3282
|
+
case "get_skill_content":
|
|
3283
|
+
return "\u8BFB\u53D6\u6280\u80FD";
|
|
3284
|
+
case "run_skill_tool":
|
|
3285
|
+
return "\u6267\u884C\u6280\u80FD";
|
|
3286
|
+
default:
|
|
3287
|
+
return businessToolDisplayName(toolCall) || "\u6267\u884C\u6B65\u9AA4";
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
function executionToolIntent(toolCall) {
|
|
3291
|
+
const normalizedName = formatToolName(toolCall.name);
|
|
3292
|
+
let args = null;
|
|
3293
|
+
try {
|
|
3294
|
+
const parsed = JSON.parse(toolCall.arguments);
|
|
3295
|
+
args = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
3296
|
+
} catch {
|
|
3297
|
+
args = null;
|
|
3298
|
+
}
|
|
3299
|
+
const getString = (key) => {
|
|
3300
|
+
const value = args?.[key];
|
|
3301
|
+
return typeof value === "string" ? value.trim() : "";
|
|
3302
|
+
};
|
|
3303
|
+
const explicitIntent = getString("description") || getString("_meta_display_name") || getString("display_name") || "";
|
|
3304
|
+
if (explicitIntent) return explicitIntent;
|
|
3305
|
+
if (normalizedName === "search_skills") return getString("query");
|
|
3306
|
+
if (normalizedName === "get_skill_content" || normalizedName === "ReadSkill") {
|
|
3307
|
+
return getString("skill_name") || getString("skill");
|
|
3308
|
+
}
|
|
3309
|
+
if (normalizedName === "FinishTask") return getString("title");
|
|
3310
|
+
return "";
|
|
3311
|
+
}
|
|
3312
|
+
function ExecutionToolRow({ toolCall }) {
|
|
3313
|
+
const normalizedName = formatToolName(toolCall.name);
|
|
3314
|
+
const typeLabel = executionToolTypeLabel(toolCall);
|
|
3315
|
+
const intent = executionToolIntent(toolCall);
|
|
3316
|
+
const label = intent ? `${typeLabel}\uFF1A${intent}` : typeLabel;
|
|
3317
|
+
const failed = toolCall.status === "error" || toolCall.status === "cancelled";
|
|
3318
|
+
const iconClass = cn(
|
|
3319
|
+
"size-3.5 shrink-0",
|
|
3320
|
+
failed ? "text-red-500" : "text-[hsl(var(--muted-foreground))]"
|
|
3321
|
+
);
|
|
3322
|
+
const icon = toolCall.status === "pending" ? /* @__PURE__ */ jsx13(LoaderCircle, { className: cn(iconClass, "animate-spin"), "aria-hidden": "true" }) : toolCall.status === "error" ? /* @__PURE__ */ jsx13(CircleAlert, { className: iconClass, "aria-hidden": "true" }) : toolCall.status === "cancelled" ? /* @__PURE__ */ jsx13(X, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "WebSearch" || normalizedName === "WebFetch" ? /* @__PURE__ */ jsx13(Earth, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "Bash" || normalizedName === "BgBash" ? /* @__PURE__ */ jsx13(Terminal, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "Write" || normalizedName === "Edit" || normalizedName === "MultiEdit" ? /* @__PURE__ */ jsx13(FilePenLine, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "Read" || normalizedName === "ReadSkill" || normalizedName === "get_skill_content" ? /* @__PURE__ */ jsx13(BookOpen, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "Grep" || normalizedName === "Glob" || normalizedName === "search_skills" ? /* @__PURE__ */ jsx13(Search, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "Agent" ? /* @__PURE__ */ jsx13(Bot, { className: iconClass, "aria-hidden": "true" }) : /* @__PURE__ */ jsx13(Wrench, { className: iconClass, "aria-hidden": "true" });
|
|
3323
|
+
const rowClassName = cn(
|
|
3324
|
+
"flex min-w-0 items-center gap-1 py-1.5 text-xs leading-[22px]",
|
|
3325
|
+
failed ? "text-red-500" : "text-[hsl(var(--muted-foreground))]"
|
|
3326
|
+
);
|
|
3327
|
+
return /* @__PURE__ */ jsxs11("div", { "data-testid": "execution-tool-intent", className: rowClassName, title: label, children: [
|
|
3328
|
+
icon,
|
|
3329
|
+
/* @__PURE__ */ jsx13("span", { className: "min-w-0 truncate", children: label })
|
|
3330
|
+
] });
|
|
3331
|
+
}
|
|
2276
3332
|
function AssistantTurnBlock({
|
|
2277
3333
|
messages,
|
|
2278
3334
|
isStreaming = false,
|
|
@@ -2280,62 +3336,319 @@ function AssistantTurnBlock({
|
|
|
2280
3336
|
onAnswer,
|
|
2281
3337
|
sessionStatus,
|
|
2282
3338
|
toolCallRenderer,
|
|
3339
|
+
hidePlanUpdateTools = false,
|
|
2283
3340
|
sessionId
|
|
2284
3341
|
}) {
|
|
2285
|
-
const
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
3342
|
+
const shouldHideToolCall = (message, toolCall) => {
|
|
3343
|
+
if (!hidePlanUpdateTools || !isPlanUpdateTool(toolCall) || parsePlanUpdate(toolCall.arguments) === null) {
|
|
3344
|
+
return false;
|
|
3345
|
+
}
|
|
3346
|
+
return toolCall.status === "done" || toolCall.status === "pending" && message.status === "streaming";
|
|
3347
|
+
};
|
|
3348
|
+
const hasInterrupted = messages.some((message) => message.status === "interrupted");
|
|
3349
|
+
const hasFailedWithoutContent = messages.some(
|
|
3350
|
+
(message) => message.status === "failed" && !hasRenderableMessageContent(message)
|
|
3351
|
+
);
|
|
3352
|
+
const finalMessage = getLastContentMessage(messages);
|
|
3353
|
+
const turnToolCalls = messages.flatMap((message) => message.tool_calls ?? []);
|
|
3354
|
+
const finalOrderedParts = finalMessage ? getOrderedMessageParts(
|
|
3355
|
+
finalMessage,
|
|
3356
|
+
(finalMessage.tool_calls ?? []).filter(
|
|
3357
|
+
(toolCall) => formatToolName(toolCall.name) !== "AskUserQuestion" && !shouldHideToolCall(finalMessage, toolCall)
|
|
3358
|
+
)
|
|
3359
|
+
) : [];
|
|
3360
|
+
const hasExecutionProcess = messages.some(
|
|
3361
|
+
(message) => message.reasoning || (message.tool_calls ?? []).some((toolCall) => !shouldHideToolCall(message, toolCall))
|
|
3362
|
+
);
|
|
3363
|
+
const latestReasoningIndex = isStreaming ? findLatestReasoningMessageIndex(messages) : -1;
|
|
3364
|
+
const hasActionableToolCall = messages.some(
|
|
3365
|
+
(message) => message.status === "failed" || message.status === "interrupted" || (message.tool_calls ?? []).some(
|
|
3366
|
+
(toolCall) => !shouldHideToolCall(message, toolCall) && (toolCall.status === "error" || toolCall.status === "cancelled")
|
|
3367
|
+
)
|
|
3368
|
+
);
|
|
3369
|
+
const questionToolCalls = messages.flatMap(
|
|
3370
|
+
(message) => (message.tool_calls ?? []).filter(
|
|
3371
|
+
(toolCall) => formatToolName(toolCall.name) === "AskUserQuestion"
|
|
3372
|
+
)
|
|
3373
|
+
);
|
|
3374
|
+
const activeQuestionId = questionToolCalls.filter((toolCall) => toolCall.status === "pending").at(-1)?.id;
|
|
3375
|
+
const [displayMode, setDisplayMode] = useState13(
|
|
3376
|
+
() => isStreaming || hasActionableToolCall ? "detail" : "compact"
|
|
3377
|
+
);
|
|
3378
|
+
const userSelectedDisplayModeRef = useRef11(false);
|
|
3379
|
+
const wasStreamingRef = useRef11(isStreaming);
|
|
3380
|
+
useEffect10(() => {
|
|
3381
|
+
if (wasStreamingRef.current && !isStreaming && !userSelectedDisplayModeRef.current) {
|
|
3382
|
+
setDisplayMode(hasActionableToolCall ? "detail" : "compact");
|
|
3383
|
+
}
|
|
3384
|
+
wasStreamingRef.current = isStreaming;
|
|
3385
|
+
}, [hasActionableToolCall, isStreaming]);
|
|
3386
|
+
const effectiveMode = resolveTurnDisplayMode({ isStreaming, displayMode });
|
|
3387
|
+
const executionDurationMs = getExecutionDurationMs({ messages, isStreaming });
|
|
3388
|
+
const [clock, setClock] = useState13(() => Date.now());
|
|
3389
|
+
const hasLiveStartTime = messages.some(
|
|
3390
|
+
(message) => message.timestamp != null && Number.isFinite(Date.parse(message.timestamp))
|
|
3391
|
+
);
|
|
3392
|
+
useEffect10(() => {
|
|
3393
|
+
if (!isStreaming || !hasLiveStartTime) return;
|
|
3394
|
+
const timer = window.setInterval(() => setClock(Date.now()), 1e3);
|
|
3395
|
+
return () => window.clearInterval(timer);
|
|
3396
|
+
}, [hasLiveStartTime, isStreaming]);
|
|
3397
|
+
const liveExecutionDurationMs = isStreaming ? getExecutionDurationMs({ messages, isStreaming, now: clock }) : executionDurationMs;
|
|
3398
|
+
const memoryRefs = collectMemoryRefs(messages);
|
|
3399
|
+
if (!hasExecutionProcess) {
|
|
3400
|
+
return /* @__PURE__ */ jsxs11(
|
|
3401
|
+
"div",
|
|
3402
|
+
{
|
|
3403
|
+
"aria-busy": isStreaming || void 0,
|
|
3404
|
+
className: "blade-chat-assistant-turn flex flex-col gap-3",
|
|
3405
|
+
children: [
|
|
3406
|
+
memoryRefs.length > 0 ? /* @__PURE__ */ jsx13(MemoryRefsHint, { refs: memoryRefs }) : null,
|
|
3407
|
+
hasInterrupted && /* @__PURE__ */ jsx13("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" }),
|
|
3408
|
+
hasFailedWithoutContent && /* @__PURE__ */ jsx13("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" }),
|
|
3409
|
+
messages.map((message, index) => {
|
|
3410
|
+
return hasRenderableMessageContent(message) ? /* @__PURE__ */ jsx13(
|
|
3411
|
+
"div",
|
|
3412
|
+
{
|
|
3413
|
+
className: "flex flex-col gap-3",
|
|
3414
|
+
children: /* @__PURE__ */ jsx13(
|
|
3415
|
+
AssistantMessageContent,
|
|
3416
|
+
{
|
|
3417
|
+
message,
|
|
3418
|
+
sessionId,
|
|
3419
|
+
streaming: isStreaming && index === messages.length - 1
|
|
3420
|
+
}
|
|
3421
|
+
)
|
|
3422
|
+
},
|
|
3423
|
+
message.entry_id ?? `${message.timestamp ?? "assistant"}-${index}`
|
|
3424
|
+
) : null;
|
|
3425
|
+
})
|
|
3426
|
+
]
|
|
3427
|
+
}
|
|
3428
|
+
);
|
|
3429
|
+
}
|
|
3430
|
+
return /* @__PURE__ */ jsxs11(
|
|
3431
|
+
"div",
|
|
3432
|
+
{
|
|
3433
|
+
"aria-busy": isStreaming || void 0,
|
|
3434
|
+
className: "blade-chat-assistant-turn flex flex-col gap-3",
|
|
3435
|
+
children: [
|
|
3436
|
+
memoryRefs.length > 0 ? /* @__PURE__ */ jsx13(MemoryRefsHint, { refs: memoryRefs }) : null,
|
|
3437
|
+
hasInterrupted && /* @__PURE__ */ jsx13("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" }),
|
|
3438
|
+
hasFailedWithoutContent && /* @__PURE__ */ jsx13("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" }),
|
|
3439
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex w-full items-start gap-2.5", children: [
|
|
3440
|
+
/* @__PURE__ */ jsx13(
|
|
3441
|
+
"span",
|
|
3442
|
+
{
|
|
3443
|
+
className: "grid size-[30px] shrink-0 place-items-center rounded-full bg-[hsl(var(--muted)/0.55)] text-[hsl(var(--foreground))]",
|
|
3444
|
+
"aria-hidden": "true",
|
|
3445
|
+
children: /* @__PURE__ */ jsx13(Bot, { size: 16 })
|
|
3446
|
+
}
|
|
3447
|
+
),
|
|
3448
|
+
/* @__PURE__ */ jsxs11("div", { className: "min-w-0 flex-1 pt-0.5", children: [
|
|
3449
|
+
/* @__PURE__ */ jsxs11(
|
|
3450
|
+
"button",
|
|
3451
|
+
{
|
|
3452
|
+
type: "button",
|
|
3453
|
+
onClick: () => {
|
|
3454
|
+
userSelectedDisplayModeRef.current = true;
|
|
3455
|
+
setDisplayMode(displayMode === "detail" ? "compact" : "detail");
|
|
3456
|
+
},
|
|
3457
|
+
"aria-expanded": effectiveMode === "detail",
|
|
3458
|
+
"aria-label": effectiveMode === "detail" ? "\u6536\u8D77\u6267\u884C\u8FC7\u7A0B" : "\u5C55\u5F00\u6267\u884C\u8FC7\u7A0B",
|
|
3459
|
+
"data-testid": "assistant-execution-summary",
|
|
3460
|
+
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",
|
|
3461
|
+
children: [
|
|
3462
|
+
/* @__PURE__ */ jsx13("span", { className: "min-w-0 truncate", children: executionSummaryLabel({
|
|
3463
|
+
messages,
|
|
3464
|
+
isStreaming,
|
|
3465
|
+
durationMs: liveExecutionDurationMs,
|
|
3466
|
+
sessionStatus,
|
|
3467
|
+
askAnswers
|
|
3468
|
+
}) }),
|
|
3469
|
+
/* @__PURE__ */ jsx13(
|
|
3470
|
+
ChevronRight,
|
|
3471
|
+
{
|
|
3472
|
+
size: 14,
|
|
3473
|
+
style: { transitionDuration: "260ms", transitionTimingFunction: "cubic-bezier(0.25, 0.1, 0.25, 1)" },
|
|
3474
|
+
className: cn(
|
|
3475
|
+
"shrink-0 transition-transform",
|
|
3476
|
+
effectiveMode === "detail" && "rotate-90"
|
|
3477
|
+
),
|
|
3478
|
+
"aria-hidden": "true"
|
|
3479
|
+
}
|
|
3480
|
+
)
|
|
3481
|
+
]
|
|
3482
|
+
}
|
|
3483
|
+
),
|
|
3484
|
+
/* @__PURE__ */ jsx13("div", { className: "mt-3 h-px w-full bg-[hsl(var(--border)/0.75)]" })
|
|
3485
|
+
] })
|
|
3486
|
+
] }),
|
|
3487
|
+
effectiveMode === "detail" ? /* @__PURE__ */ jsx13("div", { className: "ml-10 flex flex-col gap-3 pt-1", children: messages.map((message, index) => {
|
|
3488
|
+
const isLast = index === messages.length - 1;
|
|
3489
|
+
const streamingThis = isStreaming && isLast;
|
|
3490
|
+
const text = getMessageText(message);
|
|
3491
|
+
const toolCalls = (message.tool_calls ?? []).filter(
|
|
3492
|
+
(toolCall) => formatToolName(toolCall.name) !== "AskUserQuestion" && !shouldHideToolCall(message, toolCall)
|
|
3493
|
+
);
|
|
3494
|
+
const orderedParts = getOrderedMessageParts(message, toolCalls);
|
|
3495
|
+
const showReasoning = !!message.reasoning && isStreaming && index === latestReasoningIndex;
|
|
3496
|
+
return /* @__PURE__ */ jsxs11(
|
|
3497
|
+
"div",
|
|
3498
|
+
{
|
|
3499
|
+
className: "flex flex-col gap-3",
|
|
3500
|
+
children: [
|
|
3501
|
+
showReasoning && message.reasoning ? /* @__PURE__ */ jsx13(ThinkingBlock, { reasoning: message.reasoning, isStreaming: streamingThis && !text }) : null,
|
|
3502
|
+
orderedParts.length > 0 ? orderedParts.map(
|
|
3503
|
+
(part) => part.type === "text" ? /* @__PURE__ */ jsx13(
|
|
3504
|
+
AssistantMessageContent,
|
|
3505
|
+
{
|
|
3506
|
+
message: { ...message, content: part.content, tool_calls: turnToolCalls },
|
|
3507
|
+
sessionId,
|
|
3508
|
+
streaming: streamingThis,
|
|
3509
|
+
compact: true
|
|
3510
|
+
},
|
|
3511
|
+
part.key
|
|
3512
|
+
) : /* @__PURE__ */ jsx13("div", { className: "flex flex-col gap-0.5", children: part.toolCalls.map((toolCall) => {
|
|
3513
|
+
const custom = toolCallRenderer?.(toolCall);
|
|
3514
|
+
return custom !== null && custom !== void 0 ? /* @__PURE__ */ jsx13("div", { children: custom }, toolCall.id) : formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */ jsx13(AgentLoopBlock, { toolCall }, toolCall.id) : /* @__PURE__ */ jsx13(ExecutionToolRow, { toolCall }, toolCall.id);
|
|
3515
|
+
}) }, part.key)
|
|
3516
|
+
) : hasRenderableMessageContent(message) && message !== finalMessage ? /* @__PURE__ */ jsx13(
|
|
3517
|
+
AssistantMessageContent,
|
|
3518
|
+
{
|
|
3519
|
+
message,
|
|
3520
|
+
sessionId,
|
|
3521
|
+
streaming: streamingThis,
|
|
3522
|
+
compact: true
|
|
3523
|
+
}
|
|
3524
|
+
) : null,
|
|
3525
|
+
orderedParts.length === 0 && toolCalls.length > 0 ? /* @__PURE__ */ jsx13("div", { className: "flex flex-col gap-0.5", children: toolCalls.map((toolCall) => {
|
|
3526
|
+
const custom = toolCallRenderer?.(toolCall);
|
|
3527
|
+
return custom !== null && custom !== void 0 ? /* @__PURE__ */ jsx13("div", { children: custom }, toolCall.id) : formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */ jsx13(AgentLoopBlock, { toolCall }, toolCall.id) : /* @__PURE__ */ jsx13(ExecutionToolRow, { toolCall }, toolCall.id);
|
|
3528
|
+
}) }) : null
|
|
3529
|
+
]
|
|
3530
|
+
},
|
|
3531
|
+
message.entry_id ?? `${message.timestamp ?? "assistant"}-${index}`
|
|
3532
|
+
);
|
|
3533
|
+
}) }) : null,
|
|
3534
|
+
finalMessage && (effectiveMode === "compact" || finalOrderedParts.length === 0) ? /* @__PURE__ */ jsx13("div", { className: "ml-10", children: /* @__PURE__ */ jsx13(
|
|
3535
|
+
AssistantMessageContent,
|
|
3536
|
+
{
|
|
3537
|
+
message: finalMessage,
|
|
3538
|
+
sessionId,
|
|
3539
|
+
streaming: isStreaming && finalMessage === messages[messages.length - 1]
|
|
3540
|
+
}
|
|
3541
|
+
) }) : null,
|
|
3542
|
+
questionToolCalls.map((toolCall) => /* @__PURE__ */ jsx13(
|
|
3543
|
+
ToolCallBlock,
|
|
3544
|
+
{
|
|
3545
|
+
toolCall,
|
|
3546
|
+
answerData: askAnswers?.[toolCall.id],
|
|
3547
|
+
onAnswer,
|
|
3548
|
+
answered: sessionStatus !== "waiting_for_input",
|
|
3549
|
+
sessionStatus,
|
|
3550
|
+
isActiveQuestion: toolCall.id === activeQuestionId,
|
|
3551
|
+
renderer: toolCallRenderer
|
|
3552
|
+
},
|
|
3553
|
+
toolCall.id
|
|
3554
|
+
))
|
|
3555
|
+
]
|
|
3556
|
+
}
|
|
3557
|
+
);
|
|
3558
|
+
}
|
|
3559
|
+
function collectMemoryRefs(messages) {
|
|
3560
|
+
const refs = /* @__PURE__ */ new Map();
|
|
3561
|
+
for (const message of messages) {
|
|
3562
|
+
for (const ref of message.memory_refs ?? []) if (!refs.has(ref.id)) refs.set(ref.id, ref);
|
|
3563
|
+
}
|
|
3564
|
+
return [...refs.values()];
|
|
3565
|
+
}
|
|
3566
|
+
function MemoryRefsHint({ refs }) {
|
|
3567
|
+
const [expanded, setExpanded] = useState13(false);
|
|
3568
|
+
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";
|
|
3569
|
+
return /* @__PURE__ */ jsxs11("div", { className: "blade-chat-memory-refs ml-1 w-full max-w-[680px]", children: [
|
|
3570
|
+
/* @__PURE__ */ jsxs11("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: [
|
|
3571
|
+
/* @__PURE__ */ jsx13(BookOpen, { size: 12 }),
|
|
3572
|
+
/* @__PURE__ */ jsxs11("span", { children: [
|
|
3573
|
+
label,
|
|
3574
|
+
"\uFF08",
|
|
3575
|
+
refs.length,
|
|
3576
|
+
"\uFF09"
|
|
3577
|
+
] }),
|
|
3578
|
+
/* @__PURE__ */ jsx13(ChevronRight, { size: 10, className: cn("transition-transform", expanded && "rotate-90") })
|
|
3579
|
+
] }),
|
|
3580
|
+
expanded ? /* @__PURE__ */ jsx13("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__ */ jsxs11("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: [
|
|
3581
|
+
/* @__PURE__ */ jsx13("p", { className: "line-clamp-2 break-words leading-5", children: ref.content_preview }),
|
|
3582
|
+
ref.skill_name ? /* @__PURE__ */ jsx13("span", { className: "mt-1 inline-flex text-[10px] text-[hsl(var(--primary))]", children: ref.skill_name }) : null
|
|
3583
|
+
] }, ref.id)) }) : null
|
|
3584
|
+
] });
|
|
3585
|
+
}
|
|
3586
|
+
function AssistantMessageContent({
|
|
3587
|
+
message,
|
|
3588
|
+
sessionId,
|
|
3589
|
+
streaming,
|
|
3590
|
+
compact = false
|
|
3591
|
+
}) {
|
|
3592
|
+
const text = getMessageText(message);
|
|
3593
|
+
const imageParts = getImageParts(message.content);
|
|
3594
|
+
const fileParts = getFileParts(message.content);
|
|
3595
|
+
const failed = message.status === "failed";
|
|
3596
|
+
const failedBadge = failed ? /* @__PURE__ */ jsx13("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;
|
|
3597
|
+
const textContent = text ? /* @__PURE__ */ jsx13(
|
|
3598
|
+
"div",
|
|
3599
|
+
{
|
|
3600
|
+
className: cn(
|
|
3601
|
+
"blade-chat-assistant-text",
|
|
3602
|
+
compact ? "text-xs leading-[22px] text-[hsl(var(--foreground))]" : "text-[15px] leading-8 text-[hsl(var(--foreground))]"
|
|
3603
|
+
),
|
|
3604
|
+
children: /* @__PURE__ */ jsx13(
|
|
3605
|
+
MarkdownContent,
|
|
2300
3606
|
{
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
3607
|
+
mode: streaming ? "streaming" : "static",
|
|
3608
|
+
className: "blade-chat-prose",
|
|
3609
|
+
sessionId,
|
|
3610
|
+
children: text
|
|
3611
|
+
}
|
|
3612
|
+
)
|
|
3613
|
+
}
|
|
3614
|
+
) : null;
|
|
3615
|
+
if (imageParts.length === 0 && fileParts.length === 0) {
|
|
3616
|
+
if (!failed) return textContent;
|
|
3617
|
+
return failedBadge || textContent ? /* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-2", children: [
|
|
3618
|
+
failedBadge,
|
|
3619
|
+
textContent
|
|
3620
|
+
] }) : null;
|
|
3621
|
+
}
|
|
3622
|
+
return /* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-3", children: [
|
|
3623
|
+
failedBadge,
|
|
3624
|
+
imageParts.length > 0 ? /* @__PURE__ */ jsx13("div", { className: "grid gap-2", children: imageParts.map((part) => /* @__PURE__ */ jsx13(
|
|
3625
|
+
"img",
|
|
3626
|
+
{
|
|
3627
|
+
src: part.image_url.url,
|
|
3628
|
+
alt: "\u6D88\u606F\u9644\u4EF6",
|
|
3629
|
+
className: "max-h-72 rounded-xl border border-[hsl(var(--border))] object-cover"
|
|
3630
|
+
},
|
|
3631
|
+
part.image_url.url
|
|
3632
|
+
)) }) : null,
|
|
3633
|
+
fileParts.length > 0 ? /* @__PURE__ */ jsx13("div", { className: "flex flex-wrap gap-1.5", children: fileParts.map((part) => /* @__PURE__ */ jsxs11(
|
|
3634
|
+
"div",
|
|
3635
|
+
{
|
|
3636
|
+
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))]",
|
|
3637
|
+
title: part.name,
|
|
3638
|
+
children: [
|
|
3639
|
+
/* @__PURE__ */ jsx13(FileText, { size: 12, className: "shrink-0" }),
|
|
3640
|
+
/* @__PURE__ */ jsx13("span", { className: "max-w-56 truncate", children: part.name })
|
|
3641
|
+
]
|
|
3642
|
+
},
|
|
3643
|
+
`${part.name}-${part.data.slice(0, 32)}`
|
|
3644
|
+
)) }) : null,
|
|
3645
|
+
textContent
|
|
2333
3646
|
] });
|
|
2334
3647
|
}
|
|
2335
3648
|
|
|
2336
3649
|
// src/components/RenderErrorBoundary.tsx
|
|
2337
3650
|
import { Component } from "react";
|
|
2338
|
-
import { jsx as
|
|
3651
|
+
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2339
3652
|
function getFirstComponentName(componentStack) {
|
|
2340
3653
|
const match = componentStack.match(/\n\s+at\s+([^\s(]+)/);
|
|
2341
3654
|
return match?.[1] ?? null;
|
|
@@ -2368,26 +3681,26 @@ var RenderErrorBoundary = class extends Component {
|
|
|
2368
3681
|
return children;
|
|
2369
3682
|
}
|
|
2370
3683
|
const componentName = getFirstComponentName(componentStack);
|
|
2371
|
-
return /* @__PURE__ */
|
|
2372
|
-
/* @__PURE__ */
|
|
2373
|
-
/* @__PURE__ */
|
|
2374
|
-
/* @__PURE__ */
|
|
3684
|
+
return /* @__PURE__ */ jsx14("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__ */ jsxs12("div", { className: "flex items-start gap-2", children: [
|
|
3685
|
+
/* @__PURE__ */ jsx14(TriangleAlert, { className: "mt-0.5 h-4 w-4 shrink-0 text-amber-300" }),
|
|
3686
|
+
/* @__PURE__ */ jsxs12("div", { className: "min-w-0 flex-1", children: [
|
|
3687
|
+
/* @__PURE__ */ jsxs12("div", { className: "font-medium", children: [
|
|
2375
3688
|
label,
|
|
2376
3689
|
"\u6E32\u67D3\u5931\u8D25"
|
|
2377
3690
|
] }),
|
|
2378
|
-
/* @__PURE__ */
|
|
3691
|
+
/* @__PURE__ */ jsxs12("div", { className: "mt-1 break-words text-xs leading-5 text-amber-100/75", children: [
|
|
2379
3692
|
componentName ? `\u7EC4\u4EF6\uFF1A${componentName}\u3002` : null,
|
|
2380
3693
|
error.message || "\u53D1\u751F\u4E86\u672A\u9884\u671F\u7684\u6E32\u67D3\u9519\u8BEF\u3002"
|
|
2381
3694
|
] }),
|
|
2382
|
-
details ? /* @__PURE__ */
|
|
3695
|
+
details ? /* @__PURE__ */ jsx14("div", { className: "mt-1 truncate text-xs text-amber-100/55", children: details }) : null
|
|
2383
3696
|
] })
|
|
2384
3697
|
] }) });
|
|
2385
3698
|
}
|
|
2386
3699
|
};
|
|
2387
3700
|
|
|
2388
3701
|
// src/components/PostChatFollowupBlock.tsx
|
|
2389
|
-
import { useCallback as
|
|
2390
|
-
import { Fragment as
|
|
3702
|
+
import { useCallback as useCallback6, useEffect as useEffect11, useRef as useRef12, useState as useState14 } from "react";
|
|
3703
|
+
import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2391
3704
|
function emitInteraction(callback, event) {
|
|
2392
3705
|
try {
|
|
2393
3706
|
callback?.(event);
|
|
@@ -2397,9 +3710,6 @@ function emitInteraction(callback, event) {
|
|
|
2397
3710
|
function basename(path) {
|
|
2398
3711
|
return path.split(/[\\/]/).filter(Boolean).pop() || path;
|
|
2399
3712
|
}
|
|
2400
|
-
function isVideo(path) {
|
|
2401
|
-
return /\.(?:mp4|mov|webm|mkv|avi|m4v)$/i.test(path);
|
|
2402
|
-
}
|
|
2403
3713
|
function ArtifactCard({
|
|
2404
3714
|
artifact,
|
|
2405
3715
|
sessionId,
|
|
@@ -2409,10 +3719,10 @@ function ArtifactCard({
|
|
|
2409
3719
|
onArtifactOpened
|
|
2410
3720
|
}) {
|
|
2411
3721
|
const client = useBladeClient();
|
|
2412
|
-
const [downloading, setDownloading] =
|
|
3722
|
+
const [downloading, setDownloading] = useState14(false);
|
|
2413
3723
|
const name = artifact.label || basename(artifact.target);
|
|
2414
3724
|
if (artifact.kind === "link") {
|
|
2415
|
-
return /* @__PURE__ */
|
|
3725
|
+
return /* @__PURE__ */ jsxs13(
|
|
2416
3726
|
"a",
|
|
2417
3727
|
{
|
|
2418
3728
|
href: artifact.target,
|
|
@@ -2423,51 +3733,55 @@ function ArtifactCard({
|
|
|
2423
3733
|
${artifact.target}`,
|
|
2424
3734
|
className: "group relative flex min-w-0 items-center gap-1.5 rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-2 py-1.5 text-xs text-[hsl(var(--card-foreground))] hover:bg-[hsl(var(--accent))]",
|
|
2425
3735
|
children: [
|
|
2426
|
-
/* @__PURE__ */
|
|
2427
|
-
/* @__PURE__ */
|
|
2428
|
-
/* @__PURE__ */
|
|
3736
|
+
/* @__PURE__ */ jsx15(Globe, { size: 15, className: "shrink-0 text-[hsl(var(--primary))]" }),
|
|
3737
|
+
/* @__PURE__ */ jsx15("span", { className: "min-w-0 flex-1 truncate font-medium", children: name }),
|
|
3738
|
+
/* @__PURE__ */ jsx15(ArrowUpRight, { size: 13, className: "absolute right-2 opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100" })
|
|
2429
3739
|
]
|
|
2430
3740
|
}
|
|
2431
3741
|
);
|
|
2432
3742
|
}
|
|
2433
|
-
const
|
|
2434
|
-
|
|
2435
|
-
|
|
3743
|
+
const fileName = basename(artifact.target);
|
|
3744
|
+
const downloadUrl = sessionId ? client.buildAuthedUrl(
|
|
3745
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/files/${encodeURIComponent(artifact.target)}`
|
|
3746
|
+
) : void 0;
|
|
3747
|
+
const handleDownload = async (event) => {
|
|
3748
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
3749
|
+
event.preventDefault();
|
|
3750
|
+
if (!sessionId || downloading) return;
|
|
3751
|
+
setDownloading(true);
|
|
3752
|
+
emitInteraction(onInteraction, {
|
|
3753
|
+
type: "artifact_download_started",
|
|
3754
|
+
sessionId,
|
|
3755
|
+
assistantEntryId,
|
|
3756
|
+
artifactIndex,
|
|
3757
|
+
artifactKind: "file"
|
|
3758
|
+
});
|
|
3759
|
+
try {
|
|
3760
|
+
await client.sessions.downloadFile(sessionId, artifact.target, fileName);
|
|
3761
|
+
emitInteraction(onInteraction, {
|
|
3762
|
+
type: "artifact_download_succeeded",
|
|
3763
|
+
sessionId,
|
|
3764
|
+
assistantEntryId,
|
|
3765
|
+
artifactIndex,
|
|
3766
|
+
artifactKind: "file"
|
|
3767
|
+
});
|
|
3768
|
+
} catch {
|
|
3769
|
+
} finally {
|
|
3770
|
+
setDownloading(false);
|
|
3771
|
+
}
|
|
3772
|
+
};
|
|
3773
|
+
return /* @__PURE__ */ jsx15(
|
|
3774
|
+
"a",
|
|
2436
3775
|
{
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
onClick:
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
artifactIndex,
|
|
2447
|
-
artifactKind: "file"
|
|
2448
|
-
});
|
|
2449
|
-
try {
|
|
2450
|
-
await client.sessions.downloadFile(sessionId, artifact.target, basename(artifact.target));
|
|
2451
|
-
emitInteraction(onInteraction, {
|
|
2452
|
-
type: "artifact_download_succeeded",
|
|
2453
|
-
sessionId,
|
|
2454
|
-
assistantEntryId,
|
|
2455
|
-
artifactIndex,
|
|
2456
|
-
artifactKind: "file"
|
|
2457
|
-
});
|
|
2458
|
-
} catch {
|
|
2459
|
-
} finally {
|
|
2460
|
-
setDownloading(false);
|
|
2461
|
-
}
|
|
2462
|
-
},
|
|
2463
|
-
title: name,
|
|
2464
|
-
"aria-label": `\u4E0B\u8F7D ${name}`,
|
|
2465
|
-
className: "group relative flex min-w-0 items-center gap-1.5 rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-2 py-1.5 text-left text-xs text-[hsl(var(--card-foreground))] hover:bg-[hsl(var(--accent))] disabled:opacity-60",
|
|
2466
|
-
children: [
|
|
2467
|
-
/* @__PURE__ */ jsx13(Icon2, { size: 15, className: "shrink-0 text-[hsl(var(--primary))]" }),
|
|
2468
|
-
/* @__PURE__ */ jsx13("span", { className: "min-w-0 flex-1 truncate font-medium", children: name }),
|
|
2469
|
-
/* @__PURE__ */ jsx13(Download, { size: 13, className: "absolute right-2 opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100" })
|
|
2470
|
-
]
|
|
3776
|
+
href: downloadUrl,
|
|
3777
|
+
download: fileName,
|
|
3778
|
+
onClick: handleDownload,
|
|
3779
|
+
title: fileName,
|
|
3780
|
+
"aria-label": `\u4E0B\u8F7D\u6587\u4EF6\uFF1A${fileName}`,
|
|
3781
|
+
"aria-disabled": !sessionId || void 0,
|
|
3782
|
+
"aria-busy": downloading || void 0,
|
|
3783
|
+
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",
|
|
3784
|
+
children: fileName
|
|
2471
3785
|
}
|
|
2472
3786
|
);
|
|
2473
3787
|
}
|
|
@@ -2484,17 +3798,17 @@ function feedbackReasonLabel(reason) {
|
|
|
2484
3798
|
}
|
|
2485
3799
|
function HistoricalResultFeedback({ feedback }) {
|
|
2486
3800
|
const label = feedbackReasonLabel(feedback.reason);
|
|
2487
|
-
return /* @__PURE__ */
|
|
3801
|
+
return /* @__PURE__ */ jsxs13(
|
|
2488
3802
|
"section",
|
|
2489
3803
|
{
|
|
2490
3804
|
"aria-label": "\u5386\u53F2\u7ED3\u679C\u53CD\u9988",
|
|
2491
3805
|
className: "mt-3 w-fit max-w-full rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--muted)/0.2)] px-3 py-2 text-xs text-[hsl(var(--muted-foreground))]",
|
|
2492
3806
|
children: [
|
|
2493
|
-
/* @__PURE__ */
|
|
3807
|
+
/* @__PURE__ */ jsxs13("span", { children: [
|
|
2494
3808
|
"\u4F60\u5BF9\u6B64\u8F6E\u7ED3\u679C\u7684\u8BC4\u4EF7\uFF1A",
|
|
2495
3809
|
feedback.helpful ? "\u6709\u5E2E\u52A9" : "\u6CA1\u5E2E\u52A9"
|
|
2496
3810
|
] }),
|
|
2497
|
-
label ? /* @__PURE__ */
|
|
3811
|
+
label ? /* @__PURE__ */ jsxs13("span", { children: [
|
|
2498
3812
|
" \xB7 ",
|
|
2499
3813
|
label
|
|
2500
3814
|
] }) : null
|
|
@@ -2511,15 +3825,15 @@ function ResultFeedback({
|
|
|
2511
3825
|
onFeedbackSaved
|
|
2512
3826
|
}) {
|
|
2513
3827
|
const client = useBladeClient();
|
|
2514
|
-
const [saved, setSaved] =
|
|
2515
|
-
const [helpful, setHelpful] =
|
|
2516
|
-
const [reason, setReason] =
|
|
2517
|
-
const [saving, setSaving] =
|
|
2518
|
-
const [saveError, setSaveError] =
|
|
2519
|
-
const reportedShown =
|
|
2520
|
-
const latestChoice =
|
|
3828
|
+
const [saved, setSaved] = useState14(savedFeedback ?? null);
|
|
3829
|
+
const [helpful, setHelpful] = useState14(savedFeedback?.helpful ?? null);
|
|
3830
|
+
const [reason, setReason] = useState14(savedFeedback?.reason ?? null);
|
|
3831
|
+
const [saving, setSaving] = useState14(false);
|
|
3832
|
+
const [saveError, setSaveError] = useState14(false);
|
|
3833
|
+
const reportedShown = useRef12(false);
|
|
3834
|
+
const latestChoice = useRef12(null);
|
|
2521
3835
|
const eligible = followup.feedback_eligible === true && Boolean(sessionId) && !isViewer;
|
|
2522
|
-
|
|
3836
|
+
useEffect11(() => {
|
|
2523
3837
|
if (!eligible || reportedShown.current) return;
|
|
2524
3838
|
reportedShown.current = true;
|
|
2525
3839
|
emitInteraction(onInteraction, {
|
|
@@ -2528,13 +3842,13 @@ function ResultFeedback({
|
|
|
2528
3842
|
assistantEntryId: followup.assistant_entry_id
|
|
2529
3843
|
});
|
|
2530
3844
|
}, [eligible, followup.assistant_entry_id, onInteraction, sessionId]);
|
|
2531
|
-
|
|
3845
|
+
useEffect11(() => {
|
|
2532
3846
|
if (!savedFeedback || latestChoice.current) return;
|
|
2533
3847
|
setSaved(savedFeedback);
|
|
2534
3848
|
setHelpful(savedFeedback.helpful);
|
|
2535
3849
|
setReason(savedFeedback.reason);
|
|
2536
3850
|
}, [savedFeedback]);
|
|
2537
|
-
const submit =
|
|
3851
|
+
const submit = useCallback6(
|
|
2538
3852
|
async (nextHelpful, nextReason) => {
|
|
2539
3853
|
if (!sessionId) return;
|
|
2540
3854
|
const choice = { helpful: nextHelpful, reason: nextReason };
|
|
@@ -2569,15 +3883,15 @@ function ResultFeedback({
|
|
|
2569
3883
|
[client, followup.assistant_entry_id, onFeedbackSaved, onInteraction, sessionId]
|
|
2570
3884
|
);
|
|
2571
3885
|
if (!eligible) return null;
|
|
2572
|
-
return /* @__PURE__ */
|
|
3886
|
+
return /* @__PURE__ */ jsxs13(
|
|
2573
3887
|
"section",
|
|
2574
3888
|
{
|
|
2575
3889
|
"aria-label": "\u7ED3\u679C\u53CD\u9988",
|
|
2576
3890
|
className: "flex flex-col gap-2 border-t border-[hsl(var(--border))] pt-3",
|
|
2577
3891
|
children: [
|
|
2578
|
-
/* @__PURE__ */
|
|
2579
|
-
/* @__PURE__ */
|
|
2580
|
-
/* @__PURE__ */
|
|
3892
|
+
/* @__PURE__ */ jsx15("div", { className: "text-xs font-medium text-[hsl(var(--muted-foreground))]", children: "\u76EE\u524D\u7684\u6574\u4F53\u7ED3\u679C\u6709\u5E2E\u52A9\u5417\uFF1F" }),
|
|
3893
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex flex-wrap gap-1.5", children: [
|
|
3894
|
+
/* @__PURE__ */ jsx15(
|
|
2581
3895
|
"button",
|
|
2582
3896
|
{
|
|
2583
3897
|
type: "button",
|
|
@@ -2588,7 +3902,7 @@ function ResultFeedback({
|
|
|
2588
3902
|
children: "\u6709\u5E2E\u52A9"
|
|
2589
3903
|
}
|
|
2590
3904
|
),
|
|
2591
|
-
/* @__PURE__ */
|
|
3905
|
+
/* @__PURE__ */ jsx15(
|
|
2592
3906
|
"button",
|
|
2593
3907
|
{
|
|
2594
3908
|
type: "button",
|
|
@@ -2600,7 +3914,7 @@ function ResultFeedback({
|
|
|
2600
3914
|
}
|
|
2601
3915
|
)
|
|
2602
3916
|
] }),
|
|
2603
|
-
helpful === false ? /* @__PURE__ */
|
|
3917
|
+
helpful === false ? /* @__PURE__ */ jsx15("div", { className: "flex flex-wrap gap-1.5", "aria-label": "\u6CA1\u5E2E\u52A9\u7684\u4E3B\u8981\u539F\u56E0", children: FEEDBACK_REASONS.map((item) => /* @__PURE__ */ jsx15(
|
|
2604
3918
|
"button",
|
|
2605
3919
|
{
|
|
2606
3920
|
type: "button",
|
|
@@ -2612,9 +3926,9 @@ function ResultFeedback({
|
|
|
2612
3926
|
},
|
|
2613
3927
|
item.value
|
|
2614
3928
|
)) }) : null,
|
|
2615
|
-
saveError ? /* @__PURE__ */
|
|
2616
|
-
/* @__PURE__ */
|
|
2617
|
-
/* @__PURE__ */
|
|
3929
|
+
saveError ? /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2 text-xs text-[hsl(var(--destructive))]", children: [
|
|
3930
|
+
/* @__PURE__ */ jsx15("span", { children: "\u53CD\u9988\u6682\u672A\u4FDD\u5B58\uFF0C\u53EF\u91CD\u8BD5" }),
|
|
3931
|
+
/* @__PURE__ */ jsx15(
|
|
2618
3932
|
"button",
|
|
2619
3933
|
{
|
|
2620
3934
|
type: "button",
|
|
@@ -2626,7 +3940,7 @@ function ResultFeedback({
|
|
|
2626
3940
|
children: "\u91CD\u8BD5"
|
|
2627
3941
|
}
|
|
2628
3942
|
)
|
|
2629
|
-
] }) : saved ? /* @__PURE__ */
|
|
3943
|
+
] }) : saved ? /* @__PURE__ */ jsx15("div", { className: "text-[11px] text-[hsl(var(--muted-foreground))]", children: "\u5DF2\u4FDD\u5B58\uFF0C\u53EF\u968F\u65F6\u4FEE\u6539" }) : null
|
|
2630
3944
|
]
|
|
2631
3945
|
}
|
|
2632
3946
|
);
|
|
@@ -2640,14 +3954,14 @@ function PostChatFollowupBlock({
|
|
|
2640
3954
|
savedFeedback,
|
|
2641
3955
|
onFeedbackSaved
|
|
2642
3956
|
}) {
|
|
2643
|
-
const [expanded, setExpanded] =
|
|
2644
|
-
const adopted =
|
|
2645
|
-
const reportedSuggestions =
|
|
2646
|
-
const reportedArtifacts =
|
|
2647
|
-
const openedArtifacts =
|
|
3957
|
+
const [expanded, setExpanded] = useState14(false);
|
|
3958
|
+
const adopted = useRef12(/* @__PURE__ */ new Set());
|
|
3959
|
+
const reportedSuggestions = useRef12(false);
|
|
3960
|
+
const reportedArtifacts = useRef12(/* @__PURE__ */ new Set());
|
|
3961
|
+
const openedArtifacts = useRef12(/* @__PURE__ */ new Set());
|
|
2648
3962
|
const artifacts = followup.final_artifacts ?? [];
|
|
2649
3963
|
const visibleArtifacts = expanded ? artifacts : artifacts.slice(0, 3);
|
|
2650
|
-
|
|
3964
|
+
useEffect11(() => {
|
|
2651
3965
|
if (!reportedSuggestions.current && followup.suggestions.length > 0) {
|
|
2652
3966
|
reportedSuggestions.current = true;
|
|
2653
3967
|
emitInteraction(onInteraction, {
|
|
@@ -2677,7 +3991,7 @@ function PostChatFollowupBlock({
|
|
|
2677
3991
|
sessionId,
|
|
2678
3992
|
visibleArtifacts
|
|
2679
3993
|
]);
|
|
2680
|
-
const reportArtifactOpened =
|
|
3994
|
+
const reportArtifactOpened = useCallback6(
|
|
2681
3995
|
(artifactIndex, artifactKind) => {
|
|
2682
3996
|
if (openedArtifacts.current.has(artifactIndex)) return;
|
|
2683
3997
|
openedArtifacts.current.add(artifactIndex);
|
|
@@ -2693,15 +4007,15 @@ function PostChatFollowupBlock({
|
|
|
2693
4007
|
);
|
|
2694
4008
|
if (!followup.recaption && artifacts.length === 0 && followup.suggestions.length === 0 && !followup.feedback_eligible)
|
|
2695
4009
|
return null;
|
|
2696
|
-
return /* @__PURE__ */
|
|
2697
|
-
followup.recaption || artifacts.length > 0 ? /* @__PURE__ */
|
|
2698
|
-
/* @__PURE__ */
|
|
2699
|
-
/* @__PURE__ */
|
|
4010
|
+
return /* @__PURE__ */ jsxs13("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: [
|
|
4011
|
+
followup.recaption || artifacts.length > 0 ? /* @__PURE__ */ jsxs13("section", { "aria-label": "\u672C\u8F6E\u5C0F\u7ED3", className: "flex flex-col gap-2", children: [
|
|
4012
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1.5 text-xs font-medium text-[hsl(var(--muted-foreground))]", children: [
|
|
4013
|
+
/* @__PURE__ */ jsx15(Sparkles, { size: 14 }),
|
|
2700
4014
|
"\u672C\u8F6E\u5C0F\u7ED3"
|
|
2701
4015
|
] }),
|
|
2702
|
-
followup.recaption ? /* @__PURE__ */
|
|
2703
|
-
artifacts.length > 0 ? /* @__PURE__ */
|
|
2704
|
-
/* @__PURE__ */
|
|
4016
|
+
followup.recaption ? /* @__PURE__ */ jsx15("p", { className: "text-[13px] leading-5", children: followup.recaption }) : null,
|
|
4017
|
+
artifacts.length > 0 ? /* @__PURE__ */ jsxs13(Fragment3, { children: [
|
|
4018
|
+
/* @__PURE__ */ jsx15("div", { className: "grid max-w-full grid-cols-3 gap-1.5", children: visibleArtifacts.map((artifact, artifactIndex) => /* @__PURE__ */ jsx15(
|
|
2705
4019
|
ArtifactCard,
|
|
2706
4020
|
{
|
|
2707
4021
|
artifact,
|
|
@@ -2713,7 +4027,7 @@ function PostChatFollowupBlock({
|
|
|
2713
4027
|
},
|
|
2714
4028
|
`${artifact.kind}:${artifactIndex}`
|
|
2715
4029
|
)) }),
|
|
2716
|
-
artifacts.length > 3 ? /* @__PURE__ */
|
|
4030
|
+
artifacts.length > 3 ? /* @__PURE__ */ jsxs13(
|
|
2717
4031
|
"button",
|
|
2718
4032
|
{
|
|
2719
4033
|
type: "button",
|
|
@@ -2722,15 +4036,15 @@ function PostChatFollowupBlock({
|
|
|
2722
4036
|
className: "flex w-fit items-center gap-0.5 text-[11px] text-[hsl(var(--muted-foreground))]",
|
|
2723
4037
|
children: [
|
|
2724
4038
|
expanded ? "\u6536\u8D77" : `\u5C55\u5F00 ${artifacts.length - 3} \u4E2A`,
|
|
2725
|
-
/* @__PURE__ */
|
|
4039
|
+
/* @__PURE__ */ jsx15(ChevronDown, { size: 13, className: expanded ? "rotate-180" : void 0 })
|
|
2726
4040
|
]
|
|
2727
4041
|
}
|
|
2728
4042
|
) : null
|
|
2729
4043
|
] }) : null
|
|
2730
4044
|
] }) : null,
|
|
2731
|
-
followup.suggestions.length > 0 ? /* @__PURE__ */
|
|
2732
|
-
/* @__PURE__ */
|
|
2733
|
-
followup.suggestions.map((suggestion, suggestionIndex) => /* @__PURE__ */
|
|
4045
|
+
followup.suggestions.length > 0 ? /* @__PURE__ */ jsxs13("section", { "aria-label": "\u4E0B\u4E00\u6B65\u5EFA\u8BAE", className: "flex flex-col gap-1.5", children: [
|
|
4046
|
+
/* @__PURE__ */ jsx15("div", { className: "text-xs font-medium text-[hsl(var(--muted-foreground))]", children: "\u4E0B\u4E00\u6B65\u53EF\u4EE5" }),
|
|
4047
|
+
followup.suggestions.map((suggestion, suggestionIndex) => /* @__PURE__ */ jsxs13(
|
|
2734
4048
|
"button",
|
|
2735
4049
|
{
|
|
2736
4050
|
type: "button",
|
|
@@ -2749,14 +4063,14 @@ function PostChatFollowupBlock({
|
|
|
2749
4063
|
},
|
|
2750
4064
|
className: "group flex items-center gap-2 rounded-xl bg-[hsl(var(--muted)/0.62)] px-3 py-2 text-left text-[13px] disabled:cursor-default disabled:opacity-60",
|
|
2751
4065
|
children: [
|
|
2752
|
-
/* @__PURE__ */
|
|
2753
|
-
/* @__PURE__ */
|
|
4066
|
+
/* @__PURE__ */ jsx15("span", { children: suggestion }),
|
|
4067
|
+
/* @__PURE__ */ jsx15(ArrowRight, { size: 14, className: "ml-auto shrink-0" })
|
|
2754
4068
|
]
|
|
2755
4069
|
},
|
|
2756
4070
|
suggestion
|
|
2757
4071
|
))
|
|
2758
4072
|
] }) : null,
|
|
2759
|
-
/* @__PURE__ */
|
|
4073
|
+
/* @__PURE__ */ jsx15(
|
|
2760
4074
|
ResultFeedback,
|
|
2761
4075
|
{
|
|
2762
4076
|
followup,
|
|
@@ -2771,8 +4085,91 @@ function PostChatFollowupBlock({
|
|
|
2771
4085
|
}
|
|
2772
4086
|
|
|
2773
4087
|
// src/components/UserMessageBubble.tsx
|
|
2774
|
-
import {
|
|
2775
|
-
|
|
4088
|
+
import {
|
|
4089
|
+
chatErrorForDisplay,
|
|
4090
|
+
getFileParts as getFileParts2,
|
|
4091
|
+
getImageParts as getImageParts2,
|
|
4092
|
+
getTextContent as getTextContent2
|
|
4093
|
+
} from "@blade-hq/agent-client";
|
|
4094
|
+
|
|
4095
|
+
// src/lib/whatif-prompt.ts
|
|
4096
|
+
var HEADER_RE = /^以下消息和 step 产物标记为 deprecated_by_rerun,请基于最新用户假设从 step(\d+) 开始完整重新推演,不要复用旧结论。$/;
|
|
4097
|
+
var QUOTE_HEADER_RE = /^\[步骤(\d+)\s*·\s*(.+?)\]$/;
|
|
4098
|
+
var USER_INPUT_TAG = "[\u7528\u6237\u8F93\u5165]";
|
|
4099
|
+
function parseWhatIfPrompt(text) {
|
|
4100
|
+
const lines = text.replace(/\r\n/g, "\n").trimEnd().split("\n");
|
|
4101
|
+
const headerMatch = lines[0]?.match(HEADER_RE);
|
|
4102
|
+
if (!headerMatch) return null;
|
|
4103
|
+
const userTagIdx = lines.indexOf(USER_INPUT_TAG);
|
|
4104
|
+
const hasUserTag = userTagIdx >= 0;
|
|
4105
|
+
const quoteBlockEndExclusive = hasUserTag ? userTagIdx : lines.length;
|
|
4106
|
+
const quoteHeaderIdxs = [];
|
|
4107
|
+
let quoteBlockFound = false;
|
|
4108
|
+
for (let i = 1; i < quoteBlockEndExclusive; i++) {
|
|
4109
|
+
if (!quoteBlockFound && lines[i].trim() === "[\u5F15\u7528]") {
|
|
4110
|
+
quoteBlockFound = true;
|
|
4111
|
+
} else if (quoteBlockFound && QUOTE_HEADER_RE.test(lines[i])) {
|
|
4112
|
+
quoteHeaderIdxs.push(i);
|
|
4113
|
+
}
|
|
4114
|
+
}
|
|
4115
|
+
let legacyUserTextStart = -1;
|
|
4116
|
+
if (!hasUserTag && quoteHeaderIdxs.length > 0) {
|
|
4117
|
+
const lastSnapshotStart = quoteHeaderIdxs.at(-1) + 1;
|
|
4118
|
+
let i = lines.length - 1;
|
|
4119
|
+
while (i >= lastSnapshotStart && lines[i].trim() === "") i -= 1;
|
|
4120
|
+
while (i >= lastSnapshotStart && lines[i].trim() !== "") i -= 1;
|
|
4121
|
+
if (i >= lastSnapshotStart) legacyUserTextStart = i + 1;
|
|
4122
|
+
}
|
|
4123
|
+
const quotes = quoteHeaderIdxs.map((headerIdx, index) => {
|
|
4124
|
+
const match = lines[headerIdx].match(QUOTE_HEADER_RE);
|
|
4125
|
+
const nextHeader = quoteHeaderIdxs[index + 1];
|
|
4126
|
+
const end = nextHeader ?? (legacyUserTextStart >= 0 ? legacyUserTextStart : quoteBlockEndExclusive);
|
|
4127
|
+
const snapshotLines = lines.slice(headerIdx + 1, end);
|
|
4128
|
+
while (snapshotLines.at(-1)?.trim() === "") snapshotLines.pop();
|
|
4129
|
+
return {
|
|
4130
|
+
stepNumber: Number.parseInt(match[1], 10),
|
|
4131
|
+
label: match[2].trim(),
|
|
4132
|
+
snapshot: snapshotLines.join("\n")
|
|
4133
|
+
};
|
|
4134
|
+
});
|
|
4135
|
+
const userTextStart = hasUserTag ? userTagIdx + 1 : legacyUserTextStart;
|
|
4136
|
+
const userLines = userTextStart >= 0 ? lines.slice(userTextStart) : [];
|
|
4137
|
+
while (userLines[0]?.trim() === "") userLines.shift();
|
|
4138
|
+
while (userLines.at(-1)?.trim() === "") userLines.pop();
|
|
4139
|
+
const fromStep = Number.parseInt(headerMatch[1], 10);
|
|
4140
|
+
return {
|
|
4141
|
+
fromStep: Number.isFinite(fromStep) ? fromStep : null,
|
|
4142
|
+
quotes,
|
|
4143
|
+
userText: userLines.join("\n")
|
|
4144
|
+
};
|
|
4145
|
+
}
|
|
4146
|
+
|
|
4147
|
+
// src/components/WhatIfUserBubble.tsx
|
|
4148
|
+
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
4149
|
+
function WhatIfUserBubble({ parsed, onQuoteClick }) {
|
|
4150
|
+
const { fromStep, quotes, userText } = parsed;
|
|
4151
|
+
return /* @__PURE__ */ jsxs14("div", { className: "flex flex-col items-end gap-2", children: [
|
|
4152
|
+
/* @__PURE__ */ jsxs14("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: [
|
|
4153
|
+
/* @__PURE__ */ jsx16(RefreshCcw, { size: 10 }),
|
|
4154
|
+
/* @__PURE__ */ jsx16("span", { children: fromStep != null ? `\u91CD\u8DD1\u81EA step ${fromStep}` : "\u91CD\u8DD1" })
|
|
4155
|
+
] }),
|
|
4156
|
+
quotes.length > 0 && /* @__PURE__ */ jsx16("div", { className: "flex max-w-[min(72vw,42rem)] flex-col items-stretch gap-2", children: quotes.map((quote, index) => {
|
|
4157
|
+
const clickable = quote.stepNumber != null && !!onQuoteClick;
|
|
4158
|
+
const label = quote.stepNumber != null ? `\u6B65\u9AA4${quote.stepNumber} \xB7 ${quote.label}` : quote.label;
|
|
4159
|
+
return /* @__PURE__ */ jsxs14("div", { className: "rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card)/0.8)] px-3 py-2 text-left", children: [
|
|
4160
|
+
/* @__PURE__ */ jsxs14("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: [
|
|
4161
|
+
/* @__PURE__ */ jsx16("span", { children: "\u21B3" }),
|
|
4162
|
+
/* @__PURE__ */ jsx16("span", { className: "truncate", children: label })
|
|
4163
|
+
] }),
|
|
4164
|
+
quote.snapshot ? /* @__PURE__ */ jsx16("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__ */ jsx16(MarkdownContent, { className: "blade-chat-prose", children: quote.snapshot }) }) : null
|
|
4165
|
+
] }, `${quote.stepNumber ?? "x"}-${index}`);
|
|
4166
|
+
}) }),
|
|
4167
|
+
userText && /* @__PURE__ */ jsx16("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__ */ jsx16(MarkdownContent, { className: "blade-chat-prose", children: userText }) })
|
|
4168
|
+
] });
|
|
4169
|
+
}
|
|
4170
|
+
|
|
4171
|
+
// src/components/UserMessageBubble.tsx
|
|
4172
|
+
import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2776
4173
|
function isUserMessage(message) {
|
|
2777
4174
|
return message.role === "user";
|
|
2778
4175
|
}
|
|
@@ -2782,10 +4179,14 @@ function isErrorMessage(message) {
|
|
|
2782
4179
|
var isSending = (message) => message.status === "streaming";
|
|
2783
4180
|
function UserMessageBubble({ message, className }) {
|
|
2784
4181
|
const text = getTextContent2(message.content).trim();
|
|
2785
|
-
const fileParts =
|
|
2786
|
-
const imageParts =
|
|
2787
|
-
|
|
2788
|
-
|
|
4182
|
+
const fileParts = getFileParts2(message.content);
|
|
4183
|
+
const imageParts = getImageParts2(message.content);
|
|
4184
|
+
const whatifParsed = text && imageParts.length === 0 && fileParts.length === 0 ? parseWhatIfPrompt(text) : null;
|
|
4185
|
+
if (whatifParsed) {
|
|
4186
|
+
return /* @__PURE__ */ jsx17("div", { className: cn("blade-chat-user-row flex justify-end", className), children: /* @__PURE__ */ jsx17("div", { className: "blade-chat-user-col flex max-w-[72%] flex-col items-end gap-3", children: /* @__PURE__ */ jsx17(WhatIfUserBubble, { parsed: whatifParsed }) }) });
|
|
4187
|
+
}
|
|
4188
|
+
return /* @__PURE__ */ jsx17("div", { className: cn("blade-chat-user-row flex justify-end", className), children: /* @__PURE__ */ jsxs15("div", { className: "blade-chat-user-col flex max-w-[72%] flex-col items-end gap-3", children: [
|
|
4189
|
+
imageParts.length > 0 && /* @__PURE__ */ jsx17("div", { className: "grid gap-2", children: imageParts.map((part) => /* @__PURE__ */ jsx17(
|
|
2789
4190
|
"img",
|
|
2790
4191
|
{
|
|
2791
4192
|
src: part.image_url.url,
|
|
@@ -2794,21 +4195,21 @@ function UserMessageBubble({ message, className }) {
|
|
|
2794
4195
|
},
|
|
2795
4196
|
part.image_url.url
|
|
2796
4197
|
)) }),
|
|
2797
|
-
fileParts.length > 0 && /* @__PURE__ */
|
|
4198
|
+
fileParts.length > 0 && /* @__PURE__ */ jsx17("div", { className: "flex flex-col items-end gap-1.5", children: fileParts.map((part) => /* @__PURE__ */ jsxs15(
|
|
2798
4199
|
"div",
|
|
2799
4200
|
{
|
|
2800
4201
|
className: "flex items-center gap-1.5 rounded-lg border border-[hsl(var(--user-msg-border))] bg-[hsl(var(--muted)/0.3)] px-2.5 py-1.5 text-xs text-[hsl(var(--muted-foreground))]",
|
|
2801
4202
|
children: [
|
|
2802
|
-
/* @__PURE__ */
|
|
2803
|
-
/* @__PURE__ */
|
|
4203
|
+
/* @__PURE__ */ jsx17(FileText, { size: 12, className: "shrink-0" }),
|
|
4204
|
+
/* @__PURE__ */ jsx17("span", { className: "max-w-56 truncate", title: part.name, children: part.name })
|
|
2804
4205
|
]
|
|
2805
4206
|
},
|
|
2806
4207
|
`${part.name}-${part.data.length}`
|
|
2807
4208
|
)) }),
|
|
2808
|
-
text && /* @__PURE__ */
|
|
2809
|
-
text && isSending(message) && /* @__PURE__ */
|
|
2810
|
-
/* @__PURE__ */
|
|
2811
|
-
/* @__PURE__ */
|
|
4209
|
+
text && /* @__PURE__ */ jsx17("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__ */ jsx17(MarkdownContent, { className: "blade-chat-prose", children: text }) }),
|
|
4210
|
+
text && isSending(message) && /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-1 pr-1 text-[11px] font-medium text-[hsl(var(--muted-foreground))/0.85]", children: [
|
|
4211
|
+
/* @__PURE__ */ jsx17(LoaderCircle, { size: 11, className: "animate-spin", "aria-hidden": "true" }),
|
|
4212
|
+
/* @__PURE__ */ jsx17("span", { children: "\u53D1\u9001\u4E2D" })
|
|
2812
4213
|
] })
|
|
2813
4214
|
] }) });
|
|
2814
4215
|
}
|
|
@@ -2816,12 +4217,12 @@ function ErrorMessageBlock({
|
|
|
2816
4217
|
message,
|
|
2817
4218
|
className
|
|
2818
4219
|
}) {
|
|
2819
|
-
const text = getTextContent2(message.content);
|
|
2820
|
-
return /* @__PURE__ */
|
|
4220
|
+
const text = chatErrorForDisplay(getTextContent2(message.content));
|
|
4221
|
+
return /* @__PURE__ */ jsx17("div", { className: cn("blade-chat-error-row flex min-w-0 justify-start", className), children: /* @__PURE__ */ jsx17("div", { className: "blade-chat-error-block min-w-0 max-w-full whitespace-pre-wrap break-words border-l-[3px] border-[hsl(var(--border))] px-3 py-1 text-left text-sm leading-7 text-[hsl(var(--muted-foreground))] [overflow-wrap:anywhere]", children: text }) });
|
|
2821
4222
|
}
|
|
2822
4223
|
|
|
2823
4224
|
// src/components/MessageList.tsx
|
|
2824
|
-
import { jsx as
|
|
4225
|
+
import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2825
4226
|
function parseModeChange(message) {
|
|
2826
4227
|
if (message.kind !== "mode_change" || typeof message.content !== "string") {
|
|
2827
4228
|
return null;
|
|
@@ -2862,18 +4263,30 @@ function MessageList({
|
|
|
2862
4263
|
askAnswers,
|
|
2863
4264
|
onAnswer,
|
|
2864
4265
|
toolCallRenderer,
|
|
4266
|
+
hidePlanUpdateTools = false,
|
|
2865
4267
|
emptyState,
|
|
2866
4268
|
className,
|
|
2867
4269
|
sessionId,
|
|
2868
4270
|
isViewer = false,
|
|
2869
4271
|
onFollowupInteraction,
|
|
2870
4272
|
resultFeedbackByEntry = /* @__PURE__ */ new Map(),
|
|
2871
|
-
onResultFeedbackSaved
|
|
4273
|
+
onResultFeedbackSaved,
|
|
4274
|
+
historyPaging
|
|
2872
4275
|
}) {
|
|
4276
|
+
const visibleRootMessages = messages.filter((message) => {
|
|
4277
|
+
if ((message.loop_name ?? "root") !== "root") return false;
|
|
4278
|
+
if (isHiddenInternalMessage(message)) return false;
|
|
4279
|
+
if (message.kind === "context") return false;
|
|
4280
|
+
return message.role !== "tool" || getPlanningDividerKind(message) !== null;
|
|
4281
|
+
});
|
|
4282
|
+
const userMessages = visibleRootMessages.filter((message) => isUserMessage(message));
|
|
4283
|
+
const latestUserMessage = userMessages.at(-1);
|
|
4284
|
+
const shouldPinLatestUser = latestUserMessage != null && (latestUserMessage.entry_id == null ? isStreaming : latestUserMessage.entry_id.startsWith("local-user-"));
|
|
2873
4285
|
const renderBlocks = useMemo7(() => {
|
|
2874
4286
|
const visible = messages.filter((message) => {
|
|
2875
4287
|
if ((message.loop_name ?? "root") !== "root") return false;
|
|
2876
4288
|
if (isHiddenInternalMessage(message)) return false;
|
|
4289
|
+
if (message.kind === "context") return false;
|
|
2877
4290
|
if (message.kind === "compaction") return true;
|
|
2878
4291
|
return message.role !== "tool" || getPlanningDividerKind(message) !== null;
|
|
2879
4292
|
});
|
|
@@ -2916,7 +4329,7 @@ function MessageList({
|
|
|
2916
4329
|
blocks.push({
|
|
2917
4330
|
type: "message",
|
|
2918
4331
|
message,
|
|
2919
|
-
key: message.entry_id ?? `${message.role}-${blocks.length}`
|
|
4332
|
+
key: message.render_id ?? message.entry_id ?? `${message.role}-${blocks.length}`
|
|
2920
4333
|
});
|
|
2921
4334
|
}
|
|
2922
4335
|
flushAssistant();
|
|
@@ -2943,98 +4356,228 @@ function MessageList({
|
|
|
2943
4356
|
}
|
|
2944
4357
|
return blocks;
|
|
2945
4358
|
}, [messages, isStreaming]);
|
|
2946
|
-
return /* @__PURE__ */
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
4359
|
+
return /* @__PURE__ */ jsxs16("div", { className: cn("blade-chat-messages relative min-h-0 flex-1", className), children: [
|
|
4360
|
+
isStreaming ? /* @__PURE__ */ jsx18("output", { className: "sr-only", children: "\u6B63\u5728\u751F\u6210\u56DE\u590D" }) : null,
|
|
4361
|
+
/* @__PURE__ */ jsxs16(
|
|
4362
|
+
StickToBottom,
|
|
4363
|
+
{
|
|
4364
|
+
className: "h-full overflow-y-hidden",
|
|
4365
|
+
initial: "instant",
|
|
4366
|
+
resize: "instant",
|
|
4367
|
+
children: [
|
|
4368
|
+
/* @__PURE__ */ jsx18(StickToBottom.Content, { className: "blade-chat-messages-scroll", children: /* @__PURE__ */ jsxs16("div", { className: "blade-chat-messages-content mx-auto max-w-[748px]", children: [
|
|
4369
|
+
historyPaging && (historyPaging.hasOlder || historyPaging.loading) ? /* @__PURE__ */ jsx18(LoadOlderSentinel, { ...historyPaging }) : null,
|
|
4370
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex min-w-0 flex-col", children: [
|
|
4371
|
+
renderBlocks.length === 0 ? emptyState ?? /* @__PURE__ */ jsxs16("div", { className: "blade-chat-empty", children: [
|
|
4372
|
+
/* @__PURE__ */ jsx18(MessageSquare, { size: 40, strokeWidth: 1.5 }),
|
|
4373
|
+
/* @__PURE__ */ jsx18("span", { className: "text-base font-medium", children: "\u5F00\u59CB\u5BF9\u8BDD" }),
|
|
4374
|
+
/* @__PURE__ */ jsx18("span", { className: "text-sm opacity-60", children: "\u5728\u4E0B\u65B9\u8F93\u5165\u6D88\u606F\u5F00\u59CB\u804A\u5929" })
|
|
4375
|
+
] }) : renderBlocks.map((block) => {
|
|
4376
|
+
if (block.type === "message") {
|
|
4377
|
+
return /* @__PURE__ */ jsx18("div", { "data-entry-id": block.message.entry_id, children: isUserMessage(block.message) ? /* @__PURE__ */ jsx18(UserMessageBubble, { message: block.message }) : isErrorMessage(block.message) ? /* @__PURE__ */ jsx18(ErrorMessageBlock, { message: block.message }) : null }, block.key);
|
|
4378
|
+
}
|
|
4379
|
+
if (block.type === "assistant_turn") {
|
|
4380
|
+
const blockFeedback = block.messages.map(
|
|
4381
|
+
(message) => message.entry_id ? resultFeedbackByEntry.get(message.entry_id) : void 0
|
|
4382
|
+
).find((feedback) => feedback != null);
|
|
4383
|
+
const hasActiveFollowup = Boolean(
|
|
4384
|
+
postChatFollowup && block.messages.some(
|
|
4385
|
+
(message) => message.entry_id === postChatFollowup.assistant_entry_id
|
|
4386
|
+
)
|
|
4387
|
+
);
|
|
4388
|
+
return /* @__PURE__ */ jsx18("div", { "data-entry-id": block.messages[0]?.entry_id, children: /* @__PURE__ */ jsxs16(
|
|
4389
|
+
RenderErrorBoundary,
|
|
4390
|
+
{
|
|
4391
|
+
label: "\u52A9\u624B\u6D88\u606F",
|
|
4392
|
+
details: block.key,
|
|
4393
|
+
resetKey: getMessageResetSignature(block.messages),
|
|
4394
|
+
children: [
|
|
4395
|
+
/* @__PURE__ */ jsx18(
|
|
4396
|
+
AssistantTurnBlock,
|
|
4397
|
+
{
|
|
4398
|
+
messages: block.messages,
|
|
4399
|
+
isStreaming: block.isStreaming,
|
|
4400
|
+
askAnswers,
|
|
4401
|
+
onAnswer,
|
|
4402
|
+
sessionStatus,
|
|
4403
|
+
toolCallRenderer,
|
|
4404
|
+
hidePlanUpdateTools,
|
|
4405
|
+
sessionId
|
|
4406
|
+
}
|
|
4407
|
+
),
|
|
4408
|
+
blockFeedback && !hasActiveFollowup ? /* @__PURE__ */ jsx18(HistoricalResultFeedback, { feedback: blockFeedback }) : null,
|
|
4409
|
+
hasActiveFollowup && postChatFollowup ? /* @__PURE__ */ jsx18(
|
|
4410
|
+
PostChatFollowupBlock,
|
|
4411
|
+
{
|
|
4412
|
+
followup: postChatFollowup,
|
|
4413
|
+
sessionId,
|
|
4414
|
+
onSuggestion,
|
|
4415
|
+
isViewer,
|
|
4416
|
+
onInteraction: onFollowupInteraction,
|
|
4417
|
+
savedFeedback: blockFeedback,
|
|
4418
|
+
onFeedbackSaved: onResultFeedbackSaved
|
|
4419
|
+
}
|
|
4420
|
+
) : null
|
|
4421
|
+
]
|
|
4422
|
+
}
|
|
4423
|
+
) }, block.key);
|
|
4424
|
+
}
|
|
4425
|
+
if (block.type === "compaction") {
|
|
4426
|
+
return /* @__PURE__ */ jsxs16(
|
|
4427
|
+
"div",
|
|
4428
|
+
{
|
|
4429
|
+
className: "flex items-center gap-2 text-xs text-[hsl(var(--muted-foreground))]",
|
|
4430
|
+
children: [
|
|
4431
|
+
/* @__PURE__ */ jsx18(Layers, { size: 12 }),
|
|
4432
|
+
/* @__PURE__ */ jsx18("span", { children: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29" })
|
|
4433
|
+
]
|
|
4434
|
+
},
|
|
4435
|
+
block.key
|
|
4436
|
+
);
|
|
4437
|
+
}
|
|
4438
|
+
return /* @__PURE__ */ jsx18(PlanningDivider, { kind: block.kind }, block.key);
|
|
4439
|
+
}),
|
|
4440
|
+
sessionStatus === "interrupted" && !isStreaming ? /* @__PURE__ */ jsx18("div", { className: "flex", children: /* @__PURE__ */ jsx18("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
|
|
4441
|
+
] })
|
|
4442
|
+
] }) }),
|
|
4443
|
+
/* @__PURE__ */ jsx18(
|
|
4444
|
+
PinLatestUserMessage,
|
|
3004
4445
|
{
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
]
|
|
4446
|
+
userMessageCount: userMessages.length,
|
|
4447
|
+
shouldPinLatestUser,
|
|
4448
|
+
isStreaming,
|
|
4449
|
+
targetKey: latestUserMessage?.render_id ?? latestUserMessage?.entry_id ?? (latestUserMessage ? `user:${userMessages.length}` : null)
|
|
3010
4450
|
},
|
|
3011
|
-
|
|
3012
|
-
)
|
|
4451
|
+
sessionId ?? "no-session"
|
|
4452
|
+
),
|
|
4453
|
+
/* @__PURE__ */ jsx18(ScrollToBottomButton, {})
|
|
4454
|
+
]
|
|
4455
|
+
},
|
|
4456
|
+
sessionId ?? "no-session"
|
|
4457
|
+
)
|
|
4458
|
+
] });
|
|
4459
|
+
}
|
|
4460
|
+
function LoadOlderSentinel({
|
|
4461
|
+
hasOlder,
|
|
4462
|
+
loading,
|
|
4463
|
+
loadOlder
|
|
4464
|
+
}) {
|
|
4465
|
+
const { contentRef, scrollRef } = useStickToBottomContext();
|
|
4466
|
+
const sentinelRef = useRef13(null);
|
|
4467
|
+
const stateRef = useRef13({ hasOlder, loading, loadOlder });
|
|
4468
|
+
stateRef.current = { hasOlder, loading, loadOlder };
|
|
4469
|
+
useEffect12(() => {
|
|
4470
|
+
const sentinel = sentinelRef.current;
|
|
4471
|
+
const scroller = scrollRef.current;
|
|
4472
|
+
if (!sentinel || !scroller || typeof IntersectionObserver === "undefined") return;
|
|
4473
|
+
let entered = false;
|
|
4474
|
+
let disposed = false;
|
|
4475
|
+
const firstVisible = () => {
|
|
4476
|
+
const top = scroller.getBoundingClientRect().top;
|
|
4477
|
+
for (const row of contentRef.current?.querySelectorAll("[data-entry-id]") ?? []) {
|
|
4478
|
+
const rect = row.getBoundingClientRect();
|
|
4479
|
+
if (rect.bottom >= top) return { id: row.dataset.entryId, offset: rect.top - top };
|
|
4480
|
+
}
|
|
4481
|
+
return null;
|
|
4482
|
+
};
|
|
4483
|
+
const load = async () => {
|
|
4484
|
+
if (disposed || !stateRef.current.hasOlder || stateRef.current.loading) return;
|
|
4485
|
+
const commitSnapshot = { anchor: null, height: scroller.scrollHeight };
|
|
4486
|
+
let advanced = false;
|
|
4487
|
+
try {
|
|
4488
|
+
advanced = await stateRef.current.loadOlder(() => {
|
|
4489
|
+
commitSnapshot.anchor = firstVisible();
|
|
4490
|
+
commitSnapshot.height = scroller.scrollHeight;
|
|
4491
|
+
});
|
|
4492
|
+
} catch {
|
|
4493
|
+
return;
|
|
4494
|
+
}
|
|
4495
|
+
if (disposed) return;
|
|
4496
|
+
await new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
4497
|
+
if (disposed) return;
|
|
4498
|
+
const anchor = commitSnapshot.anchor;
|
|
4499
|
+
const heightBefore = commitSnapshot.height;
|
|
4500
|
+
const anchored = anchor?.id ? Array.from(contentRef.current?.querySelectorAll("[data-entry-id]") ?? []).find((row) => row.dataset.entryId === anchor.id) : null;
|
|
4501
|
+
if (anchored && anchor) {
|
|
4502
|
+
scroller.scrollTop += anchored.getBoundingClientRect().top - scroller.getBoundingClientRect().top - anchor.offset;
|
|
4503
|
+
} else {
|
|
4504
|
+
scroller.scrollTop += scroller.scrollHeight - heightBefore;
|
|
4505
|
+
}
|
|
4506
|
+
if (!disposed && advanced && stateRef.current.hasOlder && !stateRef.current.loading && scroller.scrollHeight <= scroller.clientHeight) {
|
|
4507
|
+
await load();
|
|
4508
|
+
}
|
|
4509
|
+
};
|
|
4510
|
+
const observer = new IntersectionObserver(
|
|
4511
|
+
([entry]) => {
|
|
4512
|
+
if (!entry?.isIntersecting) {
|
|
4513
|
+
entered = false;
|
|
4514
|
+
return;
|
|
3013
4515
|
}
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
4516
|
+
if (entered) return;
|
|
4517
|
+
entered = true;
|
|
4518
|
+
void load();
|
|
4519
|
+
},
|
|
4520
|
+
{ root: scroller, rootMargin: "160px 0px 0px" }
|
|
4521
|
+
);
|
|
4522
|
+
observer.observe(sentinel);
|
|
4523
|
+
return () => {
|
|
4524
|
+
disposed = true;
|
|
4525
|
+
observer.disconnect();
|
|
4526
|
+
};
|
|
4527
|
+
}, [contentRef, scrollRef]);
|
|
4528
|
+
return /* @__PURE__ */ jsx18("div", { ref: sentinelRef, "data-history-sentinel": true, className: "flex h-6 items-center justify-center", children: loading ? /* @__PURE__ */ jsx18("span", { className: "text-xs text-[hsl(var(--muted-foreground))]", children: "\u6B63\u5728\u52A0\u8F7D\u66F4\u65E9\u6D88\u606F\u2026" }) : null });
|
|
3021
4529
|
}
|
|
3022
|
-
function
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
4530
|
+
function PinLatestUserMessage({
|
|
4531
|
+
userMessageCount,
|
|
4532
|
+
shouldPinLatestUser,
|
|
4533
|
+
isStreaming,
|
|
4534
|
+
targetKey
|
|
4535
|
+
}) {
|
|
4536
|
+
const { contentRef, scrollRef, scrollToBottom, stopScroll } = useStickToBottomContext();
|
|
4537
|
+
const previousCountRef = useRef13(userMessageCount);
|
|
4538
|
+
const spacerHeightRef = useRef13(0);
|
|
4539
|
+
const getScrollElement = useCallback7(() => scrollRef.current, [scrollRef]);
|
|
4540
|
+
const getContentElement = useCallback7(() => contentRef.current, [contentRef]);
|
|
4541
|
+
const getTargetElement = useCallback7(() => {
|
|
4542
|
+
const rows = contentRef.current?.querySelectorAll(".blade-chat-user-row");
|
|
4543
|
+
return rows?.item((rows?.length ?? 0) - 1) ?? null;
|
|
4544
|
+
}, [contentRef]);
|
|
4545
|
+
const getSpacerHeight = useCallback7(() => spacerHeightRef.current, []);
|
|
4546
|
+
const setSpacerHeight = useCallback7(
|
|
4547
|
+
(height) => {
|
|
4548
|
+
spacerHeightRef.current = height;
|
|
4549
|
+
const content = contentRef.current;
|
|
4550
|
+
if (!content) return;
|
|
4551
|
+
if (height > 0) content.style.setProperty("--blade-chat-pin-spacer", `${height}px`);
|
|
4552
|
+
else content.style.removeProperty("--blade-chat-pin-spacer");
|
|
4553
|
+
},
|
|
4554
|
+
[contentRef]
|
|
4555
|
+
);
|
|
4556
|
+
useMessagePin({
|
|
4557
|
+
targetKey,
|
|
4558
|
+
pinTarget: shouldPinLatestUser,
|
|
4559
|
+
isStreaming,
|
|
4560
|
+
getScrollElement,
|
|
4561
|
+
getContentElement,
|
|
4562
|
+
getTargetElement,
|
|
4563
|
+
getSpacerHeight,
|
|
4564
|
+
setSpacerHeight,
|
|
4565
|
+
stopAutoScroll: stopScroll,
|
|
4566
|
+
scrollToBottom
|
|
4567
|
+
});
|
|
4568
|
+
useEffect12(() => {
|
|
4569
|
+
if (userMessageCount > previousCountRef.current && !shouldPinLatestUser) {
|
|
3027
4570
|
scrollToBottom("instant");
|
|
3028
4571
|
}
|
|
3029
4572
|
previousCountRef.current = userMessageCount;
|
|
3030
|
-
}, [
|
|
4573
|
+
}, [scrollToBottom, shouldPinLatestUser, userMessageCount]);
|
|
3031
4574
|
return null;
|
|
3032
4575
|
}
|
|
3033
4576
|
function ScrollToBottomButton() {
|
|
3034
4577
|
const { isAtBottom, scrollToBottom } = useStickToBottomContext();
|
|
3035
|
-
const [visible, setVisible] =
|
|
3036
|
-
const hideTimerRef =
|
|
3037
|
-
|
|
4578
|
+
const [visible, setVisible] = useState15(false);
|
|
4579
|
+
const hideTimerRef = useRef13(null);
|
|
4580
|
+
useEffect12(() => {
|
|
3038
4581
|
if (isAtBottom) {
|
|
3039
4582
|
if (!hideTimerRef.current) {
|
|
3040
4583
|
hideTimerRef.current = setTimeout(() => {
|
|
@@ -3056,7 +4599,7 @@ function ScrollToBottomButton() {
|
|
|
3056
4599
|
}
|
|
3057
4600
|
};
|
|
3058
4601
|
}, [isAtBottom]);
|
|
3059
|
-
const handleClick =
|
|
4602
|
+
const handleClick = useCallback7(() => {
|
|
3060
4603
|
if (hideTimerRef.current) {
|
|
3061
4604
|
clearTimeout(hideTimerRef.current);
|
|
3062
4605
|
hideTimerRef.current = null;
|
|
@@ -3065,7 +4608,7 @@ function ScrollToBottomButton() {
|
|
|
3065
4608
|
scrollToBottom();
|
|
3066
4609
|
}, [scrollToBottom]);
|
|
3067
4610
|
if (!visible) return null;
|
|
3068
|
-
return /* @__PURE__ */
|
|
4611
|
+
return /* @__PURE__ */ jsxs16(
|
|
3069
4612
|
"button",
|
|
3070
4613
|
{
|
|
3071
4614
|
type: "button",
|
|
@@ -3073,25 +4616,25 @@ function ScrollToBottomButton() {
|
|
|
3073
4616
|
"aria-label": "\u6EDA\u52A8\u5230\u5E95\u90E8",
|
|
3074
4617
|
className: "blade-chat-scroll-bottom absolute bottom-4 right-4 flex items-center gap-1 rounded-full border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-3 py-1.5 text-xs text-[hsl(var(--muted-foreground))] shadow-lg transition-colors hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--foreground))]",
|
|
3075
4618
|
children: [
|
|
3076
|
-
/* @__PURE__ */
|
|
3077
|
-
/* @__PURE__ */
|
|
4619
|
+
/* @__PURE__ */ jsx18(ChevronDown, { size: 14 }),
|
|
4620
|
+
/* @__PURE__ */ jsx18("span", { className: "blade-chat-scroll-bottom-label", children: "\u6EDA\u52A8\u5230\u5E95\u90E8" })
|
|
3078
4621
|
]
|
|
3079
4622
|
}
|
|
3080
4623
|
);
|
|
3081
4624
|
}
|
|
3082
4625
|
function PlanningDivider({ kind }) {
|
|
3083
|
-
return /* @__PURE__ */
|
|
3084
|
-
/* @__PURE__ */
|
|
3085
|
-
/* @__PURE__ */
|
|
3086
|
-
/* @__PURE__ */
|
|
3087
|
-
/* @__PURE__ */
|
|
4626
|
+
return /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-3 py-1", children: [
|
|
4627
|
+
/* @__PURE__ */ jsx18("div", { className: "h-px flex-1 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent" }),
|
|
4628
|
+
/* @__PURE__ */ jsxs16("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: [
|
|
4629
|
+
/* @__PURE__ */ jsx18(Lightbulb, { size: 12 }),
|
|
4630
|
+
/* @__PURE__ */ jsx18("span", { children: kind === "enter" ? "\u8FDB\u5165\u89C4\u5212\u6A21\u5F0F" : "\u89C4\u5212\u5B8C\u6210" })
|
|
3088
4631
|
] }),
|
|
3089
|
-
/* @__PURE__ */
|
|
4632
|
+
/* @__PURE__ */ jsx18("div", { className: "h-px flex-1 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent" })
|
|
3090
4633
|
] });
|
|
3091
4634
|
}
|
|
3092
4635
|
|
|
3093
4636
|
// src/components/ChatSurface.tsx
|
|
3094
|
-
import { jsx as
|
|
4637
|
+
import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
3095
4638
|
function themeAttr(theme) {
|
|
3096
4639
|
return theme === "dark" ? "dark" : void 0;
|
|
3097
4640
|
}
|
|
@@ -3111,6 +4654,7 @@ function ChatSurface({
|
|
|
3111
4654
|
onInputChange,
|
|
3112
4655
|
onSuggestion,
|
|
3113
4656
|
onSend,
|
|
4657
|
+
onAppend,
|
|
3114
4658
|
onStop,
|
|
3115
4659
|
sessionStatus,
|
|
3116
4660
|
askAnswers,
|
|
@@ -3121,9 +4665,12 @@ function ChatSurface({
|
|
|
3121
4665
|
onResultFeedbackSaved,
|
|
3122
4666
|
onFollowupInteraction,
|
|
3123
4667
|
beforeInput,
|
|
4668
|
+
showPlanUpdates = false,
|
|
4669
|
+
planRevealRevision = 0,
|
|
4670
|
+
historyPaging,
|
|
3124
4671
|
banner
|
|
3125
4672
|
}) {
|
|
3126
|
-
return /* @__PURE__ */
|
|
4673
|
+
return /* @__PURE__ */ jsxs17(
|
|
3127
4674
|
"div",
|
|
3128
4675
|
{
|
|
3129
4676
|
"data-theme": themeAttr(theme),
|
|
@@ -3132,14 +4679,14 @@ function ChatSurface({
|
|
|
3132
4679
|
classNames?.root
|
|
3133
4680
|
),
|
|
3134
4681
|
children: [
|
|
3135
|
-
/* @__PURE__ */
|
|
4682
|
+
/* @__PURE__ */ jsx19(ConnectionBanner, { connection, className: classNames?.banner }),
|
|
3136
4683
|
banner,
|
|
3137
|
-
errorMessage && /* @__PURE__ */
|
|
3138
|
-
/* @__PURE__ */
|
|
3139
|
-
/* @__PURE__ */
|
|
4684
|
+
errorMessage && /* @__PURE__ */ jsxs17("div", { className: "blade-chat-error-bar flex items-start gap-2 border-b px-4 py-3 text-sm", children: [
|
|
4685
|
+
/* @__PURE__ */ jsx19(CircleAlert, { size: 16, className: "mt-0.5 shrink-0" }),
|
|
4686
|
+
/* @__PURE__ */ jsx19("span", { className: "min-w-0 whitespace-pre-wrap break-words [overflow-wrap:anywhere]", children: chatErrorForDisplay2(errorMessage) })
|
|
3140
4687
|
] }),
|
|
3141
4688
|
slots?.header,
|
|
3142
|
-
/* @__PURE__ */
|
|
4689
|
+
/* @__PURE__ */ jsx19(
|
|
3143
4690
|
MessageList,
|
|
3144
4691
|
{
|
|
3145
4692
|
messages,
|
|
@@ -3150,26 +4697,40 @@ function ChatSurface({
|
|
|
3150
4697
|
askAnswers,
|
|
3151
4698
|
onAnswer,
|
|
3152
4699
|
toolCallRenderer: renderers?.toolCall,
|
|
4700
|
+
hidePlanUpdateTools: showPlanUpdates,
|
|
3153
4701
|
emptyState: slots?.emptyState,
|
|
3154
4702
|
className: classNames?.messageList,
|
|
3155
4703
|
sessionId,
|
|
3156
4704
|
isViewer,
|
|
3157
4705
|
resultFeedbackByEntry,
|
|
3158
4706
|
onResultFeedbackSaved,
|
|
3159
|
-
onFollowupInteraction
|
|
4707
|
+
onFollowupInteraction,
|
|
4708
|
+
historyPaging
|
|
3160
4709
|
}
|
|
3161
4710
|
),
|
|
4711
|
+
showPlanUpdates ? /* @__PURE__ */ jsx19(
|
|
4712
|
+
CurrentPlanPanel,
|
|
4713
|
+
{
|
|
4714
|
+
messages,
|
|
4715
|
+
running: isStreaming,
|
|
4716
|
+
revealRevision: planRevealRevision,
|
|
4717
|
+
sessionId,
|
|
4718
|
+
className: "border-t border-[hsl(var(--border))]"
|
|
4719
|
+
}
|
|
4720
|
+
) : null,
|
|
3162
4721
|
beforeInput,
|
|
3163
|
-
/* @__PURE__ */
|
|
4722
|
+
/* @__PURE__ */ jsx19(
|
|
3164
4723
|
ChatInput,
|
|
3165
4724
|
{
|
|
3166
4725
|
value: inputText,
|
|
3167
4726
|
onValueChange: onInputChange,
|
|
3168
4727
|
onSend,
|
|
4728
|
+
onAppend,
|
|
3169
4729
|
onStop,
|
|
3170
4730
|
isStreaming,
|
|
3171
4731
|
isStopping,
|
|
3172
4732
|
placeholder,
|
|
4733
|
+
queueKey: sessionId,
|
|
3173
4734
|
className: classNames?.chatInput
|
|
3174
4735
|
}
|
|
3175
4736
|
),
|
|
@@ -3180,13 +4741,13 @@ function ChatSurface({
|
|
|
3180
4741
|
}
|
|
3181
4742
|
|
|
3182
4743
|
// src/components/AgentChat.tsx
|
|
3183
|
-
import { Fragment as
|
|
4744
|
+
import { Fragment as Fragment4, jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
3184
4745
|
function isUnauthorizedError(error) {
|
|
3185
4746
|
return error instanceof BladeApiError && error.status === 401;
|
|
3186
4747
|
}
|
|
3187
4748
|
function LoginCard({ client, onLoggedIn }) {
|
|
3188
|
-
const [loggingIn, setLoggingIn] =
|
|
3189
|
-
const [loginError, setLoginError] =
|
|
4749
|
+
const [loggingIn, setLoggingIn] = useState16(false);
|
|
4750
|
+
const [loginError, setLoginError] = useState16(null);
|
|
3190
4751
|
const handleLogin = async () => {
|
|
3191
4752
|
setLoggingIn(true);
|
|
3192
4753
|
setLoginError(null);
|
|
@@ -3199,11 +4760,11 @@ function LoginCard({ client, onLoggedIn }) {
|
|
|
3199
4760
|
setLoggingIn(false);
|
|
3200
4761
|
}
|
|
3201
4762
|
};
|
|
3202
|
-
return /* @__PURE__ */
|
|
3203
|
-
/* @__PURE__ */
|
|
3204
|
-
/* @__PURE__ */
|
|
3205
|
-
/* @__PURE__ */
|
|
3206
|
-
/* @__PURE__ */
|
|
4763
|
+
return /* @__PURE__ */ jsx20("div", { className: "blade-chat-login flex flex-1 items-center justify-center p-6", children: /* @__PURE__ */ jsxs18("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: [
|
|
4764
|
+
/* @__PURE__ */ jsx20(LockKeyhole, { size: 28, className: "text-[hsl(var(--muted-foreground))]" }),
|
|
4765
|
+
/* @__PURE__ */ jsx20("div", { className: "text-base font-medium text-[hsl(var(--foreground))]", children: "\u9700\u8981\u767B\u5F55\u540E\u4F7F\u7528" }),
|
|
4766
|
+
/* @__PURE__ */ jsx20("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" }),
|
|
4767
|
+
/* @__PURE__ */ jsx20(
|
|
3207
4768
|
"button",
|
|
3208
4769
|
{
|
|
3209
4770
|
type: "button",
|
|
@@ -3213,20 +4774,20 @@ function LoginCard({ client, onLoggedIn }) {
|
|
|
3213
4774
|
children: loggingIn ? "\u767B\u5F55\u4E2D\u2026" : "\u767B\u5F55"
|
|
3214
4775
|
}
|
|
3215
4776
|
),
|
|
3216
|
-
loginError && /* @__PURE__ */
|
|
4777
|
+
loginError && /* @__PURE__ */ jsx20("div", { className: "text-xs text-[hsl(var(--destructive))]", children: loginError })
|
|
3217
4778
|
] }) });
|
|
3218
4779
|
}
|
|
3219
4780
|
function AgentChat(props) {
|
|
3220
4781
|
const client = useBladeClient();
|
|
3221
|
-
const [attempt, setAttempt] =
|
|
3222
|
-
const [needLogin, setNeedLogin] =
|
|
4782
|
+
const [attempt, setAttempt] = useState16(0);
|
|
4783
|
+
const [needLogin, setNeedLogin] = useState16(() => !client.hasToken());
|
|
3223
4784
|
if (needLogin) {
|
|
3224
|
-
return /* @__PURE__ */
|
|
4785
|
+
return /* @__PURE__ */ jsx20(
|
|
3225
4786
|
"div",
|
|
3226
4787
|
{
|
|
3227
4788
|
"data-theme": themeAttr(props.theme),
|
|
3228
4789
|
className: cn("blade-chat flex min-h-0 flex-1 flex-col", props.classNames?.root),
|
|
3229
|
-
children: /* @__PURE__ */
|
|
4790
|
+
children: /* @__PURE__ */ jsx20(
|
|
3230
4791
|
LoginCard,
|
|
3231
4792
|
{
|
|
3232
4793
|
client,
|
|
@@ -3239,7 +4800,7 @@ function AgentChat(props) {
|
|
|
3239
4800
|
}
|
|
3240
4801
|
);
|
|
3241
4802
|
}
|
|
3242
|
-
return /* @__PURE__ */
|
|
4803
|
+
return /* @__PURE__ */ jsx20(ChatSessionView, { ...props, onUnauthorized: () => setNeedLogin(true) }, attempt);
|
|
3243
4804
|
}
|
|
3244
4805
|
function ChatSessionView({
|
|
3245
4806
|
sessionId,
|
|
@@ -3253,20 +4814,43 @@ function ChatSessionView({
|
|
|
3253
4814
|
placeholder,
|
|
3254
4815
|
theme,
|
|
3255
4816
|
onFollowupInteraction,
|
|
4817
|
+
sessionPluginControls,
|
|
3256
4818
|
onUnauthorized
|
|
3257
4819
|
}) {
|
|
3258
4820
|
const client = useBladeClient();
|
|
4821
|
+
const [planRevealRevisions, setPlanRevealRevisions] = useState16(
|
|
4822
|
+
() => /* @__PURE__ */ new Map()
|
|
4823
|
+
);
|
|
4824
|
+
const handleSessionConnected = useCallback8((connectedSession) => {
|
|
4825
|
+
return connectedSession.on("toolResult", ({ toolCall, turn, source }) => {
|
|
4826
|
+
if (source === "reconnect_replay" || (turn.loop_id || "root") !== "root" || toolCall.status !== "done" || !isPlanUpdateTool(toolCall) || !parsePlanUpdate(toolCall.arguments)) {
|
|
4827
|
+
return;
|
|
4828
|
+
}
|
|
4829
|
+
setPlanRevealRevisions((current) => {
|
|
4830
|
+
const next = new Map(current);
|
|
4831
|
+
next.set(connectedSession.sessionId, (current.get(connectedSession.sessionId) ?? 0) + 1);
|
|
4832
|
+
return next;
|
|
4833
|
+
});
|
|
4834
|
+
});
|
|
4835
|
+
}, []);
|
|
3259
4836
|
const { session, state, error } = useAgentSession(sessionId, {
|
|
3260
4837
|
createOptions,
|
|
3261
|
-
onSessionCreated
|
|
4838
|
+
onSessionCreated,
|
|
4839
|
+
onSessionConnected: handleSessionConnected
|
|
3262
4840
|
});
|
|
3263
4841
|
const replay = useReplay(session);
|
|
3264
|
-
const [stopRequested, setStopRequested] =
|
|
3265
|
-
const [inputText, setInputText] =
|
|
3266
|
-
const [resultFeedback, setResultFeedback] =
|
|
4842
|
+
const [stopRequested, setStopRequested] = useState16(false);
|
|
4843
|
+
const [inputText, setInputText] = useState16("");
|
|
4844
|
+
const [resultFeedback, setResultFeedback] = useState16([]);
|
|
3267
4845
|
const resolvedSessionId = session?.sessionId;
|
|
3268
4846
|
const isViewer = state?.viewerRole === "viewer";
|
|
3269
|
-
|
|
4847
|
+
const onSessionReadyRef = useRef14(onSessionReady);
|
|
4848
|
+
const readySessionRef = useRef14(null);
|
|
4849
|
+
const hasOnSessionReady = onSessionReady !== void 0;
|
|
4850
|
+
useEffect13(() => {
|
|
4851
|
+
onSessionReadyRef.current = onSessionReady;
|
|
4852
|
+
}, [onSessionReady]);
|
|
4853
|
+
useEffect13(() => {
|
|
3270
4854
|
setResultFeedback([]);
|
|
3271
4855
|
if (!resolvedSessionId || isViewer) return;
|
|
3272
4856
|
let cancelled = false;
|
|
@@ -3295,18 +4879,19 @@ function ChatSessionView({
|
|
|
3295
4879
|
() => new Map(resultFeedback.map((item) => [item.assistant_entry_id, item])),
|
|
3296
4880
|
[resultFeedback]
|
|
3297
4881
|
);
|
|
3298
|
-
const handleResultFeedbackSaved =
|
|
4882
|
+
const handleResultFeedbackSaved = useCallback8((saved) => {
|
|
3299
4883
|
setResultFeedback((current) => [
|
|
3300
4884
|
...current.filter((item) => item.assistant_entry_id !== saved.assistant_entry_id),
|
|
3301
4885
|
saved
|
|
3302
4886
|
]);
|
|
3303
4887
|
}, []);
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
4888
|
+
useEffect13(() => {
|
|
4889
|
+
const handler = onSessionReadyRef.current;
|
|
4890
|
+
if (!session || !hasOnSessionReady || !handler || readySessionRef.current === session) return;
|
|
4891
|
+
readySessionRef.current = session;
|
|
4892
|
+
handler(session);
|
|
4893
|
+
}, [session, hasOnSessionReady]);
|
|
4894
|
+
useEffect13(() => {
|
|
3310
4895
|
if (!session) return;
|
|
3311
4896
|
const offAttach = session.on("attachRequested", ({ label, content }) => {
|
|
3312
4897
|
setInputText((prev) => `${prev ? `${prev}
|
|
@@ -3322,12 +4907,12 @@ ${content}`);
|
|
|
3322
4907
|
offInsert();
|
|
3323
4908
|
};
|
|
3324
4909
|
}, [session]);
|
|
3325
|
-
|
|
4910
|
+
useEffect13(() => {
|
|
3326
4911
|
if (isUnauthorizedError(error)) {
|
|
3327
4912
|
onUnauthorized();
|
|
3328
4913
|
}
|
|
3329
4914
|
}, [error, onUnauthorized]);
|
|
3330
|
-
|
|
4915
|
+
useEffect13(() => {
|
|
3331
4916
|
if (!session || !commands) return;
|
|
3332
4917
|
const unsubscribes = Object.entries(commands).map(
|
|
3333
4918
|
([action, handler]) => session.onCommand(action, (payload) => handler(payload))
|
|
@@ -3337,6 +4922,7 @@ ${content}`);
|
|
|
3337
4922
|
};
|
|
3338
4923
|
}, [session, commands]);
|
|
3339
4924
|
const isStreaming = state?.isStreaming ?? false;
|
|
4925
|
+
const planRevealRevision = resolvedSessionId ? planRevealRevisions.get(resolvedSessionId) ?? 0 : 0;
|
|
3340
4926
|
const isStopping = stopRequested && isStreaming;
|
|
3341
4927
|
const connectError = error && !isUnauthorizedError(error) ? error.message || "\u8FDE\u63A5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" : null;
|
|
3342
4928
|
const errorMessage = connectError ?? state?.errorMessage ?? replay.error?.message ?? null;
|
|
@@ -3348,7 +4934,7 @@ ${content}`);
|
|
|
3348
4934
|
setStopRequested(true);
|
|
3349
4935
|
void session?.stop();
|
|
3350
4936
|
};
|
|
3351
|
-
return /* @__PURE__ */
|
|
4937
|
+
return /* @__PURE__ */ jsx20(
|
|
3352
4938
|
ChatSurface,
|
|
3353
4939
|
{
|
|
3354
4940
|
theme,
|
|
@@ -3358,8 +4944,9 @@ ${content}`);
|
|
|
3358
4944
|
slots,
|
|
3359
4945
|
placeholder,
|
|
3360
4946
|
connection: state?.connection ?? "connecting",
|
|
3361
|
-
banner: /* @__PURE__ */
|
|
3362
|
-
/* @__PURE__ */
|
|
4947
|
+
banner: /* @__PURE__ */ jsxs18(Fragment4, { children: [
|
|
4948
|
+
resolvedSessionId && !isViewer && (sessionPluginControls ? sessionPluginControls(resolvedSessionId) : /* @__PURE__ */ jsx20(SessionPluginSelector, { client, sessionId: resolvedSessionId })),
|
|
4949
|
+
/* @__PURE__ */ jsx20(
|
|
3363
4950
|
ReplayBar,
|
|
3364
4951
|
{
|
|
3365
4952
|
isReplay: replay.isReplay,
|
|
@@ -3369,7 +4956,7 @@ ${content}`);
|
|
|
3369
4956
|
onExit: () => void replay.exitToAutonomous()
|
|
3370
4957
|
}
|
|
3371
4958
|
),
|
|
3372
|
-
/* @__PURE__ */
|
|
4959
|
+
/* @__PURE__ */ jsx20(ReplayMismatchPrompt, { mismatch: replay.mismatch })
|
|
3373
4960
|
] }),
|
|
3374
4961
|
errorMessage,
|
|
3375
4962
|
messages: state?.messages ?? [],
|
|
@@ -3377,11 +4964,25 @@ ${content}`);
|
|
|
3377
4964
|
resultFeedbackByEntry,
|
|
3378
4965
|
onResultFeedbackSaved: handleResultFeedbackSaved,
|
|
3379
4966
|
isStreaming,
|
|
4967
|
+
showPlanUpdates: true,
|
|
4968
|
+
planRevealRevision,
|
|
4969
|
+
historyPaging: session && state ? {
|
|
4970
|
+
hasOlder: session.hasOlderHistory,
|
|
4971
|
+
loading: state.loadingOlder,
|
|
4972
|
+
loadOlder: async (beforeCommit) => {
|
|
4973
|
+
const before = session.getState().nextBefore;
|
|
4974
|
+
await session.loadOlderHistory({ beforeCommit });
|
|
4975
|
+
return before !== session.getState().nextBefore;
|
|
4976
|
+
}
|
|
4977
|
+
} : void 0,
|
|
3380
4978
|
isStopping,
|
|
3381
4979
|
inputText,
|
|
3382
4980
|
onInputChange: setInputText,
|
|
3383
4981
|
onSuggestion: setInputText,
|
|
3384
4982
|
onSend: handleSend,
|
|
4983
|
+
onAppend: (text) => {
|
|
4984
|
+
void session?.queue(text);
|
|
4985
|
+
},
|
|
3385
4986
|
onStop: handleStop,
|
|
3386
4987
|
sessionStatus: state?.status ?? void 0,
|
|
3387
4988
|
askAnswers: state?.askAnswers,
|
|
@@ -3398,11 +4999,11 @@ ${content}`);
|
|
|
3398
4999
|
}
|
|
3399
5000
|
|
|
3400
5001
|
// src/components/LlmChat.tsx
|
|
3401
|
-
import { useEffect as
|
|
5002
|
+
import { useEffect as useEffect14, useMemo as useMemo9, useState as useState18 } from "react";
|
|
3402
5003
|
|
|
3403
5004
|
// src/components/LlmAdvancedSettings.tsx
|
|
3404
|
-
import { useState as
|
|
3405
|
-
import { jsx as
|
|
5005
|
+
import { useState as useState17 } from "react";
|
|
5006
|
+
import { jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
3406
5007
|
var FIELDS = [
|
|
3407
5008
|
{ id: "baseURL", label: "\u6A21\u578B\u670D\u52A1\u5730\u5740", placeholder: "http://\u5185\u7F51\u5730\u5740/v1" },
|
|
3408
5009
|
{ id: "model", label: "\u6A21\u578B", placeholder: "\u6A21\u578B\u540D\u79F0" },
|
|
@@ -3448,13 +5049,13 @@ function writeOverride(settings, baseURL, override) {
|
|
|
3448
5049
|
}
|
|
3449
5050
|
function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
3450
5051
|
const normalized = normalizeAdvanced(settings);
|
|
3451
|
-
const [open, setOpen] =
|
|
3452
|
-
const [draft, setDraft] =
|
|
5052
|
+
const [open, setOpen] = useState17(false);
|
|
5053
|
+
const [draft, setDraft] = useState17(override);
|
|
3453
5054
|
if (!normalized) return null;
|
|
3454
5055
|
const fields = FIELDS.filter((field) => normalized[field.id]);
|
|
3455
5056
|
const dirty = Object.keys(override).length > 0;
|
|
3456
|
-
return /* @__PURE__ */
|
|
3457
|
-
/* @__PURE__ */
|
|
5057
|
+
return /* @__PURE__ */ jsxs19("div", { className: "blade-chat-advanced border-t border-[hsl(var(--border))] px-4 py-2 text-xs", children: [
|
|
5058
|
+
/* @__PURE__ */ jsxs19(
|
|
3458
5059
|
"button",
|
|
3459
5060
|
{
|
|
3460
5061
|
type: "button",
|
|
@@ -3464,16 +5065,16 @@ function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
|
3464
5065
|
},
|
|
3465
5066
|
className: "flex items-center gap-1.5 text-[hsl(var(--muted-foreground))] transition-colors hover:text-[hsl(var(--foreground))]",
|
|
3466
5067
|
children: [
|
|
3467
|
-
/* @__PURE__ */
|
|
5068
|
+
/* @__PURE__ */ jsx21(Settings2, { size: 13 }),
|
|
3468
5069
|
"\u9AD8\u7EA7\u8BBE\u7F6E",
|
|
3469
|
-
dirty && /* @__PURE__ */
|
|
5070
|
+
dirty && /* @__PURE__ */ jsx21("span", { className: "text-[hsl(var(--primary))]", children: "\uFF08\u5DF2\u81EA\u5B9A\u4E49\uFF09" })
|
|
3470
5071
|
]
|
|
3471
5072
|
}
|
|
3472
5073
|
),
|
|
3473
|
-
open && /* @__PURE__ */
|
|
3474
|
-
fields.map((field) => /* @__PURE__ */
|
|
3475
|
-
/* @__PURE__ */
|
|
3476
|
-
/* @__PURE__ */
|
|
5074
|
+
open && /* @__PURE__ */ jsxs19("div", { className: "mt-2 flex flex-col gap-2", children: [
|
|
5075
|
+
fields.map((field) => /* @__PURE__ */ jsxs19("label", { className: "flex flex-col gap-1", children: [
|
|
5076
|
+
/* @__PURE__ */ jsx21("span", { className: "text-[hsl(var(--muted-foreground))]", children: field.label }),
|
|
5077
|
+
/* @__PURE__ */ jsx21(
|
|
3477
5078
|
"input",
|
|
3478
5079
|
{
|
|
3479
5080
|
type: field.secret ? "password" : "text",
|
|
@@ -3484,9 +5085,9 @@ function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
|
3484
5085
|
}
|
|
3485
5086
|
)
|
|
3486
5087
|
] }, field.id)),
|
|
3487
|
-
normalized.apiKey && /* @__PURE__ */
|
|
3488
|
-
/* @__PURE__ */
|
|
3489
|
-
/* @__PURE__ */
|
|
5088
|
+
normalized.apiKey && /* @__PURE__ */ jsx21("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" }),
|
|
5089
|
+
/* @__PURE__ */ jsxs19("div", { className: "flex gap-2", children: [
|
|
5090
|
+
/* @__PURE__ */ jsx21(
|
|
3490
5091
|
"button",
|
|
3491
5092
|
{
|
|
3492
5093
|
type: "button",
|
|
@@ -3501,7 +5102,7 @@ function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
|
3501
5102
|
children: "\u4FDD\u5B58"
|
|
3502
5103
|
}
|
|
3503
5104
|
),
|
|
3504
|
-
/* @__PURE__ */
|
|
5105
|
+
/* @__PURE__ */ jsx21(
|
|
3505
5106
|
"button",
|
|
3506
5107
|
{
|
|
3507
5108
|
type: "button",
|
|
@@ -3520,7 +5121,7 @@ function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
|
3520
5121
|
}
|
|
3521
5122
|
|
|
3522
5123
|
// src/components/LlmChat.tsx
|
|
3523
|
-
import { jsx as
|
|
5124
|
+
import { jsx as jsx22 } from "react/jsx-runtime";
|
|
3524
5125
|
function LlmChat({
|
|
3525
5126
|
classNames,
|
|
3526
5127
|
renderers,
|
|
@@ -3532,11 +5133,11 @@ function LlmChat({
|
|
|
3532
5133
|
onOverrideChange,
|
|
3533
5134
|
...options
|
|
3534
5135
|
}) {
|
|
3535
|
-
const [override, setOverride] =
|
|
5136
|
+
const [override, setOverride] = useState18(() => readOverride(advanced, options.baseURL));
|
|
3536
5137
|
const effective = { ...options, ...override };
|
|
3537
5138
|
const { messages, isStreaming, error, send, stop, reset } = useLlmChat(effective);
|
|
3538
|
-
const [inputText, setInputText] =
|
|
3539
|
-
const [stopRequested, setStopRequested] =
|
|
5139
|
+
const [inputText, setInputText] = useState18("");
|
|
5140
|
+
const [stopRequested, setStopRequested] = useState18(false);
|
|
3540
5141
|
const handle = useMemo9(
|
|
3541
5142
|
() => ({
|
|
3542
5143
|
insertText: (text) => setInputText((prev) => prev ? `${prev}
|
|
@@ -3546,10 +5147,10 @@ ${text}` : text),
|
|
|
3546
5147
|
}),
|
|
3547
5148
|
[send, reset]
|
|
3548
5149
|
);
|
|
3549
|
-
|
|
5150
|
+
useEffect14(() => {
|
|
3550
5151
|
onReady?.(handle);
|
|
3551
5152
|
}, [handle, onReady]);
|
|
3552
|
-
return /* @__PURE__ */
|
|
5153
|
+
return /* @__PURE__ */ jsx22(
|
|
3553
5154
|
ChatSurface,
|
|
3554
5155
|
{
|
|
3555
5156
|
theme,
|
|
@@ -3574,7 +5175,7 @@ ${text}` : text),
|
|
|
3574
5175
|
setStopRequested(true);
|
|
3575
5176
|
stop();
|
|
3576
5177
|
},
|
|
3577
|
-
beforeInput: advanced ? /* @__PURE__ */
|
|
5178
|
+
beforeInput: advanced ? /* @__PURE__ */ jsx22(
|
|
3578
5179
|
LlmAdvancedSettingsBar,
|
|
3579
5180
|
{
|
|
3580
5181
|
settings: advanced,
|
|
@@ -3592,14 +5193,14 @@ ${text}` : text),
|
|
|
3592
5193
|
}
|
|
3593
5194
|
|
|
3594
5195
|
// src/components/ChatView.tsx
|
|
3595
|
-
import { jsx as
|
|
5196
|
+
import { jsx as jsx23 } from "react/jsx-runtime";
|
|
3596
5197
|
function ChatView(props) {
|
|
3597
5198
|
const { mode, llm, onLlmReady, ...rest } = props;
|
|
3598
5199
|
if (mode === "llm") {
|
|
3599
5200
|
if (!llm) {
|
|
3600
5201
|
throw new Error('ChatView: mode="llm" \u9700\u8981\u540C\u65F6\u4F20 llm={{ baseURL, model }}');
|
|
3601
5202
|
}
|
|
3602
|
-
return /* @__PURE__ */
|
|
5203
|
+
return /* @__PURE__ */ jsx23(
|
|
3603
5204
|
LlmChat,
|
|
3604
5205
|
{
|
|
3605
5206
|
...llm,
|
|
@@ -3612,7 +5213,232 @@ function ChatView(props) {
|
|
|
3612
5213
|
}
|
|
3613
5214
|
);
|
|
3614
5215
|
}
|
|
3615
|
-
return /* @__PURE__ */
|
|
5216
|
+
return /* @__PURE__ */ jsx23(AgentChat, { ...rest });
|
|
5217
|
+
}
|
|
5218
|
+
|
|
5219
|
+
// src/components/ContextCard.tsx
|
|
5220
|
+
import {
|
|
5221
|
+
getContextDisplayState,
|
|
5222
|
+
getContextGroupDisplayState
|
|
5223
|
+
} from "@blade-hq/agent-client";
|
|
5224
|
+
import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
5225
|
+
function ContextCard({ context, className }) {
|
|
5226
|
+
const display = getContextDisplayState(context);
|
|
5227
|
+
return /* @__PURE__ */ jsxs20(
|
|
5228
|
+
"details",
|
|
5229
|
+
{
|
|
5230
|
+
className: `blade-chat-context-card group/context-card rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] text-sm ${className ?? ""}`,
|
|
5231
|
+
children: [
|
|
5232
|
+
/* @__PURE__ */ jsxs20("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: [
|
|
5233
|
+
/* @__PURE__ */ jsx24(
|
|
5234
|
+
Layers,
|
|
5235
|
+
{
|
|
5236
|
+
size: 15,
|
|
5237
|
+
className: "blade-chat-context-icon shrink-0 text-[hsl(var(--muted-foreground))]",
|
|
5238
|
+
"aria-hidden": "true"
|
|
5239
|
+
}
|
|
5240
|
+
),
|
|
5241
|
+
/* @__PURE__ */ jsxs20("span", { className: "blade-chat-context-copy min-w-0 flex-1", children: [
|
|
5242
|
+
/* @__PURE__ */ jsx24("span", { className: "blade-chat-context-title block font-medium text-[hsl(var(--foreground))]", children: display.title }),
|
|
5243
|
+
/* @__PURE__ */ jsx24("span", { className: "blade-chat-context-status block truncate text-xs text-[hsl(var(--muted-foreground))]", children: display.summary })
|
|
5244
|
+
] }),
|
|
5245
|
+
/* @__PURE__ */ jsx24(
|
|
5246
|
+
ChevronDown,
|
|
5247
|
+
{
|
|
5248
|
+
size: 14,
|
|
5249
|
+
className: "blade-chat-context-chevron shrink-0 text-[hsl(var(--muted-foreground))] transition-transform group-open/context-card:rotate-180",
|
|
5250
|
+
"aria-hidden": "true"
|
|
5251
|
+
}
|
|
5252
|
+
)
|
|
5253
|
+
] }),
|
|
5254
|
+
/* @__PURE__ */ jsx24("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 })
|
|
5255
|
+
]
|
|
5256
|
+
}
|
|
5257
|
+
);
|
|
5258
|
+
}
|
|
5259
|
+
function ContextGroupCard({ contexts, className }) {
|
|
5260
|
+
if (contexts.length === 0) return null;
|
|
5261
|
+
const single = contexts.length === 1 ? getContextDisplayState(contexts[0]) : null;
|
|
5262
|
+
const group = single ? null : getContextGroupDisplayState(contexts);
|
|
5263
|
+
return /* @__PURE__ */ jsxs20("details", { className: `blade-chat-context-card group/context-group rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] text-sm ${className ?? ""}`, children: [
|
|
5264
|
+
/* @__PURE__ */ jsxs20("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: [
|
|
5265
|
+
/* @__PURE__ */ jsx24(
|
|
5266
|
+
Layers,
|
|
5267
|
+
{
|
|
5268
|
+
size: 15,
|
|
5269
|
+
className: "blade-chat-context-icon shrink-0 text-[hsl(var(--muted-foreground))]",
|
|
5270
|
+
"aria-hidden": "true"
|
|
5271
|
+
}
|
|
5272
|
+
),
|
|
5273
|
+
/* @__PURE__ */ jsxs20("span", { className: "blade-chat-context-copy min-w-0 flex-1", children: [
|
|
5274
|
+
/* @__PURE__ */ jsx24("span", { className: "blade-chat-context-title block font-medium text-[hsl(var(--foreground))]", children: single ? single.title : `${group?.title} \xB7 ${group?.count} \u9879` }),
|
|
5275
|
+
/* @__PURE__ */ jsx24("span", { className: "blade-chat-context-status block truncate text-xs text-[hsl(var(--muted-foreground))]", children: single ? single.summary : group?.summary })
|
|
5276
|
+
] }),
|
|
5277
|
+
/* @__PURE__ */ jsx24(
|
|
5278
|
+
ChevronDown,
|
|
5279
|
+
{
|
|
5280
|
+
size: 14,
|
|
5281
|
+
className: "blade-chat-context-chevron shrink-0 text-[hsl(var(--muted-foreground))] transition-transform group-open/context-group:rotate-180",
|
|
5282
|
+
"aria-hidden": "true"
|
|
5283
|
+
}
|
|
5284
|
+
)
|
|
5285
|
+
] }),
|
|
5286
|
+
single ? /* @__PURE__ */ jsx24("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__ */ jsx24("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__ */ jsx24(
|
|
5287
|
+
ContextCard,
|
|
5288
|
+
{
|
|
5289
|
+
context
|
|
5290
|
+
},
|
|
5291
|
+
`${context.context_kind}:${context.context_key}`
|
|
5292
|
+
)) })
|
|
5293
|
+
] });
|
|
5294
|
+
}
|
|
5295
|
+
|
|
5296
|
+
// src/components/SessionMemoryToggle.tsx
|
|
5297
|
+
import { useCallback as useCallback9, useEffect as useEffect15, useRef as useRef15, useState as useState19, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
5298
|
+
import { jsx as jsx25, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
5299
|
+
var saveStates = /* @__PURE__ */ new WeakMap();
|
|
5300
|
+
function getSaveState(client, sessionId) {
|
|
5301
|
+
let clientStates = saveStates.get(client);
|
|
5302
|
+
if (!clientStates) {
|
|
5303
|
+
clientStates = /* @__PURE__ */ new Map();
|
|
5304
|
+
saveStates.set(client, clientStates);
|
|
5305
|
+
}
|
|
5306
|
+
let state = clientStates.get(sessionId);
|
|
5307
|
+
if (!state) {
|
|
5308
|
+
state = { saving: false, listeners: /* @__PURE__ */ new Set() };
|
|
5309
|
+
clientStates.set(sessionId, state);
|
|
5310
|
+
}
|
|
5311
|
+
return state;
|
|
5312
|
+
}
|
|
5313
|
+
function notify(state) {
|
|
5314
|
+
for (const listener of state.listeners) listener();
|
|
5315
|
+
}
|
|
5316
|
+
function cleanupSaveState(client, sessionId, state) {
|
|
5317
|
+
if (state.saving || state.listeners.size > 0) return;
|
|
5318
|
+
const clientStates = saveStates.get(client);
|
|
5319
|
+
if (clientStates?.get(sessionId) === state) clientStates.delete(sessionId);
|
|
5320
|
+
}
|
|
5321
|
+
function SessionMemoryToggle({
|
|
5322
|
+
sessionId,
|
|
5323
|
+
enabled,
|
|
5324
|
+
client: clientProp,
|
|
5325
|
+
disabled = false,
|
|
5326
|
+
label = "\u5F53\u524D\u4F1A\u8BDD\u4F7F\u7528\u8BB0\u5FC6",
|
|
5327
|
+
className,
|
|
5328
|
+
labelClassName,
|
|
5329
|
+
inputClassName,
|
|
5330
|
+
onSaved,
|
|
5331
|
+
onError
|
|
5332
|
+
}) {
|
|
5333
|
+
const contextClient = useOptionalBladeClient();
|
|
5334
|
+
const client = clientProp ?? contextClient;
|
|
5335
|
+
if (!client) {
|
|
5336
|
+
throw new Error("SessionMemoryToggle \u5FC5\u987B\u5728 <BladeProvider> \u5185\u4F7F\u7528\u6216\u663E\u5F0F\u4F20\u5165 client");
|
|
5337
|
+
}
|
|
5338
|
+
const saveState = getSaveState(client, sessionId);
|
|
5339
|
+
const subscribe = useCallback9(
|
|
5340
|
+
(listener) => {
|
|
5341
|
+
saveState.listeners.add(listener);
|
|
5342
|
+
return () => {
|
|
5343
|
+
saveState.listeners.delete(listener);
|
|
5344
|
+
cleanupSaveState(client, sessionId, saveState);
|
|
5345
|
+
};
|
|
5346
|
+
},
|
|
5347
|
+
[client, saveState, sessionId]
|
|
5348
|
+
);
|
|
5349
|
+
const getSaving = useCallback9(() => saveState.saving, [saveState]);
|
|
5350
|
+
const saving = useSyncExternalStore2(
|
|
5351
|
+
subscribe,
|
|
5352
|
+
getSaving,
|
|
5353
|
+
getSaving
|
|
5354
|
+
);
|
|
5355
|
+
const [draftEnabled, setDraftEnabled] = useState19(enabled);
|
|
5356
|
+
const activeSessionIdRef = useRef15(sessionId);
|
|
5357
|
+
activeSessionIdRef.current = sessionId;
|
|
5358
|
+
useEffect15(() => {
|
|
5359
|
+
setDraftEnabled(enabled);
|
|
5360
|
+
}, [enabled, sessionId]);
|
|
5361
|
+
const update = useCallback9(
|
|
5362
|
+
(nextEnabled) => {
|
|
5363
|
+
const currentSaveState = getSaveState(client, sessionId);
|
|
5364
|
+
if (currentSaveState.saving) return;
|
|
5365
|
+
currentSaveState.saving = true;
|
|
5366
|
+
notify(currentSaveState);
|
|
5367
|
+
setDraftEnabled(nextEnabled);
|
|
5368
|
+
void client.sessions.updateSessionMemory(sessionId, nextEnabled).then(
|
|
5369
|
+
(updated) => {
|
|
5370
|
+
if (activeSessionIdRef.current === sessionId) {
|
|
5371
|
+
setDraftEnabled(updated.memory_enabled);
|
|
5372
|
+
}
|
|
5373
|
+
onSaved?.(sessionId, updated.memory_enabled);
|
|
5374
|
+
},
|
|
5375
|
+
(error) => {
|
|
5376
|
+
if (activeSessionIdRef.current === sessionId) setDraftEnabled(enabled);
|
|
5377
|
+
onError?.(error);
|
|
5378
|
+
}
|
|
5379
|
+
).finally(() => {
|
|
5380
|
+
currentSaveState.saving = false;
|
|
5381
|
+
notify(currentSaveState);
|
|
5382
|
+
cleanupSaveState(client, sessionId, currentSaveState);
|
|
5383
|
+
});
|
|
5384
|
+
},
|
|
5385
|
+
[client, enabled, onError, onSaved, sessionId]
|
|
5386
|
+
);
|
|
5387
|
+
return /* @__PURE__ */ jsxs21("label", { className: cn("flex items-center justify-between", className), children: [
|
|
5388
|
+
/* @__PURE__ */ jsx25("span", { className: labelClassName, children: label }),
|
|
5389
|
+
/* @__PURE__ */ jsx25(
|
|
5390
|
+
"input",
|
|
5391
|
+
{
|
|
5392
|
+
type: "checkbox",
|
|
5393
|
+
checked: draftEnabled,
|
|
5394
|
+
onChange: (event) => update(event.target.checked),
|
|
5395
|
+
disabled: disabled || saving,
|
|
5396
|
+
className: inputClassName
|
|
5397
|
+
}
|
|
5398
|
+
)
|
|
5399
|
+
] });
|
|
5400
|
+
}
|
|
5401
|
+
|
|
5402
|
+
// src/lib/agent-computer-command.ts
|
|
5403
|
+
var COMPUTER_LAUNCH_COMMAND_PATTERN = /(?:^|[\n;&|(]\s*)computer\s+launch(?:\s|$)/;
|
|
5404
|
+
function isAgentComputerCommand(command) {
|
|
5405
|
+
return COMPUTER_LAUNCH_COMMAND_PATTERN.test(command);
|
|
5406
|
+
}
|
|
5407
|
+
function isAgentComputerToolCall(argumentsJson) {
|
|
5408
|
+
if (!argumentsJson) return false;
|
|
5409
|
+
let command;
|
|
5410
|
+
try {
|
|
5411
|
+
const parsed = JSON.parse(argumentsJson);
|
|
5412
|
+
if (typeof parsed !== "object" || parsed === null) return false;
|
|
5413
|
+
command = parsed.command;
|
|
5414
|
+
} catch {
|
|
5415
|
+
return false;
|
|
5416
|
+
}
|
|
5417
|
+
return typeof command === "string" && isAgentComputerCommand(command);
|
|
5418
|
+
}
|
|
5419
|
+
var LAUNCH_SUCCESS_MARKER = "\u5DF2\u542F\u52A8 ";
|
|
5420
|
+
function resultContainsLaunchSuccessMarker(result, depth = 0) {
|
|
5421
|
+
if (depth > 2) return false;
|
|
5422
|
+
if (typeof result === "string") {
|
|
5423
|
+
if (result.includes(LAUNCH_SUCCESS_MARKER)) return true;
|
|
5424
|
+
try {
|
|
5425
|
+
return resultContainsLaunchSuccessMarker(JSON.parse(result), depth + 1);
|
|
5426
|
+
} catch {
|
|
5427
|
+
return false;
|
|
5428
|
+
}
|
|
5429
|
+
}
|
|
5430
|
+
if (typeof result === "object" && result !== null) {
|
|
5431
|
+
for (const value of Object.values(result)) {
|
|
5432
|
+
if (typeof value === "string" && value.includes(LAUNCH_SUCCESS_MARKER)) return true;
|
|
5433
|
+
}
|
|
5434
|
+
}
|
|
5435
|
+
return false;
|
|
5436
|
+
}
|
|
5437
|
+
function classifyAgentComputerLaunchOutcome(toolCall) {
|
|
5438
|
+
if (toolCall.status === "error" || toolCall.status === "cancelled") return "failed";
|
|
5439
|
+
if (toolCall.status !== "done") return "pending";
|
|
5440
|
+
if (toolCall.result === void 0 || toolCall.result === null) return "unknown";
|
|
5441
|
+
return resultContainsLaunchSuccessMarker(toolCall.result) ? "succeeded" : "failed";
|
|
3616
5442
|
}
|
|
3617
5443
|
|
|
3618
5444
|
// src/index.ts
|
|
@@ -3621,13 +5447,33 @@ export {
|
|
|
3621
5447
|
AgentChat,
|
|
3622
5448
|
BladeProvider,
|
|
3623
5449
|
ChatView,
|
|
5450
|
+
ContextCard,
|
|
5451
|
+
ContextGroupCard,
|
|
5452
|
+
CurrentPlanPanel,
|
|
3624
5453
|
LlmChat,
|
|
3625
5454
|
MarkdownContent,
|
|
5455
|
+
MemoryRefsHint,
|
|
5456
|
+
PLAN_AUTO_COLLAPSE_MS,
|
|
5457
|
+
PlanUpdateBlock,
|
|
3626
5458
|
ReplayBar,
|
|
3627
5459
|
ReplayMismatchPrompt,
|
|
5460
|
+
SessionMemoryToggle,
|
|
5461
|
+
SessionPluginSelector,
|
|
5462
|
+
WhatIfUserBubble,
|
|
5463
|
+
classifyAgentComputerLaunchOutcome,
|
|
5464
|
+
collectMemoryRefs,
|
|
5465
|
+
getPlanUpdateDisplayState,
|
|
5466
|
+
isAgentComputerCommand,
|
|
5467
|
+
isAgentComputerToolCall,
|
|
5468
|
+
isPlanUpdateTool,
|
|
5469
|
+
normalizeAdjacentUrlFormatting,
|
|
5470
|
+
parsePlanUpdate,
|
|
5471
|
+
parseWhatIfPrompt,
|
|
5472
|
+
pickCurrentPlanStep,
|
|
3628
5473
|
useAgentSession,
|
|
3629
5474
|
useBladeClient,
|
|
3630
5475
|
useLlmChat,
|
|
5476
|
+
useMessagePin,
|
|
3631
5477
|
useReplay
|
|
3632
5478
|
};
|
|
3633
5479
|
/*! Bundled license information:
|
|
@@ -3639,29 +5485,35 @@ lucide-react/dist/esm/createLucideIcon.js:
|
|
|
3639
5485
|
lucide-react/dist/esm/icons/arrow-right.js:
|
|
3640
5486
|
lucide-react/dist/esm/icons/arrow-up-right.js:
|
|
3641
5487
|
lucide-react/dist/esm/icons/arrow-up.js:
|
|
5488
|
+
lucide-react/dist/esm/icons/book-open.js:
|
|
3642
5489
|
lucide-react/dist/esm/icons/bot.js:
|
|
3643
|
-
lucide-react/dist/esm/icons/brain.js:
|
|
3644
5490
|
lucide-react/dist/esm/icons/check.js:
|
|
3645
5491
|
lucide-react/dist/esm/icons/chevron-down.js:
|
|
3646
5492
|
lucide-react/dist/esm/icons/chevron-right.js:
|
|
3647
5493
|
lucide-react/dist/esm/icons/circle-alert.js:
|
|
5494
|
+
lucide-react/dist/esm/icons/circle-dot.js:
|
|
5495
|
+
lucide-react/dist/esm/icons/circle.js:
|
|
3648
5496
|
lucide-react/dist/esm/icons/copy.js:
|
|
3649
|
-
lucide-react/dist/esm/icons/
|
|
5497
|
+
lucide-react/dist/esm/icons/earth.js:
|
|
5498
|
+
lucide-react/dist/esm/icons/file-pen-line.js:
|
|
3650
5499
|
lucide-react/dist/esm/icons/file-text.js:
|
|
3651
|
-
lucide-react/dist/esm/icons/file.js:
|
|
3652
|
-
lucide-react/dist/esm/icons/film.js:
|
|
3653
5500
|
lucide-react/dist/esm/icons/globe.js:
|
|
3654
5501
|
lucide-react/dist/esm/icons/layers.js:
|
|
3655
5502
|
lucide-react/dist/esm/icons/lightbulb.js:
|
|
5503
|
+
lucide-react/dist/esm/icons/list-checks.js:
|
|
3656
5504
|
lucide-react/dist/esm/icons/loader-circle.js:
|
|
3657
5505
|
lucide-react/dist/esm/icons/lock-keyhole.js:
|
|
3658
5506
|
lucide-react/dist/esm/icons/message-square-more.js:
|
|
3659
5507
|
lucide-react/dist/esm/icons/message-square.js:
|
|
3660
5508
|
lucide-react/dist/esm/icons/play.js:
|
|
5509
|
+
lucide-react/dist/esm/icons/refresh-ccw.js:
|
|
5510
|
+
lucide-react/dist/esm/icons/search.js:
|
|
3661
5511
|
lucide-react/dist/esm/icons/settings-2.js:
|
|
3662
5512
|
lucide-react/dist/esm/icons/sparkles.js:
|
|
3663
5513
|
lucide-react/dist/esm/icons/square.js:
|
|
5514
|
+
lucide-react/dist/esm/icons/terminal.js:
|
|
3664
5515
|
lucide-react/dist/esm/icons/triangle-alert.js:
|
|
5516
|
+
lucide-react/dist/esm/icons/wrench.js:
|
|
3665
5517
|
lucide-react/dist/esm/icons/x.js:
|
|
3666
5518
|
lucide-react/dist/esm/lucide-react.js:
|
|
3667
5519
|
(**
|