@blade-hq/agent-react 2610.0.0-beta.3 → 2610.0.0-beta.31
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 +54 -2
- package/dist/components/AgentLoopBlock.d.ts +1 -4
- package/dist/components/AskUserQuestionBlock.d.ts +9 -0
- package/dist/components/AssistantTurnBlock.d.ts +17 -5
- 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/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 +8 -0
- package/dist/embed/entry.d.ts +14 -0
- package/dist/hooks/use-message-pin.d.ts +22 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +1624 -452
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-computer-command.d.ts +34 -0
- package/dist/lib/whatif-prompt.d.ts +18 -0
- package/dist/style.css +88 -1
- package/dist/style.full.css +89 -2
- package/package.json +2 -2
- package/public-api.md +340 -5
package/dist/index.js
CHANGED
|
@@ -245,6 +245,8 @@ function useLlmChat(options) {
|
|
|
245
245
|
const [error, setError] = useState3(null);
|
|
246
246
|
const [isStreaming, setIsStreaming] = useState3(false);
|
|
247
247
|
const abortRef = useRef2(null);
|
|
248
|
+
const activeStartedAtRef = useRef2(null);
|
|
249
|
+
const assistantTimingsRef = useRef2(/* @__PURE__ */ new WeakMap());
|
|
248
250
|
const generationRef = useRef2(0);
|
|
249
251
|
const historyRef = useRef2([]);
|
|
250
252
|
const optionsRef = useRef2(options);
|
|
@@ -263,16 +265,31 @@ function useLlmChat(options) {
|
|
|
263
265
|
setFailedToolIds([]);
|
|
264
266
|
setError(null);
|
|
265
267
|
setIsStreaming(false);
|
|
268
|
+
activeStartedAtRef.current = null;
|
|
269
|
+
assistantTimingsRef.current = /* @__PURE__ */ new WeakMap();
|
|
266
270
|
}, [stop]);
|
|
267
271
|
const send = useCallback2(async (text) => {
|
|
268
272
|
const content = text.trim();
|
|
269
273
|
if (!content || abortRef.current) return false;
|
|
270
274
|
const opts = optionsRef.current;
|
|
271
275
|
const maxRounds = opts.maxToolRounds ?? DEFAULT_MAX_TOOL_ROUNDS;
|
|
276
|
+
const startedAt = Date.now();
|
|
277
|
+
activeStartedAtRef.current = startedAt;
|
|
272
278
|
const commit = (message) => {
|
|
273
279
|
historyRef.current = [...historyRef.current, message];
|
|
274
280
|
setHistory(historyRef.current);
|
|
275
281
|
};
|
|
282
|
+
const latestAssistantTiming = { current: null };
|
|
283
|
+
const commitAssistant = (message) => {
|
|
284
|
+
const timing = { startedAt };
|
|
285
|
+
assistantTimingsRef.current.set(message, timing);
|
|
286
|
+
latestAssistantTiming.current = timing;
|
|
287
|
+
commit(message);
|
|
288
|
+
return timing;
|
|
289
|
+
};
|
|
290
|
+
const finishTiming = (timing) => {
|
|
291
|
+
timing.durationMs = Math.max(0, Date.now() - timing.startedAt);
|
|
292
|
+
};
|
|
276
293
|
setError(null);
|
|
277
294
|
setIsStreaming(true);
|
|
278
295
|
setStreamingText("");
|
|
@@ -298,8 +315,11 @@ function useLlmChat(options) {
|
|
|
298
315
|
};
|
|
299
316
|
setStreamingText(null);
|
|
300
317
|
setStreamingCalls([]);
|
|
301
|
-
|
|
302
|
-
if (!result.toolCalls.length)
|
|
318
|
+
const assistantTiming = commitAssistant(assistant);
|
|
319
|
+
if (!result.toolCalls.length) {
|
|
320
|
+
finishTiming(assistantTiming);
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
303
323
|
const bail = (reason) => {
|
|
304
324
|
for (const call of result.toolCalls) {
|
|
305
325
|
commit({ role: "tool", tool_call_id: call.id, content: JSON.stringify({ error: reason }) });
|
|
@@ -308,11 +328,13 @@ function useLlmChat(options) {
|
|
|
308
328
|
};
|
|
309
329
|
if (!opts.onToolCall) {
|
|
310
330
|
bail("\u8FD9\u4E2A\u5E94\u7528\u6CA1\u6709\u63D0\u4F9B\u5DE5\u5177\u6267\u884C\u5165\u53E3");
|
|
331
|
+
finishTiming(assistantTiming);
|
|
311
332
|
return true;
|
|
312
333
|
}
|
|
313
334
|
if (round >= maxRounds) {
|
|
314
335
|
bail(`\u5DE5\u5177\u8C03\u7528\u5DF2\u8FBE\u4E0A\u9650 ${maxRounds} \u8F6E\uFF0C\u6CA1\u6709\u6267\u884C`);
|
|
315
336
|
setError(`\u5DE5\u5177\u8C03\u7528\u8D85\u8FC7 ${maxRounds} \u8F6E\u4ECD\u672A\u7ED9\u51FA\u7ED3\u8BBA\uFF0C\u5DF2\u505C\u4E0B\u3002`);
|
|
337
|
+
finishTiming(assistantTiming);
|
|
316
338
|
return false;
|
|
317
339
|
}
|
|
318
340
|
for (const call of result.toolCalls) {
|
|
@@ -337,21 +359,39 @@ function useLlmChat(options) {
|
|
|
337
359
|
setStreamingCalls([]);
|
|
338
360
|
if (controller.signal.aborted) {
|
|
339
361
|
if (generation !== generationRef.current) return false;
|
|
340
|
-
|
|
362
|
+
const timing = commitAssistant({
|
|
363
|
+
role: "assistant",
|
|
364
|
+
content: partial ? `${partial}\uFF08\u5DF2\u505C\u6B62\uFF09` : "\uFF08\u5DF2\u505C\u6B62\uFF09"
|
|
365
|
+
});
|
|
366
|
+
timing.status = "interrupted";
|
|
367
|
+
finishTiming(timing);
|
|
341
368
|
return false;
|
|
342
369
|
}
|
|
343
370
|
if (partial && generation === generationRef.current) {
|
|
344
|
-
|
|
371
|
+
const timing = commitAssistant({ role: "assistant", content: partial });
|
|
372
|
+
timing.status = "failed";
|
|
373
|
+
finishTiming(timing);
|
|
374
|
+
} else if (latestAssistantTiming.current) {
|
|
375
|
+
latestAssistantTiming.current.status = "failed";
|
|
376
|
+
finishTiming(latestAssistantTiming.current);
|
|
345
377
|
}
|
|
346
378
|
setError(err instanceof Error && err.message ? err.message : "\u5BF9\u8BDD\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5");
|
|
347
379
|
return false;
|
|
348
380
|
} finally {
|
|
349
381
|
if (abortRef.current === controller) abortRef.current = null;
|
|
382
|
+
activeStartedAtRef.current = null;
|
|
350
383
|
setIsStreaming(false);
|
|
351
384
|
}
|
|
352
385
|
}, []);
|
|
353
386
|
const messages = useMemo2(
|
|
354
|
-
() => toChatMessages(
|
|
387
|
+
() => toChatMessages(
|
|
388
|
+
history,
|
|
389
|
+
streamingText,
|
|
390
|
+
streamingCalls,
|
|
391
|
+
failedToolIds,
|
|
392
|
+
assistantTimingsRef.current,
|
|
393
|
+
activeStartedAtRef.current
|
|
394
|
+
),
|
|
355
395
|
[history, streamingText, streamingCalls, failedToolIds]
|
|
356
396
|
);
|
|
357
397
|
return { messages, isStreaming, error, send, stop, reset };
|
|
@@ -503,7 +543,7 @@ function parseSse(raw) {
|
|
|
503
543
|
if (!delta) return null;
|
|
504
544
|
return { text: delta.content, toolCallDeltas: delta.tool_calls };
|
|
505
545
|
}
|
|
506
|
-
function toChatMessages(history, streamingText, streamingCalls, failedToolIds) {
|
|
546
|
+
function toChatMessages(history, streamingText, streamingCalls, failedToolIds, assistantTimings, activeStartedAt) {
|
|
507
547
|
const results = /* @__PURE__ */ new Map();
|
|
508
548
|
for (const msg of history) {
|
|
509
549
|
if (msg.role === "tool") results.set(msg.tool_call_id, msg.content);
|
|
@@ -515,10 +555,13 @@ function toChatMessages(history, streamingText, streamingCalls, failedToolIds) {
|
|
|
515
555
|
messages.push({ role: "user", content: msg.content, status: "completed" });
|
|
516
556
|
continue;
|
|
517
557
|
}
|
|
558
|
+
const timing = assistantTimings.get(msg);
|
|
518
559
|
messages.push({
|
|
519
560
|
role: "assistant",
|
|
520
561
|
content: msg.content,
|
|
521
|
-
status: "completed",
|
|
562
|
+
status: timing?.status ?? "completed",
|
|
563
|
+
...timing ? { timestamp: new Date(timing.startedAt).toISOString() } : {},
|
|
564
|
+
...timing?.durationMs === void 0 ? {} : { duration_ms: timing.durationMs },
|
|
522
565
|
...msg.tool_calls?.length ? { tool_calls: msg.tool_calls.map((call) => toToolCallInfo(call, results, failedToolIds)) } : {}
|
|
523
566
|
});
|
|
524
567
|
}
|
|
@@ -527,6 +570,7 @@ function toChatMessages(history, streamingText, streamingCalls, failedToolIds) {
|
|
|
527
570
|
role: "assistant",
|
|
528
571
|
content: streamingText,
|
|
529
572
|
status: "streaming",
|
|
573
|
+
...activeStartedAt === null ? {} : { timestamp: new Date(activeStartedAt).toISOString() },
|
|
530
574
|
...streamingCalls.length ? { tool_calls: streamingCalls.map((call) => toToolCallInfo(call, results, failedToolIds)) } : {}
|
|
531
575
|
});
|
|
532
576
|
}
|
|
@@ -544,6 +588,184 @@ function toToolCallInfo(call, results, failedToolIds) {
|
|
|
544
588
|
};
|
|
545
589
|
}
|
|
546
590
|
|
|
591
|
+
// src/hooks/use-message-pin.ts
|
|
592
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
593
|
+
var DEFAULT_MARGIN_PX = 24;
|
|
594
|
+
var MANUAL_SCROLL_TOLERANCE_PX = 2;
|
|
595
|
+
function useMessagePin({
|
|
596
|
+
targetKey,
|
|
597
|
+
pinTarget,
|
|
598
|
+
layoutKey,
|
|
599
|
+
getScrollElement,
|
|
600
|
+
getContentElement,
|
|
601
|
+
getTargetElement,
|
|
602
|
+
getTargetScrollTop,
|
|
603
|
+
getSpacerHeight,
|
|
604
|
+
setSpacerHeight,
|
|
605
|
+
stopAutoScroll,
|
|
606
|
+
scrollToBottom,
|
|
607
|
+
margin = DEFAULT_MARGIN_PX
|
|
608
|
+
}) {
|
|
609
|
+
const pinActiveRef = useRef3(false);
|
|
610
|
+
const frameRef = useRef3(null);
|
|
611
|
+
const retryTimeoutRef = useRef3(null);
|
|
612
|
+
const manualScrollCheckTimeoutRef = useRef3(null);
|
|
613
|
+
const repositionPendingRef = useRef3(false);
|
|
614
|
+
const pinnedTargetScrollTopRef = useRef3(null);
|
|
615
|
+
const appliedTargetKeyRef = useRef3(null);
|
|
616
|
+
const initialObservedTargetKeyRef = useRef3(pinTarget ? null : targetKey);
|
|
617
|
+
const observedTargetKeyRef = useRef3(initialObservedTargetKeyRef.current);
|
|
618
|
+
const setSpacerHeightRef = useRef3(setSpacerHeight);
|
|
619
|
+
setSpacerHeightRef.current = setSpacerHeight;
|
|
620
|
+
const release = useCallback3(() => {
|
|
621
|
+
if (!pinActiveRef.current) return;
|
|
622
|
+
pinActiveRef.current = false;
|
|
623
|
+
repositionPendingRef.current = false;
|
|
624
|
+
pinnedTargetScrollTopRef.current = null;
|
|
625
|
+
if (frameRef.current != null) cancelAnimationFrame(frameRef.current);
|
|
626
|
+
frameRef.current = null;
|
|
627
|
+
if (manualScrollCheckTimeoutRef.current != null) {
|
|
628
|
+
clearTimeout(manualScrollCheckTimeoutRef.current);
|
|
629
|
+
manualScrollCheckTimeoutRef.current = null;
|
|
630
|
+
}
|
|
631
|
+
setSpacerHeightRef.current(0);
|
|
632
|
+
}, []);
|
|
633
|
+
const reposition = useCallback3(
|
|
634
|
+
() => {
|
|
635
|
+
const scroll = getScrollElement();
|
|
636
|
+
const target = getTargetElement();
|
|
637
|
+
if (!scroll || !pinActiveRef.current) return;
|
|
638
|
+
const targetScrollTop = target ? Math.max(
|
|
639
|
+
0,
|
|
640
|
+
scroll.scrollTop + target.getBoundingClientRect().top - scroll.getBoundingClientRect().top - margin
|
|
641
|
+
) : getTargetScrollTop?.(scroll);
|
|
642
|
+
if (targetScrollTop == null) return;
|
|
643
|
+
const previousSpacerHeight = getSpacerHeight();
|
|
644
|
+
const baseScrollHeight = scroll.scrollHeight - previousSpacerHeight;
|
|
645
|
+
const nextSpacerHeight = Math.max(
|
|
646
|
+
0,
|
|
647
|
+
targetScrollTop + scroll.clientHeight - baseScrollHeight
|
|
648
|
+
);
|
|
649
|
+
setSpacerHeightRef.current(nextSpacerHeight);
|
|
650
|
+
pinnedTargetScrollTopRef.current = targetScrollTop;
|
|
651
|
+
stopAutoScroll();
|
|
652
|
+
scroll.scrollTop = targetScrollTop;
|
|
653
|
+
if (nextSpacerHeight === 0 && previousSpacerHeight > 0) {
|
|
654
|
+
pinActiveRef.current = false;
|
|
655
|
+
scrollToBottom();
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
[
|
|
659
|
+
getScrollElement,
|
|
660
|
+
getSpacerHeight,
|
|
661
|
+
getTargetElement,
|
|
662
|
+
getTargetScrollTop,
|
|
663
|
+
margin,
|
|
664
|
+
scrollToBottom,
|
|
665
|
+
stopAutoScroll
|
|
666
|
+
]
|
|
667
|
+
);
|
|
668
|
+
const scheduleReposition = useCallback3(
|
|
669
|
+
() => {
|
|
670
|
+
if (!pinActiveRef.current) return;
|
|
671
|
+
repositionPendingRef.current = true;
|
|
672
|
+
if (frameRef.current != null) return;
|
|
673
|
+
frameRef.current = requestAnimationFrame(() => {
|
|
674
|
+
frameRef.current = null;
|
|
675
|
+
const shouldReposition = repositionPendingRef.current;
|
|
676
|
+
repositionPendingRef.current = false;
|
|
677
|
+
if (shouldReposition) reposition();
|
|
678
|
+
});
|
|
679
|
+
},
|
|
680
|
+
[reposition]
|
|
681
|
+
);
|
|
682
|
+
useEffect3(() => {
|
|
683
|
+
if (!targetKey) {
|
|
684
|
+
release();
|
|
685
|
+
appliedTargetKeyRef.current = null;
|
|
686
|
+
observedTargetKeyRef.current = null;
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
if (!pinTarget) {
|
|
690
|
+
if (pinActiveRef.current && appliedTargetKeyRef.current != null && appliedTargetKeyRef.current !== targetKey) {
|
|
691
|
+
release();
|
|
692
|
+
}
|
|
693
|
+
observedTargetKeyRef.current = targetKey;
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (appliedTargetKeyRef.current === targetKey) {
|
|
697
|
+
observedTargetKeyRef.current = targetKey;
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
if (observedTargetKeyRef.current === targetKey) return;
|
|
701
|
+
appliedTargetKeyRef.current = targetKey;
|
|
702
|
+
observedTargetKeyRef.current = targetKey;
|
|
703
|
+
pinActiveRef.current = true;
|
|
704
|
+
stopAutoScroll();
|
|
705
|
+
scheduleReposition();
|
|
706
|
+
if (retryTimeoutRef.current != null) clearTimeout(retryTimeoutRef.current);
|
|
707
|
+
retryTimeoutRef.current = window.setTimeout(() => {
|
|
708
|
+
retryTimeoutRef.current = null;
|
|
709
|
+
scheduleReposition();
|
|
710
|
+
}, 80);
|
|
711
|
+
}, [pinTarget, release, scheduleReposition, stopAutoScroll, targetKey]);
|
|
712
|
+
useEffect3(() => {
|
|
713
|
+
if (layoutKey !== void 0) scheduleReposition();
|
|
714
|
+
}, [layoutKey, scheduleReposition]);
|
|
715
|
+
useEffect3(() => {
|
|
716
|
+
const scroll = getScrollElement();
|
|
717
|
+
if (!scroll) return;
|
|
718
|
+
const content = getContentElement?.();
|
|
719
|
+
const observer = new ResizeObserver(scheduleReposition);
|
|
720
|
+
observer.observe(scroll);
|
|
721
|
+
if (content && content !== scroll) observer.observe(content);
|
|
722
|
+
window.addEventListener("resize", scheduleReposition);
|
|
723
|
+
window.visualViewport?.addEventListener("resize", scheduleReposition);
|
|
724
|
+
const handleScroll = () => {
|
|
725
|
+
if (!pinActiveRef.current) return;
|
|
726
|
+
if (manualScrollCheckTimeoutRef.current != null) {
|
|
727
|
+
clearTimeout(manualScrollCheckTimeoutRef.current);
|
|
728
|
+
}
|
|
729
|
+
manualScrollCheckTimeoutRef.current = window.setTimeout(() => {
|
|
730
|
+
manualScrollCheckTimeoutRef.current = null;
|
|
731
|
+
if (!pinActiveRef.current || repositionPendingRef.current) return;
|
|
732
|
+
const targetScrollTop = pinnedTargetScrollTopRef.current;
|
|
733
|
+
if (targetScrollTop != null && Math.abs(scroll.scrollTop - targetScrollTop) > MANUAL_SCROLL_TOLERANCE_PX) {
|
|
734
|
+
release();
|
|
735
|
+
}
|
|
736
|
+
}, 0);
|
|
737
|
+
};
|
|
738
|
+
scroll.addEventListener("scroll", handleScroll, { passive: true });
|
|
739
|
+
return () => {
|
|
740
|
+
observer.disconnect();
|
|
741
|
+
window.removeEventListener("resize", scheduleReposition);
|
|
742
|
+
window.visualViewport?.removeEventListener("resize", scheduleReposition);
|
|
743
|
+
scroll.removeEventListener("scroll", handleScroll);
|
|
744
|
+
};
|
|
745
|
+
}, [getContentElement, getScrollElement, release, scheduleReposition]);
|
|
746
|
+
useEffect3(
|
|
747
|
+
() => () => {
|
|
748
|
+
if (frameRef.current != null) cancelAnimationFrame(frameRef.current);
|
|
749
|
+
frameRef.current = null;
|
|
750
|
+
if (retryTimeoutRef.current != null) clearTimeout(retryTimeoutRef.current);
|
|
751
|
+
retryTimeoutRef.current = null;
|
|
752
|
+
if (manualScrollCheckTimeoutRef.current != null) {
|
|
753
|
+
clearTimeout(manualScrollCheckTimeoutRef.current);
|
|
754
|
+
}
|
|
755
|
+
manualScrollCheckTimeoutRef.current = null;
|
|
756
|
+
repositionPendingRef.current = false;
|
|
757
|
+
pinnedTargetScrollTopRef.current = null;
|
|
758
|
+
appliedTargetKeyRef.current = null;
|
|
759
|
+
observedTargetKeyRef.current = initialObservedTargetKeyRef.current;
|
|
760
|
+
pinActiveRef.current = false;
|
|
761
|
+
setSpacerHeightRef.current(0);
|
|
762
|
+
},
|
|
763
|
+
[]
|
|
764
|
+
);
|
|
765
|
+
const isActive = useCallback3(() => pinActiveRef.current, []);
|
|
766
|
+
return { release, isActive };
|
|
767
|
+
}
|
|
768
|
+
|
|
547
769
|
// src/components/AgentChat.tsx
|
|
548
770
|
import { BladeApiError, latestPostChatFollowup } from "@blade-hq/agent-client";
|
|
549
771
|
|
|
@@ -636,6 +858,18 @@ var ArrowUp = createLucideIcon("ArrowUp", [
|
|
|
636
858
|
["path", { d: "M12 19V5", key: "x0mq9r" }]
|
|
637
859
|
]);
|
|
638
860
|
|
|
861
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/book-open.js
|
|
862
|
+
var BookOpen = createLucideIcon("BookOpen", [
|
|
863
|
+
["path", { d: "M12 7v14", key: "1akyts" }],
|
|
864
|
+
[
|
|
865
|
+
"path",
|
|
866
|
+
{
|
|
867
|
+
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",
|
|
868
|
+
key: "ruj8y"
|
|
869
|
+
}
|
|
870
|
+
]
|
|
871
|
+
]);
|
|
872
|
+
|
|
639
873
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/bot.js
|
|
640
874
|
var Bot = createLucideIcon("Bot", [
|
|
641
875
|
["path", { d: "M12 8V4H8", key: "hb8ula" }],
|
|
@@ -646,31 +880,6 @@ var Bot = createLucideIcon("Bot", [
|
|
|
646
880
|
["path", { d: "M9 13v2", key: "rq6x2g" }]
|
|
647
881
|
]);
|
|
648
882
|
|
|
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
883
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/check.js
|
|
675
884
|
var Check = createLucideIcon("Check", [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]]);
|
|
676
885
|
|
|
@@ -697,11 +906,37 @@ var Copy = createLucideIcon("Copy", [
|
|
|
697
906
|
["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
907
|
]);
|
|
699
908
|
|
|
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
|
-
|
|
909
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/earth.js
|
|
910
|
+
var Earth = createLucideIcon("Earth", [
|
|
911
|
+
["path", { d: "M21.54 15H17a2 2 0 0 0-2 2v4.54", key: "1djwo0" }],
|
|
912
|
+
[
|
|
913
|
+
"path",
|
|
914
|
+
{
|
|
915
|
+
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",
|
|
916
|
+
key: "1tzkfa"
|
|
917
|
+
}
|
|
918
|
+
],
|
|
919
|
+
["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" }],
|
|
920
|
+
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]
|
|
921
|
+
]);
|
|
922
|
+
|
|
923
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/file-pen-line.js
|
|
924
|
+
var FilePenLine = createLucideIcon("FilePenLine", [
|
|
925
|
+
[
|
|
926
|
+
"path",
|
|
927
|
+
{
|
|
928
|
+
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",
|
|
929
|
+
key: "142zxg"
|
|
930
|
+
}
|
|
931
|
+
],
|
|
932
|
+
[
|
|
933
|
+
"path",
|
|
934
|
+
{
|
|
935
|
+
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",
|
|
936
|
+
key: "2t3380"
|
|
937
|
+
}
|
|
938
|
+
],
|
|
939
|
+
["path", { d: "M8 18h1", key: "13wk12" }]
|
|
705
940
|
]);
|
|
706
941
|
|
|
707
942
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/file-text.js
|
|
@@ -713,24 +948,6 @@ var FileText = createLucideIcon("FileText", [
|
|
|
713
948
|
["path", { d: "M16 17H8", key: "z1uh3a" }]
|
|
714
949
|
]);
|
|
715
950
|
|
|
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
951
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/globe.js
|
|
735
952
|
var Globe = createLucideIcon("Globe", [
|
|
736
953
|
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
|
|
@@ -806,6 +1023,20 @@ var Play = createLucideIcon("Play", [
|
|
|
806
1023
|
["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]
|
|
807
1024
|
]);
|
|
808
1025
|
|
|
1026
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/refresh-ccw.js
|
|
1027
|
+
var RefreshCcw = createLucideIcon("RefreshCcw", [
|
|
1028
|
+
["path", { d: "M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8", key: "14sxne" }],
|
|
1029
|
+
["path", { d: "M3 3v5h5", key: "1xhq8a" }],
|
|
1030
|
+
["path", { d: "M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16", key: "1hlbsb" }],
|
|
1031
|
+
["path", { d: "M16 16h5v5", key: "ccwih5" }]
|
|
1032
|
+
]);
|
|
1033
|
+
|
|
1034
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/search.js
|
|
1035
|
+
var Search = createLucideIcon("Search", [
|
|
1036
|
+
["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }],
|
|
1037
|
+
["path", { d: "m21 21-4.3-4.3", key: "1qie3q" }]
|
|
1038
|
+
]);
|
|
1039
|
+
|
|
809
1040
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/settings-2.js
|
|
810
1041
|
var Settings2 = createLucideIcon("Settings2", [
|
|
811
1042
|
["path", { d: "M20 7h-9", key: "3s1dr2" }],
|
|
@@ -834,6 +1065,12 @@ var Square = createLucideIcon("Square", [
|
|
|
834
1065
|
["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }]
|
|
835
1066
|
]);
|
|
836
1067
|
|
|
1068
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/terminal.js
|
|
1069
|
+
var Terminal = createLucideIcon("Terminal", [
|
|
1070
|
+
["polyline", { points: "4 17 10 11 4 5", key: "akl6gq" }],
|
|
1071
|
+
["line", { x1: "12", x2: "20", y1: "19", y2: "19", key: "q2wloq" }]
|
|
1072
|
+
]);
|
|
1073
|
+
|
|
837
1074
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/triangle-alert.js
|
|
838
1075
|
var TriangleAlert = createLucideIcon("TriangleAlert", [
|
|
839
1076
|
[
|
|
@@ -847,6 +1084,17 @@ var TriangleAlert = createLucideIcon("TriangleAlert", [
|
|
|
847
1084
|
["path", { d: "M12 17h.01", key: "p32p05" }]
|
|
848
1085
|
]);
|
|
849
1086
|
|
|
1087
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/wrench.js
|
|
1088
|
+
var Wrench = createLucideIcon("Wrench", [
|
|
1089
|
+
[
|
|
1090
|
+
"path",
|
|
1091
|
+
{
|
|
1092
|
+
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",
|
|
1093
|
+
key: "cbrjhi"
|
|
1094
|
+
}
|
|
1095
|
+
]
|
|
1096
|
+
]);
|
|
1097
|
+
|
|
850
1098
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/x.js
|
|
851
1099
|
var X = createLucideIcon("X", [
|
|
852
1100
|
["path", { d: "M18 6 6 18", key: "1bl5f8" }],
|
|
@@ -854,7 +1102,7 @@ var X = createLucideIcon("X", [
|
|
|
854
1102
|
]);
|
|
855
1103
|
|
|
856
1104
|
// src/components/AgentChat.tsx
|
|
857
|
-
import { useCallback as
|
|
1105
|
+
import { useCallback as useCallback8, useEffect as useEffect11, useMemo as useMemo8, useState as useState13 } from "react";
|
|
858
1106
|
|
|
859
1107
|
// src/lib/utils.ts
|
|
860
1108
|
function cn(...inputs) {
|
|
@@ -981,8 +1229,17 @@ function ReplayMismatchPrompt({ mismatch, className }) {
|
|
|
981
1229
|
);
|
|
982
1230
|
}
|
|
983
1231
|
|
|
1232
|
+
// src/components/ChatSurface.tsx
|
|
1233
|
+
import { chatErrorForDisplay as chatErrorForDisplay2 } from "@blade-hq/agent-client";
|
|
1234
|
+
|
|
984
1235
|
// src/components/ChatInput.tsx
|
|
985
1236
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1237
|
+
function isImeCompositionKey(event) {
|
|
1238
|
+
return event.isComposing || event.keyCode === 229;
|
|
1239
|
+
}
|
|
1240
|
+
function shouldSubmitChatInput(event, menuOpen) {
|
|
1241
|
+
return event.key === "Enter" && !event.shiftKey && !menuOpen && !isImeCompositionKey(event);
|
|
1242
|
+
}
|
|
986
1243
|
function ChatInput({
|
|
987
1244
|
value,
|
|
988
1245
|
onValueChange,
|
|
@@ -1002,7 +1259,12 @@ function ChatInput({
|
|
|
1002
1259
|
onValueChange("");
|
|
1003
1260
|
};
|
|
1004
1261
|
const handleKeyDown = (event) => {
|
|
1005
|
-
if (
|
|
1262
|
+
if (shouldSubmitChatInput({
|
|
1263
|
+
key: event.key,
|
|
1264
|
+
shiftKey: event.shiftKey,
|
|
1265
|
+
isComposing: event.nativeEvent.isComposing,
|
|
1266
|
+
keyCode: event.nativeEvent.keyCode
|
|
1267
|
+
}, false)) {
|
|
1006
1268
|
event.preventDefault();
|
|
1007
1269
|
void handleSend();
|
|
1008
1270
|
}
|
|
@@ -1052,24 +1314,74 @@ function ChatInput({
|
|
|
1052
1314
|
}
|
|
1053
1315
|
|
|
1054
1316
|
// src/components/ConnectionBanner.tsx
|
|
1317
|
+
import { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
|
|
1055
1318
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1319
|
+
var CONNECTION_NOTICE_DELAY_MS = 3e3;
|
|
1320
|
+
var CONNECTION_ERROR_DELAY_MS = 15e3;
|
|
1321
|
+
function useConnectionNoticePhase(connected) {
|
|
1322
|
+
const [phase, setPhase] = useState4("hidden");
|
|
1323
|
+
const connectedRef = useRef4(connected);
|
|
1324
|
+
const timersRef = useRef4([]);
|
|
1325
|
+
connectedRef.current = connected;
|
|
1326
|
+
useEffect4(() => {
|
|
1327
|
+
const clearTimers = () => {
|
|
1328
|
+
for (const timer of timersRef.current) clearTimeout(timer);
|
|
1329
|
+
timersRef.current = [];
|
|
1330
|
+
};
|
|
1331
|
+
const startGracePeriod = () => {
|
|
1332
|
+
clearTimers();
|
|
1333
|
+
setPhase("hidden");
|
|
1334
|
+
timersRef.current = [
|
|
1335
|
+
setTimeout(() => setPhase("recovering"), CONNECTION_NOTICE_DELAY_MS),
|
|
1336
|
+
setTimeout(() => setPhase("failed"), CONNECTION_ERROR_DELAY_MS)
|
|
1337
|
+
];
|
|
1338
|
+
};
|
|
1339
|
+
if (connected) {
|
|
1340
|
+
clearTimers();
|
|
1341
|
+
setPhase("hidden");
|
|
1342
|
+
} else {
|
|
1343
|
+
startGracePeriod();
|
|
1344
|
+
}
|
|
1345
|
+
const handleForeground = () => {
|
|
1346
|
+
if (!connectedRef.current) startGracePeriod();
|
|
1347
|
+
};
|
|
1348
|
+
const handleVisibilityChange = () => {
|
|
1349
|
+
if (document.visibilityState === "visible") handleForeground();
|
|
1350
|
+
};
|
|
1351
|
+
window.addEventListener("blade:app-active", handleForeground);
|
|
1352
|
+
window.addEventListener("focus", handleForeground);
|
|
1353
|
+
window.addEventListener("pageshow", handleForeground);
|
|
1354
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
1355
|
+
return () => {
|
|
1356
|
+
clearTimers();
|
|
1357
|
+
window.removeEventListener("blade:app-active", handleForeground);
|
|
1358
|
+
window.removeEventListener("focus", handleForeground);
|
|
1359
|
+
window.removeEventListener("pageshow", handleForeground);
|
|
1360
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
1361
|
+
};
|
|
1362
|
+
}, [connected]);
|
|
1363
|
+
return phase;
|
|
1364
|
+
}
|
|
1056
1365
|
function ConnectionBanner({ connection, className }) {
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
const
|
|
1366
|
+
const hasConnectedRef = useRef4(connection === "connected" || connection === "reconnecting");
|
|
1367
|
+
if (connection === "connected") hasConnectedRef.current = true;
|
|
1368
|
+
const connected = connection === "connected";
|
|
1369
|
+
const phase = useConnectionNoticePhase(connected);
|
|
1370
|
+
if (connected || phase === "hidden") return null;
|
|
1371
|
+
const recovering = phase === "recovering";
|
|
1372
|
+
const firstConnection = !hasConnectedRef.current;
|
|
1061
1373
|
return /* @__PURE__ */ jsx5("div", { className: cn("blade-chat-banner bg-[hsl(var(--background))] px-5 pt-3", className), children: /* @__PURE__ */ jsxs4(
|
|
1062
1374
|
"div",
|
|
1063
1375
|
{
|
|
1064
1376
|
className: cn(
|
|
1065
1377
|
"mx-auto flex max-w-3xl items-start gap-3 rounded-2xl border px-4 py-3",
|
|
1066
|
-
|
|
1378
|
+
recovering ? "border-amber-500/25 bg-amber-500/10 text-amber-100" : "border-rose-500/25 bg-rose-500/10 text-rose-100"
|
|
1067
1379
|
),
|
|
1068
1380
|
children: [
|
|
1069
|
-
/* @__PURE__ */ jsx5("span", { className: "mt-0.5 shrink-0", children:
|
|
1381
|
+
/* @__PURE__ */ jsx5("span", { className: "mt-0.5 shrink-0", children: recovering ? /* @__PURE__ */ jsx5(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx5(TriangleAlert, { size: 14 }) }),
|
|
1070
1382
|
/* @__PURE__ */ jsxs4("div", { className: "min-w-0", children: [
|
|
1071
|
-
/* @__PURE__ */ jsx5("div", { className: "text-sm font-medium", children:
|
|
1072
|
-
/* @__PURE__ */ jsx5("div", { className: "text-xs opacity-80", children: "\
|
|
1383
|
+
/* @__PURE__ */ jsx5("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" }),
|
|
1384
|
+
/* @__PURE__ */ jsx5("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" })
|
|
1073
1385
|
] })
|
|
1074
1386
|
]
|
|
1075
1387
|
}
|
|
@@ -1078,10 +1390,10 @@ function ConnectionBanner({ connection, className }) {
|
|
|
1078
1390
|
|
|
1079
1391
|
// src/components/MessageList.tsx
|
|
1080
1392
|
import { isHiddenInternalMessage } from "@blade-hq/agent-client";
|
|
1081
|
-
import { useCallback as
|
|
1393
|
+
import { useCallback as useCallback7, useEffect as useEffect10, useMemo as useMemo7, useRef as useRef11, useState as useState12 } from "react";
|
|
1082
1394
|
|
|
1083
1395
|
// ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/useStickToBottom.js
|
|
1084
|
-
import { useCallback as
|
|
1396
|
+
import { useCallback as useCallback4, useMemo as useMemo3, useRef as useRef5, useState as useState5 } from "react";
|
|
1085
1397
|
var DEFAULT_SPRING_ANIMATION = {
|
|
1086
1398
|
/**
|
|
1087
1399
|
* A value from 0 to 1, on how much to damp the animation.
|
|
@@ -1118,12 +1430,12 @@ globalThis.document?.addEventListener("click", () => {
|
|
|
1118
1430
|
mouseDown = false;
|
|
1119
1431
|
});
|
|
1120
1432
|
var useStickToBottom = (options = {}) => {
|
|
1121
|
-
const [escapedFromLock, updateEscapedFromLock] =
|
|
1122
|
-
const [isAtBottom, updateIsAtBottom] =
|
|
1123
|
-
const [isNearBottom, setIsNearBottom] =
|
|
1124
|
-
const optionsRef =
|
|
1433
|
+
const [escapedFromLock, updateEscapedFromLock] = useState5(false);
|
|
1434
|
+
const [isAtBottom, updateIsAtBottom] = useState5(options.initial !== false);
|
|
1435
|
+
const [isNearBottom, setIsNearBottom] = useState5(false);
|
|
1436
|
+
const optionsRef = useRef5(null);
|
|
1125
1437
|
optionsRef.current = options;
|
|
1126
|
-
const isSelecting =
|
|
1438
|
+
const isSelecting = useCallback4(() => {
|
|
1127
1439
|
if (!mouseDown) {
|
|
1128
1440
|
return false;
|
|
1129
1441
|
}
|
|
@@ -1134,11 +1446,11 @@ var useStickToBottom = (options = {}) => {
|
|
|
1134
1446
|
const range = selection.getRangeAt(0);
|
|
1135
1447
|
return range.commonAncestorContainer.contains(scrollRef.current) || scrollRef.current?.contains(range.commonAncestorContainer);
|
|
1136
1448
|
}, []);
|
|
1137
|
-
const setIsAtBottom =
|
|
1449
|
+
const setIsAtBottom = useCallback4((isAtBottom2) => {
|
|
1138
1450
|
state.isAtBottom = isAtBottom2;
|
|
1139
1451
|
updateIsAtBottom(isAtBottom2);
|
|
1140
1452
|
}, []);
|
|
1141
|
-
const setEscapedFromLock =
|
|
1453
|
+
const setEscapedFromLock = useCallback4((escapedFromLock2) => {
|
|
1142
1454
|
state.escapedFromLock = escapedFromLock2;
|
|
1143
1455
|
updateEscapedFromLock(escapedFromLock2);
|
|
1144
1456
|
}, []);
|
|
@@ -1195,7 +1507,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
1195
1507
|
}
|
|
1196
1508
|
};
|
|
1197
1509
|
}, []);
|
|
1198
|
-
const scrollToBottom =
|
|
1510
|
+
const scrollToBottom = useCallback4((scrollOptions = {}) => {
|
|
1199
1511
|
if (typeof scrollOptions === "string") {
|
|
1200
1512
|
scrollOptions = { animation: scrollOptions };
|
|
1201
1513
|
}
|
|
@@ -1280,11 +1592,11 @@ var useStickToBottom = (options = {}) => {
|
|
|
1280
1592
|
}
|
|
1281
1593
|
return next();
|
|
1282
1594
|
}, [setIsAtBottom, isSelecting, state]);
|
|
1283
|
-
const stopScroll =
|
|
1595
|
+
const stopScroll = useCallback4(() => {
|
|
1284
1596
|
setEscapedFromLock(true);
|
|
1285
1597
|
setIsAtBottom(false);
|
|
1286
1598
|
}, [setEscapedFromLock, setIsAtBottom]);
|
|
1287
|
-
const handleScroll =
|
|
1599
|
+
const handleScroll = useCallback4(({ target }) => {
|
|
1288
1600
|
if (target !== scrollRef.current) {
|
|
1289
1601
|
return;
|
|
1290
1602
|
}
|
|
@@ -1323,7 +1635,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
1323
1635
|
}
|
|
1324
1636
|
}, 1);
|
|
1325
1637
|
}, [setEscapedFromLock, setIsAtBottom, isSelecting, state]);
|
|
1326
|
-
const handleWheel =
|
|
1638
|
+
const handleWheel = useCallback4(({ target, deltaY }) => {
|
|
1327
1639
|
let element = target;
|
|
1328
1640
|
while (!["scroll", "auto"].includes(getComputedStyle(element).overflow)) {
|
|
1329
1641
|
if (!element.parentElement) {
|
|
@@ -1393,7 +1705,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
1393
1705
|
};
|
|
1394
1706
|
};
|
|
1395
1707
|
function useRefCallback(callback, deps) {
|
|
1396
|
-
const result =
|
|
1708
|
+
const result = useCallback4((ref) => {
|
|
1397
1709
|
result.current = ref;
|
|
1398
1710
|
return callback(ref);
|
|
1399
1711
|
}, deps);
|
|
@@ -1425,11 +1737,11 @@ function mergeAnimations(...animations) {
|
|
|
1425
1737
|
|
|
1426
1738
|
// ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/StickToBottom.js
|
|
1427
1739
|
import * as React from "react";
|
|
1428
|
-
import { createContext as createContext2, useContext as useContext2, useEffect as
|
|
1740
|
+
import { createContext as createContext2, useContext as useContext2, useEffect as useEffect5, useImperativeHandle, useLayoutEffect, useMemo as useMemo4, useRef as useRef6 } from "react";
|
|
1429
1741
|
var StickToBottomContext = createContext2(null);
|
|
1430
|
-
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect :
|
|
1742
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect5;
|
|
1431
1743
|
function StickToBottom({ instance, children, resize, initial, mass, damping, stiffness, targetScrollTop: currentTargetScrollTop, contextRef, ...props }) {
|
|
1432
|
-
const customTargetScrollTop =
|
|
1744
|
+
const customTargetScrollTop = useRef6(null);
|
|
1433
1745
|
const targetScrollTop = React.useCallback((target, elements) => {
|
|
1434
1746
|
const get = context?.targetScrollTop ?? currentTargetScrollTop;
|
|
1435
1747
|
return get?.(target, elements) ?? target;
|
|
@@ -1505,11 +1817,16 @@ function useStickToBottomContext() {
|
|
|
1505
1817
|
}
|
|
1506
1818
|
|
|
1507
1819
|
// src/components/AssistantTurnBlock.tsx
|
|
1508
|
-
import {
|
|
1509
|
-
|
|
1820
|
+
import {
|
|
1821
|
+
getFileParts,
|
|
1822
|
+
getImageParts,
|
|
1823
|
+
getTextContent,
|
|
1824
|
+
normalizeMessageContent
|
|
1825
|
+
} from "@blade-hq/agent-client";
|
|
1826
|
+
import { useEffect as useEffect8, useRef as useRef9, useState as useState10 } from "react";
|
|
1510
1827
|
|
|
1511
1828
|
// src/components/AgentLoopBlock.tsx
|
|
1512
|
-
import { useState as
|
|
1829
|
+
import { useState as useState6 } from "react";
|
|
1513
1830
|
|
|
1514
1831
|
// src/components/display-utils.ts
|
|
1515
1832
|
var TOOL_NAME_ALIASES = {
|
|
@@ -1525,7 +1842,9 @@ var TOOL_NAME_ALIASES = {
|
|
|
1525
1842
|
finish_task: "FinishTask",
|
|
1526
1843
|
glob: "Glob",
|
|
1527
1844
|
grep: "Grep",
|
|
1845
|
+
kb_search: "KbSearch",
|
|
1528
1846
|
ls: "Ls",
|
|
1847
|
+
multi_edit: "MultiEdit",
|
|
1529
1848
|
read: "Read",
|
|
1530
1849
|
read_skill: "ReadSkill",
|
|
1531
1850
|
web_fetch: "WebFetch",
|
|
@@ -1538,9 +1857,11 @@ var TOOL_DISPLAY_LABELS = {
|
|
|
1538
1857
|
Read: "\u8BFB\u53D6\u6587\u4EF6",
|
|
1539
1858
|
Write: "\u5199\u5165\u6587\u4EF6",
|
|
1540
1859
|
Edit: "\u7F16\u8F91\u6587\u4EF6",
|
|
1860
|
+
MultiEdit: "\u7F16\u8F91\u6587\u4EF6",
|
|
1541
1861
|
Ls: "\u5217\u51FA\u76EE\u5F55",
|
|
1542
1862
|
Glob: "\u5339\u914D\u6587\u4EF6",
|
|
1543
1863
|
Grep: "\u641C\u7D22\u6587\u672C",
|
|
1864
|
+
KbSearch: "\u68C0\u7D22\u77E5\u8BC6\u5E93",
|
|
1544
1865
|
WebSearch: "\u641C\u7D22\u7F51\u9875",
|
|
1545
1866
|
WebFetch: "\u6574\u7406\u7F51\u9875\u5185\u5BB9",
|
|
1546
1867
|
Agent: "\u6D3E\u751F\u5B50\u667A\u80FD\u4F53",
|
|
@@ -1563,6 +1884,17 @@ function getStringArgValue(args, key) {
|
|
|
1563
1884
|
const value = args?.[key];
|
|
1564
1885
|
return typeof value === "string" ? value.trim() : "";
|
|
1565
1886
|
}
|
|
1887
|
+
var SKILL_ENTRY_FILE_NAMES = /* @__PURE__ */ new Set(["skill.md", "command.md"]);
|
|
1888
|
+
var NON_SKILL_DIR_NAMES = /* @__PURE__ */ new Set([".", "..", ".agent", ".agents", ".claude", "skill_data", "skills"]);
|
|
1889
|
+
function getSkillNameFromFilePath(filePath) {
|
|
1890
|
+
if (!filePath) return null;
|
|
1891
|
+
const segments = filePath.split(/[\\/]+/).filter(Boolean);
|
|
1892
|
+
const fileName = segments.pop();
|
|
1893
|
+
if (!fileName || !SKILL_ENTRY_FILE_NAMES.has(fileName.toLowerCase())) return null;
|
|
1894
|
+
const dirName = segments.pop();
|
|
1895
|
+
if (!dirName || NON_SKILL_DIR_NAMES.has(dirName.toLowerCase())) return null;
|
|
1896
|
+
return dirName;
|
|
1897
|
+
}
|
|
1566
1898
|
function formatToolName(name) {
|
|
1567
1899
|
const trimmed = name.trim();
|
|
1568
1900
|
if (!trimmed) return name;
|
|
@@ -1587,6 +1919,12 @@ function getToolDisplayLabel(toolCall) {
|
|
|
1587
1919
|
const skillName = getStringArgValue(args, "skill") || getStringArgValue(args, "skill_name");
|
|
1588
1920
|
return skillName ? `${baseLabel}\u300C${skillName}\u300D` : baseLabel;
|
|
1589
1921
|
}
|
|
1922
|
+
if (normalized === "Read") {
|
|
1923
|
+
const skillName = getSkillNameFromFilePath(
|
|
1924
|
+
getStringArgValue(args, "file_path") || getStringArgValue(args, "path")
|
|
1925
|
+
);
|
|
1926
|
+
if (skillName) return `\u8BFB\u53D6\u6280\u80FD\u300C${skillName}\u300D`;
|
|
1927
|
+
}
|
|
1590
1928
|
if (normalized === "FinishTask") {
|
|
1591
1929
|
const title = getStringArgValue(args, "title");
|
|
1592
1930
|
return title ? `${baseLabel}\uFF1A${title}` : baseLabel;
|
|
@@ -1641,83 +1979,80 @@ function parseAgentDescription(argumentsJson) {
|
|
|
1641
1979
|
}
|
|
1642
1980
|
}
|
|
1643
1981
|
function AgentLoopBlock({ toolCall }) {
|
|
1644
|
-
const [expanded, setExpanded] =
|
|
1982
|
+
const [expanded, setExpanded] = useState6(false);
|
|
1645
1983
|
const description = parseAgentDescription(toolCall.arguments);
|
|
1646
1984
|
const running = toolCall.status === "pending" || toolCall.status === "awaiting_answer";
|
|
1647
1985
|
const failed = toolCall.status === "error" || toolCall.status === "cancelled";
|
|
1648
|
-
|
|
1986
|
+
const hasResult = toolCall.result != null;
|
|
1987
|
+
const iconClass = cn(
|
|
1988
|
+
"size-3.5 shrink-0",
|
|
1989
|
+
failed ? "text-red-500" : "text-[hsl(var(--muted-foreground))]"
|
|
1990
|
+
);
|
|
1991
|
+
return /* @__PURE__ */ jsxs5("div", { className: "blade-chat-agent-loop text-xs leading-[22px]", children: [
|
|
1649
1992
|
/* @__PURE__ */ jsxs5(
|
|
1650
|
-
"
|
|
1993
|
+
"button",
|
|
1651
1994
|
{
|
|
1995
|
+
type: "button",
|
|
1996
|
+
onClick: () => hasResult && setExpanded(!expanded),
|
|
1997
|
+
disabled: !hasResult,
|
|
1998
|
+
"aria-expanded": hasResult ? expanded : void 0,
|
|
1999
|
+
"data-testid": "execution-tool-intent",
|
|
1652
2000
|
className: cn(
|
|
1653
|
-
"
|
|
1654
|
-
|
|
2001
|
+
"flex min-w-0 items-center gap-1 py-1.5 text-left",
|
|
2002
|
+
hasResult && "cursor-pointer hover:text-[hsl(var(--foreground))]",
|
|
2003
|
+
failed ? "text-red-500" : "text-[hsl(var(--muted-foreground))]"
|
|
1655
2004
|
),
|
|
2005
|
+
title: `\u5B50\u4EFB\u52A1\uFF1A${description}`,
|
|
1656
2006
|
children: [
|
|
1657
|
-
/* @__PURE__ */
|
|
1658
|
-
|
|
2007
|
+
running ? /* @__PURE__ */ jsx6(LoaderCircle, { className: cn(iconClass, "animate-spin"), "aria-hidden": "true" }) : toolCall.status === "error" ? /* @__PURE__ */ jsx6(CircleAlert, { className: iconClass, "aria-hidden": "true" }) : toolCall.status === "cancelled" ? /* @__PURE__ */ jsx6(X, { className: iconClass, "aria-hidden": "true" }) : /* @__PURE__ */ jsx6(Bot, { className: iconClass, "aria-hidden": "true" }),
|
|
2008
|
+
/* @__PURE__ */ jsxs5("span", { className: "min-w-0 truncate", children: [
|
|
2009
|
+
"\u5B50\u4EFB\u52A1\uFF1A",
|
|
2010
|
+
description
|
|
2011
|
+
] }),
|
|
2012
|
+
hasResult ? /* @__PURE__ */ jsx6(
|
|
2013
|
+
ChevronRight,
|
|
1659
2014
|
{
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
ChevronRight,
|
|
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
|
-
]
|
|
2015
|
+
size: 14,
|
|
2016
|
+
className: cn(
|
|
2017
|
+
"shrink-0 transition-transform duration-300",
|
|
2018
|
+
expanded && "rotate-90"
|
|
2019
|
+
),
|
|
2020
|
+
"aria-hidden": "true"
|
|
1694
2021
|
}
|
|
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) })
|
|
2022
|
+
) : null
|
|
1697
2023
|
]
|
|
1698
2024
|
}
|
|
1699
2025
|
),
|
|
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
|
-
] })
|
|
2026
|
+
expanded && hasResult ? /* @__PURE__ */ jsx6("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
2027
|
] });
|
|
1705
2028
|
}
|
|
1706
2029
|
|
|
1707
2030
|
// src/components/MarkdownContent.tsx
|
|
1708
2031
|
import {
|
|
1709
|
-
useEffect as
|
|
2032
|
+
useEffect as useEffect6,
|
|
1710
2033
|
useMemo as useMemo5,
|
|
1711
|
-
useRef as
|
|
1712
|
-
useState as
|
|
2034
|
+
useRef as useRef7,
|
|
2035
|
+
useState as useState7
|
|
1713
2036
|
} from "react";
|
|
1714
2037
|
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1715
2038
|
var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
|
|
2039
|
+
function normalizeAdjacentUrlFormatting(value) {
|
|
2040
|
+
const protectedSegments = [];
|
|
2041
|
+
const protectedValue = value.replace(/(`{1,3}[\s\S]*?`{1,3}|\[[^\]]*\]\([^)]*\))/g, (segment) => {
|
|
2042
|
+
const index = protectedSegments.push(segment) - 1;
|
|
2043
|
+
return `blade-url-protected-${index}-marker`;
|
|
2044
|
+
});
|
|
2045
|
+
const normalized = protectedValue.replace(
|
|
2046
|
+
/(\*\*|__|~~|\*|_)(https?:\/\/[^\s<>]+?)\1(?=[\s。,、!?;:,.!?;:]|$)/g,
|
|
2047
|
+
(_, marker, url) => `${marker}[${url}](<${url}>)${marker}`
|
|
2048
|
+
);
|
|
2049
|
+
return normalized.replace(/blade-url-protected-(\d+)-marker/g, (_, index) => protectedSegments[Number(index)]);
|
|
2050
|
+
}
|
|
1716
2051
|
function CodeBlockPre({ children, node: _node, ...props }) {
|
|
1717
|
-
const preRef =
|
|
1718
|
-
const [copied, setCopied] =
|
|
1719
|
-
const [language, setLanguage] =
|
|
1720
|
-
|
|
2052
|
+
const preRef = useRef7(null);
|
|
2053
|
+
const [copied, setCopied] = useState7(false);
|
|
2054
|
+
const [language, setLanguage] = useState7("");
|
|
2055
|
+
useEffect6(() => {
|
|
1721
2056
|
const codeEl = preRef.current?.querySelector("code");
|
|
1722
2057
|
setLanguage(codeEl?.className.match(/language-(\S+)/)?.[1] ?? "");
|
|
1723
2058
|
}, []);
|
|
@@ -1767,7 +2102,7 @@ var MARKDOWN_COMPONENTS = {
|
|
|
1767
2102
|
};
|
|
1768
2103
|
function MarkdownContent({ children, className, mode, sessionId }) {
|
|
1769
2104
|
const resolvedChildren = useMemo5(() => {
|
|
1770
|
-
return children.replace(SYSTEM_REMINDER_RE, "");
|
|
2105
|
+
return normalizeAdjacentUrlFormatting(children.replace(SYSTEM_REMINDER_RE, ""));
|
|
1771
2106
|
}, [children]);
|
|
1772
2107
|
return /* @__PURE__ */ jsx7(
|
|
1773
2108
|
_r,
|
|
@@ -1788,11 +2123,40 @@ function Shimmer({ children = "\u6B63\u5728\u601D\u8003...", className }) {
|
|
|
1788
2123
|
}
|
|
1789
2124
|
|
|
1790
2125
|
// src/components/ToolCallBlock.tsx
|
|
1791
|
-
import { useState as
|
|
2126
|
+
import { useState as useState9 } from "react";
|
|
1792
2127
|
|
|
1793
2128
|
// src/components/AskUserQuestionBlock.tsx
|
|
1794
|
-
import { useEffect as
|
|
2129
|
+
import { useEffect as useEffect7, useMemo as useMemo6, useRef as useRef8, useState as useState8 } from "react";
|
|
1795
2130
|
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2131
|
+
var CUSTOM_TEXTAREA_MAX_HEIGHT = 160;
|
|
2132
|
+
function resizeCustomTextarea(textarea) {
|
|
2133
|
+
textarea.style.height = "auto";
|
|
2134
|
+
textarea.style.height = `${Math.min(textarea.scrollHeight, CUSTOM_TEXTAREA_MAX_HEIGHT)}px`;
|
|
2135
|
+
textarea.style.overflowY = textarea.scrollHeight > CUSTOM_TEXTAREA_MAX_HEIGHT ? "auto" : "hidden";
|
|
2136
|
+
}
|
|
2137
|
+
function useAutoResizeTextarea(value) {
|
|
2138
|
+
const textareaRef = useRef8(null);
|
|
2139
|
+
useEffect7(() => {
|
|
2140
|
+
const textarea = textareaRef.current;
|
|
2141
|
+
if (textarea?.value === value) resizeCustomTextarea(textarea);
|
|
2142
|
+
}, [value]);
|
|
2143
|
+
useEffect7(() => {
|
|
2144
|
+
const textarea = textareaRef.current;
|
|
2145
|
+
if (!textarea || typeof ResizeObserver === "undefined") return;
|
|
2146
|
+
let previousWidth = textarea.clientWidth;
|
|
2147
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
2148
|
+
if (!entry || entry.contentRect.width === previousWidth) return;
|
|
2149
|
+
previousWidth = entry.contentRect.width;
|
|
2150
|
+
resizeCustomTextarea(textarea);
|
|
2151
|
+
});
|
|
2152
|
+
observer.observe(textarea);
|
|
2153
|
+
return () => observer.disconnect();
|
|
2154
|
+
}, []);
|
|
2155
|
+
return textareaRef;
|
|
2156
|
+
}
|
|
2157
|
+
function indentAnswerContinuationLines(answer) {
|
|
2158
|
+
return answer.replaceAll("\n", "\n ");
|
|
2159
|
+
}
|
|
1796
2160
|
function AskUserQuestionBlock({
|
|
1797
2161
|
data,
|
|
1798
2162
|
answered,
|
|
@@ -1801,18 +2165,19 @@ function AskUserQuestionBlock({
|
|
|
1801
2165
|
answerData,
|
|
1802
2166
|
onAnswer
|
|
1803
2167
|
}) {
|
|
1804
|
-
const [selections, setSelections] =
|
|
1805
|
-
const [customTexts, setCustomTexts] =
|
|
1806
|
-
const [usingCustom, setUsingCustom] =
|
|
1807
|
-
const [
|
|
1808
|
-
|
|
2168
|
+
const [selections, setSelections] = useState8(/* @__PURE__ */ new Map());
|
|
2169
|
+
const [customTexts, setCustomTexts] = useState8(/* @__PURE__ */ new Map());
|
|
2170
|
+
const [usingCustom, setUsingCustom] = useState8(/* @__PURE__ */ new Set());
|
|
2171
|
+
const [note, setNote] = useState8("");
|
|
2172
|
+
const [submitted, setSubmitted] = useState8(false);
|
|
2173
|
+
useEffect7(() => {
|
|
1809
2174
|
if (sessionStatus === "failed" || sessionStatus === "interrupted") {
|
|
1810
2175
|
setSubmitted(false);
|
|
1811
2176
|
}
|
|
1812
2177
|
}, [sessionStatus]);
|
|
1813
2178
|
const displayAnswerState = useMemo6(() => {
|
|
1814
2179
|
if (!(answered && answerData)) {
|
|
1815
|
-
return { selections, customTexts, usingCustom };
|
|
2180
|
+
return { selections, customTexts, usingCustom, note };
|
|
1816
2181
|
}
|
|
1817
2182
|
const nextSelections = /* @__PURE__ */ new Map();
|
|
1818
2183
|
const nextCustomTexts = /* @__PURE__ */ new Map();
|
|
@@ -1828,9 +2193,10 @@ function AskUserQuestionBlock({
|
|
|
1828
2193
|
return {
|
|
1829
2194
|
selections: nextSelections,
|
|
1830
2195
|
customTexts: nextCustomTexts,
|
|
1831
|
-
usingCustom: nextUsingCustom
|
|
2196
|
+
usingCustom: nextUsingCustom,
|
|
2197
|
+
note: answerData.note ?? ""
|
|
1832
2198
|
};
|
|
1833
|
-
}, [answerData, answered, customTexts, selections, usingCustom]);
|
|
2199
|
+
}, [answerData, answered, customTexts, note, selections, usingCustom]);
|
|
1834
2200
|
const toggleOption = (qIdx, optIdx, multi) => {
|
|
1835
2201
|
if (answered || submitted) return;
|
|
1836
2202
|
setSelections((prev) => {
|
|
@@ -1878,6 +2244,7 @@ function AskUserQuestionBlock({
|
|
|
1878
2244
|
const allAnswered = data.questions.every((_, i) => getAnswer(i) !== null);
|
|
1879
2245
|
const handleSubmit = () => {
|
|
1880
2246
|
if (answered || submitted || !allAnswered || !onAnswer) return;
|
|
2247
|
+
const trimmedNote = note.trim();
|
|
1881
2248
|
const nextAnswerData = {
|
|
1882
2249
|
selections: Object.fromEntries(
|
|
1883
2250
|
Array.from(selections.entries()).map(([qIdx, optionIndexes]) => [
|
|
@@ -1887,11 +2254,17 @@ function AskUserQuestionBlock({
|
|
|
1887
2254
|
),
|
|
1888
2255
|
custom: Object.fromEntries(
|
|
1889
2256
|
Array.from(usingCustom).map((qIdx) => [qIdx, (customTexts.get(qIdx) ?? "").trim()]).filter(([, text2]) => text2.length > 0)
|
|
1890
|
-
)
|
|
2257
|
+
),
|
|
2258
|
+
...trimmedNote ? { note: trimmedNote } : {}
|
|
1891
2259
|
};
|
|
1892
|
-
const parts = data.questions.map(
|
|
1893
|
-
|
|
1894
|
-
|
|
2260
|
+
const parts = data.questions.map(
|
|
2261
|
+
(q, i) => `- ${q.question} -> ${indentAnswerContinuationLines(getAnswer(i) ?? "")}`
|
|
2262
|
+
);
|
|
2263
|
+
const text = [
|
|
2264
|
+
`\u5173\u4E8E\u9700\u8981\u786E\u8BA4\u7684\u95EE\u9898\uFF0C\u7528\u6237\u7684\u56DE\u7B54\u5982\u4E0B\uFF1A
|
|
2265
|
+
${parts.join("\n")}`,
|
|
2266
|
+
trimmedNote ? `\u8865\u5145\u8BF4\u660E\uFF1A${indentAnswerContinuationLines(trimmedNote)}` : ""
|
|
2267
|
+
].filter(Boolean).join("\n");
|
|
1895
2268
|
setSubmitted(true);
|
|
1896
2269
|
onAnswer(text, toolCallId, nextAnswerData);
|
|
1897
2270
|
};
|
|
@@ -1923,6 +2296,15 @@ ${parts.join("\n")}`;
|
|
|
1923
2296
|
},
|
|
1924
2297
|
q.question
|
|
1925
2298
|
)),
|
|
2299
|
+
/* @__PURE__ */ jsx9(
|
|
2300
|
+
NoteField,
|
|
2301
|
+
{
|
|
2302
|
+
answered,
|
|
2303
|
+
submitted,
|
|
2304
|
+
note: displayAnswerState.note,
|
|
2305
|
+
onChange: setNote
|
|
2306
|
+
}
|
|
2307
|
+
),
|
|
1926
2308
|
!answered && !submitted && onAnswer && /* @__PURE__ */ jsx9(
|
|
1927
2309
|
"button",
|
|
1928
2310
|
{
|
|
@@ -1961,6 +2343,7 @@ function QuestionCard({
|
|
|
1961
2343
|
onCustomChange
|
|
1962
2344
|
}) {
|
|
1963
2345
|
const multi = question.multiSelect ?? false;
|
|
2346
|
+
const customTextareaRef = useAutoResizeTextarea(customText);
|
|
1964
2347
|
return /* @__PURE__ */ jsxs7("div", { children: [
|
|
1965
2348
|
/* @__PURE__ */ jsxs7("div", { className: cn("flex items-start gap-2", answered ? "mb-2" : "mb-3"), children: [
|
|
1966
2349
|
/* @__PURE__ */ jsx9(
|
|
@@ -2037,25 +2420,26 @@ function QuestionCard({
|
|
|
2037
2420
|
"div",
|
|
2038
2421
|
{
|
|
2039
2422
|
className: cn(
|
|
2040
|
-
"flex items-
|
|
2423
|
+
"flex items-start gap-2 rounded-lg border transition-all focus-within:ring-2 focus-within:ring-[hsl(var(--ring)/0.35)]",
|
|
2041
2424
|
answered ? "px-2.5 py-1.5" : "px-3 py-2.5",
|
|
2042
2425
|
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
2426
|
answered && "cursor-default opacity-70"
|
|
2044
2427
|
),
|
|
2045
2428
|
children: [
|
|
2046
|
-
/* @__PURE__ */ jsx9("span", { className: "shrink-0 text-xs text-[hsl(var(--muted-foreground))]", children: "\u5176\u4ED6\uFF1A" }),
|
|
2429
|
+
/* @__PURE__ */ jsx9("span", { className: "shrink-0 pt-1 text-xs text-[hsl(var(--muted-foreground))]", children: "\u5176\u4ED6\uFF1A" }),
|
|
2047
2430
|
/* @__PURE__ */ jsx9(
|
|
2048
|
-
"
|
|
2431
|
+
"textarea",
|
|
2049
2432
|
{
|
|
2050
|
-
|
|
2433
|
+
ref: customTextareaRef,
|
|
2434
|
+
rows: 2,
|
|
2051
2435
|
value: customText,
|
|
2052
|
-
|
|
2436
|
+
readOnly: answered,
|
|
2053
2437
|
onChange: (e) => onCustomChange(qIdx, e.target.value),
|
|
2054
2438
|
onFocus: () => onCustomFocus(qIdx),
|
|
2055
2439
|
"aria-label": "\u81EA\u5B9A\u4E49\u56DE\u7B54",
|
|
2056
2440
|
placeholder: "\u8F93\u5165\u4F60\u7684\u7B54\u6848...",
|
|
2057
2441
|
className: cn(
|
|
2058
|
-
"min-w-0 flex-1 bg-transparent text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground)/0.5)]",
|
|
2442
|
+
"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
2443
|
answered ? "text-xs" : "text-sm"
|
|
2060
2444
|
)
|
|
2061
2445
|
}
|
|
@@ -2066,6 +2450,49 @@ function QuestionCard({
|
|
|
2066
2450
|
] })
|
|
2067
2451
|
] });
|
|
2068
2452
|
}
|
|
2453
|
+
function NoteField({
|
|
2454
|
+
answered,
|
|
2455
|
+
submitted,
|
|
2456
|
+
note,
|
|
2457
|
+
onChange
|
|
2458
|
+
}) {
|
|
2459
|
+
const textareaRef = useAutoResizeTextarea(note);
|
|
2460
|
+
const readOnly = answered || submitted;
|
|
2461
|
+
if (answered && !note.trim()) return null;
|
|
2462
|
+
return /* @__PURE__ */ jsxs7(
|
|
2463
|
+
"label",
|
|
2464
|
+
{
|
|
2465
|
+
className: cn(
|
|
2466
|
+
"block rounded-lg border transition-all focus-within:ring-2 focus-within:ring-[hsl(var(--ring)/0.35)]",
|
|
2467
|
+
answered ? "px-2.5 py-1.5" : "px-3 py-2.5",
|
|
2468
|
+
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))]",
|
|
2469
|
+
readOnly && "cursor-default opacity-70"
|
|
2470
|
+
),
|
|
2471
|
+
children: [
|
|
2472
|
+
/* @__PURE__ */ jsx9("span", { className: "mb-1.5 block text-xs text-[hsl(var(--muted-foreground))]", children: "\u8865\u5145\u8BF4\u660E\uFF08\u53EF\u9009\uFF09" }),
|
|
2473
|
+
/* @__PURE__ */ jsx9(
|
|
2474
|
+
"textarea",
|
|
2475
|
+
{
|
|
2476
|
+
ref: textareaRef,
|
|
2477
|
+
rows: 2,
|
|
2478
|
+
value: note,
|
|
2479
|
+
readOnly,
|
|
2480
|
+
onChange: (event) => {
|
|
2481
|
+
if (readOnly) return;
|
|
2482
|
+
onChange(event.target.value);
|
|
2483
|
+
},
|
|
2484
|
+
"aria-label": "\u8865\u5145\u8BF4\u660E",
|
|
2485
|
+
placeholder: "\u9009\u5B8C\u8FD8\u53EF\u4EE5\u518D\u8BB2\u4E24\u53E5\uFF0C\u7A7A\u7740\u5C31\u5F53\u6CA1\u6709",
|
|
2486
|
+
className: cn(
|
|
2487
|
+
"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)]",
|
|
2488
|
+
answered ? "text-xs" : "text-sm"
|
|
2489
|
+
)
|
|
2490
|
+
}
|
|
2491
|
+
)
|
|
2492
|
+
]
|
|
2493
|
+
}
|
|
2494
|
+
);
|
|
2495
|
+
}
|
|
2069
2496
|
function parseAskUserQuestion(toolResult) {
|
|
2070
2497
|
if (!toolResult) return null;
|
|
2071
2498
|
try {
|
|
@@ -2088,6 +2515,26 @@ function parseAskUserQuestion(toolResult) {
|
|
|
2088
2515
|
}
|
|
2089
2516
|
return null;
|
|
2090
2517
|
}
|
|
2518
|
+
function parseAskUserQuestionError(toolResult) {
|
|
2519
|
+
if (!toolResult) return null;
|
|
2520
|
+
try {
|
|
2521
|
+
const parsed = JSON.parse(toolResult);
|
|
2522
|
+
let detail = null;
|
|
2523
|
+
if (typeof parsed.error === "string") detail = parsed.error;
|
|
2524
|
+
if (parsed.error && typeof parsed.error === "object") {
|
|
2525
|
+
const message = parsed.error.message;
|
|
2526
|
+
if (typeof message === "string") detail = message;
|
|
2527
|
+
}
|
|
2528
|
+
if (!detail && typeof parsed.message === "string") detail = parsed.message;
|
|
2529
|
+
if (!detail) return null;
|
|
2530
|
+
return {
|
|
2531
|
+
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",
|
|
2532
|
+
detail
|
|
2533
|
+
};
|
|
2534
|
+
} catch {
|
|
2535
|
+
return null;
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2091
2538
|
function normalizeQuestionItem(value) {
|
|
2092
2539
|
if (!value || typeof value !== "object") return null;
|
|
2093
2540
|
const item = value;
|
|
@@ -2116,9 +2563,10 @@ import { Fragment, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
|
2116
2563
|
function resolveAskQuestionState({
|
|
2117
2564
|
toolStatus,
|
|
2118
2565
|
hasAnswerData,
|
|
2119
|
-
fallbackAnswered
|
|
2566
|
+
fallbackAnswered,
|
|
2567
|
+
fallbackAwaiting
|
|
2120
2568
|
}) {
|
|
2121
|
-
const awaitingAnswer = !hasAnswerData && toolStatus === "awaiting_answer";
|
|
2569
|
+
const awaitingAnswer = !hasAnswerData && (toolStatus === "awaiting_answer" || toolStatus === "pending" && fallbackAwaiting === true);
|
|
2122
2570
|
return {
|
|
2123
2571
|
awaitingAnswer,
|
|
2124
2572
|
answered: hasAnswerData || !awaitingAnswer && (Boolean(fallbackAnswered) || toolStatus === "done" || toolStatus === "cancelled" || toolStatus === "error")
|
|
@@ -2130,9 +2578,10 @@ function ToolCallBlock({
|
|
|
2130
2578
|
answered,
|
|
2131
2579
|
answerData,
|
|
2132
2580
|
sessionStatus,
|
|
2581
|
+
isActiveQuestion,
|
|
2133
2582
|
renderer
|
|
2134
2583
|
}) {
|
|
2135
|
-
const [expanded, setExpanded] =
|
|
2584
|
+
const [expanded, setExpanded] = useState9(false);
|
|
2136
2585
|
const normalizedName = formatToolName(toolCall.name);
|
|
2137
2586
|
if (renderer) {
|
|
2138
2587
|
const custom = renderer(toolCall);
|
|
@@ -2145,7 +2594,8 @@ function ToolCallBlock({
|
|
|
2145
2594
|
const questionState = resolveAskQuestionState({
|
|
2146
2595
|
toolStatus: toolCall.status,
|
|
2147
2596
|
hasAnswerData: Boolean(answerData),
|
|
2148
|
-
fallbackAnswered: answered
|
|
2597
|
+
fallbackAnswered: answered,
|
|
2598
|
+
fallbackAwaiting: isActiveQuestion === true && (sessionStatus === "paused" || sessionStatus === "waiting_for_input")
|
|
2149
2599
|
});
|
|
2150
2600
|
const canAnswer = questionState.awaitingAnswer && Boolean(onAnswer);
|
|
2151
2601
|
if (askData) {
|
|
@@ -2167,9 +2617,16 @@ function ToolCallBlock({
|
|
|
2167
2617
|
/* @__PURE__ */ jsx10("span", { children: "\u6B63\u5728\u51C6\u5907\u95EE\u9898\u2026" })
|
|
2168
2618
|
] });
|
|
2169
2619
|
}
|
|
2620
|
+
const errorDetail = parseAskUserQuestionError(
|
|
2621
|
+
typeof toolCall.result === "string" ? toolCall.result : null
|
|
2622
|
+
);
|
|
2170
2623
|
return /* @__PURE__ */ jsxs8("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: [
|
|
2171
2624
|
/* @__PURE__ */ jsx10("div", { className: "font-semibold", children: "\u9009\u62E9\u9898\u5185\u5BB9\u6682\u65F6\u65E0\u6CD5\u663E\u793A" }),
|
|
2172
|
-
/* @__PURE__ */ jsx10("div", { className: "mt-1 text-xs leading-5 text-[hsl(var(--muted-foreground))]", children: "\u6536\u5230\u7684\u4EA4\u4E92\u6570\u636E\u4E0D\u5B8C\u6574\u3002\u8BF7\u8BA9\u667A\u80FD\u4F53\u91CD\u65B0\u63D0\u95EE\u3002" })
|
|
2625
|
+
/* @__PURE__ */ jsx10("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" }),
|
|
2626
|
+
errorDetail?.detail ? /* @__PURE__ */ jsxs8("details", { className: "mt-2 text-xs text-[hsl(var(--muted-foreground))]", children: [
|
|
2627
|
+
/* @__PURE__ */ jsx10("summary", { className: "cursor-pointer", children: "\u67E5\u770B\u5177\u4F53\u539F\u56E0" }),
|
|
2628
|
+
/* @__PURE__ */ jsx10("div", { className: "mt-1 break-words font-mono", children: errorDetail.detail })
|
|
2629
|
+
] }) : null
|
|
2173
2630
|
] });
|
|
2174
2631
|
}
|
|
2175
2632
|
const tone = getToolTone(toolCall.status);
|
|
@@ -2234,45 +2691,233 @@ function buildAskUserPayload(argumentsJson) {
|
|
|
2234
2691
|
// src/components/AssistantTurnBlock.tsx
|
|
2235
2692
|
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2236
2693
|
function ThinkingBlock({ reasoning, isStreaming }) {
|
|
2237
|
-
const [open, setOpen] =
|
|
2238
|
-
|
|
2694
|
+
const [open, setOpen] = useState10(false);
|
|
2695
|
+
if (!isStreaming) return null;
|
|
2696
|
+
return /* @__PURE__ */ jsxs9("div", { className: "blade-chat-thinking text-xs", children: [
|
|
2239
2697
|
/* @__PURE__ */ jsxs9(
|
|
2240
2698
|
"button",
|
|
2241
2699
|
{
|
|
2242
2700
|
type: "button",
|
|
2243
2701
|
onClick: () => setOpen(!open),
|
|
2244
2702
|
"aria-expanded": open,
|
|
2245
|
-
className: "inline-flex items-center gap-1
|
|
2703
|
+
className: "group/thinking inline-flex items-center gap-1 text-[hsl(var(--muted-foreground))] transition-colors hover:text-[hsl(var(--foreground))]",
|
|
2246
2704
|
children: [
|
|
2247
|
-
/* @__PURE__ */ jsx11(
|
|
2248
|
-
isStreaming ? /* @__PURE__ */ jsx11(Shimmer, { className: "text-xs", children: "\u6B63\u5728\u601D\u8003" }) : /* @__PURE__ */ jsx11("span", { children: "\u601D\u8003\u8FC7\u7A0B" }),
|
|
2249
|
-
/* @__PURE__ */ jsxs9("span", { className: "text-[hsl(var(--muted-foreground))]/70", children: [
|
|
2250
|
-
"\xB7 ",
|
|
2251
|
-
new Intl.NumberFormat("zh-CN").format(reasoning.length),
|
|
2252
|
-
" \u5B57"
|
|
2253
|
-
] }),
|
|
2705
|
+
/* @__PURE__ */ jsx11(Shimmer, { className: "text-xs", children: "\u6B63\u5728\u601D\u8003" }),
|
|
2254
2706
|
/* @__PURE__ */ jsx11(
|
|
2255
|
-
|
|
2707
|
+
ChevronRight,
|
|
2256
2708
|
{
|
|
2257
|
-
size:
|
|
2258
|
-
className: cn(
|
|
2709
|
+
size: 14,
|
|
2710
|
+
className: cn(
|
|
2711
|
+
"shrink-0 opacity-0 transition-[opacity,transform] group-hover/thinking:opacity-100",
|
|
2712
|
+
open && "rotate-90 opacity-100"
|
|
2713
|
+
)
|
|
2259
2714
|
}
|
|
2260
2715
|
)
|
|
2261
2716
|
]
|
|
2262
2717
|
}
|
|
2263
2718
|
),
|
|
2264
|
-
open
|
|
2719
|
+
open ? /* @__PURE__ */ jsx11("div", { className: "mt-1.5 whitespace-pre-wrap text-xs leading-[22px] text-[hsl(var(--muted-foreground))]", children: reasoning }) : null
|
|
2265
2720
|
] });
|
|
2266
2721
|
}
|
|
2267
2722
|
function getMessageText(message) {
|
|
2268
2723
|
return getTextContent(normalizeMessageContent(message.content)).trim();
|
|
2269
2724
|
}
|
|
2725
|
+
function hasRenderableMessageContent(message) {
|
|
2726
|
+
return Boolean(getMessageText(message)) || getImageParts(message.content).length > 0 || getFileParts(message.content).length > 0;
|
|
2727
|
+
}
|
|
2728
|
+
function getLastContentMessage(messages) {
|
|
2729
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
2730
|
+
if (hasRenderableMessageContent(messages[index])) return messages[index];
|
|
2731
|
+
}
|
|
2732
|
+
return null;
|
|
2733
|
+
}
|
|
2734
|
+
function getOrderedMessageParts(message, toolCalls) {
|
|
2735
|
+
const blocks = message.blocks ?? [];
|
|
2736
|
+
if (!blocks.some((block) => block.type === "text") || !blocks.some((block) => block.type === "tool_use")) return [];
|
|
2737
|
+
const toolsById = new Map(toolCalls.map((toolCall) => [toolCall.id, toolCall]));
|
|
2738
|
+
const seenToolIds = /* @__PURE__ */ new Set();
|
|
2739
|
+
const parts = [];
|
|
2740
|
+
for (const [index, block] of blocks.entries()) {
|
|
2741
|
+
if (block.type === "text" && block.content != null && block.content !== "") {
|
|
2742
|
+
const content = Array.isArray(block.content) ? block.content : String(block.content);
|
|
2743
|
+
parts.push({ type: "text", key: `text-${index}`, content });
|
|
2744
|
+
}
|
|
2745
|
+
if (block.type !== "tool_use" || !block.tool_call_id) continue;
|
|
2746
|
+
const toolCall = toolsById.get(block.tool_call_id);
|
|
2747
|
+
if (!toolCall) continue;
|
|
2748
|
+
seenToolIds.add(toolCall.id);
|
|
2749
|
+
const previous = parts[parts.length - 1];
|
|
2750
|
+
if (previous?.type === "tools") previous.toolCalls.push(toolCall);
|
|
2751
|
+
else parts.push({ type: "tools", key: `tools-${index}`, toolCalls: [toolCall] });
|
|
2752
|
+
}
|
|
2753
|
+
const missingTools = toolCalls.filter((toolCall) => !seenToolIds.has(toolCall.id));
|
|
2754
|
+
if (seenToolIds.size === 0) return [];
|
|
2755
|
+
if (missingTools.length > 0) {
|
|
2756
|
+
parts.push({ type: "tools", key: "tools-missing", toolCalls: missingTools });
|
|
2757
|
+
}
|
|
2758
|
+
return parts;
|
|
2759
|
+
}
|
|
2270
2760
|
function findLatestReasoningMessageIndex(messages) {
|
|
2271
2761
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
2272
2762
|
if (messages[index].reasoning) return index;
|
|
2273
2763
|
}
|
|
2274
2764
|
return -1;
|
|
2275
2765
|
}
|
|
2766
|
+
function resolveTurnDisplayMode({
|
|
2767
|
+
isStreaming: _isStreaming,
|
|
2768
|
+
displayMode
|
|
2769
|
+
}) {
|
|
2770
|
+
return displayMode;
|
|
2771
|
+
}
|
|
2772
|
+
function formatExecutionDuration(durationMs) {
|
|
2773
|
+
const totalSeconds = Math.max(0, Math.round(durationMs / 1e3));
|
|
2774
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
2775
|
+
const seconds = totalSeconds % 60;
|
|
2776
|
+
return minutes > 0 ? `${minutes}\u5206${seconds}\u79D2` : `${seconds}\u79D2`;
|
|
2777
|
+
}
|
|
2778
|
+
function getExecutionDurationMs({
|
|
2779
|
+
messages,
|
|
2780
|
+
isStreaming,
|
|
2781
|
+
now = Date.now()
|
|
2782
|
+
}) {
|
|
2783
|
+
const knownDuration = messages.reduce(
|
|
2784
|
+
(total, message) => {
|
|
2785
|
+
if (typeof message.duration_ms === "number" && message.duration_ms > 0) {
|
|
2786
|
+
return total + message.duration_ms;
|
|
2787
|
+
}
|
|
2788
|
+
return total + (message.tool_calls ?? []).reduce(
|
|
2789
|
+
(toolTotal, toolCall) => toolTotal + (typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 ? toolCall.duration_ms : 0),
|
|
2790
|
+
0
|
|
2791
|
+
);
|
|
2792
|
+
},
|
|
2793
|
+
0
|
|
2794
|
+
);
|
|
2795
|
+
if (!isStreaming) return knownDuration;
|
|
2796
|
+
const startedAt = messages.map((message) => message.timestamp ? Date.parse(message.timestamp) : Number.NaN).filter((value) => Number.isFinite(value)).sort((a, b) => a - b)[0];
|
|
2797
|
+
if (startedAt === void 0) return knownDuration;
|
|
2798
|
+
return Math.max(knownDuration, now - startedAt);
|
|
2799
|
+
}
|
|
2800
|
+
function findLastExceptionalEvent(messages) {
|
|
2801
|
+
for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex -= 1) {
|
|
2802
|
+
const messageStatus = messages[messageIndex].status;
|
|
2803
|
+
if (messageStatus === "failed") return { messageIndex, status: "error" };
|
|
2804
|
+
if (messageStatus === "interrupted") return { messageIndex, status: "cancelled" };
|
|
2805
|
+
const toolCalls = messages[messageIndex].tool_calls ?? [];
|
|
2806
|
+
for (let toolIndex = toolCalls.length - 1; toolIndex >= 0; toolIndex -= 1) {
|
|
2807
|
+
const status = toolCalls[toolIndex].status;
|
|
2808
|
+
if (status === "error" || status === "cancelled") {
|
|
2809
|
+
return { messageIndex, status };
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
return null;
|
|
2814
|
+
}
|
|
2815
|
+
function executionSummaryLabel({
|
|
2816
|
+
messages,
|
|
2817
|
+
isStreaming,
|
|
2818
|
+
durationMs,
|
|
2819
|
+
sessionStatus,
|
|
2820
|
+
askAnswers
|
|
2821
|
+
}) {
|
|
2822
|
+
if (isStreaming) {
|
|
2823
|
+
return durationMs > 0 ? `\u6B63\u5728\u6267\u884C ${formatExecutionDuration(durationMs)}` : "\u6B63\u5728\u6267\u884C";
|
|
2824
|
+
}
|
|
2825
|
+
if (sessionStatus === "waiting_for_input" && messages.some(
|
|
2826
|
+
(message) => (message.tool_calls ?? []).some(
|
|
2827
|
+
(toolCall) => formatToolName(toolCall.name) === "AskUserQuestion" && toolCall.status === "awaiting_answer" && !askAnswers?.[toolCall.id]
|
|
2828
|
+
)
|
|
2829
|
+
)) {
|
|
2830
|
+
return "\u7B49\u5F85\u8F93\u5165";
|
|
2831
|
+
}
|
|
2832
|
+
const completedLabel = durationMs > 0 ? `\u6267\u884C\u5B8C\u6210 ${formatExecutionDuration(durationMs)}` : "\u6267\u884C\u5B8C\u6210";
|
|
2833
|
+
const lastExceptionalEvent = findLastExceptionalEvent(messages);
|
|
2834
|
+
if (lastExceptionalEvent) {
|
|
2835
|
+
const recovered = messages.slice(lastExceptionalEvent.messageIndex + 1).some(hasRenderableMessageContent);
|
|
2836
|
+
if (lastExceptionalEvent.status === "error") {
|
|
2837
|
+
return recovered ? `${completedLabel} \xB7 \u90E8\u5206\u6B65\u9AA4\u5931\u8D25` : "\u6267\u884C\u5931\u8D25";
|
|
2838
|
+
}
|
|
2839
|
+
return recovered ? `${completedLabel} \xB7 \u90E8\u5206\u6B65\u9AA4\u672A\u5B8C\u6210` : "\u6267\u884C\u5DF2\u4E2D\u65AD";
|
|
2840
|
+
}
|
|
2841
|
+
return completedLabel;
|
|
2842
|
+
}
|
|
2843
|
+
function businessToolDisplayName(toolCall) {
|
|
2844
|
+
const displayName = toolCall.display_name?.trim() ?? "";
|
|
2845
|
+
if (!displayName) return "";
|
|
2846
|
+
const rawName = toolCall.name.trim();
|
|
2847
|
+
return displayName !== rawName && formatToolName(displayName) !== formatToolName(rawName) ? displayName : "";
|
|
2848
|
+
}
|
|
2849
|
+
function executionToolTypeLabel(toolCall) {
|
|
2850
|
+
switch (formatToolName(toolCall.name)) {
|
|
2851
|
+
case "WebSearch":
|
|
2852
|
+
case "WebFetch":
|
|
2853
|
+
return "\u7F51\u7EDC\u68C0\u7D22";
|
|
2854
|
+
case "Bash":
|
|
2855
|
+
case "BgBash":
|
|
2856
|
+
return "\u547D\u4EE4\u6267\u884C";
|
|
2857
|
+
case "Read":
|
|
2858
|
+
case "ReadSkill":
|
|
2859
|
+
return "\u5185\u5BB9\u8BFB\u53D6";
|
|
2860
|
+
case "Write":
|
|
2861
|
+
case "Edit":
|
|
2862
|
+
case "MultiEdit":
|
|
2863
|
+
return "\u6587\u4EF6\u5904\u7406";
|
|
2864
|
+
case "Grep":
|
|
2865
|
+
case "Glob":
|
|
2866
|
+
return "\u5185\u5BB9\u641C\u7D22";
|
|
2867
|
+
case "Agent":
|
|
2868
|
+
return "\u5B50\u4EFB\u52A1";
|
|
2869
|
+
case "search_skills":
|
|
2870
|
+
return "\u6280\u80FD\u68C0\u7D22";
|
|
2871
|
+
case "get_skill_content":
|
|
2872
|
+
return "\u8BFB\u53D6\u6280\u80FD";
|
|
2873
|
+
case "run_skill_tool":
|
|
2874
|
+
return "\u6267\u884C\u6280\u80FD";
|
|
2875
|
+
default:
|
|
2876
|
+
return businessToolDisplayName(toolCall) || "\u6267\u884C\u6B65\u9AA4";
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
function executionToolIntent(toolCall) {
|
|
2880
|
+
const normalizedName = formatToolName(toolCall.name);
|
|
2881
|
+
let args = null;
|
|
2882
|
+
try {
|
|
2883
|
+
const parsed = JSON.parse(toolCall.arguments);
|
|
2884
|
+
args = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
2885
|
+
} catch {
|
|
2886
|
+
args = null;
|
|
2887
|
+
}
|
|
2888
|
+
const getString = (key) => {
|
|
2889
|
+
const value = args?.[key];
|
|
2890
|
+
return typeof value === "string" ? value.trim() : "";
|
|
2891
|
+
};
|
|
2892
|
+
const explicitIntent = getString("description") || getString("_meta_display_name") || getString("display_name") || "";
|
|
2893
|
+
if (explicitIntent) return explicitIntent;
|
|
2894
|
+
if (normalizedName === "search_skills") return getString("query");
|
|
2895
|
+
if (normalizedName === "get_skill_content" || normalizedName === "ReadSkill") {
|
|
2896
|
+
return getString("skill_name") || getString("skill");
|
|
2897
|
+
}
|
|
2898
|
+
if (normalizedName === "FinishTask") return getString("title");
|
|
2899
|
+
return "";
|
|
2900
|
+
}
|
|
2901
|
+
function ExecutionToolRow({ toolCall }) {
|
|
2902
|
+
const normalizedName = formatToolName(toolCall.name);
|
|
2903
|
+
const typeLabel = executionToolTypeLabel(toolCall);
|
|
2904
|
+
const intent = executionToolIntent(toolCall);
|
|
2905
|
+
const label = intent ? `${typeLabel}\uFF1A${intent}` : typeLabel;
|
|
2906
|
+
const failed = toolCall.status === "error" || toolCall.status === "cancelled";
|
|
2907
|
+
const iconClass = cn(
|
|
2908
|
+
"size-3.5 shrink-0",
|
|
2909
|
+
failed ? "text-red-500" : "text-[hsl(var(--muted-foreground))]"
|
|
2910
|
+
);
|
|
2911
|
+
const icon = toolCall.status === "pending" ? /* @__PURE__ */ jsx11(LoaderCircle, { className: cn(iconClass, "animate-spin"), "aria-hidden": "true" }) : toolCall.status === "error" ? /* @__PURE__ */ jsx11(CircleAlert, { className: iconClass, "aria-hidden": "true" }) : toolCall.status === "cancelled" ? /* @__PURE__ */ jsx11(X, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "WebSearch" || normalizedName === "WebFetch" ? /* @__PURE__ */ jsx11(Earth, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "Bash" || normalizedName === "BgBash" ? /* @__PURE__ */ jsx11(Terminal, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "Write" || normalizedName === "Edit" || normalizedName === "MultiEdit" ? /* @__PURE__ */ jsx11(FilePenLine, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "Read" || normalizedName === "ReadSkill" || normalizedName === "get_skill_content" ? /* @__PURE__ */ jsx11(BookOpen, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "Grep" || normalizedName === "Glob" || normalizedName === "search_skills" ? /* @__PURE__ */ jsx11(Search, { className: iconClass, "aria-hidden": "true" }) : normalizedName === "Agent" ? /* @__PURE__ */ jsx11(Bot, { className: iconClass, "aria-hidden": "true" }) : /* @__PURE__ */ jsx11(Wrench, { className: iconClass, "aria-hidden": "true" });
|
|
2912
|
+
const rowClassName = cn(
|
|
2913
|
+
"flex min-w-0 items-center gap-1 py-1.5 text-xs leading-[22px]",
|
|
2914
|
+
failed ? "text-red-500" : "text-[hsl(var(--muted-foreground))]"
|
|
2915
|
+
);
|
|
2916
|
+
return /* @__PURE__ */ jsxs9("div", { "data-testid": "execution-tool-intent", className: rowClassName, title: label, children: [
|
|
2917
|
+
icon,
|
|
2918
|
+
/* @__PURE__ */ jsx11("span", { className: "min-w-0 truncate", children: label })
|
|
2919
|
+
] });
|
|
2920
|
+
}
|
|
2276
2921
|
function AssistantTurnBlock({
|
|
2277
2922
|
messages,
|
|
2278
2923
|
isStreaming = false,
|
|
@@ -2283,53 +2928,302 @@ function AssistantTurnBlock({
|
|
|
2283
2928
|
sessionId
|
|
2284
2929
|
}) {
|
|
2285
2930
|
const hasInterrupted = messages.some((message) => message.status === "interrupted");
|
|
2286
|
-
const
|
|
2287
|
-
(message) =>
|
|
2931
|
+
const hasFailedWithoutContent = messages.some(
|
|
2932
|
+
(message) => message.status === "failed" && !hasRenderableMessageContent(message)
|
|
2933
|
+
);
|
|
2934
|
+
const finalMessage = getLastContentMessage(messages);
|
|
2935
|
+
const turnToolCalls = messages.flatMap((message) => message.tool_calls ?? []);
|
|
2936
|
+
const finalOrderedParts = finalMessage ? getOrderedMessageParts(
|
|
2937
|
+
finalMessage,
|
|
2938
|
+
(finalMessage.tool_calls ?? []).filter(
|
|
2939
|
+
(toolCall) => formatToolName(toolCall.name) !== "AskUserQuestion"
|
|
2940
|
+
)
|
|
2941
|
+
) : [];
|
|
2942
|
+
const hasExecutionProcess = messages.some(
|
|
2943
|
+
(message) => message.reasoning || (message.tool_calls?.length ?? 0) > 0
|
|
2288
2944
|
);
|
|
2289
2945
|
const latestReasoningIndex = isStreaming ? findLatestReasoningMessageIndex(messages) : -1;
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2946
|
+
const hasActionableToolCall = messages.some(
|
|
2947
|
+
(message) => message.status === "failed" || message.status === "interrupted" || (message.tool_calls ?? []).some(
|
|
2948
|
+
(toolCall) => toolCall.status === "error" || toolCall.status === "cancelled"
|
|
2949
|
+
)
|
|
2950
|
+
);
|
|
2951
|
+
const questionToolCalls = messages.flatMap(
|
|
2952
|
+
(message) => (message.tool_calls ?? []).filter(
|
|
2953
|
+
(toolCall) => formatToolName(toolCall.name) === "AskUserQuestion"
|
|
2954
|
+
)
|
|
2955
|
+
);
|
|
2956
|
+
const activeQuestionId = questionToolCalls.filter((toolCall) => toolCall.status === "pending").at(-1)?.id;
|
|
2957
|
+
const [displayMode, setDisplayMode] = useState10(
|
|
2958
|
+
() => isStreaming || hasActionableToolCall ? "detail" : "compact"
|
|
2959
|
+
);
|
|
2960
|
+
const userSelectedDisplayModeRef = useRef9(false);
|
|
2961
|
+
const wasStreamingRef = useRef9(isStreaming);
|
|
2962
|
+
useEffect8(() => {
|
|
2963
|
+
if (wasStreamingRef.current && !isStreaming && !userSelectedDisplayModeRef.current) {
|
|
2964
|
+
setDisplayMode(hasActionableToolCall ? "detail" : "compact");
|
|
2965
|
+
}
|
|
2966
|
+
wasStreamingRef.current = isStreaming;
|
|
2967
|
+
}, [hasActionableToolCall, isStreaming]);
|
|
2968
|
+
const effectiveMode = resolveTurnDisplayMode({ isStreaming, displayMode });
|
|
2969
|
+
const executionDurationMs = getExecutionDurationMs({ messages, isStreaming });
|
|
2970
|
+
const [clock, setClock] = useState10(() => Date.now());
|
|
2971
|
+
const hasLiveStartTime = messages.some(
|
|
2972
|
+
(message) => message.timestamp != null && Number.isFinite(Date.parse(message.timestamp))
|
|
2973
|
+
);
|
|
2974
|
+
useEffect8(() => {
|
|
2975
|
+
if (!isStreaming || !hasLiveStartTime) return;
|
|
2976
|
+
const timer = window.setInterval(() => setClock(Date.now()), 1e3);
|
|
2977
|
+
return () => window.clearInterval(timer);
|
|
2978
|
+
}, [hasLiveStartTime, isStreaming]);
|
|
2979
|
+
const liveExecutionDurationMs = isStreaming ? getExecutionDurationMs({ messages, isStreaming, now: clock }) : executionDurationMs;
|
|
2980
|
+
const memoryRefs = collectMemoryRefs(messages);
|
|
2981
|
+
if (!hasExecutionProcess) {
|
|
2982
|
+
return /* @__PURE__ */ jsxs9(
|
|
2983
|
+
"div",
|
|
2984
|
+
{
|
|
2985
|
+
"aria-busy": isStreaming || void 0,
|
|
2986
|
+
className: "blade-chat-assistant-turn flex flex-col gap-3",
|
|
2987
|
+
children: [
|
|
2988
|
+
memoryRefs.length > 0 ? /* @__PURE__ */ jsx11(MemoryRefsHint, { refs: memoryRefs }) : null,
|
|
2989
|
+
hasInterrupted && /* @__PURE__ */ jsx11("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" }),
|
|
2990
|
+
hasFailedWithoutContent && /* @__PURE__ */ jsx11("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" }),
|
|
2991
|
+
messages.map((message, index) => {
|
|
2992
|
+
return hasRenderableMessageContent(message) ? /* @__PURE__ */ jsx11(
|
|
2993
|
+
"div",
|
|
2306
2994
|
{
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2995
|
+
className: "flex flex-col gap-3",
|
|
2996
|
+
children: /* @__PURE__ */ jsx11(
|
|
2997
|
+
AssistantMessageContent,
|
|
2998
|
+
{
|
|
2999
|
+
message,
|
|
3000
|
+
sessionId,
|
|
3001
|
+
streaming: isStreaming && index === messages.length - 1
|
|
3002
|
+
}
|
|
3003
|
+
)
|
|
3004
|
+
},
|
|
3005
|
+
message.entry_id ?? `${message.timestamp ?? "assistant"}-${index}`
|
|
3006
|
+
) : null;
|
|
3007
|
+
})
|
|
3008
|
+
]
|
|
3009
|
+
}
|
|
3010
|
+
);
|
|
3011
|
+
}
|
|
3012
|
+
return /* @__PURE__ */ jsxs9(
|
|
3013
|
+
"div",
|
|
3014
|
+
{
|
|
3015
|
+
"aria-busy": isStreaming || void 0,
|
|
3016
|
+
className: "blade-chat-assistant-turn flex flex-col gap-3",
|
|
3017
|
+
children: [
|
|
3018
|
+
memoryRefs.length > 0 ? /* @__PURE__ */ jsx11(MemoryRefsHint, { refs: memoryRefs }) : null,
|
|
3019
|
+
hasInterrupted && /* @__PURE__ */ jsx11("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" }),
|
|
3020
|
+
hasFailedWithoutContent && /* @__PURE__ */ jsx11("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" }),
|
|
3021
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex w-full items-start gap-2.5", children: [
|
|
3022
|
+
/* @__PURE__ */ jsx11(
|
|
3023
|
+
"span",
|
|
3024
|
+
{
|
|
3025
|
+
className: "grid size-[30px] shrink-0 place-items-center rounded-full bg-[hsl(var(--muted)/0.55)] text-[hsl(var(--foreground))]",
|
|
3026
|
+
"aria-hidden": "true",
|
|
3027
|
+
children: /* @__PURE__ */ jsx11(Bot, { size: 16 })
|
|
3028
|
+
}
|
|
3029
|
+
),
|
|
3030
|
+
/* @__PURE__ */ jsxs9("div", { className: "min-w-0 flex-1 pt-0.5", children: [
|
|
3031
|
+
/* @__PURE__ */ jsxs9(
|
|
3032
|
+
"button",
|
|
3033
|
+
{
|
|
3034
|
+
type: "button",
|
|
3035
|
+
onClick: () => {
|
|
3036
|
+
userSelectedDisplayModeRef.current = true;
|
|
3037
|
+
setDisplayMode(displayMode === "detail" ? "compact" : "detail");
|
|
2323
3038
|
},
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
3039
|
+
"aria-expanded": effectiveMode === "detail",
|
|
3040
|
+
"aria-label": effectiveMode === "detail" ? "\u6536\u8D77\u6267\u884C\u8FC7\u7A0B" : "\u5C55\u5F00\u6267\u884C\u8FC7\u7A0B",
|
|
3041
|
+
"data-testid": "assistant-execution-summary",
|
|
3042
|
+
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",
|
|
3043
|
+
children: [
|
|
3044
|
+
/* @__PURE__ */ jsx11("span", { className: "min-w-0 truncate", children: executionSummaryLabel({
|
|
3045
|
+
messages,
|
|
3046
|
+
isStreaming,
|
|
3047
|
+
durationMs: liveExecutionDurationMs,
|
|
3048
|
+
sessionStatus,
|
|
3049
|
+
askAnswers
|
|
3050
|
+
}) }),
|
|
3051
|
+
/* @__PURE__ */ jsx11(
|
|
3052
|
+
ChevronRight,
|
|
3053
|
+
{
|
|
3054
|
+
size: 14,
|
|
3055
|
+
className: cn(
|
|
3056
|
+
"shrink-0 transition-transform duration-300",
|
|
3057
|
+
effectiveMode === "detail" && "rotate-90"
|
|
3058
|
+
),
|
|
3059
|
+
"aria-hidden": "true"
|
|
3060
|
+
}
|
|
3061
|
+
)
|
|
3062
|
+
]
|
|
3063
|
+
}
|
|
3064
|
+
),
|
|
3065
|
+
/* @__PURE__ */ jsx11("div", { className: "mt-3 h-px w-full bg-[hsl(var(--border)/0.75)]" })
|
|
3066
|
+
] })
|
|
3067
|
+
] }),
|
|
3068
|
+
effectiveMode === "detail" ? /* @__PURE__ */ jsx11("div", { className: "ml-10 flex flex-col gap-3 pt-1", children: messages.map((message, index) => {
|
|
3069
|
+
const isLast = index === messages.length - 1;
|
|
3070
|
+
const streamingThis = isStreaming && isLast;
|
|
3071
|
+
const text = getMessageText(message);
|
|
3072
|
+
const toolCalls = (message.tool_calls ?? []).filter(
|
|
3073
|
+
(toolCall) => formatToolName(toolCall.name) !== "AskUserQuestion"
|
|
3074
|
+
);
|
|
3075
|
+
const orderedParts = getOrderedMessageParts(message, toolCalls);
|
|
3076
|
+
const showReasoning = !!message.reasoning && isStreaming && index === latestReasoningIndex;
|
|
3077
|
+
return /* @__PURE__ */ jsxs9(
|
|
3078
|
+
"div",
|
|
3079
|
+
{
|
|
3080
|
+
className: "flex flex-col gap-3",
|
|
3081
|
+
children: [
|
|
3082
|
+
showReasoning && message.reasoning ? /* @__PURE__ */ jsx11(ThinkingBlock, { reasoning: message.reasoning, isStreaming: streamingThis && !text }) : null,
|
|
3083
|
+
orderedParts.length > 0 ? orderedParts.map(
|
|
3084
|
+
(part) => part.type === "text" ? /* @__PURE__ */ jsx11(
|
|
3085
|
+
AssistantMessageContent,
|
|
3086
|
+
{
|
|
3087
|
+
message: { ...message, content: part.content, tool_calls: turnToolCalls },
|
|
3088
|
+
sessionId,
|
|
3089
|
+
streaming: streamingThis,
|
|
3090
|
+
compact: true
|
|
3091
|
+
},
|
|
3092
|
+
part.key
|
|
3093
|
+
) : /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-0.5", children: part.toolCalls.map((toolCall) => {
|
|
3094
|
+
const custom = toolCallRenderer?.(toolCall);
|
|
3095
|
+
return custom !== null && custom !== void 0 ? /* @__PURE__ */ jsx11("div", { children: custom }, toolCall.id) : formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */ jsx11(AgentLoopBlock, { toolCall }, toolCall.id) : /* @__PURE__ */ jsx11(ExecutionToolRow, { toolCall }, toolCall.id);
|
|
3096
|
+
}) }, part.key)
|
|
3097
|
+
) : hasRenderableMessageContent(message) && message !== finalMessage ? /* @__PURE__ */ jsx11(
|
|
3098
|
+
AssistantMessageContent,
|
|
3099
|
+
{
|
|
3100
|
+
message,
|
|
3101
|
+
sessionId,
|
|
3102
|
+
streaming: streamingThis,
|
|
3103
|
+
compact: true
|
|
3104
|
+
}
|
|
3105
|
+
) : null,
|
|
3106
|
+
orderedParts.length === 0 && toolCalls.length > 0 ? /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-0.5", children: toolCalls.map((toolCall) => {
|
|
3107
|
+
const custom = toolCallRenderer?.(toolCall);
|
|
3108
|
+
return custom !== null && custom !== void 0 ? /* @__PURE__ */ jsx11("div", { children: custom }, toolCall.id) : formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */ jsx11(AgentLoopBlock, { toolCall }, toolCall.id) : /* @__PURE__ */ jsx11(ExecutionToolRow, { toolCall }, toolCall.id);
|
|
3109
|
+
}) }) : null
|
|
3110
|
+
]
|
|
3111
|
+
},
|
|
3112
|
+
message.entry_id ?? `${message.timestamp ?? "assistant"}-${index}`
|
|
3113
|
+
);
|
|
3114
|
+
}) }) : null,
|
|
3115
|
+
finalMessage && (effectiveMode === "compact" || finalOrderedParts.length === 0) ? /* @__PURE__ */ jsx11("div", { className: "ml-10", children: /* @__PURE__ */ jsx11(
|
|
3116
|
+
AssistantMessageContent,
|
|
3117
|
+
{
|
|
3118
|
+
message: finalMessage,
|
|
3119
|
+
sessionId,
|
|
3120
|
+
streaming: isStreaming && finalMessage === messages[messages.length - 1]
|
|
3121
|
+
}
|
|
3122
|
+
) }) : null,
|
|
3123
|
+
questionToolCalls.map((toolCall) => /* @__PURE__ */ jsx11(
|
|
3124
|
+
ToolCallBlock,
|
|
3125
|
+
{
|
|
3126
|
+
toolCall,
|
|
3127
|
+
answerData: askAnswers?.[toolCall.id],
|
|
3128
|
+
onAnswer,
|
|
3129
|
+
answered: sessionStatus !== "waiting_for_input",
|
|
3130
|
+
sessionStatus,
|
|
3131
|
+
isActiveQuestion: toolCall.id === activeQuestionId,
|
|
3132
|
+
renderer: toolCallRenderer
|
|
3133
|
+
},
|
|
3134
|
+
toolCall.id
|
|
3135
|
+
))
|
|
3136
|
+
]
|
|
3137
|
+
}
|
|
3138
|
+
);
|
|
3139
|
+
}
|
|
3140
|
+
function collectMemoryRefs(messages) {
|
|
3141
|
+
const refs = /* @__PURE__ */ new Map();
|
|
3142
|
+
for (const message of messages) {
|
|
3143
|
+
for (const ref of message.memory_refs ?? []) if (!refs.has(ref.id)) refs.set(ref.id, ref);
|
|
3144
|
+
}
|
|
3145
|
+
return [...refs.values()];
|
|
3146
|
+
}
|
|
3147
|
+
function MemoryRefsHint({ refs }) {
|
|
3148
|
+
const [expanded, setExpanded] = useState10(false);
|
|
3149
|
+
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";
|
|
3150
|
+
return /* @__PURE__ */ jsxs9("div", { className: "blade-chat-memory-refs ml-1 w-full max-w-[680px]", children: [
|
|
3151
|
+
/* @__PURE__ */ jsxs9("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: [
|
|
3152
|
+
/* @__PURE__ */ jsx11(BookOpen, { size: 12 }),
|
|
3153
|
+
/* @__PURE__ */ jsxs9("span", { children: [
|
|
3154
|
+
label,
|
|
3155
|
+
"\uFF08",
|
|
3156
|
+
refs.length,
|
|
3157
|
+
"\uFF09"
|
|
3158
|
+
] }),
|
|
3159
|
+
/* @__PURE__ */ jsx11(ChevronRight, { size: 10, className: cn("transition-transform", expanded && "rotate-90") })
|
|
3160
|
+
] }),
|
|
3161
|
+
expanded ? /* @__PURE__ */ jsx11("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__ */ jsxs9("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: [
|
|
3162
|
+
/* @__PURE__ */ jsx11("p", { className: "line-clamp-2 break-words leading-5", children: ref.content_preview }),
|
|
3163
|
+
ref.skill_name ? /* @__PURE__ */ jsx11("span", { className: "mt-1 inline-flex text-[10px] text-[hsl(var(--primary))]", children: ref.skill_name }) : null
|
|
3164
|
+
] }, ref.id)) }) : null
|
|
3165
|
+
] });
|
|
3166
|
+
}
|
|
3167
|
+
function AssistantMessageContent({
|
|
3168
|
+
message,
|
|
3169
|
+
sessionId,
|
|
3170
|
+
streaming,
|
|
3171
|
+
compact = false
|
|
3172
|
+
}) {
|
|
3173
|
+
const text = getMessageText(message);
|
|
3174
|
+
const imageParts = getImageParts(message.content);
|
|
3175
|
+
const fileParts = getFileParts(message.content);
|
|
3176
|
+
const failed = message.status === "failed";
|
|
3177
|
+
const failedBadge = failed ? /* @__PURE__ */ jsx11("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;
|
|
3178
|
+
const textContent = text ? /* @__PURE__ */ jsx11(
|
|
3179
|
+
"div",
|
|
3180
|
+
{
|
|
3181
|
+
className: cn(
|
|
3182
|
+
"blade-chat-assistant-text",
|
|
3183
|
+
compact ? "text-xs leading-[22px] text-[hsl(var(--foreground))]" : "text-[15px] leading-8 text-[hsl(var(--foreground))]"
|
|
3184
|
+
),
|
|
3185
|
+
children: /* @__PURE__ */ jsx11(
|
|
3186
|
+
MarkdownContent,
|
|
3187
|
+
{
|
|
3188
|
+
mode: streaming ? "streaming" : "static",
|
|
3189
|
+
className: "blade-chat-prose",
|
|
3190
|
+
sessionId,
|
|
3191
|
+
children: text
|
|
3192
|
+
}
|
|
3193
|
+
)
|
|
3194
|
+
}
|
|
3195
|
+
) : null;
|
|
3196
|
+
if (imageParts.length === 0 && fileParts.length === 0) {
|
|
3197
|
+
if (!failed) return textContent;
|
|
3198
|
+
return failedBadge || textContent ? /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-2", children: [
|
|
3199
|
+
failedBadge,
|
|
3200
|
+
textContent
|
|
3201
|
+
] }) : null;
|
|
3202
|
+
}
|
|
3203
|
+
return /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-3", children: [
|
|
3204
|
+
failedBadge,
|
|
3205
|
+
imageParts.length > 0 ? /* @__PURE__ */ jsx11("div", { className: "grid gap-2", children: imageParts.map((part) => /* @__PURE__ */ jsx11(
|
|
3206
|
+
"img",
|
|
3207
|
+
{
|
|
3208
|
+
src: part.image_url.url,
|
|
3209
|
+
alt: "\u6D88\u606F\u9644\u4EF6",
|
|
3210
|
+
className: "max-h-72 rounded-xl border border-[hsl(var(--border))] object-cover"
|
|
3211
|
+
},
|
|
3212
|
+
part.image_url.url
|
|
3213
|
+
)) }) : null,
|
|
3214
|
+
fileParts.length > 0 ? /* @__PURE__ */ jsx11("div", { className: "flex flex-wrap gap-1.5", children: fileParts.map((part) => /* @__PURE__ */ jsxs9(
|
|
3215
|
+
"div",
|
|
3216
|
+
{
|
|
3217
|
+
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))]",
|
|
3218
|
+
title: part.name,
|
|
3219
|
+
children: [
|
|
3220
|
+
/* @__PURE__ */ jsx11(FileText, { size: 12, className: "shrink-0" }),
|
|
3221
|
+
/* @__PURE__ */ jsx11("span", { className: "max-w-56 truncate", children: part.name })
|
|
3222
|
+
]
|
|
3223
|
+
},
|
|
3224
|
+
`${part.name}-${part.data.slice(0, 32)}`
|
|
3225
|
+
)) }) : null,
|
|
3226
|
+
textContent
|
|
2333
3227
|
] });
|
|
2334
3228
|
}
|
|
2335
3229
|
|
|
@@ -2386,7 +3280,7 @@ var RenderErrorBoundary = class extends Component {
|
|
|
2386
3280
|
};
|
|
2387
3281
|
|
|
2388
3282
|
// src/components/PostChatFollowupBlock.tsx
|
|
2389
|
-
import { useCallback as
|
|
3283
|
+
import { useCallback as useCallback6, useEffect as useEffect9, useRef as useRef10, useState as useState11 } from "react";
|
|
2390
3284
|
import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2391
3285
|
function emitInteraction(callback, event) {
|
|
2392
3286
|
try {
|
|
@@ -2397,9 +3291,6 @@ function emitInteraction(callback, event) {
|
|
|
2397
3291
|
function basename(path) {
|
|
2398
3292
|
return path.split(/[\\/]/).filter(Boolean).pop() || path;
|
|
2399
3293
|
}
|
|
2400
|
-
function isVideo(path) {
|
|
2401
|
-
return /\.(?:mp4|mov|webm|mkv|avi|m4v)$/i.test(path);
|
|
2402
|
-
}
|
|
2403
3294
|
function ArtifactCard({
|
|
2404
3295
|
artifact,
|
|
2405
3296
|
sessionId,
|
|
@@ -2409,7 +3300,7 @@ function ArtifactCard({
|
|
|
2409
3300
|
onArtifactOpened
|
|
2410
3301
|
}) {
|
|
2411
3302
|
const client = useBladeClient();
|
|
2412
|
-
const [downloading, setDownloading] =
|
|
3303
|
+
const [downloading, setDownloading] = useState11(false);
|
|
2413
3304
|
const name = artifact.label || basename(artifact.target);
|
|
2414
3305
|
if (artifact.kind === "link") {
|
|
2415
3306
|
return /* @__PURE__ */ jsxs11(
|
|
@@ -2430,44 +3321,48 @@ ${artifact.target}`,
|
|
|
2430
3321
|
}
|
|
2431
3322
|
);
|
|
2432
3323
|
}
|
|
2433
|
-
const
|
|
2434
|
-
|
|
2435
|
-
|
|
3324
|
+
const fileName = basename(artifact.target);
|
|
3325
|
+
const downloadUrl = sessionId ? client.buildAuthedUrl(
|
|
3326
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/files/${encodeURIComponent(artifact.target)}`
|
|
3327
|
+
) : void 0;
|
|
3328
|
+
const handleDownload = async (event) => {
|
|
3329
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
3330
|
+
event.preventDefault();
|
|
3331
|
+
if (!sessionId || downloading) return;
|
|
3332
|
+
setDownloading(true);
|
|
3333
|
+
emitInteraction(onInteraction, {
|
|
3334
|
+
type: "artifact_download_started",
|
|
3335
|
+
sessionId,
|
|
3336
|
+
assistantEntryId,
|
|
3337
|
+
artifactIndex,
|
|
3338
|
+
artifactKind: "file"
|
|
3339
|
+
});
|
|
3340
|
+
try {
|
|
3341
|
+
await client.sessions.downloadFile(sessionId, artifact.target, fileName);
|
|
3342
|
+
emitInteraction(onInteraction, {
|
|
3343
|
+
type: "artifact_download_succeeded",
|
|
3344
|
+
sessionId,
|
|
3345
|
+
assistantEntryId,
|
|
3346
|
+
artifactIndex,
|
|
3347
|
+
artifactKind: "file"
|
|
3348
|
+
});
|
|
3349
|
+
} catch {
|
|
3350
|
+
} finally {
|
|
3351
|
+
setDownloading(false);
|
|
3352
|
+
}
|
|
3353
|
+
};
|
|
3354
|
+
return /* @__PURE__ */ jsx13(
|
|
3355
|
+
"a",
|
|
2436
3356
|
{
|
|
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
|
-
]
|
|
3357
|
+
href: downloadUrl,
|
|
3358
|
+
download: fileName,
|
|
3359
|
+
onClick: handleDownload,
|
|
3360
|
+
title: fileName,
|
|
3361
|
+
"aria-label": `\u4E0B\u8F7D\u6587\u4EF6\uFF1A${fileName}`,
|
|
3362
|
+
"aria-disabled": !sessionId || void 0,
|
|
3363
|
+
"aria-busy": downloading || void 0,
|
|
3364
|
+
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",
|
|
3365
|
+
children: fileName
|
|
2471
3366
|
}
|
|
2472
3367
|
);
|
|
2473
3368
|
}
|
|
@@ -2511,15 +3406,15 @@ function ResultFeedback({
|
|
|
2511
3406
|
onFeedbackSaved
|
|
2512
3407
|
}) {
|
|
2513
3408
|
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 =
|
|
3409
|
+
const [saved, setSaved] = useState11(savedFeedback ?? null);
|
|
3410
|
+
const [helpful, setHelpful] = useState11(savedFeedback?.helpful ?? null);
|
|
3411
|
+
const [reason, setReason] = useState11(savedFeedback?.reason ?? null);
|
|
3412
|
+
const [saving, setSaving] = useState11(false);
|
|
3413
|
+
const [saveError, setSaveError] = useState11(false);
|
|
3414
|
+
const reportedShown = useRef10(false);
|
|
3415
|
+
const latestChoice = useRef10(null);
|
|
2521
3416
|
const eligible = followup.feedback_eligible === true && Boolean(sessionId) && !isViewer;
|
|
2522
|
-
|
|
3417
|
+
useEffect9(() => {
|
|
2523
3418
|
if (!eligible || reportedShown.current) return;
|
|
2524
3419
|
reportedShown.current = true;
|
|
2525
3420
|
emitInteraction(onInteraction, {
|
|
@@ -2528,13 +3423,13 @@ function ResultFeedback({
|
|
|
2528
3423
|
assistantEntryId: followup.assistant_entry_id
|
|
2529
3424
|
});
|
|
2530
3425
|
}, [eligible, followup.assistant_entry_id, onInteraction, sessionId]);
|
|
2531
|
-
|
|
3426
|
+
useEffect9(() => {
|
|
2532
3427
|
if (!savedFeedback || latestChoice.current) return;
|
|
2533
3428
|
setSaved(savedFeedback);
|
|
2534
3429
|
setHelpful(savedFeedback.helpful);
|
|
2535
3430
|
setReason(savedFeedback.reason);
|
|
2536
3431
|
}, [savedFeedback]);
|
|
2537
|
-
const submit =
|
|
3432
|
+
const submit = useCallback6(
|
|
2538
3433
|
async (nextHelpful, nextReason) => {
|
|
2539
3434
|
if (!sessionId) return;
|
|
2540
3435
|
const choice = { helpful: nextHelpful, reason: nextReason };
|
|
@@ -2640,14 +3535,14 @@ function PostChatFollowupBlock({
|
|
|
2640
3535
|
savedFeedback,
|
|
2641
3536
|
onFeedbackSaved
|
|
2642
3537
|
}) {
|
|
2643
|
-
const [expanded, setExpanded] =
|
|
2644
|
-
const adopted =
|
|
2645
|
-
const reportedSuggestions =
|
|
2646
|
-
const reportedArtifacts =
|
|
2647
|
-
const openedArtifacts =
|
|
3538
|
+
const [expanded, setExpanded] = useState11(false);
|
|
3539
|
+
const adopted = useRef10(/* @__PURE__ */ new Set());
|
|
3540
|
+
const reportedSuggestions = useRef10(false);
|
|
3541
|
+
const reportedArtifacts = useRef10(/* @__PURE__ */ new Set());
|
|
3542
|
+
const openedArtifacts = useRef10(/* @__PURE__ */ new Set());
|
|
2648
3543
|
const artifacts = followup.final_artifacts ?? [];
|
|
2649
3544
|
const visibleArtifacts = expanded ? artifacts : artifacts.slice(0, 3);
|
|
2650
|
-
|
|
3545
|
+
useEffect9(() => {
|
|
2651
3546
|
if (!reportedSuggestions.current && followup.suggestions.length > 0) {
|
|
2652
3547
|
reportedSuggestions.current = true;
|
|
2653
3548
|
emitInteraction(onInteraction, {
|
|
@@ -2677,7 +3572,7 @@ function PostChatFollowupBlock({
|
|
|
2677
3572
|
sessionId,
|
|
2678
3573
|
visibleArtifacts
|
|
2679
3574
|
]);
|
|
2680
|
-
const reportArtifactOpened =
|
|
3575
|
+
const reportArtifactOpened = useCallback6(
|
|
2681
3576
|
(artifactIndex, artifactKind) => {
|
|
2682
3577
|
if (openedArtifacts.current.has(artifactIndex)) return;
|
|
2683
3578
|
openedArtifacts.current.add(artifactIndex);
|
|
@@ -2771,8 +3666,91 @@ function PostChatFollowupBlock({
|
|
|
2771
3666
|
}
|
|
2772
3667
|
|
|
2773
3668
|
// src/components/UserMessageBubble.tsx
|
|
2774
|
-
import {
|
|
3669
|
+
import {
|
|
3670
|
+
chatErrorForDisplay,
|
|
3671
|
+
getFileParts as getFileParts2,
|
|
3672
|
+
getImageParts as getImageParts2,
|
|
3673
|
+
getTextContent as getTextContent2
|
|
3674
|
+
} from "@blade-hq/agent-client";
|
|
3675
|
+
|
|
3676
|
+
// src/lib/whatif-prompt.ts
|
|
3677
|
+
var HEADER_RE = /^以下消息和 step 产物标记为 deprecated_by_rerun,请基于最新用户假设从 step(\d+) 开始完整重新推演,不要复用旧结论。$/;
|
|
3678
|
+
var QUOTE_HEADER_RE = /^\[步骤(\d+)\s*·\s*(.+?)\]$/;
|
|
3679
|
+
var USER_INPUT_TAG = "[\u7528\u6237\u8F93\u5165]";
|
|
3680
|
+
function parseWhatIfPrompt(text) {
|
|
3681
|
+
const lines = text.replace(/\r\n/g, "\n").trimEnd().split("\n");
|
|
3682
|
+
const headerMatch = lines[0]?.match(HEADER_RE);
|
|
3683
|
+
if (!headerMatch) return null;
|
|
3684
|
+
const userTagIdx = lines.indexOf(USER_INPUT_TAG);
|
|
3685
|
+
const hasUserTag = userTagIdx >= 0;
|
|
3686
|
+
const quoteBlockEndExclusive = hasUserTag ? userTagIdx : lines.length;
|
|
3687
|
+
const quoteHeaderIdxs = [];
|
|
3688
|
+
let quoteBlockFound = false;
|
|
3689
|
+
for (let i = 1; i < quoteBlockEndExclusive; i++) {
|
|
3690
|
+
if (!quoteBlockFound && lines[i].trim() === "[\u5F15\u7528]") {
|
|
3691
|
+
quoteBlockFound = true;
|
|
3692
|
+
} else if (quoteBlockFound && QUOTE_HEADER_RE.test(lines[i])) {
|
|
3693
|
+
quoteHeaderIdxs.push(i);
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
let legacyUserTextStart = -1;
|
|
3697
|
+
if (!hasUserTag && quoteHeaderIdxs.length > 0) {
|
|
3698
|
+
const lastSnapshotStart = quoteHeaderIdxs.at(-1) + 1;
|
|
3699
|
+
let i = lines.length - 1;
|
|
3700
|
+
while (i >= lastSnapshotStart && lines[i].trim() === "") i -= 1;
|
|
3701
|
+
while (i >= lastSnapshotStart && lines[i].trim() !== "") i -= 1;
|
|
3702
|
+
if (i >= lastSnapshotStart) legacyUserTextStart = i + 1;
|
|
3703
|
+
}
|
|
3704
|
+
const quotes = quoteHeaderIdxs.map((headerIdx, index) => {
|
|
3705
|
+
const match = lines[headerIdx].match(QUOTE_HEADER_RE);
|
|
3706
|
+
const nextHeader = quoteHeaderIdxs[index + 1];
|
|
3707
|
+
const end = nextHeader ?? (legacyUserTextStart >= 0 ? legacyUserTextStart : quoteBlockEndExclusive);
|
|
3708
|
+
const snapshotLines = lines.slice(headerIdx + 1, end);
|
|
3709
|
+
while (snapshotLines.at(-1)?.trim() === "") snapshotLines.pop();
|
|
3710
|
+
return {
|
|
3711
|
+
stepNumber: Number.parseInt(match[1], 10),
|
|
3712
|
+
label: match[2].trim(),
|
|
3713
|
+
snapshot: snapshotLines.join("\n")
|
|
3714
|
+
};
|
|
3715
|
+
});
|
|
3716
|
+
const userTextStart = hasUserTag ? userTagIdx + 1 : legacyUserTextStart;
|
|
3717
|
+
const userLines = userTextStart >= 0 ? lines.slice(userTextStart) : [];
|
|
3718
|
+
while (userLines[0]?.trim() === "") userLines.shift();
|
|
3719
|
+
while (userLines.at(-1)?.trim() === "") userLines.pop();
|
|
3720
|
+
const fromStep = Number.parseInt(headerMatch[1], 10);
|
|
3721
|
+
return {
|
|
3722
|
+
fromStep: Number.isFinite(fromStep) ? fromStep : null,
|
|
3723
|
+
quotes,
|
|
3724
|
+
userText: userLines.join("\n")
|
|
3725
|
+
};
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3728
|
+
// src/components/WhatIfUserBubble.tsx
|
|
2775
3729
|
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3730
|
+
function WhatIfUserBubble({ parsed, onQuoteClick }) {
|
|
3731
|
+
const { fromStep, quotes, userText } = parsed;
|
|
3732
|
+
return /* @__PURE__ */ jsxs12("div", { className: "flex flex-col items-end gap-2", children: [
|
|
3733
|
+
/* @__PURE__ */ jsxs12("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: [
|
|
3734
|
+
/* @__PURE__ */ jsx14(RefreshCcw, { size: 10 }),
|
|
3735
|
+
/* @__PURE__ */ jsx14("span", { children: fromStep != null ? `\u91CD\u8DD1\u81EA step ${fromStep}` : "\u91CD\u8DD1" })
|
|
3736
|
+
] }),
|
|
3737
|
+
quotes.length > 0 && /* @__PURE__ */ jsx14("div", { className: "flex max-w-[min(72vw,42rem)] flex-col items-stretch gap-2", children: quotes.map((quote, index) => {
|
|
3738
|
+
const clickable = quote.stepNumber != null && !!onQuoteClick;
|
|
3739
|
+
const label = quote.stepNumber != null ? `\u6B65\u9AA4${quote.stepNumber} \xB7 ${quote.label}` : quote.label;
|
|
3740
|
+
return /* @__PURE__ */ jsxs12("div", { className: "rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card)/0.8)] px-3 py-2 text-left", children: [
|
|
3741
|
+
/* @__PURE__ */ jsxs12("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: [
|
|
3742
|
+
/* @__PURE__ */ jsx14("span", { children: "\u21B3" }),
|
|
3743
|
+
/* @__PURE__ */ jsx14("span", { className: "truncate", children: label })
|
|
3744
|
+
] }),
|
|
3745
|
+
quote.snapshot ? /* @__PURE__ */ jsx14("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__ */ jsx14(MarkdownContent, { className: "blade-chat-prose", children: quote.snapshot }) }) : null
|
|
3746
|
+
] }, `${quote.stepNumber ?? "x"}-${index}`);
|
|
3747
|
+
}) }),
|
|
3748
|
+
userText && /* @__PURE__ */ jsx14("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__ */ jsx14(MarkdownContent, { className: "blade-chat-prose", children: userText }) })
|
|
3749
|
+
] });
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
// src/components/UserMessageBubble.tsx
|
|
3753
|
+
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2776
3754
|
function isUserMessage(message) {
|
|
2777
3755
|
return message.role === "user";
|
|
2778
3756
|
}
|
|
@@ -2782,10 +3760,14 @@ function isErrorMessage(message) {
|
|
|
2782
3760
|
var isSending = (message) => message.status === "streaming";
|
|
2783
3761
|
function UserMessageBubble({ message, className }) {
|
|
2784
3762
|
const text = getTextContent2(message.content).trim();
|
|
2785
|
-
const fileParts =
|
|
2786
|
-
const imageParts =
|
|
2787
|
-
|
|
2788
|
-
|
|
3763
|
+
const fileParts = getFileParts2(message.content);
|
|
3764
|
+
const imageParts = getImageParts2(message.content);
|
|
3765
|
+
const whatifParsed = text && imageParts.length === 0 && fileParts.length === 0 ? parseWhatIfPrompt(text) : null;
|
|
3766
|
+
if (whatifParsed) {
|
|
3767
|
+
return /* @__PURE__ */ jsx15("div", { className: cn("blade-chat-user-row flex justify-end", className), children: /* @__PURE__ */ jsx15("div", { className: "blade-chat-user-col flex max-w-[72%] flex-col items-end gap-3", children: /* @__PURE__ */ jsx15(WhatIfUserBubble, { parsed: whatifParsed }) }) });
|
|
3768
|
+
}
|
|
3769
|
+
return /* @__PURE__ */ jsx15("div", { className: cn("blade-chat-user-row flex justify-end", className), children: /* @__PURE__ */ jsxs13("div", { className: "blade-chat-user-col flex max-w-[72%] flex-col items-end gap-3", children: [
|
|
3770
|
+
imageParts.length > 0 && /* @__PURE__ */ jsx15("div", { className: "grid gap-2", children: imageParts.map((part) => /* @__PURE__ */ jsx15(
|
|
2789
3771
|
"img",
|
|
2790
3772
|
{
|
|
2791
3773
|
src: part.image_url.url,
|
|
@@ -2794,21 +3776,21 @@ function UserMessageBubble({ message, className }) {
|
|
|
2794
3776
|
},
|
|
2795
3777
|
part.image_url.url
|
|
2796
3778
|
)) }),
|
|
2797
|
-
fileParts.length > 0 && /* @__PURE__ */
|
|
3779
|
+
fileParts.length > 0 && /* @__PURE__ */ jsx15("div", { className: "flex flex-col items-end gap-1.5", children: fileParts.map((part) => /* @__PURE__ */ jsxs13(
|
|
2798
3780
|
"div",
|
|
2799
3781
|
{
|
|
2800
3782
|
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
3783
|
children: [
|
|
2802
|
-
/* @__PURE__ */
|
|
2803
|
-
/* @__PURE__ */
|
|
3784
|
+
/* @__PURE__ */ jsx15(FileText, { size: 12, className: "shrink-0" }),
|
|
3785
|
+
/* @__PURE__ */ jsx15("span", { className: "max-w-56 truncate", title: part.name, children: part.name })
|
|
2804
3786
|
]
|
|
2805
3787
|
},
|
|
2806
3788
|
`${part.name}-${part.data.length}`
|
|
2807
3789
|
)) }),
|
|
2808
|
-
text && /* @__PURE__ */
|
|
2809
|
-
text && isSending(message) && /* @__PURE__ */
|
|
2810
|
-
/* @__PURE__ */
|
|
2811
|
-
/* @__PURE__ */
|
|
3790
|
+
text && /* @__PURE__ */ jsx15("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__ */ jsx15(MarkdownContent, { className: "blade-chat-prose", children: text }) }),
|
|
3791
|
+
text && isSending(message) && /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1 pr-1 text-[11px] font-medium text-[hsl(var(--muted-foreground))/0.85]", children: [
|
|
3792
|
+
/* @__PURE__ */ jsx15(LoaderCircle, { size: 11, className: "animate-spin", "aria-hidden": "true" }),
|
|
3793
|
+
/* @__PURE__ */ jsx15("span", { children: "\u53D1\u9001\u4E2D" })
|
|
2812
3794
|
] })
|
|
2813
3795
|
] }) });
|
|
2814
3796
|
}
|
|
@@ -2816,12 +3798,12 @@ function ErrorMessageBlock({
|
|
|
2816
3798
|
message,
|
|
2817
3799
|
className
|
|
2818
3800
|
}) {
|
|
2819
|
-
const text = getTextContent2(message.content);
|
|
2820
|
-
return /* @__PURE__ */
|
|
3801
|
+
const text = chatErrorForDisplay(getTextContent2(message.content));
|
|
3802
|
+
return /* @__PURE__ */ jsx15("div", { className: cn("blade-chat-error-row flex min-w-0 justify-start", className), children: /* @__PURE__ */ jsx15("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
3803
|
}
|
|
2822
3804
|
|
|
2823
3805
|
// src/components/MessageList.tsx
|
|
2824
|
-
import { jsx as
|
|
3806
|
+
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2825
3807
|
function parseModeChange(message) {
|
|
2826
3808
|
if (message.kind !== "mode_change" || typeof message.content !== "string") {
|
|
2827
3809
|
return null;
|
|
@@ -2870,10 +3852,14 @@ function MessageList({
|
|
|
2870
3852
|
resultFeedbackByEntry = /* @__PURE__ */ new Map(),
|
|
2871
3853
|
onResultFeedbackSaved
|
|
2872
3854
|
}) {
|
|
3855
|
+
const userMessages = messages.filter((message) => isUserMessage(message));
|
|
3856
|
+
const latestUserMessage = userMessages.at(-1);
|
|
3857
|
+
const shouldPinLatestUser = latestUserMessage != null && (latestUserMessage.entry_id == null || latestUserMessage.entry_id.startsWith("local-user-"));
|
|
2873
3858
|
const renderBlocks = useMemo7(() => {
|
|
2874
3859
|
const visible = messages.filter((message) => {
|
|
2875
3860
|
if ((message.loop_name ?? "root") !== "root") return false;
|
|
2876
3861
|
if (isHiddenInternalMessage(message)) return false;
|
|
3862
|
+
if (message.kind === "context") return false;
|
|
2877
3863
|
if (message.kind === "compaction") return true;
|
|
2878
3864
|
return message.role !== "tool" || getPlanningDividerKind(message) !== null;
|
|
2879
3865
|
});
|
|
@@ -2916,7 +3902,7 @@ function MessageList({
|
|
|
2916
3902
|
blocks.push({
|
|
2917
3903
|
type: "message",
|
|
2918
3904
|
message,
|
|
2919
|
-
key: message.entry_id ?? `${message.role}-${blocks.length}`
|
|
3905
|
+
key: message.render_id ?? message.entry_id ?? `${message.role}-${blocks.length}`
|
|
2920
3906
|
});
|
|
2921
3907
|
}
|
|
2922
3908
|
flushAssistant();
|
|
@@ -2943,98 +3929,151 @@ function MessageList({
|
|
|
2943
3929
|
}
|
|
2944
3930
|
return blocks;
|
|
2945
3931
|
}, [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
|
-
|
|
3932
|
+
return /* @__PURE__ */ jsxs14("div", { className: cn("blade-chat-messages relative min-h-0 flex-1", className), children: [
|
|
3933
|
+
isStreaming ? /* @__PURE__ */ jsx16("output", { className: "sr-only", children: "\u6B63\u5728\u751F\u6210\u56DE\u590D" }) : null,
|
|
3934
|
+
/* @__PURE__ */ jsxs14(
|
|
3935
|
+
StickToBottom,
|
|
3936
|
+
{
|
|
3937
|
+
className: "h-full overflow-y-hidden",
|
|
3938
|
+
initial: "instant",
|
|
3939
|
+
resize: "instant",
|
|
3940
|
+
children: [
|
|
3941
|
+
/* @__PURE__ */ jsx16(StickToBottom.Content, { className: "blade-chat-messages-scroll", children: /* @__PURE__ */ jsx16("div", { className: "blade-chat-messages-content mx-auto max-w-[748px]", children: /* @__PURE__ */ jsxs14("div", { className: "flex min-w-0 flex-col", children: [
|
|
3942
|
+
renderBlocks.length === 0 ? emptyState ?? /* @__PURE__ */ jsxs14("div", { className: "blade-chat-empty", children: [
|
|
3943
|
+
/* @__PURE__ */ jsx16(MessageSquare, { size: 40, strokeWidth: 1.5 }),
|
|
3944
|
+
/* @__PURE__ */ jsx16("span", { className: "text-base font-medium", children: "\u5F00\u59CB\u5BF9\u8BDD" }),
|
|
3945
|
+
/* @__PURE__ */ jsx16("span", { className: "text-sm opacity-60", children: "\u5728\u4E0B\u65B9\u8F93\u5165\u6D88\u606F\u5F00\u59CB\u804A\u5929" })
|
|
3946
|
+
] }) : renderBlocks.map((block) => {
|
|
3947
|
+
if (block.type === "message") {
|
|
3948
|
+
return /* @__PURE__ */ jsx16("div", { "data-entry-id": block.message.entry_id, children: isUserMessage(block.message) ? /* @__PURE__ */ jsx16(UserMessageBubble, { message: block.message }) : isErrorMessage(block.message) ? /* @__PURE__ */ jsx16(ErrorMessageBlock, { message: block.message }) : null }, block.key);
|
|
3949
|
+
}
|
|
3950
|
+
if (block.type === "assistant_turn") {
|
|
3951
|
+
const blockFeedback = block.messages.map(
|
|
3952
|
+
(message) => message.entry_id ? resultFeedbackByEntry.get(message.entry_id) : void 0
|
|
3953
|
+
).find((feedback) => feedback != null);
|
|
3954
|
+
const hasActiveFollowup = Boolean(
|
|
3955
|
+
postChatFollowup && block.messages.some(
|
|
3956
|
+
(message) => message.entry_id === postChatFollowup.assistant_entry_id
|
|
3957
|
+
)
|
|
3958
|
+
);
|
|
3959
|
+
return /* @__PURE__ */ jsx16("div", { "data-entry-id": block.messages[0]?.entry_id, children: /* @__PURE__ */ jsxs14(
|
|
3960
|
+
RenderErrorBoundary,
|
|
2974
3961
|
{
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
3962
|
+
label: "\u52A9\u624B\u6D88\u606F",
|
|
3963
|
+
details: block.key,
|
|
3964
|
+
resetKey: getMessageResetSignature(block.messages),
|
|
3965
|
+
children: [
|
|
3966
|
+
/* @__PURE__ */ jsx16(
|
|
3967
|
+
AssistantTurnBlock,
|
|
3968
|
+
{
|
|
3969
|
+
messages: block.messages,
|
|
3970
|
+
isStreaming: block.isStreaming,
|
|
3971
|
+
askAnswers,
|
|
3972
|
+
onAnswer,
|
|
3973
|
+
sessionStatus,
|
|
3974
|
+
toolCallRenderer,
|
|
3975
|
+
sessionId
|
|
3976
|
+
}
|
|
3977
|
+
),
|
|
3978
|
+
blockFeedback && !hasActiveFollowup ? /* @__PURE__ */ jsx16(HistoricalResultFeedback, { feedback: blockFeedback }) : null,
|
|
3979
|
+
hasActiveFollowup && postChatFollowup ? /* @__PURE__ */ jsx16(
|
|
3980
|
+
PostChatFollowupBlock,
|
|
3981
|
+
{
|
|
3982
|
+
followup: postChatFollowup,
|
|
3983
|
+
sessionId,
|
|
3984
|
+
onSuggestion,
|
|
3985
|
+
isViewer,
|
|
3986
|
+
onInteraction: onFollowupInteraction,
|
|
3987
|
+
savedFeedback: blockFeedback,
|
|
3988
|
+
onFeedbackSaved: onResultFeedbackSaved
|
|
3989
|
+
}
|
|
3990
|
+
) : null
|
|
3991
|
+
]
|
|
2982
3992
|
}
|
|
2983
|
-
),
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
3993
|
+
) }, block.key);
|
|
3994
|
+
}
|
|
3995
|
+
if (block.type === "compaction") {
|
|
3996
|
+
return /* @__PURE__ */ jsxs14(
|
|
3997
|
+
"div",
|
|
2987
3998
|
{
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
}
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
"div",
|
|
3999
|
+
className: "flex items-center gap-2 text-xs text-[hsl(var(--muted-foreground))]",
|
|
4000
|
+
children: [
|
|
4001
|
+
/* @__PURE__ */ jsx16(Layers, { size: 12 }),
|
|
4002
|
+
/* @__PURE__ */ jsx16("span", { children: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29" })
|
|
4003
|
+
]
|
|
4004
|
+
},
|
|
4005
|
+
block.key
|
|
4006
|
+
);
|
|
4007
|
+
}
|
|
4008
|
+
return /* @__PURE__ */ jsx16(PlanningDivider, { kind: block.kind }, block.key);
|
|
4009
|
+
}),
|
|
4010
|
+
sessionStatus === "interrupted" && !isStreaming ? /* @__PURE__ */ jsx16("div", { className: "flex", children: /* @__PURE__ */ jsx16("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
|
|
4011
|
+
] }) }) }),
|
|
4012
|
+
/* @__PURE__ */ jsx16(
|
|
4013
|
+
PinLatestUserMessage,
|
|
3004
4014
|
{
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
/* @__PURE__ */ jsx15("span", { children: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29" })
|
|
3009
|
-
]
|
|
4015
|
+
userMessageCount: userMessages.length,
|
|
4016
|
+
shouldPinLatestUser,
|
|
4017
|
+
targetKey: latestUserMessage?.render_id ?? latestUserMessage?.entry_id ?? (latestUserMessage ? `user:${userMessages.length}` : null)
|
|
3010
4018
|
},
|
|
3011
|
-
|
|
3012
|
-
)
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
}
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
/* @__PURE__ */ jsx15(ScrollToBottomButton, {})
|
|
3020
|
-
] }) });
|
|
4019
|
+
sessionId ?? "no-session"
|
|
4020
|
+
),
|
|
4021
|
+
/* @__PURE__ */ jsx16(ScrollToBottomButton, {})
|
|
4022
|
+
]
|
|
4023
|
+
},
|
|
4024
|
+
sessionId ?? "no-session"
|
|
4025
|
+
)
|
|
4026
|
+
] });
|
|
3021
4027
|
}
|
|
3022
|
-
function
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
4028
|
+
function PinLatestUserMessage({
|
|
4029
|
+
userMessageCount,
|
|
4030
|
+
shouldPinLatestUser,
|
|
4031
|
+
targetKey
|
|
4032
|
+
}) {
|
|
4033
|
+
const { contentRef, scrollRef, scrollToBottom, stopScroll } = useStickToBottomContext();
|
|
4034
|
+
const previousCountRef = useRef11(userMessageCount);
|
|
4035
|
+
const spacerHeightRef = useRef11(0);
|
|
4036
|
+
const getScrollElement = useCallback7(() => scrollRef.current, [scrollRef]);
|
|
4037
|
+
const getContentElement = useCallback7(() => contentRef.current, [contentRef]);
|
|
4038
|
+
const getTargetElement = useCallback7(() => {
|
|
4039
|
+
const rows = contentRef.current?.querySelectorAll(".blade-chat-user-row");
|
|
4040
|
+
return rows?.item((rows?.length ?? 0) - 1) ?? null;
|
|
4041
|
+
}, [contentRef]);
|
|
4042
|
+
const getSpacerHeight = useCallback7(() => spacerHeightRef.current, []);
|
|
4043
|
+
const setSpacerHeight = useCallback7(
|
|
4044
|
+
(height) => {
|
|
4045
|
+
spacerHeightRef.current = height;
|
|
4046
|
+
const content = contentRef.current;
|
|
4047
|
+
if (!content) return;
|
|
4048
|
+
if (height > 0) content.style.setProperty("--blade-chat-pin-spacer", `${height}px`);
|
|
4049
|
+
else content.style.removeProperty("--blade-chat-pin-spacer");
|
|
4050
|
+
},
|
|
4051
|
+
[contentRef]
|
|
4052
|
+
);
|
|
4053
|
+
useMessagePin({
|
|
4054
|
+
targetKey,
|
|
4055
|
+
pinTarget: shouldPinLatestUser,
|
|
4056
|
+
getScrollElement,
|
|
4057
|
+
getContentElement,
|
|
4058
|
+
getTargetElement,
|
|
4059
|
+
getSpacerHeight,
|
|
4060
|
+
setSpacerHeight,
|
|
4061
|
+
stopAutoScroll: stopScroll,
|
|
4062
|
+
scrollToBottom
|
|
4063
|
+
});
|
|
4064
|
+
useEffect10(() => {
|
|
4065
|
+
if (userMessageCount > previousCountRef.current && !shouldPinLatestUser) {
|
|
3027
4066
|
scrollToBottom("instant");
|
|
3028
4067
|
}
|
|
3029
4068
|
previousCountRef.current = userMessageCount;
|
|
3030
|
-
}, [
|
|
4069
|
+
}, [scrollToBottom, shouldPinLatestUser, userMessageCount]);
|
|
3031
4070
|
return null;
|
|
3032
4071
|
}
|
|
3033
4072
|
function ScrollToBottomButton() {
|
|
3034
4073
|
const { isAtBottom, scrollToBottom } = useStickToBottomContext();
|
|
3035
|
-
const [visible, setVisible] =
|
|
3036
|
-
const hideTimerRef =
|
|
3037
|
-
|
|
4074
|
+
const [visible, setVisible] = useState12(false);
|
|
4075
|
+
const hideTimerRef = useRef11(null);
|
|
4076
|
+
useEffect10(() => {
|
|
3038
4077
|
if (isAtBottom) {
|
|
3039
4078
|
if (!hideTimerRef.current) {
|
|
3040
4079
|
hideTimerRef.current = setTimeout(() => {
|
|
@@ -3056,7 +4095,7 @@ function ScrollToBottomButton() {
|
|
|
3056
4095
|
}
|
|
3057
4096
|
};
|
|
3058
4097
|
}, [isAtBottom]);
|
|
3059
|
-
const handleClick =
|
|
4098
|
+
const handleClick = useCallback7(() => {
|
|
3060
4099
|
if (hideTimerRef.current) {
|
|
3061
4100
|
clearTimeout(hideTimerRef.current);
|
|
3062
4101
|
hideTimerRef.current = null;
|
|
@@ -3065,7 +4104,7 @@ function ScrollToBottomButton() {
|
|
|
3065
4104
|
scrollToBottom();
|
|
3066
4105
|
}, [scrollToBottom]);
|
|
3067
4106
|
if (!visible) return null;
|
|
3068
|
-
return /* @__PURE__ */
|
|
4107
|
+
return /* @__PURE__ */ jsxs14(
|
|
3069
4108
|
"button",
|
|
3070
4109
|
{
|
|
3071
4110
|
type: "button",
|
|
@@ -3073,25 +4112,25 @@ function ScrollToBottomButton() {
|
|
|
3073
4112
|
"aria-label": "\u6EDA\u52A8\u5230\u5E95\u90E8",
|
|
3074
4113
|
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
4114
|
children: [
|
|
3076
|
-
/* @__PURE__ */
|
|
3077
|
-
/* @__PURE__ */
|
|
4115
|
+
/* @__PURE__ */ jsx16(ChevronDown, { size: 14 }),
|
|
4116
|
+
/* @__PURE__ */ jsx16("span", { className: "blade-chat-scroll-bottom-label", children: "\u6EDA\u52A8\u5230\u5E95\u90E8" })
|
|
3078
4117
|
]
|
|
3079
4118
|
}
|
|
3080
4119
|
);
|
|
3081
4120
|
}
|
|
3082
4121
|
function PlanningDivider({ kind }) {
|
|
3083
|
-
return /* @__PURE__ */
|
|
3084
|
-
/* @__PURE__ */
|
|
3085
|
-
/* @__PURE__ */
|
|
3086
|
-
/* @__PURE__ */
|
|
3087
|
-
/* @__PURE__ */
|
|
4122
|
+
return /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-3 py-1", children: [
|
|
4123
|
+
/* @__PURE__ */ jsx16("div", { className: "h-px flex-1 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent" }),
|
|
4124
|
+
/* @__PURE__ */ jsxs14("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: [
|
|
4125
|
+
/* @__PURE__ */ jsx16(Lightbulb, { size: 12 }),
|
|
4126
|
+
/* @__PURE__ */ jsx16("span", { children: kind === "enter" ? "\u8FDB\u5165\u89C4\u5212\u6A21\u5F0F" : "\u89C4\u5212\u5B8C\u6210" })
|
|
3088
4127
|
] }),
|
|
3089
|
-
/* @__PURE__ */
|
|
4128
|
+
/* @__PURE__ */ jsx16("div", { className: "h-px flex-1 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent" })
|
|
3090
4129
|
] });
|
|
3091
4130
|
}
|
|
3092
4131
|
|
|
3093
4132
|
// src/components/ChatSurface.tsx
|
|
3094
|
-
import { jsx as
|
|
4133
|
+
import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
3095
4134
|
function themeAttr(theme) {
|
|
3096
4135
|
return theme === "dark" ? "dark" : void 0;
|
|
3097
4136
|
}
|
|
@@ -3123,7 +4162,7 @@ function ChatSurface({
|
|
|
3123
4162
|
beforeInput,
|
|
3124
4163
|
banner
|
|
3125
4164
|
}) {
|
|
3126
|
-
return /* @__PURE__ */
|
|
4165
|
+
return /* @__PURE__ */ jsxs15(
|
|
3127
4166
|
"div",
|
|
3128
4167
|
{
|
|
3129
4168
|
"data-theme": themeAttr(theme),
|
|
@@ -3132,14 +4171,14 @@ function ChatSurface({
|
|
|
3132
4171
|
classNames?.root
|
|
3133
4172
|
),
|
|
3134
4173
|
children: [
|
|
3135
|
-
/* @__PURE__ */
|
|
4174
|
+
/* @__PURE__ */ jsx17(ConnectionBanner, { connection, className: classNames?.banner }),
|
|
3136
4175
|
banner,
|
|
3137
|
-
errorMessage && /* @__PURE__ */
|
|
3138
|
-
/* @__PURE__ */
|
|
3139
|
-
/* @__PURE__ */
|
|
4176
|
+
errorMessage && /* @__PURE__ */ jsxs15("div", { className: "blade-chat-error-bar flex items-start gap-2 border-b px-4 py-3 text-sm", children: [
|
|
4177
|
+
/* @__PURE__ */ jsx17(CircleAlert, { size: 16, className: "mt-0.5 shrink-0" }),
|
|
4178
|
+
/* @__PURE__ */ jsx17("span", { className: "min-w-0 whitespace-pre-wrap break-words [overflow-wrap:anywhere]", children: chatErrorForDisplay2(errorMessage) })
|
|
3140
4179
|
] }),
|
|
3141
4180
|
slots?.header,
|
|
3142
|
-
/* @__PURE__ */
|
|
4181
|
+
/* @__PURE__ */ jsx17(
|
|
3143
4182
|
MessageList,
|
|
3144
4183
|
{
|
|
3145
4184
|
messages,
|
|
@@ -3160,7 +4199,7 @@ function ChatSurface({
|
|
|
3160
4199
|
}
|
|
3161
4200
|
),
|
|
3162
4201
|
beforeInput,
|
|
3163
|
-
/* @__PURE__ */
|
|
4202
|
+
/* @__PURE__ */ jsx17(
|
|
3164
4203
|
ChatInput,
|
|
3165
4204
|
{
|
|
3166
4205
|
value: inputText,
|
|
@@ -3180,13 +4219,13 @@ function ChatSurface({
|
|
|
3180
4219
|
}
|
|
3181
4220
|
|
|
3182
4221
|
// src/components/AgentChat.tsx
|
|
3183
|
-
import { Fragment as Fragment3, jsx as
|
|
4222
|
+
import { Fragment as Fragment3, jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
3184
4223
|
function isUnauthorizedError(error) {
|
|
3185
4224
|
return error instanceof BladeApiError && error.status === 401;
|
|
3186
4225
|
}
|
|
3187
4226
|
function LoginCard({ client, onLoggedIn }) {
|
|
3188
|
-
const [loggingIn, setLoggingIn] =
|
|
3189
|
-
const [loginError, setLoginError] =
|
|
4227
|
+
const [loggingIn, setLoggingIn] = useState13(false);
|
|
4228
|
+
const [loginError, setLoginError] = useState13(null);
|
|
3190
4229
|
const handleLogin = async () => {
|
|
3191
4230
|
setLoggingIn(true);
|
|
3192
4231
|
setLoginError(null);
|
|
@@ -3199,11 +4238,11 @@ function LoginCard({ client, onLoggedIn }) {
|
|
|
3199
4238
|
setLoggingIn(false);
|
|
3200
4239
|
}
|
|
3201
4240
|
};
|
|
3202
|
-
return /* @__PURE__ */
|
|
3203
|
-
/* @__PURE__ */
|
|
3204
|
-
/* @__PURE__ */
|
|
3205
|
-
/* @__PURE__ */
|
|
3206
|
-
/* @__PURE__ */
|
|
4241
|
+
return /* @__PURE__ */ jsx18("div", { className: "blade-chat-login flex flex-1 items-center justify-center p-6", children: /* @__PURE__ */ jsxs16("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: [
|
|
4242
|
+
/* @__PURE__ */ jsx18(LockKeyhole, { size: 28, className: "text-[hsl(var(--muted-foreground))]" }),
|
|
4243
|
+
/* @__PURE__ */ jsx18("div", { className: "text-base font-medium text-[hsl(var(--foreground))]", children: "\u9700\u8981\u767B\u5F55\u540E\u4F7F\u7528" }),
|
|
4244
|
+
/* @__PURE__ */ jsx18("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" }),
|
|
4245
|
+
/* @__PURE__ */ jsx18(
|
|
3207
4246
|
"button",
|
|
3208
4247
|
{
|
|
3209
4248
|
type: "button",
|
|
@@ -3213,20 +4252,20 @@ function LoginCard({ client, onLoggedIn }) {
|
|
|
3213
4252
|
children: loggingIn ? "\u767B\u5F55\u4E2D\u2026" : "\u767B\u5F55"
|
|
3214
4253
|
}
|
|
3215
4254
|
),
|
|
3216
|
-
loginError && /* @__PURE__ */
|
|
4255
|
+
loginError && /* @__PURE__ */ jsx18("div", { className: "text-xs text-[hsl(var(--destructive))]", children: loginError })
|
|
3217
4256
|
] }) });
|
|
3218
4257
|
}
|
|
3219
4258
|
function AgentChat(props) {
|
|
3220
4259
|
const client = useBladeClient();
|
|
3221
|
-
const [attempt, setAttempt] =
|
|
3222
|
-
const [needLogin, setNeedLogin] =
|
|
4260
|
+
const [attempt, setAttempt] = useState13(0);
|
|
4261
|
+
const [needLogin, setNeedLogin] = useState13(() => !client.hasToken());
|
|
3223
4262
|
if (needLogin) {
|
|
3224
|
-
return /* @__PURE__ */
|
|
4263
|
+
return /* @__PURE__ */ jsx18(
|
|
3225
4264
|
"div",
|
|
3226
4265
|
{
|
|
3227
4266
|
"data-theme": themeAttr(props.theme),
|
|
3228
4267
|
className: cn("blade-chat flex min-h-0 flex-1 flex-col", props.classNames?.root),
|
|
3229
|
-
children: /* @__PURE__ */
|
|
4268
|
+
children: /* @__PURE__ */ jsx18(
|
|
3230
4269
|
LoginCard,
|
|
3231
4270
|
{
|
|
3232
4271
|
client,
|
|
@@ -3239,7 +4278,7 @@ function AgentChat(props) {
|
|
|
3239
4278
|
}
|
|
3240
4279
|
);
|
|
3241
4280
|
}
|
|
3242
|
-
return /* @__PURE__ */
|
|
4281
|
+
return /* @__PURE__ */ jsx18(ChatSessionView, { ...props, onUnauthorized: () => setNeedLogin(true) }, attempt);
|
|
3243
4282
|
}
|
|
3244
4283
|
function ChatSessionView({
|
|
3245
4284
|
sessionId,
|
|
@@ -3261,12 +4300,12 @@ function ChatSessionView({
|
|
|
3261
4300
|
onSessionCreated
|
|
3262
4301
|
});
|
|
3263
4302
|
const replay = useReplay(session);
|
|
3264
|
-
const [stopRequested, setStopRequested] =
|
|
3265
|
-
const [inputText, setInputText] =
|
|
3266
|
-
const [resultFeedback, setResultFeedback] =
|
|
4303
|
+
const [stopRequested, setStopRequested] = useState13(false);
|
|
4304
|
+
const [inputText, setInputText] = useState13("");
|
|
4305
|
+
const [resultFeedback, setResultFeedback] = useState13([]);
|
|
3267
4306
|
const resolvedSessionId = session?.sessionId;
|
|
3268
4307
|
const isViewer = state?.viewerRole === "viewer";
|
|
3269
|
-
|
|
4308
|
+
useEffect11(() => {
|
|
3270
4309
|
setResultFeedback([]);
|
|
3271
4310
|
if (!resolvedSessionId || isViewer) return;
|
|
3272
4311
|
let cancelled = false;
|
|
@@ -3295,18 +4334,18 @@ function ChatSessionView({
|
|
|
3295
4334
|
() => new Map(resultFeedback.map((item) => [item.assistant_entry_id, item])),
|
|
3296
4335
|
[resultFeedback]
|
|
3297
4336
|
);
|
|
3298
|
-
const handleResultFeedbackSaved =
|
|
4337
|
+
const handleResultFeedbackSaved = useCallback8((saved) => {
|
|
3299
4338
|
setResultFeedback((current) => [
|
|
3300
4339
|
...current.filter((item) => item.assistant_entry_id !== saved.assistant_entry_id),
|
|
3301
4340
|
saved
|
|
3302
4341
|
]);
|
|
3303
4342
|
}, []);
|
|
3304
|
-
|
|
4343
|
+
useEffect11(() => {
|
|
3305
4344
|
if (session) {
|
|
3306
4345
|
onSessionReady?.(session);
|
|
3307
4346
|
}
|
|
3308
4347
|
}, [session, onSessionReady]);
|
|
3309
|
-
|
|
4348
|
+
useEffect11(() => {
|
|
3310
4349
|
if (!session) return;
|
|
3311
4350
|
const offAttach = session.on("attachRequested", ({ label, content }) => {
|
|
3312
4351
|
setInputText((prev) => `${prev ? `${prev}
|
|
@@ -3322,12 +4361,12 @@ ${content}`);
|
|
|
3322
4361
|
offInsert();
|
|
3323
4362
|
};
|
|
3324
4363
|
}, [session]);
|
|
3325
|
-
|
|
4364
|
+
useEffect11(() => {
|
|
3326
4365
|
if (isUnauthorizedError(error)) {
|
|
3327
4366
|
onUnauthorized();
|
|
3328
4367
|
}
|
|
3329
4368
|
}, [error, onUnauthorized]);
|
|
3330
|
-
|
|
4369
|
+
useEffect11(() => {
|
|
3331
4370
|
if (!session || !commands) return;
|
|
3332
4371
|
const unsubscribes = Object.entries(commands).map(
|
|
3333
4372
|
([action, handler]) => session.onCommand(action, (payload) => handler(payload))
|
|
@@ -3348,7 +4387,7 @@ ${content}`);
|
|
|
3348
4387
|
setStopRequested(true);
|
|
3349
4388
|
void session?.stop();
|
|
3350
4389
|
};
|
|
3351
|
-
return /* @__PURE__ */
|
|
4390
|
+
return /* @__PURE__ */ jsx18(
|
|
3352
4391
|
ChatSurface,
|
|
3353
4392
|
{
|
|
3354
4393
|
theme,
|
|
@@ -3358,8 +4397,8 @@ ${content}`);
|
|
|
3358
4397
|
slots,
|
|
3359
4398
|
placeholder,
|
|
3360
4399
|
connection: state?.connection ?? "connecting",
|
|
3361
|
-
banner: /* @__PURE__ */
|
|
3362
|
-
/* @__PURE__ */
|
|
4400
|
+
banner: /* @__PURE__ */ jsxs16(Fragment3, { children: [
|
|
4401
|
+
/* @__PURE__ */ jsx18(
|
|
3363
4402
|
ReplayBar,
|
|
3364
4403
|
{
|
|
3365
4404
|
isReplay: replay.isReplay,
|
|
@@ -3369,7 +4408,7 @@ ${content}`);
|
|
|
3369
4408
|
onExit: () => void replay.exitToAutonomous()
|
|
3370
4409
|
}
|
|
3371
4410
|
),
|
|
3372
|
-
/* @__PURE__ */
|
|
4411
|
+
/* @__PURE__ */ jsx18(ReplayMismatchPrompt, { mismatch: replay.mismatch })
|
|
3373
4412
|
] }),
|
|
3374
4413
|
errorMessage,
|
|
3375
4414
|
messages: state?.messages ?? [],
|
|
@@ -3398,11 +4437,11 @@ ${content}`);
|
|
|
3398
4437
|
}
|
|
3399
4438
|
|
|
3400
4439
|
// src/components/LlmChat.tsx
|
|
3401
|
-
import { useEffect as
|
|
4440
|
+
import { useEffect as useEffect12, useMemo as useMemo9, useState as useState15 } from "react";
|
|
3402
4441
|
|
|
3403
4442
|
// src/components/LlmAdvancedSettings.tsx
|
|
3404
|
-
import { useState as
|
|
3405
|
-
import { jsx as
|
|
4443
|
+
import { useState as useState14 } from "react";
|
|
4444
|
+
import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
3406
4445
|
var FIELDS = [
|
|
3407
4446
|
{ id: "baseURL", label: "\u6A21\u578B\u670D\u52A1\u5730\u5740", placeholder: "http://\u5185\u7F51\u5730\u5740/v1" },
|
|
3408
4447
|
{ id: "model", label: "\u6A21\u578B", placeholder: "\u6A21\u578B\u540D\u79F0" },
|
|
@@ -3448,13 +4487,13 @@ function writeOverride(settings, baseURL, override) {
|
|
|
3448
4487
|
}
|
|
3449
4488
|
function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
3450
4489
|
const normalized = normalizeAdvanced(settings);
|
|
3451
|
-
const [open, setOpen] =
|
|
3452
|
-
const [draft, setDraft] =
|
|
4490
|
+
const [open, setOpen] = useState14(false);
|
|
4491
|
+
const [draft, setDraft] = useState14(override);
|
|
3453
4492
|
if (!normalized) return null;
|
|
3454
4493
|
const fields = FIELDS.filter((field) => normalized[field.id]);
|
|
3455
4494
|
const dirty = Object.keys(override).length > 0;
|
|
3456
|
-
return /* @__PURE__ */
|
|
3457
|
-
/* @__PURE__ */
|
|
4495
|
+
return /* @__PURE__ */ jsxs17("div", { className: "blade-chat-advanced border-t border-[hsl(var(--border))] px-4 py-2 text-xs", children: [
|
|
4496
|
+
/* @__PURE__ */ jsxs17(
|
|
3458
4497
|
"button",
|
|
3459
4498
|
{
|
|
3460
4499
|
type: "button",
|
|
@@ -3464,16 +4503,16 @@ function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
|
3464
4503
|
},
|
|
3465
4504
|
className: "flex items-center gap-1.5 text-[hsl(var(--muted-foreground))] transition-colors hover:text-[hsl(var(--foreground))]",
|
|
3466
4505
|
children: [
|
|
3467
|
-
/* @__PURE__ */
|
|
4506
|
+
/* @__PURE__ */ jsx19(Settings2, { size: 13 }),
|
|
3468
4507
|
"\u9AD8\u7EA7\u8BBE\u7F6E",
|
|
3469
|
-
dirty && /* @__PURE__ */
|
|
4508
|
+
dirty && /* @__PURE__ */ jsx19("span", { className: "text-[hsl(var(--primary))]", children: "\uFF08\u5DF2\u81EA\u5B9A\u4E49\uFF09" })
|
|
3470
4509
|
]
|
|
3471
4510
|
}
|
|
3472
4511
|
),
|
|
3473
|
-
open && /* @__PURE__ */
|
|
3474
|
-
fields.map((field) => /* @__PURE__ */
|
|
3475
|
-
/* @__PURE__ */
|
|
3476
|
-
/* @__PURE__ */
|
|
4512
|
+
open && /* @__PURE__ */ jsxs17("div", { className: "mt-2 flex flex-col gap-2", children: [
|
|
4513
|
+
fields.map((field) => /* @__PURE__ */ jsxs17("label", { className: "flex flex-col gap-1", children: [
|
|
4514
|
+
/* @__PURE__ */ jsx19("span", { className: "text-[hsl(var(--muted-foreground))]", children: field.label }),
|
|
4515
|
+
/* @__PURE__ */ jsx19(
|
|
3477
4516
|
"input",
|
|
3478
4517
|
{
|
|
3479
4518
|
type: field.secret ? "password" : "text",
|
|
@@ -3484,9 +4523,9 @@ function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
|
3484
4523
|
}
|
|
3485
4524
|
)
|
|
3486
4525
|
] }, field.id)),
|
|
3487
|
-
normalized.apiKey && /* @__PURE__ */
|
|
3488
|
-
/* @__PURE__ */
|
|
3489
|
-
/* @__PURE__ */
|
|
4526
|
+
normalized.apiKey && /* @__PURE__ */ jsx19("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" }),
|
|
4527
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex gap-2", children: [
|
|
4528
|
+
/* @__PURE__ */ jsx19(
|
|
3490
4529
|
"button",
|
|
3491
4530
|
{
|
|
3492
4531
|
type: "button",
|
|
@@ -3501,7 +4540,7 @@ function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
|
3501
4540
|
children: "\u4FDD\u5B58"
|
|
3502
4541
|
}
|
|
3503
4542
|
),
|
|
3504
|
-
/* @__PURE__ */
|
|
4543
|
+
/* @__PURE__ */ jsx19(
|
|
3505
4544
|
"button",
|
|
3506
4545
|
{
|
|
3507
4546
|
type: "button",
|
|
@@ -3520,7 +4559,7 @@ function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
|
3520
4559
|
}
|
|
3521
4560
|
|
|
3522
4561
|
// src/components/LlmChat.tsx
|
|
3523
|
-
import { jsx as
|
|
4562
|
+
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
3524
4563
|
function LlmChat({
|
|
3525
4564
|
classNames,
|
|
3526
4565
|
renderers,
|
|
@@ -3532,11 +4571,11 @@ function LlmChat({
|
|
|
3532
4571
|
onOverrideChange,
|
|
3533
4572
|
...options
|
|
3534
4573
|
}) {
|
|
3535
|
-
const [override, setOverride] =
|
|
4574
|
+
const [override, setOverride] = useState15(() => readOverride(advanced, options.baseURL));
|
|
3536
4575
|
const effective = { ...options, ...override };
|
|
3537
4576
|
const { messages, isStreaming, error, send, stop, reset } = useLlmChat(effective);
|
|
3538
|
-
const [inputText, setInputText] =
|
|
3539
|
-
const [stopRequested, setStopRequested] =
|
|
4577
|
+
const [inputText, setInputText] = useState15("");
|
|
4578
|
+
const [stopRequested, setStopRequested] = useState15(false);
|
|
3540
4579
|
const handle = useMemo9(
|
|
3541
4580
|
() => ({
|
|
3542
4581
|
insertText: (text) => setInputText((prev) => prev ? `${prev}
|
|
@@ -3546,10 +4585,10 @@ ${text}` : text),
|
|
|
3546
4585
|
}),
|
|
3547
4586
|
[send, reset]
|
|
3548
4587
|
);
|
|
3549
|
-
|
|
4588
|
+
useEffect12(() => {
|
|
3550
4589
|
onReady?.(handle);
|
|
3551
4590
|
}, [handle, onReady]);
|
|
3552
|
-
return /* @__PURE__ */
|
|
4591
|
+
return /* @__PURE__ */ jsx20(
|
|
3553
4592
|
ChatSurface,
|
|
3554
4593
|
{
|
|
3555
4594
|
theme,
|
|
@@ -3574,7 +4613,7 @@ ${text}` : text),
|
|
|
3574
4613
|
setStopRequested(true);
|
|
3575
4614
|
stop();
|
|
3576
4615
|
},
|
|
3577
|
-
beforeInput: advanced ? /* @__PURE__ */
|
|
4616
|
+
beforeInput: advanced ? /* @__PURE__ */ jsx20(
|
|
3578
4617
|
LlmAdvancedSettingsBar,
|
|
3579
4618
|
{
|
|
3580
4619
|
settings: advanced,
|
|
@@ -3592,14 +4631,14 @@ ${text}` : text),
|
|
|
3592
4631
|
}
|
|
3593
4632
|
|
|
3594
4633
|
// src/components/ChatView.tsx
|
|
3595
|
-
import { jsx as
|
|
4634
|
+
import { jsx as jsx21 } from "react/jsx-runtime";
|
|
3596
4635
|
function ChatView(props) {
|
|
3597
4636
|
const { mode, llm, onLlmReady, ...rest } = props;
|
|
3598
4637
|
if (mode === "llm") {
|
|
3599
4638
|
if (!llm) {
|
|
3600
4639
|
throw new Error('ChatView: mode="llm" \u9700\u8981\u540C\u65F6\u4F20 llm={{ baseURL, model }}');
|
|
3601
4640
|
}
|
|
3602
|
-
return /* @__PURE__ */
|
|
4641
|
+
return /* @__PURE__ */ jsx21(
|
|
3603
4642
|
LlmChat,
|
|
3604
4643
|
{
|
|
3605
4644
|
...llm,
|
|
@@ -3612,7 +4651,126 @@ function ChatView(props) {
|
|
|
3612
4651
|
}
|
|
3613
4652
|
);
|
|
3614
4653
|
}
|
|
3615
|
-
return /* @__PURE__ */
|
|
4654
|
+
return /* @__PURE__ */ jsx21(AgentChat, { ...rest });
|
|
4655
|
+
}
|
|
4656
|
+
|
|
4657
|
+
// src/components/ContextCard.tsx
|
|
4658
|
+
import {
|
|
4659
|
+
getContextDisplayState,
|
|
4660
|
+
getContextGroupDisplayState
|
|
4661
|
+
} from "@blade-hq/agent-client";
|
|
4662
|
+
import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
4663
|
+
function ContextCard({ context, className }) {
|
|
4664
|
+
const display = getContextDisplayState(context);
|
|
4665
|
+
return /* @__PURE__ */ jsxs18(
|
|
4666
|
+
"details",
|
|
4667
|
+
{
|
|
4668
|
+
className: `blade-chat-context-card group/context-card rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] text-sm ${className ?? ""}`,
|
|
4669
|
+
children: [
|
|
4670
|
+
/* @__PURE__ */ jsxs18("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: [
|
|
4671
|
+
/* @__PURE__ */ jsx22(
|
|
4672
|
+
Layers,
|
|
4673
|
+
{
|
|
4674
|
+
size: 15,
|
|
4675
|
+
className: "blade-chat-context-icon shrink-0 text-[hsl(var(--muted-foreground))]",
|
|
4676
|
+
"aria-hidden": "true"
|
|
4677
|
+
}
|
|
4678
|
+
),
|
|
4679
|
+
/* @__PURE__ */ jsxs18("span", { className: "blade-chat-context-copy min-w-0 flex-1", children: [
|
|
4680
|
+
/* @__PURE__ */ jsx22("span", { className: "blade-chat-context-title block font-medium text-[hsl(var(--foreground))]", children: display.title }),
|
|
4681
|
+
/* @__PURE__ */ jsx22("span", { className: "blade-chat-context-status block truncate text-xs text-[hsl(var(--muted-foreground))]", children: display.summary })
|
|
4682
|
+
] }),
|
|
4683
|
+
/* @__PURE__ */ jsx22(
|
|
4684
|
+
ChevronDown,
|
|
4685
|
+
{
|
|
4686
|
+
size: 14,
|
|
4687
|
+
className: "blade-chat-context-chevron shrink-0 text-[hsl(var(--muted-foreground))] transition-transform group-open/context-card:rotate-180",
|
|
4688
|
+
"aria-hidden": "true"
|
|
4689
|
+
}
|
|
4690
|
+
)
|
|
4691
|
+
] }),
|
|
4692
|
+
/* @__PURE__ */ jsx22("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 })
|
|
4693
|
+
]
|
|
4694
|
+
}
|
|
4695
|
+
);
|
|
4696
|
+
}
|
|
4697
|
+
function ContextGroupCard({ contexts, className }) {
|
|
4698
|
+
if (contexts.length === 0) return null;
|
|
4699
|
+
const single = contexts.length === 1 ? getContextDisplayState(contexts[0]) : null;
|
|
4700
|
+
const group = single ? null : getContextGroupDisplayState(contexts);
|
|
4701
|
+
return /* @__PURE__ */ jsxs18("details", { className: `blade-chat-context-card group/context-group rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] text-sm ${className ?? ""}`, children: [
|
|
4702
|
+
/* @__PURE__ */ jsxs18("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: [
|
|
4703
|
+
/* @__PURE__ */ jsx22(
|
|
4704
|
+
Layers,
|
|
4705
|
+
{
|
|
4706
|
+
size: 15,
|
|
4707
|
+
className: "blade-chat-context-icon shrink-0 text-[hsl(var(--muted-foreground))]",
|
|
4708
|
+
"aria-hidden": "true"
|
|
4709
|
+
}
|
|
4710
|
+
),
|
|
4711
|
+
/* @__PURE__ */ jsxs18("span", { className: "blade-chat-context-copy min-w-0 flex-1", children: [
|
|
4712
|
+
/* @__PURE__ */ jsx22("span", { className: "blade-chat-context-title block font-medium text-[hsl(var(--foreground))]", children: single ? single.title : `${group?.title} \xB7 ${group?.count} \u9879` }),
|
|
4713
|
+
/* @__PURE__ */ jsx22("span", { className: "blade-chat-context-status block truncate text-xs text-[hsl(var(--muted-foreground))]", children: single ? single.summary : group?.summary })
|
|
4714
|
+
] }),
|
|
4715
|
+
/* @__PURE__ */ jsx22(
|
|
4716
|
+
ChevronDown,
|
|
4717
|
+
{
|
|
4718
|
+
size: 14,
|
|
4719
|
+
className: "blade-chat-context-chevron shrink-0 text-[hsl(var(--muted-foreground))] transition-transform group-open/context-group:rotate-180",
|
|
4720
|
+
"aria-hidden": "true"
|
|
4721
|
+
}
|
|
4722
|
+
)
|
|
4723
|
+
] }),
|
|
4724
|
+
single ? /* @__PURE__ */ jsx22("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__ */ jsx22("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__ */ jsx22(
|
|
4725
|
+
ContextCard,
|
|
4726
|
+
{
|
|
4727
|
+
context
|
|
4728
|
+
},
|
|
4729
|
+
`${context.context_kind}:${context.context_key}`
|
|
4730
|
+
)) })
|
|
4731
|
+
] });
|
|
4732
|
+
}
|
|
4733
|
+
|
|
4734
|
+
// src/lib/agent-computer-command.ts
|
|
4735
|
+
var COMPUTER_LAUNCH_COMMAND_PATTERN = /(?:^|[\n;&|(]\s*)computer\s+launch(?:\s|$)/;
|
|
4736
|
+
function isAgentComputerCommand(command) {
|
|
4737
|
+
return COMPUTER_LAUNCH_COMMAND_PATTERN.test(command);
|
|
4738
|
+
}
|
|
4739
|
+
function isAgentComputerToolCall(argumentsJson) {
|
|
4740
|
+
if (!argumentsJson) return false;
|
|
4741
|
+
let command;
|
|
4742
|
+
try {
|
|
4743
|
+
const parsed = JSON.parse(argumentsJson);
|
|
4744
|
+
if (typeof parsed !== "object" || parsed === null) return false;
|
|
4745
|
+
command = parsed.command;
|
|
4746
|
+
} catch {
|
|
4747
|
+
return false;
|
|
4748
|
+
}
|
|
4749
|
+
return typeof command === "string" && isAgentComputerCommand(command);
|
|
4750
|
+
}
|
|
4751
|
+
var LAUNCH_SUCCESS_MARKER = "\u5DF2\u542F\u52A8 ";
|
|
4752
|
+
function resultContainsLaunchSuccessMarker(result, depth = 0) {
|
|
4753
|
+
if (depth > 2) return false;
|
|
4754
|
+
if (typeof result === "string") {
|
|
4755
|
+
if (result.includes(LAUNCH_SUCCESS_MARKER)) return true;
|
|
4756
|
+
try {
|
|
4757
|
+
return resultContainsLaunchSuccessMarker(JSON.parse(result), depth + 1);
|
|
4758
|
+
} catch {
|
|
4759
|
+
return false;
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4762
|
+
if (typeof result === "object" && result !== null) {
|
|
4763
|
+
for (const value of Object.values(result)) {
|
|
4764
|
+
if (typeof value === "string" && value.includes(LAUNCH_SUCCESS_MARKER)) return true;
|
|
4765
|
+
}
|
|
4766
|
+
}
|
|
4767
|
+
return false;
|
|
4768
|
+
}
|
|
4769
|
+
function classifyAgentComputerLaunchOutcome(toolCall) {
|
|
4770
|
+
if (toolCall.status === "error" || toolCall.status === "cancelled") return "failed";
|
|
4771
|
+
if (toolCall.status !== "done") return "pending";
|
|
4772
|
+
if (toolCall.result === void 0 || toolCall.result === null) return "unknown";
|
|
4773
|
+
return resultContainsLaunchSuccessMarker(toolCall.result) ? "succeeded" : "failed";
|
|
3616
4774
|
}
|
|
3617
4775
|
|
|
3618
4776
|
// src/index.ts
|
|
@@ -3621,13 +4779,24 @@ export {
|
|
|
3621
4779
|
AgentChat,
|
|
3622
4780
|
BladeProvider,
|
|
3623
4781
|
ChatView,
|
|
4782
|
+
ContextCard,
|
|
4783
|
+
ContextGroupCard,
|
|
3624
4784
|
LlmChat,
|
|
3625
4785
|
MarkdownContent,
|
|
4786
|
+
MemoryRefsHint,
|
|
3626
4787
|
ReplayBar,
|
|
3627
4788
|
ReplayMismatchPrompt,
|
|
4789
|
+
WhatIfUserBubble,
|
|
4790
|
+
classifyAgentComputerLaunchOutcome,
|
|
4791
|
+
collectMemoryRefs,
|
|
4792
|
+
isAgentComputerCommand,
|
|
4793
|
+
isAgentComputerToolCall,
|
|
4794
|
+
normalizeAdjacentUrlFormatting,
|
|
4795
|
+
parseWhatIfPrompt,
|
|
3628
4796
|
useAgentSession,
|
|
3629
4797
|
useBladeClient,
|
|
3630
4798
|
useLlmChat,
|
|
4799
|
+
useMessagePin,
|
|
3631
4800
|
useReplay
|
|
3632
4801
|
};
|
|
3633
4802
|
/*! Bundled license information:
|
|
@@ -3639,17 +4808,16 @@ lucide-react/dist/esm/createLucideIcon.js:
|
|
|
3639
4808
|
lucide-react/dist/esm/icons/arrow-right.js:
|
|
3640
4809
|
lucide-react/dist/esm/icons/arrow-up-right.js:
|
|
3641
4810
|
lucide-react/dist/esm/icons/arrow-up.js:
|
|
4811
|
+
lucide-react/dist/esm/icons/book-open.js:
|
|
3642
4812
|
lucide-react/dist/esm/icons/bot.js:
|
|
3643
|
-
lucide-react/dist/esm/icons/brain.js:
|
|
3644
4813
|
lucide-react/dist/esm/icons/check.js:
|
|
3645
4814
|
lucide-react/dist/esm/icons/chevron-down.js:
|
|
3646
4815
|
lucide-react/dist/esm/icons/chevron-right.js:
|
|
3647
4816
|
lucide-react/dist/esm/icons/circle-alert.js:
|
|
3648
4817
|
lucide-react/dist/esm/icons/copy.js:
|
|
3649
|
-
lucide-react/dist/esm/icons/
|
|
4818
|
+
lucide-react/dist/esm/icons/earth.js:
|
|
4819
|
+
lucide-react/dist/esm/icons/file-pen-line.js:
|
|
3650
4820
|
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
4821
|
lucide-react/dist/esm/icons/globe.js:
|
|
3654
4822
|
lucide-react/dist/esm/icons/layers.js:
|
|
3655
4823
|
lucide-react/dist/esm/icons/lightbulb.js:
|
|
@@ -3658,10 +4826,14 @@ lucide-react/dist/esm/icons/lock-keyhole.js:
|
|
|
3658
4826
|
lucide-react/dist/esm/icons/message-square-more.js:
|
|
3659
4827
|
lucide-react/dist/esm/icons/message-square.js:
|
|
3660
4828
|
lucide-react/dist/esm/icons/play.js:
|
|
4829
|
+
lucide-react/dist/esm/icons/refresh-ccw.js:
|
|
4830
|
+
lucide-react/dist/esm/icons/search.js:
|
|
3661
4831
|
lucide-react/dist/esm/icons/settings-2.js:
|
|
3662
4832
|
lucide-react/dist/esm/icons/sparkles.js:
|
|
3663
4833
|
lucide-react/dist/esm/icons/square.js:
|
|
4834
|
+
lucide-react/dist/esm/icons/terminal.js:
|
|
3664
4835
|
lucide-react/dist/esm/icons/triangle-alert.js:
|
|
4836
|
+
lucide-react/dist/esm/icons/wrench.js:
|
|
3665
4837
|
lucide-react/dist/esm/icons/x.js:
|
|
3666
4838
|
lucide-react/dist/esm/lucide-react.js:
|
|
3667
4839
|
(**
|