@blade-hq/agent-kit 0.5.2 → 0.5.4

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.
Files changed (31) hide show
  1. package/dist/{SkillStatusBar-DpbkD4Jx.d.ts → SkillStatusBar-BKAGU9tr.d.ts} +5 -3
  2. package/dist/{blade-client-CFvmjs5R.d.ts → blade-client-R3cLVOYs.d.ts} +25 -3
  3. package/dist/{chunk-4ZEOWH6U.js → chunk-3ZEWA2YM.js} +2 -2
  4. package/dist/{chunk-LVLGDM76.js → chunk-557R7QZV.js} +2 -2
  5. package/dist/{chunk-PTMCGZFG.js → chunk-HWOVPFWG.js} +29 -15
  6. package/dist/chunk-HWOVPFWG.js.map +1 -0
  7. package/dist/{chunk-YW2THJUX.js → chunk-NEI66DW6.js} +2 -2
  8. package/dist/{chunk-7JX26TWV.js → chunk-Q6CSM5DE.js} +60 -4
  9. package/dist/chunk-Q6CSM5DE.js.map +1 -0
  10. package/dist/{chunk-EV225FLR.js → chunk-S6EPGDAL.js} +227 -191
  11. package/dist/chunk-S6EPGDAL.js.map +1 -0
  12. package/dist/client/index.d.ts +1 -1
  13. package/dist/client/index.js +1 -1
  14. package/dist/react/api/vibe-coding.d.ts +2 -2
  15. package/dist/react/api/vibe-coding.js +2 -2
  16. package/dist/react/components/chat/index.d.ts +3 -3
  17. package/dist/react/components/chat/index.js +5 -5
  18. package/dist/react/components/plan/index.js +3 -3
  19. package/dist/react/components/session/index.js +3 -3
  20. package/dist/react/components/workspace/index.js +32 -4
  21. package/dist/react/components/workspace/index.js.map +1 -1
  22. package/dist/react/index.d.ts +9 -7
  23. package/dist/react/index.js +6 -6
  24. package/dist/style.css +1 -1
  25. package/package.json +1 -1
  26. package/dist/chunk-7JX26TWV.js.map +0 -1
  27. package/dist/chunk-EV225FLR.js.map +0 -1
  28. package/dist/chunk-PTMCGZFG.js.map +0 -1
  29. /package/dist/{chunk-4ZEOWH6U.js.map → chunk-3ZEWA2YM.js.map} +0 -0
  30. /package/dist/{chunk-LVLGDM76.js.map → chunk-557R7QZV.js.map} +0 -0
  31. /package/dist/{chunk-YW2THJUX.js.map → chunk-NEI66DW6.js.map} +0 -0
@@ -8,13 +8,13 @@ import {
8
8
  getCodeLanguageFromFilename,
9
9
  parseAskUserQuestion,
10
10
  useHighlightedCodeHtml
11
- } from "./chunk-LVLGDM76.js";
11
+ } from "./chunk-557R7QZV.js";
12
12
  import {
13
13
  Collapsible,
14
14
  CollapsibleContent,
15
15
  CollapsibleTrigger,
16
16
  resolveSessionFilePreviewTarget
17
- } from "./chunk-4ZEOWH6U.js";
17
+ } from "./chunk-3ZEWA2YM.js";
18
18
  import {
19
19
  buildMessageContent,
20
20
  buildToolPreviewKey,
@@ -57,7 +57,7 @@ import {
57
57
  useUiBridgeStore,
58
58
  useUiStore,
59
59
  writeFile
60
- } from "./chunk-PTMCGZFG.js";
60
+ } from "./chunk-HWOVPFWG.js";
61
61
  import {
62
62
  registerBridgeIframe,
63
63
  tapBridgeEvent
@@ -66,7 +66,7 @@ import {
66
66
  ModelOption,
67
67
  ModelsConfig,
68
68
  ModelsResource
69
- } from "./chunk-7JX26TWV.js";
69
+ } from "./chunk-Q6CSM5DE.js";
70
70
  import {
71
71
  cn,
72
72
  copyToClipboard
@@ -80,13 +80,15 @@ import { Eye } from "lucide-react";
80
80
  import { useCallback as useCallback13 } from "react";
81
81
 
82
82
  // src/react/hooks/use-chat.ts
83
- import { useCallback, useEffect } from "react";
83
+ import { useCallback, useEffect, useRef, useState } from "react";
84
84
  var EMPTY_MESSAGES = [];
85
85
  function useChat(sessionId) {
86
86
  const messages = useChatStore((s) => s.messages[sessionId] ?? EMPTY_MESSAGES);
87
87
  const isStreaming = useChatStore((s) => s.isStreaming[sessionId] ?? false);
88
88
  const mode = useSessionStore((s) => s.modes[sessionId] ?? "executing");
89
89
  const setAnswerCallback = useAnswerCallbackStore((s) => s.setAnswerCallback);
90
+ const [isStopping, setIsStopping] = useState(false);
91
+ const previousSessionIdRef = useRef(sessionId);
90
92
  const send = useCallback(
91
93
  (msg, mode2, askuserAnswer, options) => {
92
94
  const synthesizedAnswer = askuserAnswer ?? buildPendingAskUserAnswer(sessionId, msg);
@@ -94,9 +96,27 @@ function useChat(sessionId) {
94
96
  },
95
97
  [sessionId]
96
98
  );
97
- const stop = useCallback(() => {
98
- getSocket().stop(sessionId);
99
- }, [sessionId]);
99
+ const stop = useCallback(async () => {
100
+ if (isStopping) return;
101
+ setIsStopping(true);
102
+ try {
103
+ await getSocket().stop(sessionId);
104
+ } catch (error) {
105
+ console.error("[chat] stop request failed", error);
106
+ } finally {
107
+ setIsStopping(false);
108
+ }
109
+ }, [isStopping, sessionId]);
110
+ useEffect(() => {
111
+ if (previousSessionIdRef.current !== sessionId) {
112
+ previousSessionIdRef.current = sessionId;
113
+ setIsStopping(false);
114
+ return;
115
+ }
116
+ if (!isStreaming) {
117
+ setIsStopping(false);
118
+ }
119
+ }, [isStreaming, sessionId]);
100
120
  const answer = useCallback(
101
121
  (msg, toolCallId, answerData) => {
102
122
  send(msg, mode, { tool_call_id: toolCallId, ...answerData });
@@ -107,7 +127,7 @@ function useChat(sessionId) {
107
127
  setAnswerCallback(sessionId, answer);
108
128
  return () => setAnswerCallback(sessionId, void 0);
109
129
  }, [answer, sessionId, setAnswerCallback]);
110
- return { messages, isStreaming, send, stop };
130
+ return { messages, isStreaming, isStopping, send, stop };
111
131
  }
112
132
  function buildPendingAskUserAnswer(sessionId, message) {
113
133
  const session = useSessionStore.getState().sessions.find((item) => item.id === sessionId);
@@ -180,17 +200,17 @@ import {
180
200
  useEffect as useEffect7,
181
201
  useEffectEvent as useEffectEvent2,
182
202
  useMemo as useMemo7,
183
- useRef as useRef4,
184
- useState as useState7
203
+ useRef as useRef5,
204
+ useState as useState8
185
205
  } from "react";
186
206
  import { createRoot } from "react-dom/client";
187
207
  import { toast } from "sonner";
188
208
 
189
209
  // src/react/asr/use-tiptap-voice-input.ts
190
- import { useCallback as useCallback3, useEffect as useEffect3, useEffectEvent, useRef as useRef2 } from "react";
210
+ import { useCallback as useCallback3, useEffect as useEffect3, useEffectEvent, useRef as useRef3 } from "react";
191
211
 
192
212
  // src/react/asr/use-voice-input.ts
193
- import { useCallback as useCallback2, useEffect as useEffect2, useRef, useState } from "react";
213
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
194
214
 
195
215
  // src/react/asr/voice-input-support.ts
196
216
  function isIpHost(hostname) {
@@ -237,14 +257,14 @@ function matchesRequest(data, requestId) {
237
257
  return !rid || rid === requestId;
238
258
  }
239
259
  function useVoiceInput(options) {
240
- const [status, setStatus] = useState("idle");
241
- const [error, setError] = useState(null);
242
- const [level, setLevel] = useState(0);
243
- const sessionRef = useRef(null);
244
- const startingRef = useRef(false);
245
- const stoppingRef = useRef(false);
246
- const unmountedRef = useRef(false);
247
- const optionsRef = useRef(options);
260
+ const [status, setStatus] = useState2("idle");
261
+ const [error, setError] = useState2(null);
262
+ const [level, setLevel] = useState2(0);
263
+ const sessionRef = useRef2(null);
264
+ const startingRef = useRef2(false);
265
+ const stoppingRef = useRef2(false);
266
+ const unmountedRef = useRef2(false);
267
+ const optionsRef = useRef2(options);
248
268
  optionsRef.current = options;
249
269
  const reportError = useCallback2((err) => {
250
270
  const payload = typeof err === "string" ? { title: err } : err;
@@ -499,8 +519,8 @@ function useVoiceInput(options) {
499
519
  // src/react/asr/use-tiptap-voice-input.ts
500
520
  function useTiptapVoiceInput(options) {
501
521
  const { editorRef, workletUrl, onError } = options;
502
- const partialRangeRef = useRef2(null);
503
- const lastPartialLenRef = useRef2(0);
522
+ const partialRangeRef = useRef3(null);
523
+ const lastPartialLenRef = useRef3(0);
504
524
  const handlePartial = useEffectEvent((text) => {
505
525
  const ed = editorRef.current;
506
526
  if (!ed) return;
@@ -619,7 +639,7 @@ function VoiceWaveform({ level, color = "currentColor", size = 16 }) {
619
639
 
620
640
  // src/react/components/model/ModelSelector.tsx
621
641
  import { Loader2, Sparkles } from "lucide-react";
622
- import { useEffect as useEffect4, useMemo, useRef as useRef3, useState as useState2 } from "react";
642
+ import { useEffect as useEffect4, useMemo, useRef as useRef4, useState as useState3 } from "react";
623
643
 
624
644
  // src/react/hooks/use-model-preferences.ts
625
645
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
@@ -727,9 +747,9 @@ function ModelSelector({
727
747
  placement = "bottom"
728
748
  }) {
729
749
  const { models, defaultModel, isLoading } = useModelConfig();
730
- const [isOpen, setIsOpen] = useState2(false);
731
- const [query, setQuery] = useState2("");
732
- const rootRef = useRef3(null);
750
+ const [isOpen, setIsOpen] = useState3(false);
751
+ const [query, setQuery] = useState3("");
752
+ const rootRef = useRef4(null);
733
753
  useEffect4(() => {
734
754
  if (!isOpen) return;
735
755
  const closeOnPointerDown = (event) => {
@@ -838,7 +858,7 @@ function ModelSelector({
838
858
  }
839
859
 
840
860
  // src/react/hooks/use-input-history.ts
841
- import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo2, useState as useState3 } from "react";
861
+ import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo2, useState as useState4 } from "react";
842
862
  var MAX_HISTORY_ENTRIES = 50;
843
863
  function getStorageKey(sessionId) {
844
864
  return sessionId ? `input-history:${sessionId}` : null;
@@ -863,9 +883,9 @@ function readEntries(storageKey) {
863
883
  }
864
884
  function useInputHistory(sessionId) {
865
885
  const storageKey = useMemo2(() => getStorageKey(sessionId), [sessionId]);
866
- const [entries, setEntries] = useState3([]);
867
- const [cursor, setCursor] = useState3(null);
868
- const [draft, setDraft] = useState3("");
886
+ const [entries, setEntries] = useState4([]);
887
+ const [cursor, setCursor] = useState4(null);
888
+ const [draft, setDraft] = useState4("");
869
889
  useEffect5(() => {
870
890
  setEntries(readEntries(storageKey));
871
891
  setCursor(null);
@@ -1004,7 +1024,7 @@ function skillDisplayName(skill) {
1004
1024
 
1005
1025
  // src/react/components/chat/FileCompletionMenu.tsx
1006
1026
  import { ChevronRight, File, Folder } from "lucide-react";
1007
- import { forwardRef, useCallback as useCallback5, useImperativeHandle, useMemo as useMemo4, useState as useState4 } from "react";
1027
+ import { forwardRef, useCallback as useCallback5, useImperativeHandle, useMemo as useMemo4, useState as useState5 } from "react";
1008
1028
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1009
1029
  var ROOT_DIR = ".";
1010
1030
  function isVisibleEntry(entry) {
@@ -1108,7 +1128,7 @@ var FileCompletionMenu = forwardRef(
1108
1128
  }
1109
1129
  );
1110
1130
  var FileCompletionMenuContent = forwardRef(function FileCompletionMenuContent2({ command, editor, filterQuery, items, onExit, range, sessionId }, ref) {
1111
- const [rawSelectedIndex, setRawSelectedIndex] = useState4(0);
1131
+ const [rawSelectedIndex, setRawSelectedIndex] = useState5(0);
1112
1132
  const filteredEntries = useMemo4(() => filterEntries(items, filterQuery), [filterQuery, items]);
1113
1133
  const selectedIndex = filteredEntries.length === 0 ? 0 : Math.min(rawSelectedIndex, filteredEntries.length - 1);
1114
1134
  const handleSelect = useCallback5(
@@ -1189,11 +1209,11 @@ var FileCompletionMenuContent = forwardRef(function FileCompletionMenuContent2({
1189
1209
  });
1190
1210
 
1191
1211
  // src/react/components/chat/SkillCompletionMenu.tsx
1192
- import { forwardRef as forwardRef2, useCallback as useCallback6, useImperativeHandle as useImperativeHandle2, useState as useState5 } from "react";
1212
+ import { forwardRef as forwardRef2, useCallback as useCallback6, useImperativeHandle as useImperativeHandle2, useState as useState6 } from "react";
1193
1213
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1194
1214
  var SkillCompletionMenu = forwardRef2(
1195
1215
  function SkillCompletionMenu2({ command, items }, ref) {
1196
- const [rawSelectedIndex, setRawSelectedIndex] = useState5(0);
1216
+ const [rawSelectedIndex, setRawSelectedIndex] = useState6(0);
1197
1217
  const selectedIndex = items.length === 0 ? 0 : Math.min(rawSelectedIndex, items.length - 1);
1198
1218
  const handleSelect = useCallback6(
1199
1219
  (item) => {
@@ -1298,7 +1318,7 @@ var SkillCompletionMenu = forwardRef2(
1298
1318
 
1299
1319
  // src/react/components/chat/BackgroundTasksPill.tsx
1300
1320
  import { FileText, Square, Terminal } from "lucide-react";
1301
- import { useState as useState6 } from "react";
1321
+ import { useState as useState7 } from "react";
1302
1322
 
1303
1323
  // src/react/hooks/use-background-tasks.ts
1304
1324
  import { useQuery as useQuery2 } from "@tanstack/react-query";
@@ -1347,8 +1367,8 @@ function statusClass(status) {
1347
1367
  }
1348
1368
  function BackgroundTasksPill({ sessionId }) {
1349
1369
  const { data: tasks } = useBackgroundTasks(sessionId);
1350
- const [open, setOpen] = useState6(false);
1351
- const [logTask, setLogTask] = useState6(null);
1370
+ const [open, setOpen] = useState7(false);
1371
+ const [logTask, setLogTask] = useState7(null);
1352
1372
  if (tasks.length === 0) return null;
1353
1373
  const runningCount = tasks.filter((task) => task.status === "running").length;
1354
1374
  const openTaskLog = async (task) => {
@@ -2131,20 +2151,38 @@ function ComposerFilePill({
2131
2151
  const isFile = attachment.kind === "file";
2132
2152
  const isFailed = isFile && attachment.status === "failed";
2133
2153
  const isUploading = isFile && attachment.status === "uploading";
2154
+ const uploadPercent = isUploading && typeof attachment.uploadProgress === "number" ? Math.max(0, Math.min(100, Math.round(attachment.uploadProgress * 100))) : null;
2134
2155
  return /* @__PURE__ */ jsxs8(
2135
2156
  "div",
2136
2157
  {
2137
- className: `flex shrink-0 items-center gap-1.5 rounded-full border px-2.5 py-1 text-[11px] ${isFailed ? "border-red-500/30 bg-red-500/5 text-red-400" : "border-[hsl(var(--border))] bg-[hsl(var(--card))] text-[hsl(var(--foreground))]"}`,
2158
+ className: `relative flex shrink-0 items-center gap-1.5 overflow-hidden rounded-full border px-2.5 py-1 text-[11px] ${isFailed ? "border-red-500/30 bg-red-500/5 text-red-400" : "border-[hsl(var(--border))] bg-[hsl(var(--card))] text-[hsl(var(--foreground))]"}`,
2138
2159
  children: [
2139
- isUploading ? /* @__PURE__ */ jsx9(Loader22, { size: 12, className: "shrink-0 animate-spin text-[hsl(var(--muted-foreground))]" }) : /* @__PURE__ */ jsx9(Icon, { size: 12, className: "shrink-0 text-[hsl(var(--muted-foreground))]" }),
2140
- /* @__PURE__ */ jsx9("span", { className: "max-w-32 truncate", children: attachment.name }),
2160
+ uploadPercent !== null ? /* @__PURE__ */ jsx9(
2161
+ "span",
2162
+ {
2163
+ className: "absolute inset-y-0 left-0 bg-[hsl(var(--primary))]/10 transition-[width]",
2164
+ style: { width: `${uploadPercent}%` }
2165
+ }
2166
+ ) : null,
2167
+ isUploading ? /* @__PURE__ */ jsx9(
2168
+ Loader22,
2169
+ {
2170
+ size: 12,
2171
+ className: "relative z-10 shrink-0 animate-spin text-[hsl(var(--muted-foreground))]"
2172
+ }
2173
+ ) : /* @__PURE__ */ jsx9(Icon, { size: 12, className: "relative z-10 shrink-0 text-[hsl(var(--muted-foreground))]" }),
2174
+ /* @__PURE__ */ jsx9("span", { className: "relative z-10 max-w-32 truncate", children: attachment.name }),
2175
+ uploadPercent !== null ? /* @__PURE__ */ jsxs8("span", { className: "relative z-10 font-mono text-[10px] tabular-nums text-[hsl(var(--muted-foreground))]", children: [
2176
+ uploadPercent,
2177
+ "%"
2178
+ ] }) : null,
2141
2179
  /* @__PURE__ */ jsx9(
2142
2180
  "button",
2143
2181
  {
2144
2182
  type: "button",
2145
2183
  onClick: () => onRemove(attachment.id),
2146
2184
  "aria-label": `\u79FB\u9664 ${attachment.name}`,
2147
- className: "ml-0.5 inline-flex shrink-0 items-center justify-center rounded-full text-[hsl(var(--muted-foreground))] transition hover:text-rose-400",
2185
+ className: "relative z-10 ml-0.5 inline-flex shrink-0 items-center justify-center rounded-full text-[hsl(var(--muted-foreground))] transition hover:text-rose-400",
2148
2186
  children: /* @__PURE__ */ jsx9(X2, { size: 10 })
2149
2187
  }
2150
2188
  )
@@ -2158,13 +2196,13 @@ function AddContextDialog({
2158
2196
  onAdd
2159
2197
  }) {
2160
2198
  const CONTEXT_INLINE_THRESHOLD = 200;
2161
- const [label, setLabel] = useState7("");
2162
- const [content, setContent] = useState7("");
2163
- const [showSessionPicker, setShowSessionPicker] = useState7(false);
2164
- const [sessions, setSessions] = useState7([]);
2165
- const [loadingSessions, setLoadingSessions] = useState7(false);
2166
- const [importingId, setImportingId] = useState7(null);
2167
- const [isImportProcessing, setIsImportProcessing] = useState7(false);
2199
+ const [label, setLabel] = useState8("");
2200
+ const [content, setContent] = useState8("");
2201
+ const [showSessionPicker, setShowSessionPicker] = useState8(false);
2202
+ const [sessions, setSessions] = useState8([]);
2203
+ const [loadingSessions, setLoadingSessions] = useState8(false);
2204
+ const [importingId, setImportingId] = useState8(null);
2205
+ const [isImportProcessing, setIsImportProcessing] = useState8(false);
2168
2206
  const sanitizeContextFolderName = (raw) => {
2169
2207
  const normalized = raw.trim().toLowerCase();
2170
2208
  const safe = normalized.replace(/[^\w\-.\u4e00-\u9fa5]+/g, "_").replace(/^_+|_+$/g, "");
@@ -2394,7 +2432,7 @@ function ComposerContextPill({
2394
2432
  content,
2395
2433
  onRemove
2396
2434
  }) {
2397
- const [showDetail, setShowDetail] = useState7(false);
2435
+ const [showDetail, setShowDetail] = useState8(false);
2398
2436
  const tokenK = formatTokenK(content);
2399
2437
  return /* @__PURE__ */ jsxs8(Fragment2, { children: [
2400
2438
  /* @__PURE__ */ jsxs8("div", { className: "flex shrink-0 items-center gap-1.5 rounded-full border border-[hsl(var(--border))] bg-[hsl(var(--accent))] px-2.5 py-1 text-[11px] text-[hsl(var(--foreground))]", children: [
@@ -2589,6 +2627,7 @@ function ChatInput({
2589
2627
  onSend,
2590
2628
  onStop,
2591
2629
  isStreaming,
2630
+ isStopping = false,
2592
2631
  mode = "executing",
2593
2632
  onToggleMode,
2594
2633
  renderAttachments,
@@ -2608,13 +2647,13 @@ function ChatInput({
2608
2647
  onResyncSkills,
2609
2648
  isResyncingSkills = false
2610
2649
  }) {
2611
- const [input, setInputInternal] = useState7(externalDraft?.value ?? "");
2650
+ const [input, setInputInternal] = useState8(externalDraft?.value ?? "");
2612
2651
  const setInput = useEffectEvent2((value) => {
2613
2652
  setInputInternal(value);
2614
2653
  externalDraft?.setValue(value);
2615
2654
  });
2616
2655
  const externalDraftValue = externalDraft?.value;
2617
- const inputRef = useRef4(input);
2656
+ const inputRef = useRef5(input);
2618
2657
  inputRef.current = input;
2619
2658
  useEffect7(() => {
2620
2659
  if (externalDraftValue == null) return;
@@ -2624,7 +2663,7 @@ function ChatInput({
2624
2663
  }
2625
2664
  setInputInternal(externalDraftValue);
2626
2665
  }, [externalDraftValue]);
2627
- const [composerAttachments, setComposerAttachmentsInternal] = useState7(
2666
+ const [composerAttachments, setComposerAttachmentsInternal] = useState8(
2628
2667
  externalAttachments?.value ?? []
2629
2668
  );
2630
2669
  const setComposerAttachments = useEffectEvent2(
@@ -2637,24 +2676,24 @@ function ChatInput({
2637
2676
  }
2638
2677
  );
2639
2678
  const externalAttachmentsValue = externalAttachments?.value;
2640
- const composerAttachmentsRef = useRef4(composerAttachments);
2679
+ const composerAttachmentsRef = useRef5(composerAttachments);
2641
2680
  composerAttachmentsRef.current = composerAttachments;
2642
2681
  useEffect7(() => {
2643
2682
  if (externalAttachmentsValue == null) return;
2644
2683
  if (composerAttachmentsRef.current === externalAttachmentsValue) return;
2645
2684
  setComposerAttachmentsInternal(externalAttachmentsValue);
2646
2685
  }, [externalAttachmentsValue]);
2647
- const [dragging, setDragging] = useState7(false);
2648
- const [isEditorFocused, setIsEditorFocused] = useState7(false);
2649
- const [oversizedFiles, setOversizedFiles] = useState7([]);
2650
- const [selectedModel, setSelectedModel] = useState7("");
2686
+ const [dragging, setDragging] = useState8(false);
2687
+ const [isEditorFocused, setIsEditorFocused] = useState8(false);
2688
+ const [oversizedFiles, setOversizedFiles] = useState8([]);
2689
+ const [selectedModel, setSelectedModel] = useState8("");
2651
2690
  const { setPreferredModel } = usePreferredModel();
2652
2691
  const queryClient = useQueryClient2();
2653
- const actionMenuRef = useRef4(null);
2654
- const fileInputRef = useRef4(null);
2655
- const folderInputRef = useRef4(null);
2656
- const editorRef = useRef4(null);
2657
- const localImageUrlsRef = useRef4(/* @__PURE__ */ new Map());
2692
+ const actionMenuRef = useRef5(null);
2693
+ const fileInputRef = useRef5(null);
2694
+ const folderInputRef = useRef5(null);
2695
+ const editorRef = useRef5(null);
2696
+ const localImageUrlsRef = useRef5(/* @__PURE__ */ new Map());
2658
2697
  const activeSessionId = useSessionStore((state) => state.activeSessionId);
2659
2698
  const sessions = useSessionStore((state) => state.sessions);
2660
2699
  const activeSessionModel = useMemo7(
@@ -2672,7 +2711,7 @@ function ChatInput({
2672
2711
  const draftAppends = useUiBridgeStore(
2673
2712
  (state) => activeSessionId ? state.draftAppends[activeSessionId] : void 0
2674
2713
  );
2675
- const { resolvedModel } = useResolvedModel(activeSessionModel);
2714
+ const { resolvedModel, hasAvailableModel, isLoading: isModelLoading } = useResolvedModel(activeSessionModel);
2676
2715
  useEffect7(() => {
2677
2716
  setSelectedModel(resolvedModel);
2678
2717
  }, [resolvedModel]);
@@ -2687,7 +2726,7 @@ function ChatInput({
2687
2726
  const inputHistory = useInputHistory(activeSessionId);
2688
2727
  const getSessionId = useEffectEvent2(() => activeSessionId);
2689
2728
  const handleSlashCommand = useEffectEvent2((commandId) => onCommand?.(commandId));
2690
- const [localImageUrls, setLocalImageUrls] = useState7({});
2729
+ const [localImageUrls, setLocalImageUrls] = useState8({});
2691
2730
  useEffect7(() => {
2692
2731
  const closeActionMenu = (event) => {
2693
2732
  const menu = actionMenuRef.current;
@@ -3016,7 +3055,7 @@ function ChatInput({
3016
3055
  editor.commands.focus("end");
3017
3056
  setRewindDraft(activeSessionId, null);
3018
3057
  }, [activeSessionId, editor, rewindDraft, setRewindDraft]);
3019
- const submittingRef = useRef4(false);
3058
+ const submittingRef = useRef5(false);
3020
3059
  useEffect7(() => {
3021
3060
  let changed = false;
3022
3061
  const nextLocalIds = /* @__PURE__ */ new Set();
@@ -3095,14 +3134,14 @@ function ChatInput({
3095
3134
  });
3096
3135
  const { isRecording, level: voiceLevel } = voice;
3097
3136
  const handleMicDisabledClick = () => toast.info("\u8BED\u97F3\u8F93\u5165\u529F\u80FD\u672A\u5F00\u542F\uFF0C\u8BF7\u5728\u540E\u7AEF .env \u914D\u7F6E ASR_API_KEY");
3098
- const voiceStopRef = useRef4(voice.stop);
3137
+ const voiceStopRef = useRef5(voice.stop);
3099
3138
  voiceStopRef.current = voice.stop;
3100
3139
  useEffect7(() => {
3101
3140
  if (isStreaming && isRecording) {
3102
3141
  void voiceStopRef.current();
3103
3142
  }
3104
3143
  }, [isStreaming, isRecording]);
3105
- const prevSessionIdRef = useRef4(activeSessionId);
3144
+ const prevSessionIdRef = useRef5(activeSessionId);
3106
3145
  useEffect7(() => {
3107
3146
  if (prevSessionIdRef.current !== activeSessionId) {
3108
3147
  prevSessionIdRef.current = activeSessionId;
@@ -3115,6 +3154,13 @@ function ChatInput({
3115
3154
  if (isStreaming || connectionStatus !== "connected") {
3116
3155
  return;
3117
3156
  }
3157
+ if (isModelLoading) {
3158
+ return;
3159
+ }
3160
+ if (!hasAvailableModel) {
3161
+ toast.error("\u6CA1\u6709\u53EF\u7528\u6A21\u578B\uFF0C\u8BF7\u68C0\u67E5\u6A21\u578B\u670D\u52A1\u914D\u7F6E");
3162
+ return;
3163
+ }
3118
3164
  if (submittingRef.current) {
3119
3165
  return;
3120
3166
  }
@@ -3153,14 +3199,24 @@ function ChatInput({
3153
3199
  if (pendingFiles.length > 0) {
3154
3200
  setComposerAttachments(
3155
3201
  (prev) => prev.map(
3156
- (a) => a.kind === "file" && a.status === "pending" ? { ...a, status: "uploading" } : a
3202
+ (a) => a.kind === "file" && a.status === "pending" ? { ...a, status: "uploading", uploadProgress: 0 } : a
3157
3203
  )
3158
3204
  );
3159
3205
  try {
3160
3206
  const result = await uploadFiles(
3161
3207
  uploadSessionId,
3162
3208
  ".",
3163
- pendingFiles.map((a) => ({ file: a.file, name: a.name }))
3209
+ pendingFiles.map((a) => ({ file: a.file, name: a.name })),
3210
+ {
3211
+ onProgress: (progress) => {
3212
+ if (typeof progress.percent !== "number") return;
3213
+ setComposerAttachments(
3214
+ (prev) => prev.map(
3215
+ (a) => pendingFiles.some((pending) => pending.id === a.id) && a.kind === "file" && a.status === "uploading" ? { ...a, uploadProgress: progress.percent } : a
3216
+ )
3217
+ );
3218
+ }
3219
+ }
3164
3220
  );
3165
3221
  const uploadResultById = /* @__PURE__ */ new Map();
3166
3222
  const failedSet = new Set(result.failed);
@@ -3176,9 +3232,15 @@ function ChatInput({
3176
3232
  updatedAttachments = composerAttachments.map((a) => {
3177
3233
  if (!uploadResultById.has(a.id)) return a;
3178
3234
  const uploadedPath = uploadResultById.get(a.id);
3179
- if (!uploadedPath) return { ...a, status: "failed" };
3235
+ if (!uploadedPath) return { ...a, status: "failed", uploadProgress: null };
3180
3236
  const actualName = uploadedPath.split("/").pop() || a.name;
3181
- return { ...a, name: actualName, status: "uploaded", uploadedPath };
3237
+ return {
3238
+ ...a,
3239
+ name: actualName,
3240
+ status: "uploaded",
3241
+ uploadedPath,
3242
+ uploadProgress: null
3243
+ };
3182
3244
  });
3183
3245
  setComposerAttachments(updatedAttachments);
3184
3246
  if (result.uploaded.length > 0) {
@@ -3220,7 +3282,7 @@ function ChatInput({
3220
3282
  const removeAttachment = (id) => {
3221
3283
  setComposerAttachments((prev) => prev.filter((attachment) => attachment.id !== id));
3222
3284
  };
3223
- const [showAddContext, setShowAddContext] = useState7(false);
3285
+ const [showAddContext, setShowAddContext] = useState8(false);
3224
3286
  const isPlanning = mode === "planning";
3225
3287
  const placeholder = isPlanning ? "\u89C4\u5212\u8FDB\u884C\u4E2D\u2026 \u53EF\u8F93\u5165\u8865\u5145\u9700\u6C42\u6216\u7B49\u5F85\u5B8C\u6210" : "\u8F93\u5165\u6D88\u606F\u2026";
3226
3288
  const attachments = renderAttachments?.() ?? null;
@@ -3238,10 +3300,11 @@ function ChatInput({
3238
3300
  (attachment) => attachment.status === "uploading"
3239
3301
  );
3240
3302
  const hasValidAttachments = composerAttachments.some((attachment) => attachment.status !== "failed");
3241
- const isSendDisabled = connectionStatus !== "connected" || hasUploadingFiles || !input.trim() && !hasValidAttachments && !hasRenderedAttachments && !hasPendingContexts;
3303
+ const isSendDisabled = connectionStatus !== "connected" || isModelLoading || !hasAvailableModel || hasUploadingFiles || !input.trim() && !hasValidAttachments && !hasRenderedAttachments && !hasPendingContexts;
3242
3304
  return /* @__PURE__ */ jsxs8(Fragment2, { children: [
3243
3305
  /* @__PURE__ */ jsx9("div", { className: `bg-[hsl(var(--background))] px-5 py-3 ${className ?? ""}`, children: /* @__PURE__ */ jsxs8("div", { className: `mx-auto ${maxWidthClassName} ${innerClassName ?? ""}`, children: [
3244
3306
  activeSessionId ? /* @__PURE__ */ jsx9(BackgroundTasksPill, { sessionId: activeSessionId }) : null,
3307
+ !isModelLoading && !hasAvailableModel ? /* @__PURE__ */ jsx9("div", { className: "mb-2 rounded-lg border border-[hsl(var(--destructive))]/30 bg-[hsl(var(--destructive))]/8 px-3 py-2 text-xs text-[hsl(var(--destructive))]", children: "\u6CA1\u6709\u53EF\u7528\u6A21\u578B\uFF0C\u8BF7\u68C0\u67E5\u6A21\u578B\u670D\u52A1\u914D\u7F6E" }) : null,
3245
3308
  /* @__PURE__ */ jsx9(
3246
3309
  "div",
3247
3310
  {
@@ -3596,10 +3659,11 @@ function ChatInput({
3596
3659
  "button",
3597
3660
  {
3598
3661
  type: "button",
3599
- onClick: onStop,
3600
- "aria-label": "\u505C\u6B62\u751F\u6210",
3601
- className: "flex h-7 w-7 items-center justify-center rounded-lg bg-[hsl(var(--destructive))] text-[hsl(var(--destructive-foreground))] transition-opacity hover:opacity-80",
3602
- children: /* @__PURE__ */ jsx9(Square2, { size: 13 })
3662
+ onClick: () => void onStop(),
3663
+ disabled: isStopping,
3664
+ "aria-label": isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u751F\u6210",
3665
+ className: "flex h-7 w-7 items-center justify-center rounded-lg bg-[hsl(var(--destructive))] text-[hsl(var(--destructive-foreground))] transition-opacity hover:opacity-80 disabled:cursor-wait disabled:opacity-70",
3666
+ children: isStopping ? /* @__PURE__ */ jsx9(Loader22, { size: 13, className: "animate-spin" }) : /* @__PURE__ */ jsx9(Square2, { size: 13 })
3603
3667
  }
3604
3668
  ) : /* @__PURE__ */ jsx9(
3605
3669
  "button",
@@ -3608,6 +3672,7 @@ function ChatInput({
3608
3672
  onClick: handleSubmit,
3609
3673
  disabled: isSendDisabled || isRecording,
3610
3674
  "aria-label": "\u53D1\u9001\u6D88\u606F",
3675
+ title: isModelLoading ? "\u6B63\u5728\u52A0\u8F7D\u6A21\u578B\u914D\u7F6E" : !hasAvailableModel ? "\u6CA1\u6709\u53EF\u7528\u6A21\u578B\uFF0C\u8BF7\u68C0\u67E5\u6A21\u578B\u670D\u52A1\u914D\u7F6E" : "\u53D1\u9001\u6D88\u606F",
3611
3676
  className: "flex h-7 w-7 items-center justify-center rounded-lg bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] transition-opacity hover:opacity-80 disabled:opacity-25",
3612
3677
  children: /* @__PURE__ */ jsx9(Send, { size: 13 })
3613
3678
  }
@@ -3689,12 +3754,12 @@ import {
3689
3754
  useEffect as useEffect16,
3690
3755
  useEffectEvent as useEffectEvent4,
3691
3756
  useMemo as useMemo17,
3692
- useRef as useRef11,
3693
- useState as useState20
3757
+ useRef as useRef12,
3758
+ useState as useState21
3694
3759
  } from "react";
3695
3760
 
3696
3761
  // ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/useStickToBottom.js
3697
- import { useCallback as useCallback7, useMemo as useMemo8, useRef as useRef5, useState as useState8 } from "react";
3762
+ import { useCallback as useCallback7, useMemo as useMemo8, useRef as useRef6, useState as useState9 } from "react";
3698
3763
  var DEFAULT_SPRING_ANIMATION = {
3699
3764
  /**
3700
3765
  * A value from 0 to 1, on how much to damp the animation.
@@ -3731,10 +3796,10 @@ globalThis.document?.addEventListener("click", () => {
3731
3796
  mouseDown = false;
3732
3797
  });
3733
3798
  var useStickToBottom = (options = {}) => {
3734
- const [escapedFromLock, updateEscapedFromLock] = useState8(false);
3735
- const [isAtBottom, updateIsAtBottom] = useState8(options.initial !== false);
3736
- const [isNearBottom, setIsNearBottom] = useState8(false);
3737
- const optionsRef = useRef5(null);
3799
+ const [escapedFromLock, updateEscapedFromLock] = useState9(false);
3800
+ const [isAtBottom, updateIsAtBottom] = useState9(options.initial !== false);
3801
+ const [isNearBottom, setIsNearBottom] = useState9(false);
3802
+ const optionsRef = useRef6(null);
3738
3803
  optionsRef.current = options;
3739
3804
  const isSelecting = useCallback7(() => {
3740
3805
  if (!mouseDown) {
@@ -4038,11 +4103,11 @@ function mergeAnimations(...animations) {
4038
4103
 
4039
4104
  // ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/StickToBottom.js
4040
4105
  import * as React from "react";
4041
- import { createContext, useContext, useEffect as useEffect8, useImperativeHandle as useImperativeHandle3, useLayoutEffect, useMemo as useMemo9, useRef as useRef6 } from "react";
4106
+ import { createContext, useContext, useEffect as useEffect8, useImperativeHandle as useImperativeHandle3, useLayoutEffect, useMemo as useMemo9, useRef as useRef7 } from "react";
4042
4107
  var StickToBottomContext = createContext(null);
4043
4108
  var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect8;
4044
4109
  function StickToBottom({ instance, children, resize, initial, mass, damping, stiffness, targetScrollTop: currentTargetScrollTop, contextRef, ...props }) {
4045
- const customTargetScrollTop = useRef6(null);
4110
+ const customTargetScrollTop = useRef7(null);
4046
4111
  const targetScrollTop = React.useCallback((target, elements) => {
4047
4112
  const get = context?.targetScrollTop ?? currentTargetScrollTop;
4048
4113
  return get?.(target, elements) ?? target;
@@ -4119,7 +4184,7 @@ function useStickToBottomContext() {
4119
4184
 
4120
4185
  // src/react/components/chat/AssistantTurnBlock.tsx
4121
4186
  import { AlertCircle, BookOpen, Check as Check4, ChevronRight as ChevronRight5 } from "lucide-react";
4122
- import { useCallback as useCallback11, useEffect as useEffect14, useMemo as useMemo15, useRef as useRef10, useState as useState17 } from "react";
4187
+ import { useCallback as useCallback11, useEffect as useEffect14, useMemo as useMemo15, useRef as useRef11, useState as useState18 } from "react";
4123
4188
 
4124
4189
  // src/react/routes.ts
4125
4190
  var MEMORIES_ROUTE = "/memories";
@@ -4137,8 +4202,8 @@ import {
4137
4202
  useContext as useContext2,
4138
4203
  useEffect as useEffect9,
4139
4204
  useMemo as useMemo11,
4140
- useRef as useRef7,
4141
- useState as useState9
4205
+ useRef as useRef8,
4206
+ useState as useState10
4142
4207
  } from "react";
4143
4208
  import { createPortal as createPortal2 } from "react-dom";
4144
4209
  import { Streamdown } from "streamdown";
@@ -4205,7 +4270,6 @@ var useReasoning = () => {
4205
4270
  }
4206
4271
  return context;
4207
4272
  };
4208
- var AUTO_CLOSE_DELAY = 3e3;
4209
4273
  var MS_IN_S = 1e3;
4210
4274
  var Reasoning = memo2(
4211
4275
  ({
@@ -4218,10 +4282,8 @@ var Reasoning = memo2(
4218
4282
  children,
4219
4283
  ...props
4220
4284
  }) => {
4221
- const resolvedDefaultOpen = defaultOpen ?? isStreaming;
4222
- const isExplicitlyClosed = defaultOpen === false;
4223
4285
  const [isOpen, setIsOpen] = useControllableState({
4224
- defaultProp: resolvedDefaultOpen,
4286
+ defaultProp: defaultOpen ?? false,
4225
4287
  onChange: onOpenChange,
4226
4288
  prop: open
4227
4289
  });
@@ -4229,12 +4291,9 @@ var Reasoning = memo2(
4229
4291
  defaultProp: void 0,
4230
4292
  prop: durationProp
4231
4293
  });
4232
- const hasEverStreamedRef = useRef7(isStreaming);
4233
- const [hasAutoClosed, setHasAutoClosed] = useState9(false);
4234
- const startTimeRef = useRef7(null);
4294
+ const startTimeRef = useRef8(null);
4235
4295
  useEffect9(() => {
4236
4296
  if (isStreaming) {
4237
- hasEverStreamedRef.current = true;
4238
4297
  if (startTimeRef.current === null) {
4239
4298
  startTimeRef.current = Date.now();
4240
4299
  }
@@ -4243,20 +4302,6 @@ var Reasoning = memo2(
4243
4302
  startTimeRef.current = null;
4244
4303
  }
4245
4304
  }, [isStreaming, setDuration]);
4246
- useEffect9(() => {
4247
- if (isStreaming && !isOpen && !isExplicitlyClosed) {
4248
- setIsOpen(true);
4249
- }
4250
- }, [isStreaming, isOpen, setIsOpen, isExplicitlyClosed]);
4251
- useEffect9(() => {
4252
- if (hasEverStreamedRef.current && !isStreaming && isOpen && !hasAutoClosed) {
4253
- const timer = setTimeout(() => {
4254
- setIsOpen(false);
4255
- setHasAutoClosed(true);
4256
- }, AUTO_CLOSE_DELAY);
4257
- return () => clearTimeout(timer);
4258
- }
4259
- }, [isStreaming, isOpen, setIsOpen, hasAutoClosed]);
4260
4305
  const handleOpenChange = useCallback9(
4261
4306
  (newOpen) => {
4262
4307
  setIsOpen(newOpen);
@@ -4270,7 +4315,7 @@ var Reasoning = memo2(
4270
4315
  return /* @__PURE__ */ jsx12(ReasoningContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx12(
4271
4316
  Collapsible,
4272
4317
  {
4273
- className: cn("not-prose mb-4", className),
4318
+ className: cn("not-prose", className),
4274
4319
  onOpenChange: handleOpenChange,
4275
4320
  open: isOpen,
4276
4321
  ...props,
@@ -4279,38 +4324,36 @@ var Reasoning = memo2(
4279
4324
  ) });
4280
4325
  }
4281
4326
  );
4282
- var defaultGetThinkingMessage = (isStreaming, duration) => {
4283
- if (isStreaming || duration === 0) {
4284
- return /* @__PURE__ */ jsx12(Shimmer, { duration: 1, children: "\u6DF1\u5EA6\u601D\u8003\u4E2D..." });
4285
- }
4286
- if (duration === void 0) {
4287
- return /* @__PURE__ */ jsx12("p", { children: "\u5DF2\u6DF1\u5EA6\u601D\u8003" });
4288
- }
4289
- return /* @__PURE__ */ jsxs10("p", { children: [
4290
- "\u5DF2\u6DF1\u5EA6\u601D\u8003 ",
4291
- duration,
4292
- " \u79D2"
4293
- ] });
4294
- };
4327
+ function formatWordCount(value) {
4328
+ return new Intl.NumberFormat("zh-CN").format(value);
4329
+ }
4295
4330
  var ReasoningTrigger = memo2(
4296
4331
  ({
4297
4332
  className,
4298
4333
  children,
4299
- getThinkingMessage = defaultGetThinkingMessage,
4334
+ wordCount,
4300
4335
  ...props
4301
4336
  }) => {
4302
4337
  const { isStreaming, isOpen, duration } = useReasoning();
4338
+ const status = isStreaming || duration === 0 ? "\u6B63\u5728\u601D\u8003" : "\u5DF2\u601D\u8003";
4339
+ const detail = `${formatWordCount(wordCount)} \u5B57`;
4303
4340
  return /* @__PURE__ */ jsx12(
4304
4341
  CollapsibleTrigger,
4305
4342
  {
4306
4343
  className: cn(
4307
- "flex w-full items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground",
4344
+ "flex w-fit items-center gap-2 rounded-full px-1 py-0.5 text-muted-foreground text-sm transition-colors hover:text-foreground",
4308
4345
  className
4309
4346
  ),
4310
4347
  ...props,
4311
4348
  children: children ?? /* @__PURE__ */ jsxs10(Fragment3, { children: [
4312
4349
  /* @__PURE__ */ jsx12(BrainIcon, { className: "size-4" }),
4313
- getThinkingMessage(isStreaming, duration),
4350
+ /* @__PURE__ */ jsxs10("span", { className: "inline-flex items-center gap-1.5", children: [
4351
+ isStreaming || duration === 0 ? /* @__PURE__ */ jsx12(Shimmer, { duration: 1, children: status }) : /* @__PURE__ */ jsx12("span", { children: status }),
4352
+ /* @__PURE__ */ jsxs10("span", { className: "text-[hsl(var(--muted-foreground))]/70", children: [
4353
+ "\xB7 ",
4354
+ detail
4355
+ ] })
4356
+ ] }),
4314
4357
  /* @__PURE__ */ jsx12(
4315
4358
  ChevronDownIcon,
4316
4359
  {
@@ -4332,7 +4375,7 @@ var ReasoningContent = memo2(
4332
4375
  CollapsibleContent,
4333
4376
  {
4334
4377
  className: cn(
4335
- "mt-4 text-sm",
4378
+ "mt-2 max-h-60 overflow-auto rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--muted))]/20 px-3 py-2 text-sm",
4336
4379
  "data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-muted-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
4337
4380
  className
4338
4381
  ),
@@ -4346,7 +4389,7 @@ function compactReasoningText(text) {
4346
4389
  }
4347
4390
  function ThinkingBadge({ reasoning, variant = "inline", onClick }) {
4348
4391
  const compactReasoning = compactReasoningText(reasoning);
4349
- const [open, setOpen] = useState9(false);
4392
+ const [open, setOpen] = useState10(false);
4350
4393
  return /* @__PURE__ */ jsxs10(
4351
4394
  "span",
4352
4395
  {
@@ -4423,10 +4466,10 @@ ReasoningContent.displayName = "ReasoningContent";
4423
4466
 
4424
4467
  // src/react/components/chat/AgentLoopBlock.tsx
4425
4468
  import { Bot, Check as Check3, ChevronRight as ChevronRight4, FileText as FileText6, Loader2 as Loader24, MessageSquareMore as MessageSquareMore2 } from "lucide-react";
4426
- import { useEffect as useEffect13, useMemo as useMemo14, useState as useState16 } from "react";
4469
+ import { useEffect as useEffect13, useMemo as useMemo14, useState as useState17 } from "react";
4427
4470
 
4428
4471
  // src/react/components/chat/ResourceIframe.tsx
4429
- import { useEffect as useEffect10, useEffectEvent as useEffectEvent3, useRef as useRef8, useState as useState10 } from "react";
4472
+ import { useEffect as useEffect10, useEffectEvent as useEffectEvent3, useRef as useRef9, useState as useState11 } from "react";
4430
4473
  import { jsx as jsx13 } from "react/jsx-runtime";
4431
4474
  function isResourceBridgeMessage(value) {
4432
4475
  return typeof value === "object" && value !== null && value.__resourceBridge === true && typeof value.action === "string";
@@ -4435,8 +4478,8 @@ var INLINE_HEIGHT_MIN = 80;
4435
4478
  var INLINE_HEIGHT_MAX = 6e3;
4436
4479
  var INLINE_HEIGHT_PADDING = 8;
4437
4480
  function ResourceIframe({ ui, sessionId }) {
4438
- const iframeRef = useRef8(null);
4439
- const iframeKeyRef = useRef8(
4481
+ const iframeRef = useRef9(null);
4482
+ const iframeKeyRef = useRef9(
4440
4483
  `iframe-${Math.random().toString(36).slice(2, 10)}`
4441
4484
  );
4442
4485
  const activeSessionId = useSessionStore((state) => state.activeSessionId);
@@ -4444,7 +4487,7 @@ function ResourceIframe({ ui, sessionId }) {
4444
4487
  const theme = useUiStore((state) => state.theme);
4445
4488
  const resourceUri = ui.resourceUri ?? ui.resourceURI;
4446
4489
  const iframeLabel = ui.title ?? resourceUri ?? "\u5DE5\u5177\u754C\u9762";
4447
- const [autoHeight, setAutoHeight] = useState10(null);
4490
+ const [autoHeight, setAutoHeight] = useState11(null);
4448
4491
  useEffect10(() => {
4449
4492
  setAutoHeight(null);
4450
4493
  }, [ui.resourceHTML, resourceUri]);
@@ -4555,7 +4598,7 @@ function ResourceIframe({ ui, sessionId }) {
4555
4598
 
4556
4599
  // src/react/components/chat/ToolCallBlock.tsx
4557
4600
  import { Check, ChevronRight as ChevronRight3, Loader2 as Loader23, MessageSquareMore, PanelRightOpen, X as X4 } from "lucide-react";
4558
- import { useMemo as useMemo12, useState as useState13 } from "react";
4601
+ import { useMemo as useMemo12, useState as useState14 } from "react";
4559
4602
 
4560
4603
  // src/react/components/chat/tool-renderers/shared.tsx
4561
4604
  import { jsx as jsx14 } from "react/jsx-runtime";
@@ -4882,21 +4925,21 @@ function FileEditRenderer({ toolCall }) {
4882
4925
  }
4883
4926
 
4884
4927
  // src/react/components/chat/tool-renderers/FileReadRenderer.tsx
4885
- import { useState as useState12 } from "react";
4928
+ import { useState as useState13 } from "react";
4886
4929
 
4887
4930
  // src/react/components/chat/ImageLightbox.tsx
4888
4931
  import { ChevronLeft, ChevronRight as ChevronRight2, Download as Download2, ExternalLink, Minus, Plus as Plus2, RotateCcw, X as X3 } from "lucide-react";
4889
- import { useCallback as useCallback10, useEffect as useEffect11, useRef as useRef9, useState as useState11 } from "react";
4932
+ import { useCallback as useCallback10, useEffect as useEffect11, useRef as useRef10, useState as useState12 } from "react";
4890
4933
  import { createPortal as createPortal3 } from "react-dom";
4891
4934
  import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
4892
4935
  function ImageLightbox({ open, onOpenChange, images, initialIndex = 0 }) {
4893
- const [currentIndex, setCurrentIndex] = useState11(initialIndex);
4894
- const [scale, setScale] = useState11(1);
4895
- const [translate, setTranslate] = useState11({ x: 0, y: 0 });
4896
- const dragging = useRef9(false);
4897
- const dragStart = useRef9({ x: 0, y: 0 });
4898
- const translateStart = useRef9({ x: 0, y: 0 });
4899
- const imgRef = useRef9(null);
4936
+ const [currentIndex, setCurrentIndex] = useState12(initialIndex);
4937
+ const [scale, setScale] = useState12(1);
4938
+ const [translate, setTranslate] = useState12({ x: 0, y: 0 });
4939
+ const dragging = useRef10(false);
4940
+ const dragStart = useRef10({ x: 0, y: 0 });
4941
+ const translateStart = useRef10({ x: 0, y: 0 });
4942
+ const imgRef = useRef10(null);
4900
4943
  const reset = useCallback10(() => {
4901
4944
  setScale(1);
4902
4945
  setTranslate({ x: 0, y: 0 });
@@ -4915,7 +4958,7 @@ function ImageLightbox({ open, onOpenChange, images, initialIndex = 0 }) {
4915
4958
  setCurrentIndex((i) => Math.min(images.length - 1, i + 1));
4916
4959
  reset();
4917
4960
  }, [images.length, reset]);
4918
- const prevOpenRef = useRef9(false);
4961
+ const prevOpenRef = useRef10(false);
4919
4962
  useEffect11(() => {
4920
4963
  if (open && !prevOpenRef.current) {
4921
4964
  setCurrentIndex(initialIndex);
@@ -5205,7 +5248,7 @@ function formatLineRange(startLine, endLine) {
5205
5248
  return startLine === endLine ? `\u7B2C ${startLine} \u884C` : `\u7B2C ${startLine}-${endLine} \u884C`;
5206
5249
  }
5207
5250
  function FileReadRenderer({ toolCall }) {
5208
- const [lightboxIndex, setLightboxIndex] = useState12(null);
5251
+ const [lightboxIndex, setLightboxIndex] = useState13(null);
5209
5252
  const argsValue = parseJsonValue(toolCall.arguments);
5210
5253
  const args = isPlainObject(argsValue) ? argsValue : null;
5211
5254
  const filePath = extractToolFilePath(toolCall);
@@ -5503,7 +5546,7 @@ function ToolCallBlock({
5503
5546
  reasoning,
5504
5547
  customization
5505
5548
  }) {
5506
- const [expanded, setExpanded] = useState13(false);
5549
+ const [expanded, setExpanded] = useState14(false);
5507
5550
  const activeSessionId = useSessionStore((s) => s.activeSessionId);
5508
5551
  const sessions = useSessionStore((s) => s.sessions);
5509
5552
  const pushArtifact = useUiStore((s) => s.pushArtifact);
@@ -5707,7 +5750,7 @@ function buildAskUserPayload(argumentsJson) {
5707
5750
  }
5708
5751
 
5709
5752
  // src/react/components/chat/UserMessageBubble.tsx
5710
- import { useState as useState15 } from "react";
5753
+ import { useState as useState16 } from "react";
5711
5754
 
5712
5755
  // src/react/lib/preview-dispatch.ts
5713
5756
  var IMAGE_EXTS = [
@@ -6040,10 +6083,10 @@ function MessageFileAttachmentList({
6040
6083
 
6041
6084
  // src/react/components/chat/MessageActions.tsx
6042
6085
  import { Check as Check2, Copy } from "lucide-react";
6043
- import { useState as useState14 } from "react";
6086
+ import { useState as useState15 } from "react";
6044
6087
  import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
6045
6088
  function MessageActions({ content, className }) {
6046
- const [copied, setCopied] = useState14(false);
6089
+ const [copied, setCopied] = useState15(false);
6047
6090
  const handleCopy = async () => {
6048
6091
  const ok = await copyToClipboard(content);
6049
6092
  if (ok) {
@@ -6233,8 +6276,8 @@ function UserMessageBubble({ message, className }) {
6233
6276
  alt: attachment.name || "\u7528\u6237\u4E0A\u4F20\u7684\u56FE\u7247"
6234
6277
  }))
6235
6278
  ];
6236
- const [lightboxIndex, setLightboxIndex] = useState15(null);
6237
- const [preview, setPreview] = useState15(null);
6279
+ const [lightboxIndex, setLightboxIndex] = useState16(null);
6280
+ const [preview, setPreview] = useState16(null);
6238
6281
  const handleTextAttachmentPreview = (attachment) => {
6239
6282
  if (!activeSessionId) return;
6240
6283
  const pathForUrl = attachment.uploadedPath ?? attachment.name;
@@ -6401,7 +6444,7 @@ function AgentLoopBlock({ toolCall, sessionId, reasoning }) {
6401
6444
  () => visibleLoopToolCalls.some((childToolCall) => childToolCall.status === "awaiting_answer"),
6402
6445
  [visibleLoopToolCalls]
6403
6446
  );
6404
- const [expanded, setExpanded] = useState16(hasAwaitingAnswer);
6447
+ const [expanded, setExpanded] = useState17(hasAwaitingAnswer);
6405
6448
  const completedToolLabels = useMemo14(
6406
6449
  () => visibleLoopToolCalls.flatMap((childToolCall) => {
6407
6450
  const isCompleted = childToolCall.status === "done" || childToolCall.status !== "pending" && childToolCall.status !== "awaiting_answer" && childToolCall.status !== "error" && childToolCall.status !== "cancelled";
@@ -6653,12 +6696,9 @@ function ExpandedChildAssistantMessage({
6653
6696
  const text = typeof message.content === "string" ? message.content.trim() : message.content.filter((part) => part.type === "text").map((part) => part.text).join("").trim();
6654
6697
  const toolCalls = message.tool_calls ?? [];
6655
6698
  const hasToolCalls = toolCalls.length > 0;
6656
- const firstToolCallId = toolCalls[0]?.id;
6657
- const toolReasoning = !isStreaming && !text && hasToolCalls ? message.reasoning : void 0;
6658
- const standaloneReasoning = !isStreaming && !text && !hasToolCalls ? message.reasoning : void 0;
6659
6699
  return /* @__PURE__ */ jsxs24("div", { className: "flex flex-col gap-2", children: [
6660
- message.reasoning && isStreaming && /* @__PURE__ */ jsxs24(Reasoning, { isStreaming, children: [
6661
- /* @__PURE__ */ jsx29(ReasoningTrigger, {}),
6700
+ message.reasoning && /* @__PURE__ */ jsxs24(Reasoning, { isStreaming, children: [
6701
+ /* @__PURE__ */ jsx29(ReasoningTrigger, { wordCount: message.reasoning.length }),
6662
6702
  /* @__PURE__ */ jsx29(ReasoningContent, { children: message.reasoning })
6663
6703
  ] }),
6664
6704
  text ? /* @__PURE__ */ jsx29(
@@ -6667,18 +6707,17 @@ function ExpandedChildAssistantMessage({
6667
6707
  text,
6668
6708
  isStreaming,
6669
6709
  sessionId,
6670
- reasoning: !isStreaming ? message.reasoning : void 0
6710
+ reasoning: void 0
6671
6711
  }
6672
6712
  ) : null,
6673
6713
  !text && isStreaming && /* @__PURE__ */ jsx29("span", { className: "pl-8 text-[12px] text-[hsl(var(--muted-foreground))]", children: "\u6B63\u5728\u751F\u6210..." }),
6674
- standaloneReasoning ? /* @__PURE__ */ jsx29("div", { className: "pl-8", children: /* @__PURE__ */ jsx29(ThinkingBadge, { reasoning: standaloneReasoning, variant: "block" }) }) : null,
6675
6714
  hasToolCalls && /* @__PURE__ */ jsx29("div", { className: "flex flex-col gap-2", children: toolCalls.map(
6676
- (toolCall, index) => formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */ jsx29(
6715
+ (toolCall) => formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */ jsx29(
6677
6716
  AgentLoopBlock,
6678
6717
  {
6679
6718
  toolCall,
6680
6719
  sessionId,
6681
- reasoning: toolCall.id === firstToolCallId ? toolReasoning : void 0
6720
+ reasoning: void 0
6682
6721
  },
6683
6722
  toolCall.id
6684
6723
  ) : /* @__PURE__ */ jsx29(
@@ -6688,7 +6727,7 @@ function ExpandedChildAssistantMessage({
6688
6727
  sessionId,
6689
6728
  level: 2,
6690
6729
  turnBlocks: message.blocks,
6691
- reasoning: index === 0 ? toolReasoning : void 0
6730
+ reasoning: void 0
6692
6731
  },
6693
6732
  toolCall.id
6694
6733
  )
@@ -6910,10 +6949,10 @@ function AssistantTurnBlock({
6910
6949
  ),
6911
6950
  [allToolCalls]
6912
6951
  );
6913
- const [displayMode, setDisplayMode] = useState17(
6952
+ const [displayMode, setDisplayMode] = useState18(
6914
6953
  defaultTurnDisplayMode({ forceExpanded, hasActionableToolCall })
6915
6954
  );
6916
- const wasStreamingRef = useRef10(isStreaming);
6955
+ const wasStreamingRef = useRef11(isStreaming);
6917
6956
  useEffect14(() => {
6918
6957
  if (wasStreamingRef.current && !isStreaming && !forceExpanded) {
6919
6958
  setDisplayMode(defaultTurnDisplayMode({ forceExpanded, hasActionableToolCall }));
@@ -7012,23 +7051,18 @@ function AssistantMessages({
7012
7051
  return messages.map(
7013
7052
  (message, index) => isRenderableAssistantMessage(message, isStreaming && index === messages.length - 1) ? (() => {
7014
7053
  const isStreamingLastMessage = isStreaming && index === messages.length - 1;
7015
- const hasText = !!getMessageText(message);
7016
7054
  const reasoning = message.reasoning;
7017
7055
  const hasReasoning = !!reasoning;
7018
- const hasAttachments = getImageParts(message.content).length > 0 || getFileParts(message.content).length > 0;
7019
7056
  const toolCalls = message.tool_calls ?? [];
7020
7057
  const hasToolCalls = toolCalls.length > 0;
7021
- const firstToolCallId = toolCalls[0]?.id;
7022
7058
  const toolRenderItems = groupDetailedToolCalls(toolCalls);
7023
- const toolReasoning = hasReasoning && !hasText && !hasAttachments && hasToolCalls && !isStreamingLastMessage ? reasoning : void 0;
7024
- const contentReasoning = hasReasoning && !isStreamingLastMessage && (hasText || hasAttachments || !hasToolCalls) ? reasoning : void 0;
7025
7059
  return /* @__PURE__ */ jsxs25(
7026
7060
  "div",
7027
7061
  {
7028
7062
  className: "flex flex-col gap-3",
7029
7063
  children: [
7030
- hasReasoning && isStreamingLastMessage && /* @__PURE__ */ jsxs25(Reasoning, { isStreaming: isStreamingLastMessage, children: [
7031
- /* @__PURE__ */ jsx30(ReasoningTrigger, {}),
7064
+ hasReasoning && /* @__PURE__ */ jsxs25(Reasoning, { isStreaming: isStreamingLastMessage, children: [
7065
+ /* @__PURE__ */ jsx30(ReasoningTrigger, { wordCount: reasoning.length }),
7032
7066
  /* @__PURE__ */ jsx30(ReasoningContent, { children: reasoning })
7033
7067
  ] }),
7034
7068
  /* @__PURE__ */ jsx30(
@@ -7037,7 +7071,7 @@ function AssistantMessages({
7037
7071
  message,
7038
7072
  isStreaming: isStreamingLastMessage,
7039
7073
  sessionId,
7040
- reasoning: contentReasoning,
7074
+ reasoning: void 0,
7041
7075
  className: customization?.classNames?.assistantText
7042
7076
  }
7043
7077
  ),
@@ -7048,7 +7082,7 @@ function AssistantMessages({
7048
7082
  {
7049
7083
  toolCalls: item.toolCalls,
7050
7084
  kind: item.kind,
7051
- reasoning: item.toolCalls[0]?.id === firstToolCallId ? toolReasoning : void 0,
7085
+ reasoning: void 0,
7052
7086
  sessionId,
7053
7087
  sessionStatus,
7054
7088
  askAnswers,
@@ -7066,7 +7100,7 @@ function AssistantMessages({
7066
7100
  {
7067
7101
  toolCall,
7068
7102
  sessionId,
7069
- reasoning: toolCall.id === firstToolCallId ? toolReasoning : void 0
7103
+ reasoning: void 0
7070
7104
  },
7071
7105
  toolCall.id
7072
7106
  ) : customization?.components?.ToolCall ? /* @__PURE__ */ jsx30(
@@ -7080,7 +7114,7 @@ function AssistantMessages({
7080
7114
  sessionStatus,
7081
7115
  level,
7082
7116
  turnBlocks: message.blocks,
7083
- reasoning: toolCall.id === firstToolCallId ? toolReasoning : void 0,
7117
+ reasoning: void 0,
7084
7118
  customization
7085
7119
  },
7086
7120
  toolCall.id
@@ -7095,7 +7129,7 @@ function AssistantMessages({
7095
7129
  sessionStatus,
7096
7130
  level,
7097
7131
  turnBlocks: message.blocks,
7098
- reasoning: toolCall.id === firstToolCallId ? toolReasoning : void 0,
7132
+ reasoning: void 0,
7099
7133
  customization
7100
7134
  },
7101
7135
  toolCall.id
@@ -7137,7 +7171,7 @@ function CompactToolGroupBlock({
7137
7171
  level,
7138
7172
  customization
7139
7173
  }) {
7140
- const [expanded, setExpanded] = useState17(false);
7174
+ const [expanded, setExpanded] = useState18(false);
7141
7175
  const indentClass = level === 2 ? "ml-3" : "ml-4";
7142
7176
  const hasError = toolCalls.some(
7143
7177
  (tc) => tc.status === "error" || tc.status === "cancelled"
@@ -7303,7 +7337,7 @@ function AssistantText({
7303
7337
  }
7304
7338
  function MemoryRefsHint({ refs: rawRefs }) {
7305
7339
  const refs = Array.isArray(rawRefs) ? rawRefs : [];
7306
- const [expanded, setExpanded] = useState17(false);
7340
+ const [expanded, setExpanded] = useState18(false);
7307
7341
  const hasSkill = refs.some((r3) => r3.skill_name);
7308
7342
  const label = hasSkill ? "\u53C2\u8003\u4E86\u8BE5\u6280\u80FD\u7684\u5386\u53F2\u7ECF\u9A8C" : "\u53C2\u8003\u4E86\u5386\u53F2\u7ECF\u9A8C";
7309
7343
  return /* @__PURE__ */ jsxs25("div", { className: "ml-4", children: [
@@ -7351,7 +7385,7 @@ function MemoryRefsHint({ refs: rawRefs }) {
7351
7385
 
7352
7386
  // src/react/components/chat/CompactionCard.tsx
7353
7387
  import { ChevronDown as ChevronDown2, ChevronRight as ChevronRight6, Loader2 as Loader25, Square as Square3, XCircle } from "lucide-react";
7354
- import { useState as useState18 } from "react";
7388
+ import { useState as useState19 } from "react";
7355
7389
  import { jsx as jsx31, jsxs as jsxs26 } from "react/jsx-runtime";
7356
7390
  var PERCENT_FORMATTER = new Intl.NumberFormat("zh-CN", {
7357
7391
  style: "percent",
@@ -7411,7 +7445,7 @@ function CompactionCard({
7411
7445
  ...compaction,
7412
7446
  status: status ?? "completed"
7413
7447
  } : activeCompaction;
7414
- const [expanded, setExpanded] = useState18(false);
7448
+ const [expanded, setExpanded] = useState19(false);
7415
7449
  if (!source || !source.compaction_id) {
7416
7450
  return null;
7417
7451
  }
@@ -7453,7 +7487,7 @@ function CompactionCard({
7453
7487
  "button",
7454
7488
  {
7455
7489
  type: "button",
7456
- onClick: () => getSocket().stop(sessionId),
7490
+ onClick: () => void getSocket().stop(sessionId),
7457
7491
  className: "shrink-0 rounded-md border border-current/15 px-2 py-0.5 text-[11px] transition-opacity hover:opacity-80",
7458
7492
  children: "\u53D6\u6D88\u6574\u7406"
7459
7493
  }
@@ -7572,7 +7606,7 @@ import {
7572
7606
  TerminalSquare,
7573
7607
  WandSparkles
7574
7608
  } from "lucide-react";
7575
- import { useEffect as useEffect15, useMemo as useMemo16, useState as useState19 } from "react";
7609
+ import { useEffect as useEffect15, useMemo as useMemo16, useState as useState20 } from "react";
7576
7610
  import { jsx as jsx33, jsxs as jsxs28 } from "react/jsx-runtime";
7577
7611
  var EMPTY_EVENTS = [];
7578
7612
  function formatElapsedDuration(durationMs) {
@@ -7651,7 +7685,7 @@ function getTurnStartedAt(events) {
7651
7685
  return null;
7652
7686
  }
7653
7687
  function useElapsedDuration(startedAt, active) {
7654
- const [now, setNow] = useState19(() => Date.now());
7688
+ const [now, setNow] = useState20(() => Date.now());
7655
7689
  useEffect15(() => {
7656
7690
  if (!active || startedAt == null) {
7657
7691
  return;
@@ -7943,10 +7977,10 @@ function MessageList({
7943
7977
  ) ?? null,
7944
7978
  [lastTurnId, renderBlocks]
7945
7979
  );
7946
- const containerRef = useRef11(null);
7947
- const scrollContainerRef = useRef11(null);
7948
- const frameRef = useRef11(null);
7949
- const [activeTurnId, setActiveTurnId] = useState20(lastTurnId);
7980
+ const containerRef = useRef12(null);
7981
+ const scrollContainerRef = useRef12(null);
7982
+ const frameRef = useRef12(null);
7983
+ const [activeTurnId, setActiveTurnId] = useState21(lastTurnId);
7950
7984
  const layoutSignature = useMemo17(
7951
7985
  () => getMessagesMeasureSignature(messages),
7952
7986
  [messages]
@@ -8244,6 +8278,7 @@ function ChatView({
8244
8278
  const {
8245
8279
  messages,
8246
8280
  isStreaming,
8281
+ isStopping,
8247
8282
  send,
8248
8283
  stop
8249
8284
  } = useChat(sessionId);
@@ -8291,6 +8326,7 @@ function ChatView({
8291
8326
  onSend: (msg, _targetSessionId, model) => send(msg, mode, void 0, { model: model || void 0 }),
8292
8327
  onStop: stop,
8293
8328
  isStreaming,
8329
+ isStopping,
8294
8330
  mode,
8295
8331
  onToggleMode: toggleMode,
8296
8332
  renderAttachments,
@@ -8360,4 +8396,4 @@ use-stick-to-bottom/dist/StickToBottom.js:
8360
8396
  * Licensed under the MIT License. See License.txt in the project root for license information.
8361
8397
  *--------------------------------------------------------------------------------------------*)
8362
8398
  */
8363
- //# sourceMappingURL=chunk-EV225FLR.js.map
8399
+ //# sourceMappingURL=chunk-S6EPGDAL.js.map