@blade-hq/agent-react 2608.0.5 → 2608.0.6

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 CHANGED
@@ -104,7 +104,7 @@ await session?.send("你好")
104
104
  <MarkdownContent content={markdownText} />
105
105
  ```
106
106
 
107
- 传了 `sessionId` 才会把智能体产出文件的 `MEDIA:` 行折叠成下载卡片(下载要用它)。`ChatView` 内部已经传好,只有单独用 `MarkdownContent` 时需要自己给:
107
+ 传了 `sessionId` 才会把智能体产出文件的 `MEDIA:` 行折叠成以文件名为文本的下载链接(下载要用它)。`ChatView` 内部已经传好,只有单独用 `MarkdownContent` 时需要自己给:
108
108
 
109
109
  ```tsx
110
110
  <MarkdownContent sessionId={sessionId}>{markdownText}</MarkdownContent>
@@ -7,7 +7,7 @@ interface Props {
7
7
  onAnswer?: (answer: string, toolCallId: string, answerData: AskUserAnswerData) => void;
8
8
  sessionStatus?: string;
9
9
  toolCallRenderer?: ToolCallRenderer;
10
- /** 传给 Markdown 渲染,用于把 MEDIA 行折叠成文件下载卡片。 */
10
+ /** 传给 Markdown 渲染,用于把 MEDIA 行折叠成文件下载链接。 */
11
11
  sessionId?: string;
12
12
  }
13
13
  /**
@@ -3,6 +3,6 @@ interface Props {
3
3
  connection: ConnectionStatus;
4
4
  className?: string;
5
5
  }
6
- /** 连接状态提示条:数据来自 SessionState.connection,正常连接时不渲染。 */
6
+ /** 连接状态提示条:短暂断线静默,超过容错时间后再逐级提示。 */
7
7
  export declare function ConnectionBanner({ connection, className }: Props): import("react/jsx-runtime").JSX.Element | null;
8
8
  export {};
@@ -8,9 +8,9 @@ export type FileCardProps = ComponentPropsWithRef<"span"> & ExtraProps & {
8
8
  sessionId?: string;
9
9
  };
10
10
  /**
11
- * 智能体产出文件的下载卡片(由 MEDIA 行折叠而来)。
11
+ * 智能体产出文件的下载链接(由 MEDIA 行折叠而来)。
12
12
  *
13
- * 整张卡片只有一个动作:下载。第一方界面点卡片是「在右侧预览」,那依赖 app 的 artifact
14
- * 面板,SDK 里没有这块,所以不摆预览入口也不写预览文案——UI 不能承诺不存在的能力。
13
+ * href 让 Vue/纯 HTML 宿主看到标准的「文件名→URL」语义;普通点击仍交给
14
+ * downloadFile,避免跨域时 download 属性被忽略,也确保使用点击时的最新 token。
15
15
  */
16
16
  export declare function FileCard({ node, "data-path": pathAttribute, "data-name": nameAttribute, dataPath, dataName, sessionId, children, className, ...props }: FileCardProps): import("react/jsx-runtime").JSX.Element;
@@ -4,13 +4,13 @@ export interface MarkdownContentProps {
4
4
  /** 流式渲染传 "streaming",历史静态内容默认 "static"。 */
5
5
  mode?: "streaming" | "static";
6
6
  /**
7
- * 会话 id。传了才会把 `MEDIA:` 行折叠成文件下载卡片——下载要用它。
7
+ * 会话 id。传了才会把 `MEDIA:` 行折叠成文件下载链接——下载要用它。
8
8
  * 不要给用户消息传:用户自己在输入框打一行 `MEDIA:` 不该被当成平台产物。
9
9
  */
10
10
  sessionId?: string;
11
11
  }
12
12
  /**
13
- * SDK 版 Markdown 渲染:附带代码块复制按钮、智能体产出文件的下载卡片;
13
+ * SDK 版 Markdown 渲染:附带代码块复制按钮、智能体产出文件的下载链接;
14
14
  * 相比第一方版本去掉了 mermaid、外链确认弹窗与文件预览。
15
15
  */
16
16
  export declare function MarkdownContent({ children, className, mode, sessionId }: MarkdownContentProps): import("react/jsx-runtime").JSX.Element;
@@ -10,7 +10,7 @@ interface Props {
10
10
  toolCallRenderer?: ToolCallRenderer;
11
11
  emptyState?: ReactNode;
12
12
  className?: string;
13
- /** 传给助手消息的 Markdown 渲染,用于把 MEDIA 行折叠成文件下载卡片。 */
13
+ /** 传给助手消息的 Markdown 渲染,用于把 MEDIA 行折叠成文件下载链接。 */
14
14
  sessionId?: string;
15
15
  }
16
16
  /**
package/dist/index.js CHANGED
@@ -173,13 +173,6 @@ var createLucideIcon = (iconName, iconNode) => {
173
173
  return Component2;
174
174
  };
175
175
 
176
- // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/archive.js
177
- var Archive = createLucideIcon("Archive", [
178
- ["rect", { width: "20", height: "5", x: "2", y: "3", rx: "1", key: "1wp1u1" }],
179
- ["path", { d: "M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8", key: "1s80jp" }],
180
- ["path", { d: "M10 12h4", key: "a56b0p" }]
181
- ]);
182
-
183
176
  // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/arrow-up.js
184
177
  var ArrowUp = createLucideIcon("ArrowUp", [
185
178
  ["path", { d: "m5 12 7-7 7 7", key: "hav0vg" }],
@@ -247,31 +240,6 @@ var Copy = createLucideIcon("Copy", [
247
240
  ["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" }]
248
241
  ]);
249
242
 
250
- // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/download.js
251
- var Download = createLucideIcon("Download", [
252
- ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
253
- ["polyline", { points: "7 10 12 15 17 10", key: "2ggqvy" }],
254
- ["line", { x1: "12", x2: "12", y1: "15", y2: "3", key: "1vk2je" }]
255
- ]);
256
-
257
- // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/file-code-2.js
258
- var FileCode2 = createLucideIcon("FileCode2", [
259
- ["path", { d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4", key: "1pf5j1" }],
260
- ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }],
261
- ["path", { d: "m5 12-3 3 3 3", key: "oke12k" }],
262
- ["path", { d: "m9 18 3-3-3-3", key: "112psh" }]
263
- ]);
264
-
265
- // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/file-spreadsheet.js
266
- var FileSpreadsheet = createLucideIcon("FileSpreadsheet", [
267
- ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", key: "1rqfz7" }],
268
- ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }],
269
- ["path", { d: "M8 13h2", key: "yr2amv" }],
270
- ["path", { d: "M14 13h2", key: "un5t4a" }],
271
- ["path", { d: "M8 17h2", key: "2yhykz" }],
272
- ["path", { d: "M14 17h2", key: "10kma7" }]
273
- ]);
274
-
275
243
  // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/file-text.js
276
244
  var FileText = createLucideIcon("FileText", [
277
245
  ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", key: "1rqfz7" }],
@@ -281,31 +249,6 @@ var FileText = createLucideIcon("FileText", [
281
249
  ["path", { d: "M16 17H8", key: "z1uh3a" }]
282
250
  ]);
283
251
 
284
- // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/file.js
285
- var File = createLucideIcon("File", [
286
- ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", key: "1rqfz7" }],
287
- ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }]
288
- ]);
289
-
290
- // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/film.js
291
- var Film = createLucideIcon("Film", [
292
- ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }],
293
- ["path", { d: "M7 3v18", key: "bbkbws" }],
294
- ["path", { d: "M3 7.5h4", key: "zfgn84" }],
295
- ["path", { d: "M3 12h18", key: "1i2n21" }],
296
- ["path", { d: "M3 16.5h4", key: "1230mu" }],
297
- ["path", { d: "M17 3v18", key: "in4fa5" }],
298
- ["path", { d: "M17 7.5h4", key: "myr1c1" }],
299
- ["path", { d: "M17 16.5h4", key: "go4c1d" }]
300
- ]);
301
-
302
- // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/image.js
303
- var Image = createLucideIcon("Image", [
304
- ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
305
- ["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
306
- ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }]
307
- ]);
308
-
309
252
  // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/layers.js
310
253
  var Layers = createLucideIcon("Layers", [
311
254
  [
@@ -369,13 +312,6 @@ var MessageSquare = createLucideIcon("MessageSquare", [
369
312
  ["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" }]
370
313
  ]);
371
314
 
372
- // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/music.js
373
- var Music = createLucideIcon("Music", [
374
- ["path", { d: "M9 18V5l12-2v13", key: "1jmyc2" }],
375
- ["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
376
- ["circle", { cx: "18", cy: "16", r: "3", key: "1hluhg" }]
377
- ]);
378
-
379
315
  // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/square.js
380
316
  var Square = createLucideIcon("Square", [
381
317
  ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }]
@@ -401,7 +337,7 @@ var X = createLucideIcon("X", [
401
337
  ]);
402
338
 
403
339
  // src/components/ChatView.tsx
404
- import { useEffect as useEffect6, useState as useState10 } from "react";
340
+ import { useEffect as useEffect7, useState as useState11 } from "react";
405
341
 
406
342
  // src/lib/utils.ts
407
343
  function cn(...inputs) {
@@ -487,24 +423,74 @@ function ChatInput({
487
423
  }
488
424
 
489
425
  // src/components/ConnectionBanner.tsx
426
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
490
427
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
428
+ var CONNECTION_NOTICE_DELAY_MS = 3e3;
429
+ var CONNECTION_ERROR_DELAY_MS = 15e3;
430
+ function useConnectionNoticePhase(connected) {
431
+ const [phase, setPhase] = useState2("hidden");
432
+ const connectedRef = useRef2(connected);
433
+ const timersRef = useRef2([]);
434
+ connectedRef.current = connected;
435
+ useEffect2(() => {
436
+ const clearTimers = () => {
437
+ for (const timer of timersRef.current) clearTimeout(timer);
438
+ timersRef.current = [];
439
+ };
440
+ const startGracePeriod = () => {
441
+ clearTimers();
442
+ setPhase("hidden");
443
+ timersRef.current = [
444
+ setTimeout(() => setPhase("recovering"), CONNECTION_NOTICE_DELAY_MS),
445
+ setTimeout(() => setPhase("failed"), CONNECTION_ERROR_DELAY_MS)
446
+ ];
447
+ };
448
+ if (connected) {
449
+ clearTimers();
450
+ setPhase("hidden");
451
+ } else {
452
+ startGracePeriod();
453
+ }
454
+ const handleForeground = () => {
455
+ if (!connectedRef.current) startGracePeriod();
456
+ };
457
+ const handleVisibilityChange = () => {
458
+ if (document.visibilityState === "visible") handleForeground();
459
+ };
460
+ window.addEventListener("blade:app-active", handleForeground);
461
+ window.addEventListener("focus", handleForeground);
462
+ window.addEventListener("pageshow", handleForeground);
463
+ document.addEventListener("visibilitychange", handleVisibilityChange);
464
+ return () => {
465
+ clearTimers();
466
+ window.removeEventListener("blade:app-active", handleForeground);
467
+ window.removeEventListener("focus", handleForeground);
468
+ window.removeEventListener("pageshow", handleForeground);
469
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
470
+ };
471
+ }, [connected]);
472
+ return phase;
473
+ }
491
474
  function ConnectionBanner({ connection, className }) {
492
- if (connection === "connected" || connection === "connecting") {
493
- return null;
494
- }
495
- const reconnecting = connection === "reconnecting";
475
+ const hasConnectedRef = useRef2(connection === "connected" || connection === "reconnecting");
476
+ if (connection === "connected") hasConnectedRef.current = true;
477
+ const connected = connection === "connected";
478
+ const phase = useConnectionNoticePhase(connected);
479
+ if (connected || phase === "hidden") return null;
480
+ const recovering = phase === "recovering";
481
+ const firstConnection = !hasConnectedRef.current;
496
482
  return /* @__PURE__ */ jsx3("div", { className: cn("blade-chat-banner bg-[hsl(var(--background))] px-5 pt-3", className), children: /* @__PURE__ */ jsxs2(
497
483
  "div",
498
484
  {
499
485
  className: cn(
500
486
  "mx-auto flex max-w-3xl items-start gap-3 rounded-2xl border px-4 py-3",
501
- reconnecting ? "border-amber-500/25 bg-amber-500/10 text-amber-100" : "border-rose-500/25 bg-rose-500/10 text-rose-100"
487
+ recovering ? "border-amber-500/25 bg-amber-500/10 text-amber-100" : "border-rose-500/25 bg-rose-500/10 text-rose-100"
502
488
  ),
503
489
  children: [
504
- /* @__PURE__ */ jsx3("span", { className: "mt-0.5 shrink-0", children: reconnecting ? /* @__PURE__ */ jsx3(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx3(TriangleAlert, { size: 14 }) }),
490
+ /* @__PURE__ */ jsx3("span", { className: "mt-0.5 shrink-0", children: recovering ? /* @__PURE__ */ jsx3(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx3(TriangleAlert, { size: 14 }) }),
505
491
  /* @__PURE__ */ jsxs2("div", { className: "min-w-0", children: [
506
- /* @__PURE__ */ jsx3("div", { className: "text-sm font-medium", children: reconnecting ? "\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF0C\u6B63\u5728\u91CD\u8FDE\u2026" : "\u8FDE\u63A5\u5DF2\u65AD\u5F00" }),
507
- /* @__PURE__ */ jsx3("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" })
492
+ /* @__PURE__ */ jsx3("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" }),
493
+ /* @__PURE__ */ jsx3("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" })
508
494
  ] })
509
495
  ]
510
496
  }
@@ -513,10 +499,10 @@ function ConnectionBanner({ connection, className }) {
513
499
 
514
500
  // src/components/MessageList.tsx
515
501
  import { isHiddenInternalMessage } from "@blade-hq/agent-client";
516
- import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo6, useRef as useRef5, useState as useState9 } from "react";
502
+ import { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo6, useRef as useRef6, useState as useState10 } from "react";
517
503
 
518
504
  // ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/useStickToBottom.js
519
- import { useCallback, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
505
+ import { useCallback, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
520
506
  var DEFAULT_SPRING_ANIMATION = {
521
507
  /**
522
508
  * A value from 0 to 1, on how much to damp the animation.
@@ -553,10 +539,10 @@ globalThis.document?.addEventListener("click", () => {
553
539
  mouseDown = false;
554
540
  });
555
541
  var useStickToBottom = (options = {}) => {
556
- const [escapedFromLock, updateEscapedFromLock] = useState2(false);
557
- const [isAtBottom, updateIsAtBottom] = useState2(options.initial !== false);
558
- const [isNearBottom, setIsNearBottom] = useState2(false);
559
- const optionsRef = useRef2(null);
542
+ const [escapedFromLock, updateEscapedFromLock] = useState3(false);
543
+ const [isAtBottom, updateIsAtBottom] = useState3(options.initial !== false);
544
+ const [isNearBottom, setIsNearBottom] = useState3(false);
545
+ const optionsRef = useRef3(null);
560
546
  optionsRef.current = options;
561
547
  const isSelecting = useCallback(() => {
562
548
  if (!mouseDown) {
@@ -860,11 +846,11 @@ function mergeAnimations(...animations) {
860
846
 
861
847
  // ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/StickToBottom.js
862
848
  import * as React from "react";
863
- import { createContext as createContext2, useContext as useContext2, useEffect as useEffect2, useImperativeHandle, useLayoutEffect, useMemo as useMemo3, useRef as useRef3 } from "react";
849
+ import { createContext as createContext2, useContext as useContext2, useEffect as useEffect3, useImperativeHandle, useLayoutEffect, useMemo as useMemo3, useRef as useRef4 } from "react";
864
850
  var StickToBottomContext = createContext2(null);
865
- var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect2;
851
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect3;
866
852
  function StickToBottom({ instance, children, resize, initial, mass, damping, stiffness, targetScrollTop: currentTargetScrollTop, contextRef, ...props }) {
867
- const customTargetScrollTop = useRef3(null);
853
+ const customTargetScrollTop = useRef4(null);
868
854
  const targetScrollTop = React.useCallback((target, elements) => {
869
855
  const get = context?.targetScrollTop ?? currentTargetScrollTop;
870
856
  return get?.(target, elements) ?? target;
@@ -941,10 +927,10 @@ function useStickToBottomContext() {
941
927
 
942
928
  // src/components/AssistantTurnBlock.tsx
943
929
  import { getTextContent, normalizeMessageContent } from "@blade-hq/agent-client";
944
- import { useState as useState8 } from "react";
930
+ import { useState as useState9 } from "react";
945
931
 
946
932
  // src/components/AgentLoopBlock.tsx
947
- import { useState as useState3 } from "react";
933
+ import { useState as useState4 } from "react";
948
934
 
949
935
  // src/components/display-utils.ts
950
936
  var TOOL_NAME_ALIASES = {
@@ -1076,7 +1062,7 @@ function parseAgentDescription(argumentsJson) {
1076
1062
  }
1077
1063
  }
1078
1064
  function AgentLoopBlock({ toolCall }) {
1079
- const [expanded, setExpanded] = useState3(false);
1065
+ const [expanded, setExpanded] = useState4(false);
1080
1066
  const description = parseAgentDescription(toolCall.arguments);
1081
1067
  const running = toolCall.status === "pending" || toolCall.status === "awaiting_answer";
1082
1068
  const failed = toolCall.status === "error" || toolCall.status === "cancelled";
@@ -1141,10 +1127,10 @@ function AgentLoopBlock({ toolCall }) {
1141
1127
 
1142
1128
  // src/components/MarkdownContent.tsx
1143
1129
  import {
1144
- useEffect as useEffect3,
1130
+ useEffect as useEffect4,
1145
1131
  useMemo as useMemo4,
1146
- useRef as useRef4,
1147
- useState as useState5
1132
+ useRef as useRef5,
1133
+ useState as useState6
1148
1134
  } from "react";
1149
1135
 
1150
1136
  // src/lib/media-tags.ts
@@ -1191,26 +1177,8 @@ function collapseMediaTags(text, options) {
1191
1177
  }
1192
1178
 
1193
1179
  // src/components/FileCard.tsx
1194
- import { useState as useState4 } from "react";
1180
+ import { useState as useState5 } from "react";
1195
1181
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1196
- var IMAGE_EXTS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "svg", "webp", "ico", "bmp"]);
1197
- var SHEET_EXTS = /* @__PURE__ */ new Set(["xlsx", "xls", "xlsm", "xlsb", "csv"]);
1198
- var TEXT_EXTS = /* @__PURE__ */ new Set(["txt", "md", "markdown", "pdf", "doc", "docx", "rtf"]);
1199
- var CODE_EXTS = /* @__PURE__ */ new Set(["js", "jsx", "ts", "tsx", "py", "go", "json", "yaml", "yml", "html", "css"]);
1200
- var VIDEO_EXTS = /* @__PURE__ */ new Set(["mp4", "webm", "mov", "mkv", "avi"]);
1201
- var AUDIO_EXTS = /* @__PURE__ */ new Set(["mp3", "wav", "ogg", "m4a", "flac"]);
1202
- var ARCHIVE_EXTS = /* @__PURE__ */ new Set(["zip", "tar", "gz", "tgz", "rar", "7z"]);
1203
- function getFileIcon(name) {
1204
- const ext = name.split(".").pop()?.toLowerCase() ?? "";
1205
- if (IMAGE_EXTS.has(ext)) return Image;
1206
- if (SHEET_EXTS.has(ext)) return FileSpreadsheet;
1207
- if (TEXT_EXTS.has(ext)) return FileText;
1208
- if (CODE_EXTS.has(ext)) return FileCode2;
1209
- if (VIDEO_EXTS.has(ext)) return Film;
1210
- if (AUDIO_EXTS.has(ext)) return Music;
1211
- if (ARCHIVE_EXTS.has(ext)) return Archive;
1212
- return File;
1213
- }
1214
1182
  function stringValue(value) {
1215
1183
  return typeof value === "string" ? value : void 0;
1216
1184
  }
@@ -1234,14 +1202,18 @@ function FileCard({
1234
1202
  ...props
1235
1203
  }) {
1236
1204
  const client = useBladeClient();
1237
- const [downloading, setDownloading] = useState4(false);
1238
- const [failed, setFailed] = useState4(false);
1205
+ const [downloading, setDownloading] = useState5(false);
1206
+ const [failed, setFailed] = useState5(false);
1239
1207
  const nodeProperties = node && typeof node === "object" && "properties" in node ? node.properties : void 0;
1240
1208
  const path = pathAttribute ?? dataPath ?? stringValue(nodeProperties?.["data-path"]) ?? stringValue(nodeProperties?.dataPath) ?? "";
1241
1209
  const name = nameAttribute ?? dataName ?? stringValue(nodeProperties?.["data-name"]) ?? stringValue(nodeProperties?.dataName) ?? extractChildrenText(children) ?? "";
1242
1210
  const disabled = !sessionId || !isSafeMediaPath(path);
1243
- const Icon2 = getFileIcon(name);
1244
- const handleDownload = async () => {
1211
+ const downloadUrl = disabled ? void 0 : client.buildAuthedUrl(
1212
+ `/api/sessions/${encodeURIComponent(sessionId)}/files/${encodeURIComponent(path)}`
1213
+ );
1214
+ const handleDownload = async (event) => {
1215
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
1216
+ event.preventDefault();
1245
1217
  if (disabled || downloading || !sessionId) return;
1246
1218
  setDownloading(true);
1247
1219
  setFailed(false);
@@ -1254,19 +1226,18 @@ function FileCard({
1254
1226
  }
1255
1227
  };
1256
1228
  return /* @__PURE__ */ jsxs4("span", { ...props, className: cn("blade-chat-file-card", className), children: [
1257
- /* @__PURE__ */ jsxs4(
1258
- "button",
1229
+ /* @__PURE__ */ jsx5(
1230
+ "a",
1259
1231
  {
1260
- type: "button",
1232
+ href: downloadUrl,
1233
+ download: name,
1261
1234
  onClick: handleDownload,
1262
- disabled: disabled || downloading,
1235
+ "aria-disabled": disabled || void 0,
1236
+ "aria-busy": downloading || void 0,
1263
1237
  "aria-label": `\u4E0B\u8F7D\u6587\u4EF6\uFF1A${name}`,
1264
- title: failed ? "\u4E0B\u8F7D\u5931\u8D25\uFF0C\u70B9\u51FB\u91CD\u8BD5" : `\u4E0B\u8F7D ${name}`,
1265
- children: [
1266
- /* @__PURE__ */ jsx5("span", { className: "blade-chat-file-card-icon", children: downloading ? /* @__PURE__ */ jsx5(LoaderCircle, { size: 15, className: "animate-spin" }) : /* @__PURE__ */ jsx5(Icon2, { size: 15 }) }),
1267
- /* @__PURE__ */ jsx5("span", { className: "blade-chat-file-card-name", children: name }),
1268
- /* @__PURE__ */ jsx5(Download, { size: 13, className: "blade-chat-file-card-action" })
1269
- ]
1238
+ title: failed ? "\u4E0B\u8F7D\u5931\u8D25\uFF0C\u70B9\u51FB\u91CD\u8BD5" : name,
1239
+ className: "blade-chat-file-link",
1240
+ children: name
1270
1241
  }
1271
1242
  ),
1272
1243
  failed && /* @__PURE__ */ jsx5("span", { className: "blade-chat-file-card-error", children: "\u4E0B\u8F7D\u5931\u8D25" })
@@ -1277,10 +1248,10 @@ function FileCard({
1277
1248
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1278
1249
  var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
1279
1250
  function CodeBlockPre({ children, node: _node, ...props }) {
1280
- const preRef = useRef4(null);
1281
- const [copied, setCopied] = useState5(false);
1282
- const [language, setLanguage] = useState5("");
1283
- useEffect3(() => {
1251
+ const preRef = useRef5(null);
1252
+ const [copied, setCopied] = useState6(false);
1253
+ const [language, setLanguage] = useState6("");
1254
+ useEffect4(() => {
1284
1255
  const codeEl = preRef.current?.querySelector("code");
1285
1256
  setLanguage(codeEl?.className.match(/language-(\S+)/)?.[1] ?? "");
1286
1257
  }, []);
@@ -1365,10 +1336,10 @@ function Shimmer({ children = "\u6B63\u5728\u601D\u8003...", className }) {
1365
1336
  }
1366
1337
 
1367
1338
  // src/components/ToolCallBlock.tsx
1368
- import { useState as useState7 } from "react";
1339
+ import { useState as useState8 } from "react";
1369
1340
 
1370
1341
  // src/components/AskUserQuestionBlock.tsx
1371
- import { useEffect as useEffect4, useMemo as useMemo5, useState as useState6 } from "react";
1342
+ import { useEffect as useEffect5, useMemo as useMemo5, useState as useState7 } from "react";
1372
1343
  import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1373
1344
  function AskUserQuestionBlock({
1374
1345
  data,
@@ -1378,11 +1349,11 @@ function AskUserQuestionBlock({
1378
1349
  answerData,
1379
1350
  onAnswer
1380
1351
  }) {
1381
- const [selections, setSelections] = useState6(/* @__PURE__ */ new Map());
1382
- const [customTexts, setCustomTexts] = useState6(/* @__PURE__ */ new Map());
1383
- const [usingCustom, setUsingCustom] = useState6(/* @__PURE__ */ new Set());
1384
- const [submitted, setSubmitted] = useState6(false);
1385
- useEffect4(() => {
1352
+ const [selections, setSelections] = useState7(/* @__PURE__ */ new Map());
1353
+ const [customTexts, setCustomTexts] = useState7(/* @__PURE__ */ new Map());
1354
+ const [usingCustom, setUsingCustom] = useState7(/* @__PURE__ */ new Set());
1355
+ const [submitted, setSubmitted] = useState7(false);
1356
+ useEffect5(() => {
1386
1357
  if (sessionStatus === "failed" || sessionStatus === "interrupted") {
1387
1358
  setSubmitted(false);
1388
1359
  }
@@ -1709,7 +1680,7 @@ function ToolCallBlock({
1709
1680
  sessionStatus,
1710
1681
  renderer
1711
1682
  }) {
1712
- const [expanded, setExpanded] = useState7(false);
1683
+ const [expanded, setExpanded] = useState8(false);
1713
1684
  const normalizedName = formatToolName(toolCall.name);
1714
1685
  if (renderer) {
1715
1686
  const custom = renderer(toolCall);
@@ -1811,7 +1782,7 @@ function buildAskUserPayload(argumentsJson) {
1811
1782
  // src/components/AssistantTurnBlock.tsx
1812
1783
  import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1813
1784
  function ThinkingBlock({ reasoning, isStreaming }) {
1814
- const [open, setOpen] = useState8(false);
1785
+ const [open, setOpen] = useState9(false);
1815
1786
  return /* @__PURE__ */ jsxs8("div", { className: "blade-chat-thinking ml-4 text-sm", children: [
1816
1787
  /* @__PURE__ */ jsxs8(
1817
1788
  "button",
@@ -2184,8 +2155,8 @@ function MessageList({
2184
2155
  }
2185
2156
  function AutoScrollOnUserSend({ userMessageCount }) {
2186
2157
  const { scrollToBottom } = useStickToBottomContext();
2187
- const previousCountRef = useRef5(userMessageCount);
2188
- useEffect5(() => {
2158
+ const previousCountRef = useRef6(userMessageCount);
2159
+ useEffect6(() => {
2189
2160
  if (userMessageCount > previousCountRef.current) {
2190
2161
  scrollToBottom("instant");
2191
2162
  }
@@ -2195,9 +2166,9 @@ function AutoScrollOnUserSend({ userMessageCount }) {
2195
2166
  }
2196
2167
  function ScrollToBottomButton() {
2197
2168
  const { isAtBottom, scrollToBottom } = useStickToBottomContext();
2198
- const [visible, setVisible] = useState9(false);
2199
- const hideTimerRef = useRef5(null);
2200
- useEffect5(() => {
2169
+ const [visible, setVisible] = useState10(false);
2170
+ const hideTimerRef = useRef6(null);
2171
+ useEffect6(() => {
2201
2172
  if (isAtBottom) {
2202
2173
  if (!hideTimerRef.current) {
2203
2174
  hideTimerRef.current = setTimeout(() => {
@@ -2262,8 +2233,8 @@ function isUnauthorizedError(error) {
2262
2233
  return error instanceof BladeApiError && error.status === 401;
2263
2234
  }
2264
2235
  function LoginCard({ client, onLoggedIn }) {
2265
- const [loggingIn, setLoggingIn] = useState10(false);
2266
- const [loginError, setLoginError] = useState10(null);
2236
+ const [loggingIn, setLoggingIn] = useState11(false);
2237
+ const [loginError, setLoginError] = useState11(null);
2267
2238
  const handleLogin = async () => {
2268
2239
  setLoggingIn(true);
2269
2240
  setLoginError(null);
@@ -2295,8 +2266,8 @@ function LoginCard({ client, onLoggedIn }) {
2295
2266
  }
2296
2267
  function ChatView(props) {
2297
2268
  const client = useBladeClient();
2298
- const [attempt, setAttempt] = useState10(0);
2299
- const [needLogin, setNeedLogin] = useState10(() => !client.hasToken());
2269
+ const [attempt, setAttempt] = useState11(0);
2270
+ const [needLogin, setNeedLogin] = useState11(() => !client.hasToken());
2300
2271
  if (needLogin) {
2301
2272
  return /* @__PURE__ */ jsx14(
2302
2273
  "div",
@@ -2335,14 +2306,14 @@ function ChatSessionView({
2335
2306
  createOptions,
2336
2307
  onSessionCreated
2337
2308
  });
2338
- const [stopRequested, setStopRequested] = useState10(false);
2339
- const [inputText, setInputText] = useState10("");
2340
- useEffect6(() => {
2309
+ const [stopRequested, setStopRequested] = useState11(false);
2310
+ const [inputText, setInputText] = useState11("");
2311
+ useEffect7(() => {
2341
2312
  if (session) {
2342
2313
  onSessionReady?.(session);
2343
2314
  }
2344
2315
  }, [session, onSessionReady]);
2345
- useEffect6(() => {
2316
+ useEffect7(() => {
2346
2317
  if (!session) return;
2347
2318
  const offAttach = session.on("attachRequested", ({ label, content }) => {
2348
2319
  setInputText((prev) => `${prev ? `${prev}
@@ -2358,12 +2329,12 @@ ${content}`);
2358
2329
  offInsert();
2359
2330
  };
2360
2331
  }, [session]);
2361
- useEffect6(() => {
2332
+ useEffect7(() => {
2362
2333
  if (isUnauthorizedError(error)) {
2363
2334
  onUnauthorized();
2364
2335
  }
2365
2336
  }, [error, onUnauthorized]);
2366
- useEffect6(() => {
2337
+ useEffect7(() => {
2367
2338
  if (!session || !commands) return;
2368
2339
  const unsubscribes = Object.entries(commands).map(
2369
2340
  ([action, handler]) => session.onCommand(action, (payload) => handler(payload))
@@ -2458,7 +2429,6 @@ lucide-react/dist/esm/shared/src/utils.js:
2458
2429
  lucide-react/dist/esm/defaultAttributes.js:
2459
2430
  lucide-react/dist/esm/Icon.js:
2460
2431
  lucide-react/dist/esm/createLucideIcon.js:
2461
- lucide-react/dist/esm/icons/archive.js:
2462
2432
  lucide-react/dist/esm/icons/arrow-up.js:
2463
2433
  lucide-react/dist/esm/icons/bot.js:
2464
2434
  lucide-react/dist/esm/icons/brain.js:
@@ -2467,20 +2437,13 @@ lucide-react/dist/esm/icons/chevron-down.js:
2467
2437
  lucide-react/dist/esm/icons/chevron-right.js:
2468
2438
  lucide-react/dist/esm/icons/circle-alert.js:
2469
2439
  lucide-react/dist/esm/icons/copy.js:
2470
- lucide-react/dist/esm/icons/download.js:
2471
- lucide-react/dist/esm/icons/file-code-2.js:
2472
- lucide-react/dist/esm/icons/file-spreadsheet.js:
2473
2440
  lucide-react/dist/esm/icons/file-text.js:
2474
- lucide-react/dist/esm/icons/file.js:
2475
- lucide-react/dist/esm/icons/film.js:
2476
- lucide-react/dist/esm/icons/image.js:
2477
2441
  lucide-react/dist/esm/icons/layers.js:
2478
2442
  lucide-react/dist/esm/icons/lightbulb.js:
2479
2443
  lucide-react/dist/esm/icons/loader-circle.js:
2480
2444
  lucide-react/dist/esm/icons/lock-keyhole.js:
2481
2445
  lucide-react/dist/esm/icons/message-square-more.js:
2482
2446
  lucide-react/dist/esm/icons/message-square.js:
2483
- lucide-react/dist/esm/icons/music.js:
2484
2447
  lucide-react/dist/esm/icons/square.js:
2485
2448
  lucide-react/dist/esm/icons/triangle-alert.js:
2486
2449
  lucide-react/dist/esm/icons/x.js: