@blade-hq/agent-react 2610.0.0-beta.0 → 2610.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -545,7 +545,7 @@ function toToolCallInfo(call, results, failedToolIds) {
545
545
  }
546
546
 
547
547
  // src/components/AgentChat.tsx
548
- import { BladeApiError } from "@blade-hq/agent-client";
548
+ import { BladeApiError, latestPostChatFollowup } from "@blade-hq/agent-client";
549
549
 
550
550
  // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/createLucideIcon.js
551
551
  import { forwardRef as forwardRef2, createElement as createElement2 } from "react";
@@ -618,11 +618,16 @@ var createLucideIcon = (iconName, iconNode) => {
618
618
  return Component2;
619
619
  };
620
620
 
621
- // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/archive.js
622
- var Archive = createLucideIcon("Archive", [
623
- ["rect", { width: "20", height: "5", x: "2", y: "3", rx: "1", key: "1wp1u1" }],
624
- ["path", { d: "M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8", key: "1s80jp" }],
625
- ["path", { d: "M10 12h4", key: "a56b0p" }]
621
+ // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/arrow-right.js
622
+ var ArrowRight = createLucideIcon("ArrowRight", [
623
+ ["path", { d: "M5 12h14", key: "1ays0h" }],
624
+ ["path", { d: "m12 5 7 7-7 7", key: "xquz4c" }]
625
+ ]);
626
+
627
+ // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/arrow-up-right.js
628
+ var ArrowUpRight = createLucideIcon("ArrowUpRight", [
629
+ ["path", { d: "M7 7h10v10", key: "1tivn9" }],
630
+ ["path", { d: "M7 17 17 7", key: "1vkiza" }]
626
631
  ]);
627
632
 
628
633
  // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/arrow-up.js
@@ -699,24 +704,6 @@ var Download = createLucideIcon("Download", [
699
704
  ["line", { x1: "12", x2: "12", y1: "15", y2: "3", key: "1vk2je" }]
700
705
  ]);
701
706
 
702
- // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/file-code-2.js
703
- var FileCode2 = createLucideIcon("FileCode2", [
704
- ["path", { d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4", key: "1pf5j1" }],
705
- ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }],
706
- ["path", { d: "m5 12-3 3 3 3", key: "oke12k" }],
707
- ["path", { d: "m9 18 3-3-3-3", key: "112psh" }]
708
- ]);
709
-
710
- // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/file-spreadsheet.js
711
- var FileSpreadsheet = createLucideIcon("FileSpreadsheet", [
712
- ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", key: "1rqfz7" }],
713
- ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }],
714
- ["path", { d: "M8 13h2", key: "yr2amv" }],
715
- ["path", { d: "M14 13h2", key: "un5t4a" }],
716
- ["path", { d: "M8 17h2", key: "2yhykz" }],
717
- ["path", { d: "M14 17h2", key: "10kma7" }]
718
- ]);
719
-
720
707
  // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/file-text.js
721
708
  var FileText = createLucideIcon("FileText", [
722
709
  ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", key: "1rqfz7" }],
@@ -744,11 +731,11 @@ var Film = createLucideIcon("Film", [
744
731
  ["path", { d: "M17 16.5h4", key: "go4c1d" }]
745
732
  ]);
746
733
 
747
- // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/image.js
748
- var Image = createLucideIcon("Image", [
749
- ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
750
- ["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
751
- ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }]
734
+ // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/globe.js
735
+ var Globe = createLucideIcon("Globe", [
736
+ ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
737
+ ["path", { d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20", key: "13o1zl" }],
738
+ ["path", { d: "M2 12h20", key: "9i4pu4" }]
752
739
  ]);
753
740
 
754
741
  // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/layers.js
@@ -814,13 +801,6 @@ var MessageSquare = createLucideIcon("MessageSquare", [
814
801
  ["path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z", key: "1lielz" }]
815
802
  ]);
816
803
 
817
- // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/music.js
818
- var Music = createLucideIcon("Music", [
819
- ["path", { d: "M9 18V5l12-2v13", key: "1jmyc2" }],
820
- ["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
821
- ["circle", { cx: "18", cy: "16", r: "3", key: "1hluhg" }]
822
- ]);
823
-
824
804
  // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/play.js
825
805
  var Play = createLucideIcon("Play", [
826
806
  ["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]
@@ -834,6 +814,21 @@ var Settings2 = createLucideIcon("Settings2", [
834
814
  ["circle", { cx: "7", cy: "7", r: "3", key: "dfmy0x" }]
835
815
  ]);
836
816
 
817
+ // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/sparkles.js
818
+ var Sparkles = createLucideIcon("Sparkles", [
819
+ [
820
+ "path",
821
+ {
822
+ d: "M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z",
823
+ key: "4pj2yx"
824
+ }
825
+ ],
826
+ ["path", { d: "M20 3v4", key: "1olli1" }],
827
+ ["path", { d: "M22 5h-4", key: "1gvqau" }],
828
+ ["path", { d: "M4 17v2", key: "vumght" }],
829
+ ["path", { d: "M5 18H3", key: "zchphs" }]
830
+ ]);
831
+
837
832
  // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/square.js
838
833
  var Square = createLucideIcon("Square", [
839
834
  ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }]
@@ -859,7 +854,7 @@ var X = createLucideIcon("X", [
859
854
  ]);
860
855
 
861
856
  // src/components/AgentChat.tsx
862
- import { useEffect as useEffect7, useState as useState12 } from "react";
857
+ import { useCallback as useCallback7, useEffect as useEffect9, useMemo as useMemo8, useState as useState13 } from "react";
863
858
 
864
859
  // src/lib/utils.ts
865
860
  function cn(...inputs) {
@@ -1057,24 +1052,74 @@ function ChatInput({
1057
1052
  }
1058
1053
 
1059
1054
  // src/components/ConnectionBanner.tsx
1055
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
1060
1056
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1057
+ var CONNECTION_NOTICE_DELAY_MS = 3e3;
1058
+ var CONNECTION_ERROR_DELAY_MS = 15e3;
1059
+ function useConnectionNoticePhase(connected) {
1060
+ const [phase, setPhase] = useState4("hidden");
1061
+ const connectedRef = useRef3(connected);
1062
+ const timersRef = useRef3([]);
1063
+ connectedRef.current = connected;
1064
+ useEffect3(() => {
1065
+ const clearTimers = () => {
1066
+ for (const timer of timersRef.current) clearTimeout(timer);
1067
+ timersRef.current = [];
1068
+ };
1069
+ const startGracePeriod = () => {
1070
+ clearTimers();
1071
+ setPhase("hidden");
1072
+ timersRef.current = [
1073
+ setTimeout(() => setPhase("recovering"), CONNECTION_NOTICE_DELAY_MS),
1074
+ setTimeout(() => setPhase("failed"), CONNECTION_ERROR_DELAY_MS)
1075
+ ];
1076
+ };
1077
+ if (connected) {
1078
+ clearTimers();
1079
+ setPhase("hidden");
1080
+ } else {
1081
+ startGracePeriod();
1082
+ }
1083
+ const handleForeground = () => {
1084
+ if (!connectedRef.current) startGracePeriod();
1085
+ };
1086
+ const handleVisibilityChange = () => {
1087
+ if (document.visibilityState === "visible") handleForeground();
1088
+ };
1089
+ window.addEventListener("blade:app-active", handleForeground);
1090
+ window.addEventListener("focus", handleForeground);
1091
+ window.addEventListener("pageshow", handleForeground);
1092
+ document.addEventListener("visibilitychange", handleVisibilityChange);
1093
+ return () => {
1094
+ clearTimers();
1095
+ window.removeEventListener("blade:app-active", handleForeground);
1096
+ window.removeEventListener("focus", handleForeground);
1097
+ window.removeEventListener("pageshow", handleForeground);
1098
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
1099
+ };
1100
+ }, [connected]);
1101
+ return phase;
1102
+ }
1061
1103
  function ConnectionBanner({ connection, className }) {
1062
- if (connection === "connected" || connection === "connecting") {
1063
- return null;
1064
- }
1065
- const reconnecting = connection === "reconnecting";
1104
+ const hasConnectedRef = useRef3(connection === "connected" || connection === "reconnecting");
1105
+ if (connection === "connected") hasConnectedRef.current = true;
1106
+ const connected = connection === "connected";
1107
+ const phase = useConnectionNoticePhase(connected);
1108
+ if (connected || phase === "hidden") return null;
1109
+ const recovering = phase === "recovering";
1110
+ const firstConnection = !hasConnectedRef.current;
1066
1111
  return /* @__PURE__ */ jsx5("div", { className: cn("blade-chat-banner bg-[hsl(var(--background))] px-5 pt-3", className), children: /* @__PURE__ */ jsxs4(
1067
1112
  "div",
1068
1113
  {
1069
1114
  className: cn(
1070
1115
  "mx-auto flex max-w-3xl items-start gap-3 rounded-2xl border px-4 py-3",
1071
- reconnecting ? "border-amber-500/25 bg-amber-500/10 text-amber-100" : "border-rose-500/25 bg-rose-500/10 text-rose-100"
1116
+ recovering ? "border-amber-500/25 bg-amber-500/10 text-amber-100" : "border-rose-500/25 bg-rose-500/10 text-rose-100"
1072
1117
  ),
1073
1118
  children: [
1074
- /* @__PURE__ */ jsx5("span", { className: "mt-0.5 shrink-0", children: reconnecting ? /* @__PURE__ */ jsx5(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx5(TriangleAlert, { size: 14 }) }),
1119
+ /* @__PURE__ */ jsx5("span", { className: "mt-0.5 shrink-0", children: recovering ? /* @__PURE__ */ jsx5(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx5(TriangleAlert, { size: 14 }) }),
1075
1120
  /* @__PURE__ */ jsxs4("div", { className: "min-w-0", children: [
1076
- /* @__PURE__ */ jsx5("div", { className: "text-sm font-medium", children: reconnecting ? "\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF0C\u6B63\u5728\u91CD\u8FDE\u2026" : "\u8FDE\u63A5\u5DF2\u65AD\u5F00" }),
1077
- /* @__PURE__ */ jsx5("div", { className: "text-xs opacity-80", children: "\u6D88\u606F\u540C\u6B65\u53EF\u80FD\u4F1A\u5EF6\u8FDF\uFF0C\u7CFB\u7EDF\u4F1A\u7EE7\u7EED\u81EA\u52A8\u91CD\u8BD5" })
1121
+ /* @__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" }),
1122
+ /* @__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" })
1078
1123
  ] })
1079
1124
  ]
1080
1125
  }
@@ -1083,10 +1128,10 @@ function ConnectionBanner({ connection, className }) {
1083
1128
 
1084
1129
  // src/components/MessageList.tsx
1085
1130
  import { isHiddenInternalMessage } from "@blade-hq/agent-client";
1086
- import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo7, useRef as useRef6, useState as useState11 } from "react";
1131
+ import { useCallback as useCallback6, useEffect as useEffect8, useMemo as useMemo7, useRef as useRef8, useState as useState12 } from "react";
1087
1132
 
1088
1133
  // ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/useStickToBottom.js
1089
- import { useCallback as useCallback3, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
1134
+ import { useCallback as useCallback3, useMemo as useMemo3, useRef as useRef4, useState as useState5 } from "react";
1090
1135
  var DEFAULT_SPRING_ANIMATION = {
1091
1136
  /**
1092
1137
  * A value from 0 to 1, on how much to damp the animation.
@@ -1123,10 +1168,10 @@ globalThis.document?.addEventListener("click", () => {
1123
1168
  mouseDown = false;
1124
1169
  });
1125
1170
  var useStickToBottom = (options = {}) => {
1126
- const [escapedFromLock, updateEscapedFromLock] = useState4(false);
1127
- const [isAtBottom, updateIsAtBottom] = useState4(options.initial !== false);
1128
- const [isNearBottom, setIsNearBottom] = useState4(false);
1129
- const optionsRef = useRef3(null);
1171
+ const [escapedFromLock, updateEscapedFromLock] = useState5(false);
1172
+ const [isAtBottom, updateIsAtBottom] = useState5(options.initial !== false);
1173
+ const [isNearBottom, setIsNearBottom] = useState5(false);
1174
+ const optionsRef = useRef4(null);
1130
1175
  optionsRef.current = options;
1131
1176
  const isSelecting = useCallback3(() => {
1132
1177
  if (!mouseDown) {
@@ -1430,11 +1475,11 @@ function mergeAnimations(...animations) {
1430
1475
 
1431
1476
  // ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/StickToBottom.js
1432
1477
  import * as React from "react";
1433
- import { createContext as createContext2, useContext as useContext2, useEffect as useEffect3, useImperativeHandle, useLayoutEffect, useMemo as useMemo4, useRef as useRef4 } from "react";
1478
+ import { createContext as createContext2, useContext as useContext2, useEffect as useEffect4, useImperativeHandle, useLayoutEffect, useMemo as useMemo4, useRef as useRef5 } from "react";
1434
1479
  var StickToBottomContext = createContext2(null);
1435
- var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect3;
1480
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect4;
1436
1481
  function StickToBottom({ instance, children, resize, initial, mass, damping, stiffness, targetScrollTop: currentTargetScrollTop, contextRef, ...props }) {
1437
- const customTargetScrollTop = useRef4(null);
1482
+ const customTargetScrollTop = useRef5(null);
1438
1483
  const targetScrollTop = React.useCallback((target, elements) => {
1439
1484
  const get = context?.targetScrollTop ?? currentTargetScrollTop;
1440
1485
  return get?.(target, elements) ?? target;
@@ -1514,7 +1559,7 @@ import { getTextContent, normalizeMessageContent } from "@blade-hq/agent-client"
1514
1559
  import { useState as useState10 } from "react";
1515
1560
 
1516
1561
  // src/components/AgentLoopBlock.tsx
1517
- import { useState as useState5 } from "react";
1562
+ import { useState as useState6 } from "react";
1518
1563
 
1519
1564
  // src/components/display-utils.ts
1520
1565
  var TOOL_NAME_ALIASES = {
@@ -1646,7 +1691,7 @@ function parseAgentDescription(argumentsJson) {
1646
1691
  }
1647
1692
  }
1648
1693
  function AgentLoopBlock({ toolCall }) {
1649
- const [expanded, setExpanded] = useState5(false);
1694
+ const [expanded, setExpanded] = useState6(false);
1650
1695
  const description = parseAgentDescription(toolCall.arguments);
1651
1696
  const running = toolCall.status === "pending" || toolCall.status === "awaiting_answer";
1652
1697
  const failed = toolCall.status === "error" || toolCall.status === "cancelled";
@@ -1711,146 +1756,18 @@ function AgentLoopBlock({ toolCall }) {
1711
1756
 
1712
1757
  // src/components/MarkdownContent.tsx
1713
1758
  import {
1714
- useEffect as useEffect4,
1759
+ useEffect as useEffect5,
1715
1760
  useMemo as useMemo5,
1716
- useRef as useRef5,
1761
+ useRef as useRef6,
1717
1762
  useState as useState7
1718
1763
  } from "react";
1719
-
1720
- // src/lib/media-tags.ts
1721
- var FILE_CARD_TAG = "blade-file-card";
1722
- var MEDIA_LINE_RE = /^[ \t]*(?:[-*+][ \t]+|\d+\.[ \t]+)?(?<wrap>[`"'*_]*)MEDIA:[ \t]*(?<path>.+?)\k<wrap>[ \t]*[。,、;;,]?[ \t]*\r?$/gm;
1723
- var MEDIA_LINE_START_RE = /^[ \t]*(?:[-*+][ \t]+|\d+\.[ \t]+)?[`"'*_]*MEDIA:/;
1724
- function basename(path) {
1725
- return path.split(/[/\\]/).filter(Boolean).pop() || path;
1726
- }
1727
- function unwrapMediaPath(path) {
1728
- if (!path.startsWith("<")) return path;
1729
- const closingBracket = path.indexOf(">");
1730
- if (closingBracket < 0) return null;
1731
- return `${path.slice(1, closingBracket)}${path.slice(closingBracket + 1)}`;
1732
- }
1733
- function isSafeMediaPath(path) {
1734
- if (!path) return false;
1735
- return !path.split(/[/\\]/).includes("..");
1736
- }
1737
- function escapeAttribute(value) {
1738
- return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1739
- }
1740
- function foldMediaLines(text) {
1741
- return text.replace(MEDIA_LINE_RE, (match, _wrap, rawPath) => {
1742
- const path = unwrapMediaPath(rawPath);
1743
- if (path === null) return match;
1744
- const name = basename(path);
1745
- if (!isSafeMediaPath(path)) {
1746
- return `\`${name}\``;
1747
- }
1748
- return `<${FILE_CARD_TAG} data-path="${escapeAttribute(path)}" data-name="${escapeAttribute(name)}">${escapeAttribute(name)}</${FILE_CARD_TAG}>`;
1749
- });
1750
- }
1751
- function collapseMediaTags(text, options) {
1752
- if (!text.includes("MEDIA:")) return text;
1753
- if (!options?.streaming) return foldMediaLines(text);
1754
- const newlineIndex = text.lastIndexOf("\n");
1755
- const head = newlineIndex < 0 ? "" : text.slice(0, newlineIndex + 1);
1756
- const lastLine = newlineIndex < 0 ? text : text.slice(newlineIndex + 1);
1757
- if (MEDIA_LINE_START_RE.test(lastLine)) {
1758
- return foldMediaLines(head);
1759
- }
1760
- return foldMediaLines(head) + lastLine;
1761
- }
1762
-
1763
- // src/components/FileCard.tsx
1764
- import { useState as useState6 } from "react";
1765
1764
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1766
- var IMAGE_EXTS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "svg", "webp", "ico", "bmp"]);
1767
- var SHEET_EXTS = /* @__PURE__ */ new Set(["xlsx", "xls", "xlsm", "xlsb", "csv"]);
1768
- var TEXT_EXTS = /* @__PURE__ */ new Set(["txt", "md", "markdown", "pdf", "doc", "docx", "rtf"]);
1769
- var CODE_EXTS = /* @__PURE__ */ new Set(["js", "jsx", "ts", "tsx", "py", "go", "json", "yaml", "yml", "html", "css"]);
1770
- var VIDEO_EXTS = /* @__PURE__ */ new Set(["mp4", "webm", "mov", "mkv", "avi"]);
1771
- var AUDIO_EXTS = /* @__PURE__ */ new Set(["mp3", "wav", "ogg", "m4a", "flac"]);
1772
- var ARCHIVE_EXTS = /* @__PURE__ */ new Set(["zip", "tar", "gz", "tgz", "rar", "7z"]);
1773
- function getFileIcon(name) {
1774
- const ext = name.split(".").pop()?.toLowerCase() ?? "";
1775
- if (IMAGE_EXTS.has(ext)) return Image;
1776
- if (SHEET_EXTS.has(ext)) return FileSpreadsheet;
1777
- if (TEXT_EXTS.has(ext)) return FileText;
1778
- if (CODE_EXTS.has(ext)) return FileCode2;
1779
- if (VIDEO_EXTS.has(ext)) return Film;
1780
- if (AUDIO_EXTS.has(ext)) return Music;
1781
- if (ARCHIVE_EXTS.has(ext)) return Archive;
1782
- return File;
1783
- }
1784
- function stringValue(value) {
1785
- return typeof value === "string" ? value : void 0;
1786
- }
1787
- function extractChildrenText(children) {
1788
- if (typeof children === "string") return children;
1789
- if (Array.isArray(children)) return children.map(extractChildrenText).join("");
1790
- if (children && typeof children === "object" && "props" in children) {
1791
- return extractChildrenText(children.props?.children);
1792
- }
1793
- return "";
1794
- }
1795
- function FileCard({
1796
- node,
1797
- "data-path": pathAttribute,
1798
- "data-name": nameAttribute,
1799
- dataPath,
1800
- dataName,
1801
- sessionId,
1802
- children,
1803
- className,
1804
- ...props
1805
- }) {
1806
- const client = useBladeClient();
1807
- const [downloading, setDownloading] = useState6(false);
1808
- const [failed, setFailed] = useState6(false);
1809
- const nodeProperties = node && typeof node === "object" && "properties" in node ? node.properties : void 0;
1810
- const path = pathAttribute ?? dataPath ?? stringValue(nodeProperties?.["data-path"]) ?? stringValue(nodeProperties?.dataPath) ?? "";
1811
- const name = nameAttribute ?? dataName ?? stringValue(nodeProperties?.["data-name"]) ?? stringValue(nodeProperties?.dataName) ?? extractChildrenText(children) ?? "";
1812
- const disabled = !sessionId || !isSafeMediaPath(path);
1813
- const Icon2 = getFileIcon(name);
1814
- const handleDownload = async () => {
1815
- if (disabled || downloading || !sessionId) return;
1816
- setDownloading(true);
1817
- setFailed(false);
1818
- try {
1819
- await client.sessions.downloadFile(sessionId, path, name);
1820
- } catch {
1821
- setFailed(true);
1822
- } finally {
1823
- setDownloading(false);
1824
- }
1825
- };
1826
- return /* @__PURE__ */ jsxs6("span", { ...props, className: cn("blade-chat-file-card", className), children: [
1827
- /* @__PURE__ */ jsxs6(
1828
- "button",
1829
- {
1830
- type: "button",
1831
- onClick: handleDownload,
1832
- disabled: disabled || downloading,
1833
- "aria-label": `\u4E0B\u8F7D\u6587\u4EF6\uFF1A${name}`,
1834
- title: failed ? "\u4E0B\u8F7D\u5931\u8D25\uFF0C\u70B9\u51FB\u91CD\u8BD5" : `\u4E0B\u8F7D ${name}`,
1835
- children: [
1836
- /* @__PURE__ */ jsx7("span", { className: "blade-chat-file-card-icon", children: downloading ? /* @__PURE__ */ jsx7(LoaderCircle, { size: 15, className: "animate-spin" }) : /* @__PURE__ */ jsx7(Icon2, { size: 15 }) }),
1837
- /* @__PURE__ */ jsx7("span", { className: "blade-chat-file-card-name", children: name }),
1838
- /* @__PURE__ */ jsx7(Download, { size: 13, className: "blade-chat-file-card-action" })
1839
- ]
1840
- }
1841
- ),
1842
- failed && /* @__PURE__ */ jsx7("span", { className: "blade-chat-file-card-error", children: "\u4E0B\u8F7D\u5931\u8D25" })
1843
- ] });
1844
- }
1845
-
1846
- // src/components/MarkdownContent.tsx
1847
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1848
1765
  var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
1849
1766
  function CodeBlockPre({ children, node: _node, ...props }) {
1850
- const preRef = useRef5(null);
1767
+ const preRef = useRef6(null);
1851
1768
  const [copied, setCopied] = useState7(false);
1852
1769
  const [language, setLanguage] = useState7("");
1853
- useEffect4(() => {
1770
+ useEffect5(() => {
1854
1771
  const codeEl = preRef.current?.querySelector("code");
1855
1772
  setLanguage(codeEl?.className.match(/language-(\S+)/)?.[1] ?? "");
1856
1773
  }, []);
@@ -1861,10 +1778,10 @@ function CodeBlockPre({ children, node: _node, ...props }) {
1861
1778
  setTimeout(() => setCopied(false), 2e3);
1862
1779
  }
1863
1780
  };
1864
- return /* @__PURE__ */ jsxs7("div", { className: "blade-chat-codeblock not-prose my-3 overflow-hidden rounded-xl border border-[hsl(var(--border))]", children: [
1865
- /* @__PURE__ */ jsxs7("div", { className: "blade-chat-codeblock-header flex h-[34px] items-center justify-between border-b border-[hsl(var(--border))] bg-[hsl(var(--muted))/0.5] pl-3.5 pr-1.5", children: [
1866
- /* @__PURE__ */ jsx8("span", { className: "font-mono text-[12px] text-[hsl(var(--muted-foreground))]", children: language || "code" }),
1867
- /* @__PURE__ */ jsxs7(
1781
+ return /* @__PURE__ */ jsxs6("div", { className: "blade-chat-codeblock not-prose my-3 overflow-hidden rounded-xl border border-[hsl(var(--border))]", children: [
1782
+ /* @__PURE__ */ jsxs6("div", { className: "blade-chat-codeblock-header flex h-[34px] items-center justify-between border-b border-[hsl(var(--border))] bg-[hsl(var(--muted))/0.5] pl-3.5 pr-1.5", children: [
1783
+ /* @__PURE__ */ jsx7("span", { className: "font-mono text-[12px] text-[hsl(var(--muted-foreground))]", children: language || "code" }),
1784
+ /* @__PURE__ */ jsxs6(
1868
1785
  "button",
1869
1786
  {
1870
1787
  type: "button",
@@ -1874,13 +1791,13 @@ function CodeBlockPre({ children, node: _node, ...props }) {
1874
1791
  copied ? "text-[hsl(var(--primary))]" : "text-[hsl(var(--muted-foreground))] hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--foreground))]"
1875
1792
  ),
1876
1793
  children: [
1877
- copied ? /* @__PURE__ */ jsx8(Check, { size: 12 }) : /* @__PURE__ */ jsx8(Copy, { size: 12 }),
1878
- /* @__PURE__ */ jsx8("span", { children: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236" })
1794
+ copied ? /* @__PURE__ */ jsx7(Check, { size: 12 }) : /* @__PURE__ */ jsx7(Copy, { size: 12 }),
1795
+ /* @__PURE__ */ jsx7("span", { children: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236" })
1879
1796
  ]
1880
1797
  }
1881
1798
  )
1882
1799
  ] }),
1883
- /* @__PURE__ */ jsx8(
1800
+ /* @__PURE__ */ jsx7(
1884
1801
  "pre",
1885
1802
  {
1886
1803
  ref: preRef,
@@ -1892,36 +1809,22 @@ function CodeBlockPre({ children, node: _node, ...props }) {
1892
1809
  ] });
1893
1810
  }
1894
1811
  function ExternalAnchor({ node: _node, children, ...props }) {
1895
- return /* @__PURE__ */ jsx8("a", { ...props, target: "_blank", rel: "noopener noreferrer", children });
1812
+ return /* @__PURE__ */ jsx7("a", { ...props, target: "_blank", rel: "noopener noreferrer", children });
1896
1813
  }
1897
1814
  var MARKDOWN_COMPONENTS = {
1898
1815
  pre: CodeBlockPre,
1899
1816
  a: ExternalAnchor
1900
1817
  };
1901
- var CUSTOM_ALLOWED_TAGS = {
1902
- [FILE_CARD_TAG]: ["dataPath", "dataName", "data-path", "data-name"]
1903
- };
1904
1818
  function MarkdownContent({ children, className, mode, sessionId }) {
1905
- const streaming = mode === "streaming";
1906
1819
  const resolvedChildren = useMemo5(() => {
1907
- const withoutReminders = children.replace(SYSTEM_REMINDER_RE, "");
1908
- if (!sessionId) return withoutReminders;
1909
- return collapseMediaTags(withoutReminders, { streaming });
1910
- }, [children, sessionId, streaming]);
1911
- const components = useMemo5(
1912
- () => ({
1913
- ...MARKDOWN_COMPONENTS,
1914
- [FILE_CARD_TAG]: (fileCardProps) => /* @__PURE__ */ jsx8(FileCard, { ...fileCardProps, sessionId })
1915
- }),
1916
- [sessionId]
1917
- );
1918
- return /* @__PURE__ */ jsx8(
1820
+ return children.replace(SYSTEM_REMINDER_RE, "");
1821
+ }, [children]);
1822
+ return /* @__PURE__ */ jsx7(
1919
1823
  _r,
1920
1824
  {
1921
1825
  className: cn("blade-chat-markdown break-words", className),
1922
1826
  mode: mode ?? "static",
1923
- components,
1924
- allowedTags: CUSTOM_ALLOWED_TAGS,
1827
+ components: MARKDOWN_COMPONENTS,
1925
1828
  children: resolvedChildren
1926
1829
  },
1927
1830
  sessionId ?? "no-session"
@@ -1929,17 +1832,17 @@ function MarkdownContent({ children, className, mode, sessionId }) {
1929
1832
  }
1930
1833
 
1931
1834
  // src/components/Shimmer.tsx
1932
- import { jsx as jsx9 } from "react/jsx-runtime";
1835
+ import { jsx as jsx8 } from "react/jsx-runtime";
1933
1836
  function Shimmer({ children = "\u6B63\u5728\u601D\u8003...", className }) {
1934
- return /* @__PURE__ */ jsx9("span", { className: cn("blade-shimmer-text text-sm font-medium", className), children });
1837
+ return /* @__PURE__ */ jsx8("span", { className: cn("blade-shimmer-text text-sm font-medium", className), children });
1935
1838
  }
1936
1839
 
1937
1840
  // src/components/ToolCallBlock.tsx
1938
1841
  import { useState as useState9 } from "react";
1939
1842
 
1940
1843
  // src/components/AskUserQuestionBlock.tsx
1941
- import { useEffect as useEffect5, useMemo as useMemo6, useState as useState8 } from "react";
1942
- import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1844
+ import { useEffect as useEffect6, useMemo as useMemo6, useState as useState8 } from "react";
1845
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1943
1846
  function AskUserQuestionBlock({
1944
1847
  data,
1945
1848
  answered,
@@ -1952,7 +1855,7 @@ function AskUserQuestionBlock({
1952
1855
  const [customTexts, setCustomTexts] = useState8(/* @__PURE__ */ new Map());
1953
1856
  const [usingCustom, setUsingCustom] = useState8(/* @__PURE__ */ new Set());
1954
1857
  const [submitted, setSubmitted] = useState8(false);
1955
- useEffect5(() => {
1858
+ useEffect6(() => {
1956
1859
  if (sessionStatus === "failed" || sessionStatus === "interrupted") {
1957
1860
  setSubmitted(false);
1958
1861
  }
@@ -2042,7 +1945,7 @@ ${parts.join("\n")}`;
2042
1945
  setSubmitted(true);
2043
1946
  onAnswer(text, toolCallId, nextAnswerData);
2044
1947
  };
2045
- return /* @__PURE__ */ jsxs8(
1948
+ return /* @__PURE__ */ jsxs7(
2046
1949
  "div",
2047
1950
  {
2048
1951
  className: cn(
@@ -2050,12 +1953,12 @@ ${parts.join("\n")}`;
2050
1953
  answered ? "max-w-2xl space-y-3 p-3 text-xs text-[hsl(var(--muted-foreground))] opacity-80" : "max-w-lg space-y-5 p-4 text-sm"
2051
1954
  ),
2052
1955
  children: [
2053
- data.source_loop?.description && /* @__PURE__ */ jsxs8("div", { className: "rounded-lg bg-[hsl(var(--muted)/0.35)] px-3 py-2 text-xs text-[hsl(var(--muted-foreground))]", children: [
1956
+ data.source_loop?.description && /* @__PURE__ */ jsxs7("div", { className: "rounded-lg bg-[hsl(var(--muted)/0.35)] px-3 py-2 text-xs text-[hsl(var(--muted-foreground))]", children: [
2054
1957
  "\u5B50\u667A\u80FD\u4F53\u300C",
2055
1958
  data.source_loop.description,
2056
1959
  "\u300D\u5728\u7B49\u5F85\u4F60\u7684\u56DE\u7B54"
2057
1960
  ] }),
2058
- data.questions.map((q, qIdx) => /* @__PURE__ */ jsx10(
1961
+ data.questions.map((q, qIdx) => /* @__PURE__ */ jsx9(
2059
1962
  QuestionCard,
2060
1963
  {
2061
1964
  question: q,
@@ -2070,7 +1973,7 @@ ${parts.join("\n")}`;
2070
1973
  },
2071
1974
  q.question
2072
1975
  )),
2073
- !answered && !submitted && onAnswer && /* @__PURE__ */ jsx10(
1976
+ !answered && !submitted && onAnswer && /* @__PURE__ */ jsx9(
2074
1977
  "button",
2075
1978
  {
2076
1979
  type: "button",
@@ -2080,14 +1983,14 @@ ${parts.join("\n")}`;
2080
1983
  children: allAnswered ? "\u786E\u8BA4" : "\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u9009\u9879"
2081
1984
  }
2082
1985
  ),
2083
- submitted && !answered && /* @__PURE__ */ jsxs8(
1986
+ submitted && !answered && /* @__PURE__ */ jsxs7(
2084
1987
  "button",
2085
1988
  {
2086
1989
  type: "button",
2087
1990
  disabled: true,
2088
1991
  className: "flex w-full items-center justify-center gap-2 rounded-lg bg-[hsl(var(--primary))] px-4 py-2 text-xs font-semibold text-[hsl(var(--primary-foreground))] opacity-80",
2089
1992
  children: [
2090
- /* @__PURE__ */ jsx10(LoaderCircle, { size: 14, className: "animate-spin" }),
1993
+ /* @__PURE__ */ jsx9(LoaderCircle, { size: 14, className: "animate-spin" }),
2091
1994
  "\u786E\u8BA4\u4E2D"
2092
1995
  ]
2093
1996
  }
@@ -2108,30 +2011,30 @@ function QuestionCard({
2108
2011
  onCustomChange
2109
2012
  }) {
2110
2013
  const multi = question.multiSelect ?? false;
2111
- return /* @__PURE__ */ jsxs8("div", { children: [
2112
- /* @__PURE__ */ jsxs8("div", { className: cn("flex items-start gap-2", answered ? "mb-2" : "mb-3"), children: [
2113
- /* @__PURE__ */ jsx10(
2014
+ return /* @__PURE__ */ jsxs7("div", { children: [
2015
+ /* @__PURE__ */ jsxs7("div", { className: cn("flex items-start gap-2", answered ? "mb-2" : "mb-3"), children: [
2016
+ /* @__PURE__ */ jsx9(
2114
2017
  MessageSquareMore,
2115
2018
  {
2116
2019
  size: answered ? 12 : 13,
2117
2020
  className: "mt-0.5 shrink-0 text-[hsl(var(--primary))]"
2118
2021
  }
2119
2022
  ),
2120
- /* @__PURE__ */ jsx10(
2023
+ /* @__PURE__ */ jsx9(
2121
2024
  "div",
2122
2025
  {
2123
2026
  className: cn(
2124
2027
  "min-w-0 flex-1 font-medium text-[hsl(var(--foreground))]",
2125
2028
  answered ? "text-xs" : "text-sm"
2126
2029
  ),
2127
- children: /* @__PURE__ */ jsx10(MarkdownContent, { className: "blade-chat-prose", children: question.question })
2030
+ children: /* @__PURE__ */ jsx9(MarkdownContent, { className: "blade-chat-prose", children: question.question })
2128
2031
  }
2129
2032
  )
2130
2033
  ] }),
2131
- /* @__PURE__ */ jsxs8("div", { className: cn("flex flex-col pl-5", answered ? "gap-1" : "gap-1.5"), children: [
2034
+ /* @__PURE__ */ jsxs7("div", { className: cn("flex flex-col pl-5", answered ? "gap-1" : "gap-1.5"), children: [
2132
2035
  question.options.map((opt, optIdx) => {
2133
2036
  const isSel = selected.has(optIdx);
2134
- return /* @__PURE__ */ jsxs8(
2037
+ return /* @__PURE__ */ jsxs7(
2135
2038
  "button",
2136
2039
  {
2137
2040
  type: "button",
@@ -2145,14 +2048,14 @@ function QuestionCard({
2145
2048
  answered && "cursor-default opacity-70"
2146
2049
  ),
2147
2050
  children: [
2148
- multi && /* @__PURE__ */ jsx10(
2051
+ multi && /* @__PURE__ */ jsx9(
2149
2052
  "div",
2150
2053
  {
2151
2054
  className: cn(
2152
2055
  "mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded border transition-colors",
2153
2056
  isSel && !answered ? "border-[hsl(var(--primary-foreground)/0.6)] bg-[hsl(var(--primary-foreground)/0.2)]" : isSel ? "border-[hsl(var(--primary)/0.45)] bg-[hsl(var(--primary)/0.12)]" : "border-[hsl(var(--border))]"
2154
2057
  ),
2155
- children: isSel && /* @__PURE__ */ jsx10(
2058
+ children: isSel && /* @__PURE__ */ jsx9(
2156
2059
  Check,
2157
2060
  {
2158
2061
  size: 9,
@@ -2161,9 +2064,9 @@ function QuestionCard({
2161
2064
  )
2162
2065
  }
2163
2066
  ),
2164
- /* @__PURE__ */ jsxs8("div", { className: "min-w-0", children: [
2165
- /* @__PURE__ */ jsx10("div", { className: cn("font-medium", answered ? "text-xs" : "text-[13px]"), children: opt.label }),
2166
- opt.description && /* @__PURE__ */ jsx10(
2067
+ /* @__PURE__ */ jsxs7("div", { className: "min-w-0", children: [
2068
+ /* @__PURE__ */ jsx9("div", { className: cn("font-medium", answered ? "text-xs" : "text-[13px]"), children: opt.label }),
2069
+ opt.description && /* @__PURE__ */ jsx9(
2167
2070
  "div",
2168
2071
  {
2169
2072
  className: cn(
@@ -2180,7 +2083,7 @@ function QuestionCard({
2180
2083
  opt.label
2181
2084
  );
2182
2085
  }),
2183
- answered && !isCustom ? null : /* @__PURE__ */ jsxs8(
2086
+ answered && !isCustom ? null : /* @__PURE__ */ jsxs7(
2184
2087
  "div",
2185
2088
  {
2186
2089
  className: cn(
@@ -2190,8 +2093,8 @@ function QuestionCard({
2190
2093
  answered && "cursor-default opacity-70"
2191
2094
  ),
2192
2095
  children: [
2193
- /* @__PURE__ */ jsx10("span", { className: "shrink-0 text-xs text-[hsl(var(--muted-foreground))]", children: "\u5176\u4ED6\uFF1A" }),
2194
- /* @__PURE__ */ jsx10(
2096
+ /* @__PURE__ */ jsx9("span", { className: "shrink-0 text-xs text-[hsl(var(--muted-foreground))]", children: "\u5176\u4ED6\uFF1A" }),
2097
+ /* @__PURE__ */ jsx9(
2195
2098
  "input",
2196
2099
  {
2197
2100
  type: "text",
@@ -2259,7 +2162,7 @@ function normalizeOptionItem(value) {
2259
2162
  }
2260
2163
 
2261
2164
  // src/components/ToolCallBlock.tsx
2262
- import { Fragment, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2165
+ import { Fragment, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
2263
2166
  function resolveAskQuestionState({
2264
2167
  toolStatus,
2265
2168
  hasAnswerData,
@@ -2284,7 +2187,7 @@ function ToolCallBlock({
2284
2187
  if (renderer) {
2285
2188
  const custom = renderer(toolCall);
2286
2189
  if (custom !== null && custom !== void 0) {
2287
- return /* @__PURE__ */ jsx11(Fragment, { children: custom });
2190
+ return /* @__PURE__ */ jsx10(Fragment, { children: custom });
2288
2191
  }
2289
2192
  }
2290
2193
  if (normalizedName === "AskUserQuestion") {
@@ -2296,7 +2199,7 @@ function ToolCallBlock({
2296
2199
  });
2297
2200
  const canAnswer = questionState.awaitingAnswer && Boolean(onAnswer);
2298
2201
  if (askData) {
2299
- return /* @__PURE__ */ jsx11(
2202
+ return /* @__PURE__ */ jsx10(
2300
2203
  AskUserQuestionBlock,
2301
2204
  {
2302
2205
  data: askData,
@@ -2309,24 +2212,24 @@ function ToolCallBlock({
2309
2212
  );
2310
2213
  }
2311
2214
  if (toolCall.status === "pending") {
2312
- return /* @__PURE__ */ jsxs9("div", { className: "ml-4 flex max-w-lg items-center gap-2 rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] p-4 text-sm text-[hsl(var(--muted-foreground))]", children: [
2313
- /* @__PURE__ */ jsx11(LoaderCircle, { size: 14, className: "animate-spin" }),
2314
- /* @__PURE__ */ jsx11("span", { children: "\u6B63\u5728\u51C6\u5907\u95EE\u9898\u2026" })
2215
+ return /* @__PURE__ */ jsxs8("div", { className: "ml-4 flex max-w-lg items-center gap-2 rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] p-4 text-sm text-[hsl(var(--muted-foreground))]", children: [
2216
+ /* @__PURE__ */ jsx10(LoaderCircle, { size: 14, className: "animate-spin" }),
2217
+ /* @__PURE__ */ jsx10("span", { children: "\u6B63\u5728\u51C6\u5907\u95EE\u9898\u2026" })
2315
2218
  ] });
2316
2219
  }
2317
- return /* @__PURE__ */ jsxs9("div", { className: "ml-4 max-w-lg rounded-xl border border-amber-500/35 bg-amber-500/10 p-4 text-sm text-[hsl(var(--foreground))]", children: [
2318
- /* @__PURE__ */ jsx11("div", { className: "font-semibold", children: "\u9009\u62E9\u9898\u5185\u5BB9\u6682\u65F6\u65E0\u6CD5\u663E\u793A" }),
2319
- /* @__PURE__ */ jsx11("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" })
2220
+ 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: [
2221
+ /* @__PURE__ */ jsx10("div", { className: "font-semibold", children: "\u9009\u62E9\u9898\u5185\u5BB9\u6682\u65F6\u65E0\u6CD5\u663E\u793A" }),
2222
+ /* @__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" })
2320
2223
  ] });
2321
2224
  }
2322
2225
  const tone = getToolTone(toolCall.status);
2323
2226
  const displayName = getToolDisplayLabel(toolCall);
2324
2227
  const toneClass = tone === "red" ? "border-l-[hsl(var(--muted-foreground)/0.5)]" : tone === "amber" ? "border-l-amber-400" : tone === "blue" ? "border-l-blue-500" : "border-l-[hsl(var(--primary))]";
2325
- const statusIcon = toolCall.status === "pending" ? /* @__PURE__ */ jsx11(LoaderCircle, { size: 11, className: "animate-spin" }) : toolCall.status === "awaiting_answer" ? /* @__PURE__ */ jsx11(MessageSquareMore, { size: 11 }) : toolCall.status === "cancelled" || toolCall.status === "error" ? /* @__PURE__ */ jsx11(X, { size: 11 }) : /* @__PURE__ */ jsx11(Check, { size: 11 });
2228
+ const statusIcon = toolCall.status === "pending" ? /* @__PURE__ */ jsx10(LoaderCircle, { size: 11, className: "animate-spin" }) : toolCall.status === "awaiting_answer" ? /* @__PURE__ */ jsx10(MessageSquareMore, { size: 11 }) : toolCall.status === "cancelled" || toolCall.status === "error" ? /* @__PURE__ */ jsx10(X, { size: 11 }) : /* @__PURE__ */ jsx10(Check, { size: 11 });
2326
2229
  const statusTextClass = tone === "red" ? "text-[hsl(var(--muted-foreground))]" : tone === "amber" ? "text-amber-300" : tone === "blue" ? "text-blue-300" : "text-[hsl(var(--primary))]";
2327
- return /* @__PURE__ */ jsxs9("div", { className: "blade-chat-tool ml-4 text-xs", children: [
2328
- /* @__PURE__ */ jsxs9("div", { className: cn("border-l-[3px] flex items-center gap-2 px-3 py-2", toneClass), children: [
2329
- /* @__PURE__ */ jsxs9(
2230
+ return /* @__PURE__ */ jsxs8("div", { className: "blade-chat-tool ml-4 text-xs", children: [
2231
+ /* @__PURE__ */ jsxs8("div", { className: cn("border-l-[3px] flex items-center gap-2 px-3 py-2", toneClass), children: [
2232
+ /* @__PURE__ */ jsxs8(
2330
2233
  "button",
2331
2234
  {
2332
2235
  type: "button",
@@ -2334,7 +2237,7 @@ function ToolCallBlock({
2334
2237
  className: "flex min-w-0 flex-1 items-center gap-2 text-left transition-colors hover:bg-white/3 focus-visible:ring-1 focus-visible:ring-[hsl(var(--ring))] focus:outline-none",
2335
2238
  "aria-expanded": expanded,
2336
2239
  children: [
2337
- /* @__PURE__ */ jsx11(
2240
+ /* @__PURE__ */ jsx10(
2338
2241
  ChevronRight,
2339
2242
  {
2340
2243
  size: 11,
@@ -2344,24 +2247,24 @@ function ToolCallBlock({
2344
2247
  )
2345
2248
  }
2346
2249
  ),
2347
- /* @__PURE__ */ jsxs9("span", { className: cn("flex shrink-0 items-center gap-1 text-[10px]", statusTextClass), children: [
2250
+ /* @__PURE__ */ jsxs8("span", { className: cn("flex shrink-0 items-center gap-1 text-[10px]", statusTextClass), children: [
2348
2251
  statusIcon,
2349
- /* @__PURE__ */ jsx11("span", { children: getToolStatusLabel(toolCall.status) })
2252
+ /* @__PURE__ */ jsx10("span", { children: getToolStatusLabel(toolCall.status) })
2350
2253
  ] }),
2351
- /* @__PURE__ */ jsx11("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: displayName })
2254
+ /* @__PURE__ */ jsx10("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: displayName })
2352
2255
  ]
2353
2256
  }
2354
2257
  ),
2355
- typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 && /* @__PURE__ */ jsx11("span", { className: "shrink-0 font-mono text-[10px] text-[hsl(var(--muted-foreground))]", children: formatToolDuration(toolCall.duration_ms) })
2258
+ typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 && /* @__PURE__ */ jsx10("span", { className: "shrink-0 font-mono text-[10px] text-[hsl(var(--muted-foreground))]", children: formatToolDuration(toolCall.duration_ms) })
2356
2259
  ] }),
2357
- expanded && /* @__PURE__ */ jsxs9("div", { className: "blade-chat-tool-detail ml-4 mt-1 rounded-xl bg-[hsl(var(--card))] px-3 py-3", children: [
2358
- /* @__PURE__ */ jsx11("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u5DE5\u5177" }),
2359
- /* @__PURE__ */ jsx11("div", { className: "mb-3 font-mono text-[11px] text-[hsl(var(--foreground))]", children: normalizedName }),
2360
- /* @__PURE__ */ jsx11("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u53C2\u6570" }),
2361
- /* @__PURE__ */ jsx11("pre", { className: "overflow-x-auto whitespace-pre-wrap rounded-md bg-[hsl(var(--muted))] p-2 font-mono text-[11px] text-[hsl(var(--foreground))]", children: formatToolArgs(toolCall.arguments) }),
2362
- toolCall.result != null && /* @__PURE__ */ jsxs9(Fragment, { children: [
2363
- /* @__PURE__ */ jsx11("div", { className: "mb-1 mt-3 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u7ED3\u679C" }),
2364
- /* @__PURE__ */ jsx11("pre", { className: "max-h-[400px] overflow-auto whitespace-pre-wrap rounded-md bg-[hsl(var(--muted))] p-2 font-mono text-[11px] text-[hsl(var(--foreground))]", children: formatToolResult(toolCall.result) })
2260
+ expanded && /* @__PURE__ */ jsxs8("div", { className: "blade-chat-tool-detail ml-4 mt-1 rounded-xl bg-[hsl(var(--card))] px-3 py-3", children: [
2261
+ /* @__PURE__ */ jsx10("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u5DE5\u5177" }),
2262
+ /* @__PURE__ */ jsx10("div", { className: "mb-3 font-mono text-[11px] text-[hsl(var(--foreground))]", children: normalizedName }),
2263
+ /* @__PURE__ */ jsx10("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u53C2\u6570" }),
2264
+ /* @__PURE__ */ jsx10("pre", { className: "overflow-x-auto whitespace-pre-wrap rounded-md bg-[hsl(var(--muted))] p-2 font-mono text-[11px] text-[hsl(var(--foreground))]", children: formatToolArgs(toolCall.arguments) }),
2265
+ toolCall.result != null && /* @__PURE__ */ jsxs8(Fragment, { children: [
2266
+ /* @__PURE__ */ jsx10("div", { className: "mb-1 mt-3 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u7ED3\u679C" }),
2267
+ /* @__PURE__ */ jsx10("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) })
2365
2268
  ] })
2366
2269
  ] })
2367
2270
  ] });
@@ -2379,11 +2282,11 @@ function buildAskUserPayload(argumentsJson) {
2379
2282
  }
2380
2283
 
2381
2284
  // src/components/AssistantTurnBlock.tsx
2382
- import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
2285
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2383
2286
  function ThinkingBlock({ reasoning, isStreaming }) {
2384
2287
  const [open, setOpen] = useState10(false);
2385
- return /* @__PURE__ */ jsxs10("div", { className: "blade-chat-thinking ml-4 text-sm", children: [
2386
- /* @__PURE__ */ jsxs10(
2288
+ return /* @__PURE__ */ jsxs9("div", { className: "blade-chat-thinking ml-4 text-sm", children: [
2289
+ /* @__PURE__ */ jsxs9(
2387
2290
  "button",
2388
2291
  {
2389
2292
  type: "button",
@@ -2391,14 +2294,14 @@ function ThinkingBlock({ reasoning, isStreaming }) {
2391
2294
  "aria-expanded": open,
2392
2295
  className: "inline-flex items-center gap-1.5 text-[hsl(var(--muted-foreground))] transition-colors hover:text-[hsl(var(--foreground))]",
2393
2296
  children: [
2394
- /* @__PURE__ */ jsx12(Brain, { size: 12, className: "shrink-0" }),
2395
- isStreaming ? /* @__PURE__ */ jsx12(Shimmer, { className: "text-xs", children: "\u6B63\u5728\u601D\u8003" }) : /* @__PURE__ */ jsx12("span", { children: "\u601D\u8003\u8FC7\u7A0B" }),
2396
- /* @__PURE__ */ jsxs10("span", { className: "text-[hsl(var(--muted-foreground))]/70", children: [
2297
+ /* @__PURE__ */ jsx11(Brain, { size: 12, className: "shrink-0" }),
2298
+ isStreaming ? /* @__PURE__ */ jsx11(Shimmer, { className: "text-xs", children: "\u6B63\u5728\u601D\u8003" }) : /* @__PURE__ */ jsx11("span", { children: "\u601D\u8003\u8FC7\u7A0B" }),
2299
+ /* @__PURE__ */ jsxs9("span", { className: "text-[hsl(var(--muted-foreground))]/70", children: [
2397
2300
  "\xB7 ",
2398
2301
  new Intl.NumberFormat("zh-CN").format(reasoning.length),
2399
2302
  " \u5B57"
2400
2303
  ] }),
2401
- /* @__PURE__ */ jsx12(
2304
+ /* @__PURE__ */ jsx11(
2402
2305
  ChevronDown,
2403
2306
  {
2404
2307
  size: 12,
@@ -2408,7 +2311,7 @@ function ThinkingBlock({ reasoning, isStreaming }) {
2408
2311
  ]
2409
2312
  }
2410
2313
  ),
2411
- open && /* @__PURE__ */ jsx12("div", { className: "mt-1.5 whitespace-pre-wrap border-l-2 border-[hsl(var(--border))] pl-3 text-[11px] leading-5 text-[hsl(var(--muted-foreground))]", children: reasoning })
2314
+ open && /* @__PURE__ */ jsx11("div", { className: "mt-1.5 whitespace-pre-wrap border-l-2 border-[hsl(var(--border))] pl-3 text-[11px] leading-5 text-[hsl(var(--muted-foreground))]", children: reasoning })
2412
2315
  ] });
2413
2316
  }
2414
2317
  function getMessageText(message) {
@@ -2434,21 +2337,21 @@ function AssistantTurnBlock({
2434
2337
  (message) => getMessageText(message) || message.reasoning || (message.tool_calls?.length ?? 0) > 0
2435
2338
  );
2436
2339
  const latestReasoningIndex = isStreaming ? findLatestReasoningMessageIndex(messages) : -1;
2437
- return /* @__PURE__ */ jsxs10("div", { className: "blade-chat-assistant-turn flex flex-col gap-3", children: [
2438
- hasInterrupted && /* @__PURE__ */ jsx12("div", { className: "ml-4 w-fit rounded-full border border-amber-500/30 bg-amber-500/10 px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.12em] text-amber-300", children: "\u5DF2\u4E2D\u65AD" }),
2340
+ return /* @__PURE__ */ jsxs9("div", { className: "blade-chat-assistant-turn flex flex-col gap-3", children: [
2341
+ 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" }),
2439
2342
  messages.map((message, index) => {
2440
2343
  const isLast = index === messages.length - 1;
2441
2344
  const streamingThis = isStreaming && isLast;
2442
2345
  const text = getMessageText(message);
2443
2346
  const toolCalls = message.tool_calls ?? [];
2444
2347
  const showReasoning = !!message.reasoning && (!isStreaming || index === latestReasoningIndex);
2445
- return /* @__PURE__ */ jsxs10(
2348
+ return /* @__PURE__ */ jsxs9(
2446
2349
  "div",
2447
2350
  {
2448
2351
  className: "flex flex-col gap-3",
2449
2352
  children: [
2450
- showReasoning && message.reasoning && /* @__PURE__ */ jsx12(ThinkingBlock, { reasoning: message.reasoning, isStreaming: streamingThis && !text }),
2451
- text && /* @__PURE__ */ jsx12("div", { className: "blade-chat-assistant-text text-[15px] leading-8 text-[hsl(var(--foreground))]", children: /* @__PURE__ */ jsx12(
2353
+ showReasoning && message.reasoning && /* @__PURE__ */ jsx11(ThinkingBlock, { reasoning: message.reasoning, isStreaming: streamingThis && !text }),
2354
+ text && /* @__PURE__ */ jsx11("div", { className: "blade-chat-assistant-text text-[15px] leading-8 text-[hsl(var(--foreground))]", children: /* @__PURE__ */ jsx11(
2452
2355
  MarkdownContent,
2453
2356
  {
2454
2357
  mode: streamingThis ? "streaming" : "static",
@@ -2457,8 +2360,8 @@ function AssistantTurnBlock({
2457
2360
  children: text
2458
2361
  }
2459
2362
  ) }),
2460
- toolCalls.length > 0 && /* @__PURE__ */ jsx12("div", { className: "flex flex-col gap-2", children: toolCalls.map(
2461
- (toolCall) => formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */ jsx12(AgentLoopBlock, { toolCall }, toolCall.id) : /* @__PURE__ */ jsx12(
2363
+ toolCalls.length > 0 && /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-2", children: toolCalls.map(
2364
+ (toolCall) => formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */ jsx11(AgentLoopBlock, { toolCall }, toolCall.id) : /* @__PURE__ */ jsx11(
2462
2365
  ToolCallBlock,
2463
2366
  {
2464
2367
  toolCall,
@@ -2476,13 +2379,13 @@ function AssistantTurnBlock({
2476
2379
  message.entry_id ?? `${message.timestamp ?? "assistant"}-${index}`
2477
2380
  );
2478
2381
  }),
2479
- isStreaming && !hasAnyContent && /* @__PURE__ */ jsx12(Shimmer, { className: "ml-4", children: "\u6B63\u5728\u751F\u6210..." })
2382
+ isStreaming && !hasAnyContent && /* @__PURE__ */ jsx11(Shimmer, { className: "ml-4", children: "\u6B63\u5728\u751F\u6210..." })
2480
2383
  ] });
2481
2384
  }
2482
2385
 
2483
2386
  // src/components/RenderErrorBoundary.tsx
2484
2387
  import { Component } from "react";
2485
- import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
2388
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
2486
2389
  function getFirstComponentName(componentStack) {
2487
2390
  const match = componentStack.match(/\n\s+at\s+([^\s(]+)/);
2488
2391
  return match?.[1] ?? null;
@@ -2515,23 +2418,408 @@ var RenderErrorBoundary = class extends Component {
2515
2418
  return children;
2516
2419
  }
2517
2420
  const componentName = getFirstComponentName(componentStack);
2518
- return /* @__PURE__ */ jsx13("div", { className: "blade-chat-render-error rounded-xl border border-amber-500/30 bg-amber-500/8 px-4 py-3 text-sm text-amber-100", children: /* @__PURE__ */ jsxs11("div", { className: "flex items-start gap-2", children: [
2519
- /* @__PURE__ */ jsx13(TriangleAlert, { className: "mt-0.5 h-4 w-4 shrink-0 text-amber-300" }),
2520
- /* @__PURE__ */ jsxs11("div", { className: "min-w-0 flex-1", children: [
2521
- /* @__PURE__ */ jsxs11("div", { className: "font-medium", children: [
2421
+ return /* @__PURE__ */ jsx12("div", { className: "blade-chat-render-error rounded-xl border border-amber-500/30 bg-amber-500/8 px-4 py-3 text-sm text-amber-100", children: /* @__PURE__ */ jsxs10("div", { className: "flex items-start gap-2", children: [
2422
+ /* @__PURE__ */ jsx12(TriangleAlert, { className: "mt-0.5 h-4 w-4 shrink-0 text-amber-300" }),
2423
+ /* @__PURE__ */ jsxs10("div", { className: "min-w-0 flex-1", children: [
2424
+ /* @__PURE__ */ jsxs10("div", { className: "font-medium", children: [
2522
2425
  label,
2523
2426
  "\u6E32\u67D3\u5931\u8D25"
2524
2427
  ] }),
2525
- /* @__PURE__ */ jsxs11("div", { className: "mt-1 break-words text-xs leading-5 text-amber-100/75", children: [
2428
+ /* @__PURE__ */ jsxs10("div", { className: "mt-1 break-words text-xs leading-5 text-amber-100/75", children: [
2526
2429
  componentName ? `\u7EC4\u4EF6\uFF1A${componentName}\u3002` : null,
2527
2430
  error.message || "\u53D1\u751F\u4E86\u672A\u9884\u671F\u7684\u6E32\u67D3\u9519\u8BEF\u3002"
2528
2431
  ] }),
2529
- details ? /* @__PURE__ */ jsx13("div", { className: "mt-1 truncate text-xs text-amber-100/55", children: details }) : null
2432
+ details ? /* @__PURE__ */ jsx12("div", { className: "mt-1 truncate text-xs text-amber-100/55", children: details }) : null
2530
2433
  ] })
2531
2434
  ] }) });
2532
2435
  }
2533
2436
  };
2534
2437
 
2438
+ // src/components/PostChatFollowupBlock.tsx
2439
+ import { useCallback as useCallback5, useEffect as useEffect7, useRef as useRef7, useState as useState11 } from "react";
2440
+ import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
2441
+ function emitInteraction(callback, event) {
2442
+ try {
2443
+ callback?.(event);
2444
+ } catch {
2445
+ }
2446
+ }
2447
+ function basename(path) {
2448
+ return path.split(/[\\/]/).filter(Boolean).pop() || path;
2449
+ }
2450
+ function isVideo(path) {
2451
+ return /\.(?:mp4|mov|webm|mkv|avi|m4v)$/i.test(path);
2452
+ }
2453
+ function ArtifactCard({
2454
+ artifact,
2455
+ sessionId,
2456
+ assistantEntryId,
2457
+ artifactIndex,
2458
+ onInteraction,
2459
+ onArtifactOpened
2460
+ }) {
2461
+ const client = useBladeClient();
2462
+ const [downloading, setDownloading] = useState11(false);
2463
+ const name = artifact.label || basename(artifact.target);
2464
+ if (artifact.kind === "link") {
2465
+ return /* @__PURE__ */ jsxs11(
2466
+ "a",
2467
+ {
2468
+ href: artifact.target,
2469
+ target: "_blank",
2470
+ rel: "noopener noreferrer",
2471
+ onClick: () => onArtifactOpened(artifactIndex, "link"),
2472
+ title: `${name}
2473
+ ${artifact.target}`,
2474
+ className: "group relative flex min-w-0 items-center gap-1.5 rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-2 py-1.5 text-xs text-[hsl(var(--card-foreground))] hover:bg-[hsl(var(--accent))]",
2475
+ children: [
2476
+ /* @__PURE__ */ jsx13(Globe, { size: 15, className: "shrink-0 text-[hsl(var(--primary))]" }),
2477
+ /* @__PURE__ */ jsx13("span", { className: "min-w-0 flex-1 truncate font-medium", children: name }),
2478
+ /* @__PURE__ */ jsx13(ArrowUpRight, { size: 13, className: "absolute right-2 opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100" })
2479
+ ]
2480
+ }
2481
+ );
2482
+ }
2483
+ const Icon2 = isVideo(artifact.target) ? Film : File;
2484
+ return /* @__PURE__ */ jsxs11(
2485
+ "button",
2486
+ {
2487
+ type: "button",
2488
+ disabled: !sessionId || downloading,
2489
+ onClick: async () => {
2490
+ if (!sessionId || downloading) return;
2491
+ setDownloading(true);
2492
+ emitInteraction(onInteraction, {
2493
+ type: "artifact_download_started",
2494
+ sessionId,
2495
+ assistantEntryId,
2496
+ artifactIndex,
2497
+ artifactKind: "file"
2498
+ });
2499
+ try {
2500
+ await client.sessions.downloadFile(sessionId, artifact.target, basename(artifact.target));
2501
+ emitInteraction(onInteraction, {
2502
+ type: "artifact_download_succeeded",
2503
+ sessionId,
2504
+ assistantEntryId,
2505
+ artifactIndex,
2506
+ artifactKind: "file"
2507
+ });
2508
+ } catch {
2509
+ } finally {
2510
+ setDownloading(false);
2511
+ }
2512
+ },
2513
+ title: name,
2514
+ "aria-label": `\u4E0B\u8F7D ${name}`,
2515
+ className: "group relative flex min-w-0 items-center gap-1.5 rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-2 py-1.5 text-left text-xs text-[hsl(var(--card-foreground))] hover:bg-[hsl(var(--accent))] disabled:opacity-60",
2516
+ children: [
2517
+ /* @__PURE__ */ jsx13(Icon2, { size: 15, className: "shrink-0 text-[hsl(var(--primary))]" }),
2518
+ /* @__PURE__ */ jsx13("span", { className: "min-w-0 flex-1 truncate font-medium", children: name }),
2519
+ /* @__PURE__ */ jsx13(Download, { size: 13, className: "absolute right-2 opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100" })
2520
+ ]
2521
+ }
2522
+ );
2523
+ }
2524
+ var FEEDBACK_REASONS = [
2525
+ { value: "not_solved", label: "\u6CA1\u89E3\u51B3\u95EE\u9898" },
2526
+ { value: "inaccurate", label: "\u5185\u5BB9\u4E0D\u51C6\u786E" },
2527
+ { value: "incomplete", label: "\u5185\u5BB9\u4E0D\u5B8C\u6574" },
2528
+ { value: "needs_major_changes", label: "\u9700\u8981\u5927\u91CF\u4FEE\u6539" },
2529
+ { value: "too_slow", label: "\u592A\u6162" },
2530
+ { value: "other", label: "\u5176\u4ED6" }
2531
+ ];
2532
+ function feedbackReasonLabel(reason) {
2533
+ return FEEDBACK_REASONS.find((item) => item.value === reason)?.label ?? null;
2534
+ }
2535
+ function HistoricalResultFeedback({ feedback }) {
2536
+ const label = feedbackReasonLabel(feedback.reason);
2537
+ return /* @__PURE__ */ jsxs11(
2538
+ "section",
2539
+ {
2540
+ "aria-label": "\u5386\u53F2\u7ED3\u679C\u53CD\u9988",
2541
+ className: "mt-3 w-fit max-w-full rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--muted)/0.2)] px-3 py-2 text-xs text-[hsl(var(--muted-foreground))]",
2542
+ children: [
2543
+ /* @__PURE__ */ jsxs11("span", { children: [
2544
+ "\u4F60\u5BF9\u6B64\u8F6E\u7ED3\u679C\u7684\u8BC4\u4EF7\uFF1A",
2545
+ feedback.helpful ? "\u6709\u5E2E\u52A9" : "\u6CA1\u5E2E\u52A9"
2546
+ ] }),
2547
+ label ? /* @__PURE__ */ jsxs11("span", { children: [
2548
+ " \xB7 ",
2549
+ label
2550
+ ] }) : null
2551
+ ]
2552
+ }
2553
+ );
2554
+ }
2555
+ function ResultFeedback({
2556
+ followup,
2557
+ sessionId,
2558
+ isViewer,
2559
+ onInteraction,
2560
+ savedFeedback,
2561
+ onFeedbackSaved
2562
+ }) {
2563
+ const client = useBladeClient();
2564
+ const [saved, setSaved] = useState11(savedFeedback ?? null);
2565
+ const [helpful, setHelpful] = useState11(savedFeedback?.helpful ?? null);
2566
+ const [reason, setReason] = useState11(savedFeedback?.reason ?? null);
2567
+ const [saving, setSaving] = useState11(false);
2568
+ const [saveError, setSaveError] = useState11(false);
2569
+ const reportedShown = useRef7(false);
2570
+ const latestChoice = useRef7(null);
2571
+ const eligible = followup.feedback_eligible === true && Boolean(sessionId) && !isViewer;
2572
+ useEffect7(() => {
2573
+ if (!eligible || reportedShown.current) return;
2574
+ reportedShown.current = true;
2575
+ emitInteraction(onInteraction, {
2576
+ type: "result_feedback_shown",
2577
+ sessionId,
2578
+ assistantEntryId: followup.assistant_entry_id
2579
+ });
2580
+ }, [eligible, followup.assistant_entry_id, onInteraction, sessionId]);
2581
+ useEffect7(() => {
2582
+ if (!savedFeedback || latestChoice.current) return;
2583
+ setSaved(savedFeedback);
2584
+ setHelpful(savedFeedback.helpful);
2585
+ setReason(savedFeedback.reason);
2586
+ }, [savedFeedback]);
2587
+ const submit = useCallback5(
2588
+ async (nextHelpful, nextReason) => {
2589
+ if (!sessionId) return;
2590
+ const choice = { helpful: nextHelpful, reason: nextReason };
2591
+ latestChoice.current = choice;
2592
+ setHelpful(nextHelpful);
2593
+ setReason(nextReason);
2594
+ setSaving(true);
2595
+ setSaveError(false);
2596
+ try {
2597
+ const result = await client.sessions.putResultFeedback(
2598
+ sessionId,
2599
+ followup.assistant_entry_id,
2600
+ choice
2601
+ );
2602
+ if (latestChoice.current !== choice) return;
2603
+ setSaved(result);
2604
+ onFeedbackSaved?.(result);
2605
+ emitInteraction(onInteraction, {
2606
+ type: "result_feedback_submitted",
2607
+ sessionId,
2608
+ assistantEntryId: followup.assistant_entry_id,
2609
+ helpful: result.helpful,
2610
+ reason: result.reason,
2611
+ updatedAt: result.updated_at
2612
+ });
2613
+ } catch {
2614
+ if (latestChoice.current === choice) setSaveError(true);
2615
+ } finally {
2616
+ if (latestChoice.current === choice) setSaving(false);
2617
+ }
2618
+ },
2619
+ [client, followup.assistant_entry_id, onFeedbackSaved, onInteraction, sessionId]
2620
+ );
2621
+ if (!eligible) return null;
2622
+ return /* @__PURE__ */ jsxs11(
2623
+ "section",
2624
+ {
2625
+ "aria-label": "\u7ED3\u679C\u53CD\u9988",
2626
+ className: "flex flex-col gap-2 border-t border-[hsl(var(--border))] pt-3",
2627
+ children: [
2628
+ /* @__PURE__ */ jsx13("div", { className: "text-xs font-medium text-[hsl(var(--muted-foreground))]", children: "\u76EE\u524D\u7684\u6574\u4F53\u7ED3\u679C\u6709\u5E2E\u52A9\u5417\uFF1F" }),
2629
+ /* @__PURE__ */ jsxs11("div", { className: "flex flex-wrap gap-1.5", children: [
2630
+ /* @__PURE__ */ jsx13(
2631
+ "button",
2632
+ {
2633
+ type: "button",
2634
+ "aria-pressed": helpful === true,
2635
+ disabled: saving,
2636
+ onClick: () => void submit(true, null),
2637
+ className: "rounded-lg border border-[hsl(var(--border))] px-2.5 py-1.5 text-xs aria-pressed:bg-[hsl(var(--primary)/0.1)] disabled:opacity-60",
2638
+ children: "\u6709\u5E2E\u52A9"
2639
+ }
2640
+ ),
2641
+ /* @__PURE__ */ jsx13(
2642
+ "button",
2643
+ {
2644
+ type: "button",
2645
+ "aria-pressed": helpful === false,
2646
+ disabled: saving,
2647
+ onClick: () => void submit(false, reason),
2648
+ className: "rounded-lg border border-[hsl(var(--border))] px-2.5 py-1.5 text-xs aria-pressed:bg-[hsl(var(--primary)/0.1)] disabled:opacity-60",
2649
+ children: "\u6CA1\u5E2E\u52A9"
2650
+ }
2651
+ )
2652
+ ] }),
2653
+ helpful === false ? /* @__PURE__ */ jsx13("div", { className: "flex flex-wrap gap-1.5", "aria-label": "\u6CA1\u5E2E\u52A9\u7684\u4E3B\u8981\u539F\u56E0", children: FEEDBACK_REASONS.map((item) => /* @__PURE__ */ jsx13(
2654
+ "button",
2655
+ {
2656
+ type: "button",
2657
+ "aria-pressed": reason === item.value,
2658
+ disabled: saving,
2659
+ onClick: () => void submit(false, item.value),
2660
+ className: "rounded-full bg-[hsl(var(--muted)/0.62)] px-2.5 py-1 text-[11px] aria-pressed:bg-[hsl(var(--primary)/0.14)] disabled:opacity-60",
2661
+ children: item.label
2662
+ },
2663
+ item.value
2664
+ )) }) : null,
2665
+ saveError ? /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2 text-xs text-[hsl(var(--destructive))]", children: [
2666
+ /* @__PURE__ */ jsx13("span", { children: "\u53CD\u9988\u6682\u672A\u4FDD\u5B58\uFF0C\u53EF\u91CD\u8BD5" }),
2667
+ /* @__PURE__ */ jsx13(
2668
+ "button",
2669
+ {
2670
+ type: "button",
2671
+ className: "underline",
2672
+ onClick: () => {
2673
+ const choice = latestChoice.current;
2674
+ if (choice) void submit(choice.helpful, choice.reason);
2675
+ },
2676
+ children: "\u91CD\u8BD5"
2677
+ }
2678
+ )
2679
+ ] }) : saved ? /* @__PURE__ */ jsx13("div", { className: "text-[11px] text-[hsl(var(--muted-foreground))]", children: "\u5DF2\u4FDD\u5B58\uFF0C\u53EF\u968F\u65F6\u4FEE\u6539" }) : null
2680
+ ]
2681
+ }
2682
+ );
2683
+ }
2684
+ function PostChatFollowupBlock({
2685
+ followup,
2686
+ sessionId,
2687
+ onSuggestion,
2688
+ isViewer = false,
2689
+ onInteraction,
2690
+ savedFeedback,
2691
+ onFeedbackSaved
2692
+ }) {
2693
+ const [expanded, setExpanded] = useState11(false);
2694
+ const adopted = useRef7(/* @__PURE__ */ new Set());
2695
+ const reportedSuggestions = useRef7(false);
2696
+ const reportedArtifacts = useRef7(/* @__PURE__ */ new Set());
2697
+ const openedArtifacts = useRef7(/* @__PURE__ */ new Set());
2698
+ const artifacts = followup.final_artifacts ?? [];
2699
+ const visibleArtifacts = expanded ? artifacts : artifacts.slice(0, 3);
2700
+ useEffect7(() => {
2701
+ if (!reportedSuggestions.current && followup.suggestions.length > 0) {
2702
+ reportedSuggestions.current = true;
2703
+ emitInteraction(onInteraction, {
2704
+ type: "suggestions_shown",
2705
+ sessionId,
2706
+ assistantEntryId: followup.assistant_entry_id,
2707
+ count: followup.suggestions.length
2708
+ });
2709
+ }
2710
+ for (let artifactIndex = 0; artifactIndex < visibleArtifacts.length; artifactIndex += 1) {
2711
+ if (reportedArtifacts.current.has(artifactIndex)) continue;
2712
+ const artifact = visibleArtifacts[artifactIndex];
2713
+ if (!artifact) continue;
2714
+ reportedArtifacts.current.add(artifactIndex);
2715
+ emitInteraction(onInteraction, {
2716
+ type: "artifact_shown",
2717
+ sessionId,
2718
+ assistantEntryId: followup.assistant_entry_id,
2719
+ artifactIndex,
2720
+ artifactKind: artifact.kind
2721
+ });
2722
+ }
2723
+ }, [
2724
+ followup.assistant_entry_id,
2725
+ followup.suggestions.length,
2726
+ onInteraction,
2727
+ sessionId,
2728
+ visibleArtifacts
2729
+ ]);
2730
+ const reportArtifactOpened = useCallback5(
2731
+ (artifactIndex, artifactKind) => {
2732
+ if (openedArtifacts.current.has(artifactIndex)) return;
2733
+ openedArtifacts.current.add(artifactIndex);
2734
+ emitInteraction(onInteraction, {
2735
+ type: "artifact_opened",
2736
+ sessionId,
2737
+ assistantEntryId: followup.assistant_entry_id,
2738
+ artifactIndex,
2739
+ artifactKind
2740
+ });
2741
+ },
2742
+ [followup.assistant_entry_id, onInteraction, sessionId]
2743
+ );
2744
+ if (!followup.recaption && artifacts.length === 0 && followup.suggestions.length === 0 && !followup.feedback_eligible)
2745
+ return null;
2746
+ return /* @__PURE__ */ jsxs11("div", { className: "mt-3 flex w-fit max-w-full flex-col gap-3 rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--muted)/0.28)] p-3 sm:max-w-[680px]", children: [
2747
+ followup.recaption || artifacts.length > 0 ? /* @__PURE__ */ jsxs11("section", { "aria-label": "\u672C\u8F6E\u5C0F\u7ED3", className: "flex flex-col gap-2", children: [
2748
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1.5 text-xs font-medium text-[hsl(var(--muted-foreground))]", children: [
2749
+ /* @__PURE__ */ jsx13(Sparkles, { size: 14 }),
2750
+ "\u672C\u8F6E\u5C0F\u7ED3"
2751
+ ] }),
2752
+ followup.recaption ? /* @__PURE__ */ jsx13("p", { className: "text-[13px] leading-5", children: followup.recaption }) : null,
2753
+ artifacts.length > 0 ? /* @__PURE__ */ jsxs11(Fragment2, { children: [
2754
+ /* @__PURE__ */ jsx13("div", { className: "grid max-w-full grid-cols-3 gap-1.5", children: visibleArtifacts.map((artifact, artifactIndex) => /* @__PURE__ */ jsx13(
2755
+ ArtifactCard,
2756
+ {
2757
+ artifact,
2758
+ sessionId,
2759
+ assistantEntryId: followup.assistant_entry_id,
2760
+ artifactIndex,
2761
+ onInteraction,
2762
+ onArtifactOpened: reportArtifactOpened
2763
+ },
2764
+ `${artifact.kind}:${artifactIndex}`
2765
+ )) }),
2766
+ artifacts.length > 3 ? /* @__PURE__ */ jsxs11(
2767
+ "button",
2768
+ {
2769
+ type: "button",
2770
+ onClick: () => setExpanded((value) => !value),
2771
+ "aria-expanded": expanded,
2772
+ className: "flex w-fit items-center gap-0.5 text-[11px] text-[hsl(var(--muted-foreground))]",
2773
+ children: [
2774
+ expanded ? "\u6536\u8D77" : `\u5C55\u5F00 ${artifacts.length - 3} \u4E2A`,
2775
+ /* @__PURE__ */ jsx13(ChevronDown, { size: 13, className: expanded ? "rotate-180" : void 0 })
2776
+ ]
2777
+ }
2778
+ ) : null
2779
+ ] }) : null
2780
+ ] }) : null,
2781
+ followup.suggestions.length > 0 ? /* @__PURE__ */ jsxs11("section", { "aria-label": "\u4E0B\u4E00\u6B65\u5EFA\u8BAE", className: "flex flex-col gap-1.5", children: [
2782
+ /* @__PURE__ */ jsx13("div", { className: "text-xs font-medium text-[hsl(var(--muted-foreground))]", children: "\u4E0B\u4E00\u6B65\u53EF\u4EE5" }),
2783
+ followup.suggestions.map((suggestion, suggestionIndex) => /* @__PURE__ */ jsxs11(
2784
+ "button",
2785
+ {
2786
+ type: "button",
2787
+ disabled: isViewer,
2788
+ onClick: () => {
2789
+ if (!adopted.current.has(suggestionIndex)) {
2790
+ adopted.current.add(suggestionIndex);
2791
+ emitInteraction(onInteraction, {
2792
+ type: "suggestion_adopted",
2793
+ sessionId,
2794
+ assistantEntryId: followup.assistant_entry_id,
2795
+ suggestionIndex
2796
+ });
2797
+ }
2798
+ onSuggestion?.(suggestion);
2799
+ },
2800
+ className: "group flex items-center gap-2 rounded-xl bg-[hsl(var(--muted)/0.62)] px-3 py-2 text-left text-[13px] disabled:cursor-default disabled:opacity-60",
2801
+ children: [
2802
+ /* @__PURE__ */ jsx13("span", { children: suggestion }),
2803
+ /* @__PURE__ */ jsx13(ArrowRight, { size: 14, className: "ml-auto shrink-0" })
2804
+ ]
2805
+ },
2806
+ suggestion
2807
+ ))
2808
+ ] }) : null,
2809
+ /* @__PURE__ */ jsx13(
2810
+ ResultFeedback,
2811
+ {
2812
+ followup,
2813
+ sessionId,
2814
+ isViewer,
2815
+ onInteraction,
2816
+ savedFeedback,
2817
+ onFeedbackSaved
2818
+ }
2819
+ )
2820
+ ] });
2821
+ }
2822
+
2535
2823
  // src/components/UserMessageBubble.tsx
2536
2824
  import { getFileParts, getImageParts, getTextContent as getTextContent2 } from "@blade-hq/agent-client";
2537
2825
  import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
@@ -2617,6 +2905,8 @@ function getMessageResetSignature(messages) {
2617
2905
  }
2618
2906
  function MessageList({
2619
2907
  messages,
2908
+ postChatFollowup,
2909
+ onSuggestion,
2620
2910
  isStreaming,
2621
2911
  sessionStatus,
2622
2912
  askAnswers,
@@ -2624,7 +2914,11 @@ function MessageList({
2624
2914
  toolCallRenderer,
2625
2915
  emptyState,
2626
2916
  className,
2627
- sessionId
2917
+ sessionId,
2918
+ isViewer = false,
2919
+ onFollowupInteraction,
2920
+ resultFeedbackByEntry = /* @__PURE__ */ new Map(),
2921
+ onResultFeedbackSaved
2628
2922
  }) {
2629
2923
  const renderBlocks = useMemo7(() => {
2630
2924
  const visible = messages.filter((message) => {
@@ -2672,7 +2966,7 @@ function MessageList({
2672
2966
  blocks.push({
2673
2967
  type: "message",
2674
2968
  message,
2675
- key: message.entry_id ?? `${message.role}-${blocks.length}`
2969
+ key: message.render_id ?? message.entry_id ?? `${message.role}-${blocks.length}`
2676
2970
  });
2677
2971
  }
2678
2972
  flushAssistant();
@@ -2710,24 +3004,47 @@ function MessageList({
2710
3004
  return /* @__PURE__ */ jsx15("div", { "data-entry-id": block.message.entry_id, children: isUserMessage(block.message) ? /* @__PURE__ */ jsx15(UserMessageBubble, { message: block.message }) : isErrorMessage(block.message) ? /* @__PURE__ */ jsx15(ErrorMessageBlock, { message: block.message }) : null }, block.key);
2711
3005
  }
2712
3006
  if (block.type === "assistant_turn") {
2713
- return /* @__PURE__ */ jsx15("div", { "data-entry-id": block.messages[0]?.entry_id, children: /* @__PURE__ */ jsx15(
3007
+ const blockFeedback = block.messages.map(
3008
+ (message) => message.entry_id ? resultFeedbackByEntry.get(message.entry_id) : void 0
3009
+ ).find((feedback) => feedback != null);
3010
+ const hasActiveFollowup = Boolean(
3011
+ postChatFollowup && block.messages.some(
3012
+ (message) => message.entry_id === postChatFollowup.assistant_entry_id
3013
+ )
3014
+ );
3015
+ return /* @__PURE__ */ jsx15("div", { "data-entry-id": block.messages[0]?.entry_id, children: /* @__PURE__ */ jsxs13(
2714
3016
  RenderErrorBoundary,
2715
3017
  {
2716
3018
  label: "\u52A9\u624B\u6D88\u606F",
2717
3019
  details: block.key,
2718
3020
  resetKey: getMessageResetSignature(block.messages),
2719
- children: /* @__PURE__ */ jsx15(
2720
- AssistantTurnBlock,
2721
- {
2722
- messages: block.messages,
2723
- isStreaming: block.isStreaming,
2724
- askAnswers,
2725
- onAnswer,
2726
- sessionStatus,
2727
- toolCallRenderer,
2728
- sessionId
2729
- }
2730
- )
3021
+ children: [
3022
+ /* @__PURE__ */ jsx15(
3023
+ AssistantTurnBlock,
3024
+ {
3025
+ messages: block.messages,
3026
+ isStreaming: block.isStreaming,
3027
+ askAnswers,
3028
+ onAnswer,
3029
+ sessionStatus,
3030
+ toolCallRenderer,
3031
+ sessionId
3032
+ }
3033
+ ),
3034
+ blockFeedback && !hasActiveFollowup ? /* @__PURE__ */ jsx15(HistoricalResultFeedback, { feedback: blockFeedback }) : null,
3035
+ hasActiveFollowup && postChatFollowup ? /* @__PURE__ */ jsx15(
3036
+ PostChatFollowupBlock,
3037
+ {
3038
+ followup: postChatFollowup,
3039
+ sessionId,
3040
+ onSuggestion,
3041
+ isViewer,
3042
+ onInteraction: onFollowupInteraction,
3043
+ savedFeedback: blockFeedback,
3044
+ onFeedbackSaved: onResultFeedbackSaved
3045
+ }
3046
+ ) : null
3047
+ ]
2731
3048
  }
2732
3049
  ) }, block.key);
2733
3050
  }
@@ -2754,8 +3071,8 @@ function MessageList({
2754
3071
  }
2755
3072
  function AutoScrollOnUserSend({ userMessageCount }) {
2756
3073
  const { scrollToBottom } = useStickToBottomContext();
2757
- const previousCountRef = useRef6(userMessageCount);
2758
- useEffect6(() => {
3074
+ const previousCountRef = useRef8(userMessageCount);
3075
+ useEffect8(() => {
2759
3076
  if (userMessageCount > previousCountRef.current) {
2760
3077
  scrollToBottom("instant");
2761
3078
  }
@@ -2765,9 +3082,9 @@ function AutoScrollOnUserSend({ userMessageCount }) {
2765
3082
  }
2766
3083
  function ScrollToBottomButton() {
2767
3084
  const { isAtBottom, scrollToBottom } = useStickToBottomContext();
2768
- const [visible, setVisible] = useState11(false);
2769
- const hideTimerRef = useRef6(null);
2770
- useEffect6(() => {
3085
+ const [visible, setVisible] = useState12(false);
3086
+ const hideTimerRef = useRef8(null);
3087
+ useEffect8(() => {
2771
3088
  if (isAtBottom) {
2772
3089
  if (!hideTimerRef.current) {
2773
3090
  hideTimerRef.current = setTimeout(() => {
@@ -2789,7 +3106,7 @@ function ScrollToBottomButton() {
2789
3106
  }
2790
3107
  };
2791
3108
  }, [isAtBottom]);
2792
- const handleClick = useCallback5(() => {
3109
+ const handleClick = useCallback6(() => {
2793
3110
  if (hideTimerRef.current) {
2794
3111
  clearTimeout(hideTimerRef.current);
2795
3112
  hideTimerRef.current = null;
@@ -2837,16 +3154,22 @@ function ChatSurface({
2837
3154
  connection,
2838
3155
  errorMessage,
2839
3156
  messages,
3157
+ postChatFollowup,
2840
3158
  isStreaming,
2841
3159
  isStopping,
2842
3160
  inputText,
2843
3161
  onInputChange,
3162
+ onSuggestion,
2844
3163
  onSend,
2845
3164
  onStop,
2846
3165
  sessionStatus,
2847
3166
  askAnswers,
2848
3167
  onAnswer,
2849
3168
  sessionId,
3169
+ isViewer,
3170
+ resultFeedbackByEntry,
3171
+ onResultFeedbackSaved,
3172
+ onFollowupInteraction,
2850
3173
  beforeInput,
2851
3174
  banner
2852
3175
  }) {
@@ -2870,6 +3193,8 @@ function ChatSurface({
2870
3193
  MessageList,
2871
3194
  {
2872
3195
  messages,
3196
+ postChatFollowup,
3197
+ onSuggestion,
2873
3198
  isStreaming,
2874
3199
  sessionStatus,
2875
3200
  askAnswers,
@@ -2877,7 +3202,11 @@ function ChatSurface({
2877
3202
  toolCallRenderer: renderers?.toolCall,
2878
3203
  emptyState: slots?.emptyState,
2879
3204
  className: classNames?.messageList,
2880
- sessionId
3205
+ sessionId,
3206
+ isViewer,
3207
+ resultFeedbackByEntry,
3208
+ onResultFeedbackSaved,
3209
+ onFollowupInteraction
2881
3210
  }
2882
3211
  ),
2883
3212
  beforeInput,
@@ -2901,13 +3230,13 @@ function ChatSurface({
2901
3230
  }
2902
3231
 
2903
3232
  // src/components/AgentChat.tsx
2904
- import { Fragment as Fragment2, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
3233
+ import { Fragment as Fragment3, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
2905
3234
  function isUnauthorizedError(error) {
2906
3235
  return error instanceof BladeApiError && error.status === 401;
2907
3236
  }
2908
3237
  function LoginCard({ client, onLoggedIn }) {
2909
- const [loggingIn, setLoggingIn] = useState12(false);
2910
- const [loginError, setLoginError] = useState12(null);
3238
+ const [loggingIn, setLoggingIn] = useState13(false);
3239
+ const [loginError, setLoginError] = useState13(null);
2911
3240
  const handleLogin = async () => {
2912
3241
  setLoggingIn(true);
2913
3242
  setLoginError(null);
@@ -2939,8 +3268,8 @@ function LoginCard({ client, onLoggedIn }) {
2939
3268
  }
2940
3269
  function AgentChat(props) {
2941
3270
  const client = useBladeClient();
2942
- const [attempt, setAttempt] = useState12(0);
2943
- const [needLogin, setNeedLogin] = useState12(() => !client.hasToken());
3271
+ const [attempt, setAttempt] = useState13(0);
3272
+ const [needLogin, setNeedLogin] = useState13(() => !client.hasToken());
2944
3273
  if (needLogin) {
2945
3274
  return /* @__PURE__ */ jsx17(
2946
3275
  "div",
@@ -2973,21 +3302,61 @@ function ChatSessionView({
2973
3302
  slots,
2974
3303
  placeholder,
2975
3304
  theme,
3305
+ onFollowupInteraction,
2976
3306
  onUnauthorized
2977
3307
  }) {
3308
+ const client = useBladeClient();
2978
3309
  const { session, state, error } = useAgentSession(sessionId, {
2979
3310
  createOptions,
2980
3311
  onSessionCreated
2981
3312
  });
2982
3313
  const replay = useReplay(session);
2983
- const [stopRequested, setStopRequested] = useState12(false);
2984
- const [inputText, setInputText] = useState12("");
2985
- useEffect7(() => {
3314
+ const [stopRequested, setStopRequested] = useState13(false);
3315
+ const [inputText, setInputText] = useState13("");
3316
+ const [resultFeedback, setResultFeedback] = useState13([]);
3317
+ const resolvedSessionId = session?.sessionId;
3318
+ const isViewer = state?.viewerRole === "viewer";
3319
+ useEffect9(() => {
3320
+ setResultFeedback([]);
3321
+ if (!resolvedSessionId || isViewer) return;
3322
+ let cancelled = false;
3323
+ void client.sessions.listResultFeedback(resolvedSessionId).then(
3324
+ (items) => {
3325
+ if (cancelled) return;
3326
+ setResultFeedback((current) => {
3327
+ const merged = new Map(items.map((item) => [item.assistant_entry_id, item]));
3328
+ for (const item of current) {
3329
+ const fetched = merged.get(item.assistant_entry_id);
3330
+ if (!fetched || fetched.updated_at < item.updated_at) {
3331
+ merged.set(item.assistant_entry_id, item);
3332
+ }
3333
+ }
3334
+ return [...merged.values()];
3335
+ });
3336
+ },
3337
+ () => {
3338
+ }
3339
+ );
3340
+ return () => {
3341
+ cancelled = true;
3342
+ };
3343
+ }, [client, isViewer, resolvedSessionId]);
3344
+ const resultFeedbackByEntry = useMemo8(
3345
+ () => new Map(resultFeedback.map((item) => [item.assistant_entry_id, item])),
3346
+ [resultFeedback]
3347
+ );
3348
+ const handleResultFeedbackSaved = useCallback7((saved) => {
3349
+ setResultFeedback((current) => [
3350
+ ...current.filter((item) => item.assistant_entry_id !== saved.assistant_entry_id),
3351
+ saved
3352
+ ]);
3353
+ }, []);
3354
+ useEffect9(() => {
2986
3355
  if (session) {
2987
3356
  onSessionReady?.(session);
2988
3357
  }
2989
3358
  }, [session, onSessionReady]);
2990
- useEffect7(() => {
3359
+ useEffect9(() => {
2991
3360
  if (!session) return;
2992
3361
  const offAttach = session.on("attachRequested", ({ label, content }) => {
2993
3362
  setInputText((prev) => `${prev ? `${prev}
@@ -3003,12 +3372,12 @@ ${content}`);
3003
3372
  offInsert();
3004
3373
  };
3005
3374
  }, [session]);
3006
- useEffect7(() => {
3375
+ useEffect9(() => {
3007
3376
  if (isUnauthorizedError(error)) {
3008
3377
  onUnauthorized();
3009
3378
  }
3010
3379
  }, [error, onUnauthorized]);
3011
- useEffect7(() => {
3380
+ useEffect9(() => {
3012
3381
  if (!session || !commands) return;
3013
3382
  const unsubscribes = Object.entries(commands).map(
3014
3383
  ([action, handler]) => session.onCommand(action, (payload) => handler(payload))
@@ -3033,12 +3402,13 @@ ${content}`);
3033
3402
  ChatSurface,
3034
3403
  {
3035
3404
  theme,
3405
+ onFollowupInteraction,
3036
3406
  classNames,
3037
3407
  renderers,
3038
3408
  slots,
3039
3409
  placeholder,
3040
3410
  connection: state?.connection ?? "connecting",
3041
- banner: /* @__PURE__ */ jsxs15(Fragment2, { children: [
3411
+ banner: /* @__PURE__ */ jsxs15(Fragment3, { children: [
3042
3412
  /* @__PURE__ */ jsx17(
3043
3413
  ReplayBar,
3044
3414
  {
@@ -3053,10 +3423,14 @@ ${content}`);
3053
3423
  ] }),
3054
3424
  errorMessage,
3055
3425
  messages: state?.messages ?? [],
3426
+ postChatFollowup: latestPostChatFollowup(state?.turns ?? []),
3427
+ resultFeedbackByEntry,
3428
+ onResultFeedbackSaved: handleResultFeedbackSaved,
3056
3429
  isStreaming,
3057
3430
  isStopping,
3058
3431
  inputText,
3059
3432
  onInputChange: setInputText,
3433
+ onSuggestion: setInputText,
3060
3434
  onSend: handleSend,
3061
3435
  onStop: handleStop,
3062
3436
  sessionStatus: state?.status ?? void 0,
@@ -3067,16 +3441,17 @@ ${content}`);
3067
3441
  askUserAnswer: { tool_call_id: toolCallId, ...answerData }
3068
3442
  });
3069
3443
  },
3070
- sessionId: session?.sessionId
3444
+ sessionId: resolvedSessionId,
3445
+ isViewer
3071
3446
  }
3072
3447
  );
3073
3448
  }
3074
3449
 
3075
3450
  // src/components/LlmChat.tsx
3076
- import { useEffect as useEffect8, useMemo as useMemo8, useState as useState14 } from "react";
3451
+ import { useEffect as useEffect10, useMemo as useMemo9, useState as useState15 } from "react";
3077
3452
 
3078
3453
  // src/components/LlmAdvancedSettings.tsx
3079
- import { useState as useState13 } from "react";
3454
+ import { useState as useState14 } from "react";
3080
3455
  import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
3081
3456
  var FIELDS = [
3082
3457
  { id: "baseURL", label: "\u6A21\u578B\u670D\u52A1\u5730\u5740", placeholder: "http://\u5185\u7F51\u5730\u5740/v1" },
@@ -3123,8 +3498,8 @@ function writeOverride(settings, baseURL, override) {
3123
3498
  }
3124
3499
  function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
3125
3500
  const normalized = normalizeAdvanced(settings);
3126
- const [open, setOpen] = useState13(false);
3127
- const [draft, setDraft] = useState13(override);
3501
+ const [open, setOpen] = useState14(false);
3502
+ const [draft, setDraft] = useState14(override);
3128
3503
  if (!normalized) return null;
3129
3504
  const fields = FIELDS.filter((field) => normalized[field.id]);
3130
3505
  const dirty = Object.keys(override).length > 0;
@@ -3207,12 +3582,12 @@ function LlmChat({
3207
3582
  onOverrideChange,
3208
3583
  ...options
3209
3584
  }) {
3210
- const [override, setOverride] = useState14(() => readOverride(advanced, options.baseURL));
3585
+ const [override, setOverride] = useState15(() => readOverride(advanced, options.baseURL));
3211
3586
  const effective = { ...options, ...override };
3212
3587
  const { messages, isStreaming, error, send, stop, reset } = useLlmChat(effective);
3213
- const [inputText, setInputText] = useState14("");
3214
- const [stopRequested, setStopRequested] = useState14(false);
3215
- const handle = useMemo8(
3588
+ const [inputText, setInputText] = useState15("");
3589
+ const [stopRequested, setStopRequested] = useState15(false);
3590
+ const handle = useMemo9(
3216
3591
  () => ({
3217
3592
  insertText: (text) => setInputText((prev) => prev ? `${prev}
3218
3593
  ${text}` : text),
@@ -3221,7 +3596,7 @@ ${text}` : text),
3221
3596
  }),
3222
3597
  [send, reset]
3223
3598
  );
3224
- useEffect8(() => {
3599
+ useEffect10(() => {
3225
3600
  onReady?.(handle);
3226
3601
  }, [handle, onReady]);
3227
3602
  return /* @__PURE__ */ jsx19(
@@ -3311,7 +3686,8 @@ lucide-react/dist/esm/shared/src/utils.js:
3311
3686
  lucide-react/dist/esm/defaultAttributes.js:
3312
3687
  lucide-react/dist/esm/Icon.js:
3313
3688
  lucide-react/dist/esm/createLucideIcon.js:
3314
- lucide-react/dist/esm/icons/archive.js:
3689
+ lucide-react/dist/esm/icons/arrow-right.js:
3690
+ lucide-react/dist/esm/icons/arrow-up-right.js:
3315
3691
  lucide-react/dist/esm/icons/arrow-up.js:
3316
3692
  lucide-react/dist/esm/icons/bot.js:
3317
3693
  lucide-react/dist/esm/icons/brain.js:
@@ -3321,21 +3697,19 @@ lucide-react/dist/esm/icons/chevron-right.js:
3321
3697
  lucide-react/dist/esm/icons/circle-alert.js:
3322
3698
  lucide-react/dist/esm/icons/copy.js:
3323
3699
  lucide-react/dist/esm/icons/download.js:
3324
- lucide-react/dist/esm/icons/file-code-2.js:
3325
- lucide-react/dist/esm/icons/file-spreadsheet.js:
3326
3700
  lucide-react/dist/esm/icons/file-text.js:
3327
3701
  lucide-react/dist/esm/icons/file.js:
3328
3702
  lucide-react/dist/esm/icons/film.js:
3329
- lucide-react/dist/esm/icons/image.js:
3703
+ lucide-react/dist/esm/icons/globe.js:
3330
3704
  lucide-react/dist/esm/icons/layers.js:
3331
3705
  lucide-react/dist/esm/icons/lightbulb.js:
3332
3706
  lucide-react/dist/esm/icons/loader-circle.js:
3333
3707
  lucide-react/dist/esm/icons/lock-keyhole.js:
3334
3708
  lucide-react/dist/esm/icons/message-square-more.js:
3335
3709
  lucide-react/dist/esm/icons/message-square.js:
3336
- lucide-react/dist/esm/icons/music.js:
3337
3710
  lucide-react/dist/esm/icons/play.js:
3338
3711
  lucide-react/dist/esm/icons/settings-2.js:
3712
+ lucide-react/dist/esm/icons/sparkles.js:
3339
3713
  lucide-react/dist/esm/icons/square.js:
3340
3714
  lucide-react/dist/esm/icons/triangle-alert.js:
3341
3715
  lucide-react/dist/esm/icons/x.js: