@blade-hq/agent-react 2610.0.0-beta.33 → 2610.0.0-beta.34

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
@@ -1144,7 +1144,7 @@ var X = createLucideIcon("X", [
1144
1144
  ]);
1145
1145
 
1146
1146
  // src/components/AgentChat.tsx
1147
- import { useCallback as useCallback8, useEffect as useEffect12, useMemo as useMemo8, useState as useState14 } from "react";
1147
+ import { useCallback as useCallback8, useEffect as useEffect13, useMemo as useMemo8, useState as useState15 } from "react";
1148
1148
 
1149
1149
  // src/lib/utils.ts
1150
1150
  function cn(...inputs) {
@@ -1613,7 +1613,8 @@ function CurrentPlanPanel({
1613
1613
  import { chatErrorForDisplay as chatErrorForDisplay2 } from "@blade-hq/agent-client";
1614
1614
 
1615
1615
  // src/components/ChatInput.tsx
1616
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1616
+ import { useEffect as useEffect5, useRef as useRef5, useState as useState5 } from "react";
1617
+ import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1617
1618
  function isImeCompositionKey(event) {
1618
1619
  return event.isComposing || event.keyCode === 229;
1619
1620
  }
@@ -1624,16 +1625,55 @@ function ChatInput({
1624
1625
  value,
1625
1626
  onValueChange,
1626
1627
  onSend,
1628
+ onAppend,
1627
1629
  onStop,
1628
1630
  isStreaming,
1629
1631
  isStopping = false,
1630
1632
  placeholder = "\u8F93\u5165\u6D88\u606F\u2026",
1631
- className
1633
+ className,
1634
+ queueKey
1632
1635
  }) {
1633
1636
  const trimmed = value.trim();
1634
- const canSend = trimmed.length > 0 && !isStreaming;
1637
+ const [sendMode, setSendMode] = useState5("direct");
1638
+ const [promptQueue, setPromptQueue] = useState5([]);
1639
+ const queueSendingRef = useRef5(false);
1640
+ const queueBlockedRef = useRef5(false);
1641
+ const previousQueueKeyRef = useRef5(queueKey);
1642
+ useEffect5(() => {
1643
+ if (previousQueueKeyRef.current === queueKey) return;
1644
+ previousQueueKeyRef.current = queueKey;
1645
+ setPromptQueue([]);
1646
+ queueBlockedRef.current = false;
1647
+ }, [queueKey]);
1648
+ const canSend = trimmed.length > 0 && (!isStreaming || sendMode === "queue");
1649
+ useEffect5(() => {
1650
+ if (isStreaming) {
1651
+ queueBlockedRef.current = false;
1652
+ return;
1653
+ }
1654
+ if (queueBlockedRef.current || promptQueue.length === 0 || queueSendingRef.current) return;
1655
+ const next = promptQueue[0];
1656
+ queueSendingRef.current = true;
1657
+ Promise.resolve().then(() => onSend(next)).then((accepted) => {
1658
+ if (accepted) setPromptQueue((current) => current.slice(1));
1659
+ else queueBlockedRef.current = true;
1660
+ }).finally(() => {
1661
+ queueSendingRef.current = false;
1662
+ });
1663
+ }, [isStreaming, onSend, promptQueue]);
1635
1664
  const handleSend = async () => {
1636
1665
  if (!canSend) return;
1666
+ if (isStreaming && sendMode === "queue") {
1667
+ setPromptQueue((current) => [...current, trimmed]);
1668
+ onValueChange("");
1669
+ return;
1670
+ }
1671
+ if (isStreaming && sendMode === "direct") {
1672
+ if (!onAppend) return;
1673
+ onAppend(trimmed);
1674
+ onValueChange("");
1675
+ return;
1676
+ }
1637
1677
  const accepted = await onSend(trimmed);
1638
1678
  if (!accepted) return;
1639
1679
  onValueChange("");
@@ -1649,61 +1689,73 @@ function ChatInput({
1649
1689
  void handleSend();
1650
1690
  }
1651
1691
  };
1652
- return /* @__PURE__ */ jsx5("div", { className: cn("blade-chat-input border-t border-[hsl(var(--border))] py-3", className), children: /* @__PURE__ */ jsxs4("div", { className: "blade-chat-input-inner mx-auto flex max-w-[748px] items-end gap-2 rounded-2xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-3 py-2", children: [
1653
- /* @__PURE__ */ jsx5(
1654
- "textarea",
1655
- {
1656
- value,
1657
- onChange: (event) => onValueChange(event.target.value),
1658
- onKeyDown: handleKeyDown,
1659
- onInput: (event) => {
1660
- const el = event.currentTarget;
1661
- el.style.height = "auto";
1662
- el.style.height = `${Math.min(el.scrollHeight, 192)}px`;
1663
- },
1664
- rows: 1,
1665
- placeholder,
1666
- "aria-label": "\u804A\u5929\u8F93\u5165",
1667
- className: "blade-chat-textarea max-h-48 min-h-[28px] flex-1 resize-none bg-transparent py-1 text-sm leading-6 text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground)/0.6)]"
1668
- }
1669
- ),
1670
- isStreaming ? /* @__PURE__ */ jsx5(
1671
- "button",
1672
- {
1673
- type: "button",
1674
- onClick: onStop,
1675
- disabled: isStopping,
1676
- "aria-label": isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D",
1677
- title: isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D",
1678
- className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--muted))] text-[hsl(var(--foreground))] transition-opacity hover:opacity-90 disabled:opacity-60",
1679
- children: isStopping ? /* @__PURE__ */ jsx5(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx5(Square, { size: 12, fill: "currentColor" })
1680
- }
1681
- ) : /* @__PURE__ */ jsx5(
1682
- "button",
1683
- {
1684
- type: "button",
1685
- onClick: handleSend,
1686
- disabled: !canSend,
1687
- "aria-label": "\u53D1\u9001\u6D88\u606F",
1688
- title: "\u53D1\u9001\u6D88\u606F",
1689
- className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",
1690
- children: /* @__PURE__ */ jsx5(ArrowUp, { size: 15 })
1691
- }
1692
- )
1693
- ] }) });
1692
+ return /* @__PURE__ */ jsxs4("div", { className: cn("blade-chat-input border-t border-[hsl(var(--border))] py-3", className), children: [
1693
+ /* @__PURE__ */ jsxs4("div", { className: "mx-auto mb-2 flex max-w-[748px] items-center justify-between px-1 text-xs text-[hsl(var(--muted-foreground))]", children: [
1694
+ /* @__PURE__ */ jsxs4("fieldset", { className: "flex items-center gap-1 rounded-md border border-[hsl(var(--border))] p-0.5", children: [
1695
+ /* @__PURE__ */ jsx5("legend", { className: "sr-only", children: "\u53D1\u9001\u65B9\u5F0F" }),
1696
+ /* @__PURE__ */ jsx5("button", { type: "button", onClick: () => setSendMode("direct"), "aria-pressed": sendMode === "direct", disabled: isStreaming && !onAppend, className: `rounded px-2 py-1 ${sendMode === "direct" ? "bg-[hsl(var(--accent))] text-[hsl(var(--foreground))]" : ""}`, children: "\u76F4\u63A5\u63D2\u5165" }),
1697
+ /* @__PURE__ */ jsx5("button", { type: "button", onClick: () => setSendMode("queue"), "aria-pressed": sendMode === "queue", className: `rounded px-2 py-1 ${sendMode === "queue" ? "bg-[hsl(var(--accent))] text-[hsl(var(--foreground))]" : ""}`, children: "\u6392\u961F\u6267\u884C" })
1698
+ ] }),
1699
+ promptQueue.length > 0 ? /* @__PURE__ */ jsxs4("details", { className: "relative", children: [
1700
+ /* @__PURE__ */ jsxs4("summary", { className: "cursor-pointer list-none rounded px-2 py-1 hover:bg-[hsl(var(--accent))]", children: [
1701
+ "\u5F85\u6267\u884C ",
1702
+ promptQueue.length,
1703
+ " \u6761"
1704
+ ] }),
1705
+ /* @__PURE__ */ jsx5("div", { className: "absolute bottom-full right-0 z-20 mb-2 w-64 rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--card))] p-2 shadow-lg", children: promptQueue.map((item, index) => /* @__PURE__ */ jsxs4("div", { className: "flex gap-2 border-b border-[hsl(var(--border))] py-2 last:border-0", children: [
1706
+ /* @__PURE__ */ jsx5("span", { className: "min-w-0 flex-1 truncate", children: item }),
1707
+ /* @__PURE__ */ jsx5("button", { type: "button", onClick: () => setPromptQueue((current) => current.filter((_, i) => i !== index)), children: "\u53D6\u6D88" })
1708
+ ] }, `${index}-${item}`)) })
1709
+ ] }) : null
1710
+ ] }),
1711
+ /* @__PURE__ */ jsxs4("div", { className: "blade-chat-input-inner mx-auto flex max-w-[748px] items-end gap-2 rounded-2xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-3 py-2", children: [
1712
+ /* @__PURE__ */ jsx5(
1713
+ "textarea",
1714
+ {
1715
+ value,
1716
+ onChange: (event) => onValueChange(event.target.value),
1717
+ onKeyDown: handleKeyDown,
1718
+ onInput: (event) => {
1719
+ const el = event.currentTarget;
1720
+ el.style.height = "auto";
1721
+ el.style.height = `${Math.min(el.scrollHeight, 192)}px`;
1722
+ },
1723
+ rows: 1,
1724
+ placeholder,
1725
+ "aria-label": "\u804A\u5929\u8F93\u5165",
1726
+ className: "blade-chat-textarea max-h-48 min-h-[28px] flex-1 resize-none bg-transparent py-1 text-sm leading-6 text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground)/0.6)]"
1727
+ }
1728
+ ),
1729
+ isStreaming ? /* @__PURE__ */ jsxs4(Fragment, { children: [
1730
+ sendMode === "queue" ? /* @__PURE__ */ jsx5("button", { type: "button", onClick: handleSend, disabled: !canSend, "aria-label": "\u52A0\u5165\u6392\u961F", title: "\u52A0\u5165\u6392\u961F", className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] disabled:opacity-40", children: /* @__PURE__ */ jsx5(ArrowUp, { size: 15 }) }) : null,
1731
+ /* @__PURE__ */ jsx5("button", { type: "button", onClick: onStop, disabled: isStopping, "aria-label": isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D", title: isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D", className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--muted))] text-[hsl(var(--foreground))] transition-opacity hover:opacity-90 disabled:opacity-60", children: isStopping ? /* @__PURE__ */ jsx5(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx5(Square, { size: 12, fill: "currentColor" }) })
1732
+ ] }) : /* @__PURE__ */ jsx5(
1733
+ "button",
1734
+ {
1735
+ type: "button",
1736
+ onClick: handleSend,
1737
+ disabled: !canSend,
1738
+ "aria-label": "\u53D1\u9001\u6D88\u606F",
1739
+ title: "\u53D1\u9001\u6D88\u606F",
1740
+ className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",
1741
+ children: /* @__PURE__ */ jsx5(ArrowUp, { size: 15 })
1742
+ }
1743
+ )
1744
+ ] })
1745
+ ] });
1694
1746
  }
1695
1747
 
1696
1748
  // src/components/ConnectionBanner.tsx
1697
- import { useEffect as useEffect5, useRef as useRef5, useState as useState5 } from "react";
1749
+ import { useEffect as useEffect6, useRef as useRef6, useState as useState6 } from "react";
1698
1750
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1699
1751
  var CONNECTION_NOTICE_DELAY_MS = 3e3;
1700
1752
  var CONNECTION_ERROR_DELAY_MS = 15e3;
1701
1753
  function useConnectionNoticePhase(connected) {
1702
- const [phase, setPhase] = useState5("hidden");
1703
- const connectedRef = useRef5(connected);
1704
- const timersRef = useRef5([]);
1754
+ const [phase, setPhase] = useState6("hidden");
1755
+ const connectedRef = useRef6(connected);
1756
+ const timersRef = useRef6([]);
1705
1757
  connectedRef.current = connected;
1706
- useEffect5(() => {
1758
+ useEffect6(() => {
1707
1759
  const clearTimers = () => {
1708
1760
  for (const timer of timersRef.current) clearTimeout(timer);
1709
1761
  timersRef.current = [];
@@ -1743,7 +1795,7 @@ function useConnectionNoticePhase(connected) {
1743
1795
  return phase;
1744
1796
  }
1745
1797
  function ConnectionBanner({ connection, className }) {
1746
- const hasConnectedRef = useRef5(connection === "connected" || connection === "reconnecting");
1798
+ const hasConnectedRef = useRef6(connection === "connected" || connection === "reconnecting");
1747
1799
  if (connection === "connected") hasConnectedRef.current = true;
1748
1800
  const connected = connection === "connected";
1749
1801
  const phase = useConnectionNoticePhase(connected);
@@ -1770,10 +1822,10 @@ function ConnectionBanner({ connection, className }) {
1770
1822
 
1771
1823
  // src/components/MessageList.tsx
1772
1824
  import { isHiddenInternalMessage } from "@blade-hq/agent-client";
1773
- import { useCallback as useCallback7, useEffect as useEffect11, useMemo as useMemo7, useRef as useRef12, useState as useState13 } from "react";
1825
+ import { useCallback as useCallback7, useEffect as useEffect12, useMemo as useMemo7, useRef as useRef13, useState as useState14 } from "react";
1774
1826
 
1775
1827
  // ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/useStickToBottom.js
1776
- import { useCallback as useCallback4, useMemo as useMemo3, useRef as useRef6, useState as useState6 } from "react";
1828
+ import { useCallback as useCallback4, useMemo as useMemo3, useRef as useRef7, useState as useState7 } from "react";
1777
1829
  var DEFAULT_SPRING_ANIMATION = {
1778
1830
  /**
1779
1831
  * A value from 0 to 1, on how much to damp the animation.
@@ -1810,10 +1862,10 @@ globalThis.document?.addEventListener("click", () => {
1810
1862
  mouseDown = false;
1811
1863
  });
1812
1864
  var useStickToBottom = (options = {}) => {
1813
- const [escapedFromLock, updateEscapedFromLock] = useState6(false);
1814
- const [isAtBottom, updateIsAtBottom] = useState6(options.initial !== false);
1815
- const [isNearBottom, setIsNearBottom] = useState6(false);
1816
- const optionsRef = useRef6(null);
1865
+ const [escapedFromLock, updateEscapedFromLock] = useState7(false);
1866
+ const [isAtBottom, updateIsAtBottom] = useState7(options.initial !== false);
1867
+ const [isNearBottom, setIsNearBottom] = useState7(false);
1868
+ const optionsRef = useRef7(null);
1817
1869
  optionsRef.current = options;
1818
1870
  const isSelecting = useCallback4(() => {
1819
1871
  if (!mouseDown) {
@@ -2117,11 +2169,11 @@ function mergeAnimations(...animations) {
2117
2169
 
2118
2170
  // ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/StickToBottom.js
2119
2171
  import * as React from "react";
2120
- import { createContext as createContext2, useContext as useContext2, useEffect as useEffect6, useImperativeHandle, useLayoutEffect, useMemo as useMemo4, useRef as useRef7 } from "react";
2172
+ import { createContext as createContext2, useContext as useContext2, useEffect as useEffect7, useImperativeHandle, useLayoutEffect, useMemo as useMemo4, useRef as useRef8 } from "react";
2121
2173
  var StickToBottomContext = createContext2(null);
2122
- var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect6;
2174
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect7;
2123
2175
  function StickToBottom({ instance, children, resize, initial, mass, damping, stiffness, targetScrollTop: currentTargetScrollTop, contextRef, ...props }) {
2124
- const customTargetScrollTop = useRef7(null);
2176
+ const customTargetScrollTop = useRef8(null);
2125
2177
  const targetScrollTop = React.useCallback((target, elements) => {
2126
2178
  const get = context?.targetScrollTop ?? currentTargetScrollTop;
2127
2179
  return get?.(target, elements) ?? target;
@@ -2203,10 +2255,10 @@ import {
2203
2255
  getTextContent,
2204
2256
  normalizeMessageContent
2205
2257
  } from "@blade-hq/agent-client";
2206
- import { useEffect as useEffect9, useRef as useRef10, useState as useState11 } from "react";
2258
+ import { useEffect as useEffect10, useRef as useRef11, useState as useState12 } from "react";
2207
2259
 
2208
2260
  // src/components/AgentLoopBlock.tsx
2209
- import { useState as useState7 } from "react";
2261
+ import { useState as useState8 } from "react";
2210
2262
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2211
2263
  function parseAgentDescription(argumentsJson) {
2212
2264
  try {
@@ -2217,7 +2269,7 @@ function parseAgentDescription(argumentsJson) {
2217
2269
  }
2218
2270
  }
2219
2271
  function AgentLoopBlock({ toolCall }) {
2220
- const [expanded, setExpanded] = useState7(false);
2272
+ const [expanded, setExpanded] = useState8(false);
2221
2273
  const description = parseAgentDescription(toolCall.arguments);
2222
2274
  const running = toolCall.status === "pending" || toolCall.status === "awaiting_answer";
2223
2275
  const failed = toolCall.status === "error" || toolCall.status === "cancelled";
@@ -2268,10 +2320,10 @@ function AgentLoopBlock({ toolCall }) {
2268
2320
 
2269
2321
  // src/components/MarkdownContent.tsx
2270
2322
  import {
2271
- useEffect as useEffect7,
2323
+ useEffect as useEffect8,
2272
2324
  useMemo as useMemo5,
2273
- useRef as useRef8,
2274
- useState as useState8
2325
+ useRef as useRef9,
2326
+ useState as useState9
2275
2327
  } from "react";
2276
2328
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2277
2329
  var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
@@ -2288,10 +2340,10 @@ function normalizeAdjacentUrlFormatting(value) {
2288
2340
  return normalized.replace(/blade-url-protected-(\d+)-marker/g, (_, index) => protectedSegments[Number(index)]);
2289
2341
  }
2290
2342
  function CodeBlockPre({ children, node: _node, ...props }) {
2291
- const preRef = useRef8(null);
2292
- const [copied, setCopied] = useState8(false);
2293
- const [language, setLanguage] = useState8("");
2294
- useEffect7(() => {
2343
+ const preRef = useRef9(null);
2344
+ const [copied, setCopied] = useState9(false);
2345
+ const [language, setLanguage] = useState9("");
2346
+ useEffect8(() => {
2295
2347
  const codeEl = preRef.current?.querySelector("code");
2296
2348
  setLanguage(codeEl?.className.match(/language-(\S+)/)?.[1] ?? "");
2297
2349
  }, []);
@@ -2362,10 +2414,10 @@ function Shimmer({ children = "\u6B63\u5728\u601D\u8003...", className }) {
2362
2414
  }
2363
2415
 
2364
2416
  // src/components/ToolCallBlock.tsx
2365
- import { useState as useState10 } from "react";
2417
+ import { useState as useState11 } from "react";
2366
2418
 
2367
2419
  // src/components/AskUserQuestionBlock.tsx
2368
- import { useEffect as useEffect8, useMemo as useMemo6, useRef as useRef9, useState as useState9 } from "react";
2420
+ import { useEffect as useEffect9, useMemo as useMemo6, useRef as useRef10, useState as useState10 } from "react";
2369
2421
  import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
2370
2422
  var CUSTOM_TEXTAREA_MAX_HEIGHT = 160;
2371
2423
  function resizeCustomTextarea(textarea) {
@@ -2374,12 +2426,12 @@ function resizeCustomTextarea(textarea) {
2374
2426
  textarea.style.overflowY = textarea.scrollHeight > CUSTOM_TEXTAREA_MAX_HEIGHT ? "auto" : "hidden";
2375
2427
  }
2376
2428
  function useAutoResizeTextarea(value) {
2377
- const textareaRef = useRef9(null);
2378
- useEffect8(() => {
2429
+ const textareaRef = useRef10(null);
2430
+ useEffect9(() => {
2379
2431
  const textarea = textareaRef.current;
2380
2432
  if (textarea?.value === value) resizeCustomTextarea(textarea);
2381
2433
  }, [value]);
2382
- useEffect8(() => {
2434
+ useEffect9(() => {
2383
2435
  const textarea = textareaRef.current;
2384
2436
  if (!textarea || typeof ResizeObserver === "undefined") return;
2385
2437
  let previousWidth = textarea.clientWidth;
@@ -2404,12 +2456,12 @@ function AskUserQuestionBlock({
2404
2456
  answerData,
2405
2457
  onAnswer
2406
2458
  }) {
2407
- const [selections, setSelections] = useState9(/* @__PURE__ */ new Map());
2408
- const [customTexts, setCustomTexts] = useState9(/* @__PURE__ */ new Map());
2409
- const [usingCustom, setUsingCustom] = useState9(/* @__PURE__ */ new Set());
2410
- const [note, setNote] = useState9("");
2411
- const [submitted, setSubmitted] = useState9(false);
2412
- useEffect8(() => {
2459
+ const [selections, setSelections] = useState10(/* @__PURE__ */ new Map());
2460
+ const [customTexts, setCustomTexts] = useState10(/* @__PURE__ */ new Map());
2461
+ const [usingCustom, setUsingCustom] = useState10(/* @__PURE__ */ new Set());
2462
+ const [note, setNote] = useState10("");
2463
+ const [submitted, setSubmitted] = useState10(false);
2464
+ useEffect9(() => {
2413
2465
  if (sessionStatus === "failed" || sessionStatus === "interrupted") {
2414
2466
  setSubmitted(false);
2415
2467
  }
@@ -2798,7 +2850,7 @@ function normalizeOptionItem(value) {
2798
2850
  }
2799
2851
 
2800
2852
  // src/components/ToolCallBlock.tsx
2801
- import { Fragment, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2853
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2802
2854
  function resolveAskQuestionState({
2803
2855
  toolStatus,
2804
2856
  hasAnswerData,
@@ -2820,12 +2872,12 @@ function ToolCallBlock({
2820
2872
  isActiveQuestion,
2821
2873
  renderer
2822
2874
  }) {
2823
- const [expanded, setExpanded] = useState10(false);
2875
+ const [expanded, setExpanded] = useState11(false);
2824
2876
  const normalizedName = formatToolName(toolCall.name);
2825
2877
  if (renderer) {
2826
2878
  const custom = renderer(toolCall);
2827
2879
  if (custom !== null && custom !== void 0) {
2828
- return /* @__PURE__ */ jsx11(Fragment, { children: custom });
2880
+ return /* @__PURE__ */ jsx11(Fragment2, { children: custom });
2829
2881
  }
2830
2882
  }
2831
2883
  if (normalizedName === "AskUserQuestion") {
@@ -2908,7 +2960,7 @@ function ToolCallBlock({
2908
2960
  /* @__PURE__ */ jsx11("div", { className: "mb-3 font-mono text-[11px] text-[hsl(var(--foreground))]", children: normalizedName }),
2909
2961
  /* @__PURE__ */ jsx11("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u53C2\u6570" }),
2910
2962
  /* @__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) }),
2911
- toolCall.result != null && /* @__PURE__ */ jsxs9(Fragment, { children: [
2963
+ toolCall.result != null && /* @__PURE__ */ jsxs9(Fragment2, { children: [
2912
2964
  /* @__PURE__ */ jsx11("div", { className: "mb-1 mt-3 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u7ED3\u679C" }),
2913
2965
  /* @__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) })
2914
2966
  ] })
@@ -2930,7 +2982,7 @@ function buildAskUserPayload(argumentsJson) {
2930
2982
  // src/components/AssistantTurnBlock.tsx
2931
2983
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
2932
2984
  function ThinkingBlock({ reasoning, isStreaming }) {
2933
- const [open, setOpen] = useState11(false);
2985
+ const [open, setOpen] = useState12(false);
2934
2986
  if (!isStreaming) return null;
2935
2987
  return /* @__PURE__ */ jsxs10("div", { className: "blade-chat-thinking text-xs", children: [
2936
2988
  /* @__PURE__ */ jsxs10(
@@ -3200,12 +3252,12 @@ function AssistantTurnBlock({
3200
3252
  )
3201
3253
  );
3202
3254
  const activeQuestionId = questionToolCalls.filter((toolCall) => toolCall.status === "pending").at(-1)?.id;
3203
- const [displayMode, setDisplayMode] = useState11(
3255
+ const [displayMode, setDisplayMode] = useState12(
3204
3256
  () => isStreaming || hasActionableToolCall ? "detail" : "compact"
3205
3257
  );
3206
- const userSelectedDisplayModeRef = useRef10(false);
3207
- const wasStreamingRef = useRef10(isStreaming);
3208
- useEffect9(() => {
3258
+ const userSelectedDisplayModeRef = useRef11(false);
3259
+ const wasStreamingRef = useRef11(isStreaming);
3260
+ useEffect10(() => {
3209
3261
  if (wasStreamingRef.current && !isStreaming && !userSelectedDisplayModeRef.current) {
3210
3262
  setDisplayMode(hasActionableToolCall ? "detail" : "compact");
3211
3263
  }
@@ -3213,11 +3265,11 @@ function AssistantTurnBlock({
3213
3265
  }, [hasActionableToolCall, isStreaming]);
3214
3266
  const effectiveMode = resolveTurnDisplayMode({ isStreaming, displayMode });
3215
3267
  const executionDurationMs = getExecutionDurationMs({ messages, isStreaming });
3216
- const [clock, setClock] = useState11(() => Date.now());
3268
+ const [clock, setClock] = useState12(() => Date.now());
3217
3269
  const hasLiveStartTime = messages.some(
3218
3270
  (message) => message.timestamp != null && Number.isFinite(Date.parse(message.timestamp))
3219
3271
  );
3220
- useEffect9(() => {
3272
+ useEffect10(() => {
3221
3273
  if (!isStreaming || !hasLiveStartTime) return;
3222
3274
  const timer = window.setInterval(() => setClock(Date.now()), 1e3);
3223
3275
  return () => window.clearInterval(timer);
@@ -3392,7 +3444,7 @@ function collectMemoryRefs(messages) {
3392
3444
  return [...refs.values()];
3393
3445
  }
3394
3446
  function MemoryRefsHint({ refs }) {
3395
- const [expanded, setExpanded] = useState11(false);
3447
+ const [expanded, setExpanded] = useState12(false);
3396
3448
  const label = refs.some((ref) => ref.skill_name) ? "\u53C2\u8003\u4E86\u8BE5\u6280\u80FD\u7684\u5386\u53F2\u7ECF\u9A8C" : "\u53C2\u8003\u4E86\u5386\u53F2\u7ECF\u9A8C";
3397
3449
  return /* @__PURE__ */ jsxs10("div", { className: "blade-chat-memory-refs ml-1 w-full max-w-[680px]", children: [
3398
3450
  /* @__PURE__ */ jsxs10("button", { type: "button", onClick: () => setExpanded((value) => !value), className: "inline-flex h-8 items-center gap-1.5 rounded-lg border border-[hsl(var(--primary)/0.22)] bg-[hsl(var(--primary)/0.07)] px-3 text-xs font-medium text-[hsl(var(--primary))]", children: [
@@ -3527,8 +3579,8 @@ var RenderErrorBoundary = class extends Component {
3527
3579
  };
3528
3580
 
3529
3581
  // src/components/PostChatFollowupBlock.tsx
3530
- import { useCallback as useCallback6, useEffect as useEffect10, useRef as useRef11, useState as useState12 } from "react";
3531
- import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
3582
+ import { useCallback as useCallback6, useEffect as useEffect11, useRef as useRef12, useState as useState13 } from "react";
3583
+ import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
3532
3584
  function emitInteraction(callback, event) {
3533
3585
  try {
3534
3586
  callback?.(event);
@@ -3547,7 +3599,7 @@ function ArtifactCard({
3547
3599
  onArtifactOpened
3548
3600
  }) {
3549
3601
  const client = useBladeClient();
3550
- const [downloading, setDownloading] = useState12(false);
3602
+ const [downloading, setDownloading] = useState13(false);
3551
3603
  const name = artifact.label || basename(artifact.target);
3552
3604
  if (artifact.kind === "link") {
3553
3605
  return /* @__PURE__ */ jsxs12(
@@ -3653,15 +3705,15 @@ function ResultFeedback({
3653
3705
  onFeedbackSaved
3654
3706
  }) {
3655
3707
  const client = useBladeClient();
3656
- const [saved, setSaved] = useState12(savedFeedback ?? null);
3657
- const [helpful, setHelpful] = useState12(savedFeedback?.helpful ?? null);
3658
- const [reason, setReason] = useState12(savedFeedback?.reason ?? null);
3659
- const [saving, setSaving] = useState12(false);
3660
- const [saveError, setSaveError] = useState12(false);
3661
- const reportedShown = useRef11(false);
3662
- const latestChoice = useRef11(null);
3708
+ const [saved, setSaved] = useState13(savedFeedback ?? null);
3709
+ const [helpful, setHelpful] = useState13(savedFeedback?.helpful ?? null);
3710
+ const [reason, setReason] = useState13(savedFeedback?.reason ?? null);
3711
+ const [saving, setSaving] = useState13(false);
3712
+ const [saveError, setSaveError] = useState13(false);
3713
+ const reportedShown = useRef12(false);
3714
+ const latestChoice = useRef12(null);
3663
3715
  const eligible = followup.feedback_eligible === true && Boolean(sessionId) && !isViewer;
3664
- useEffect10(() => {
3716
+ useEffect11(() => {
3665
3717
  if (!eligible || reportedShown.current) return;
3666
3718
  reportedShown.current = true;
3667
3719
  emitInteraction(onInteraction, {
@@ -3670,7 +3722,7 @@ function ResultFeedback({
3670
3722
  assistantEntryId: followup.assistant_entry_id
3671
3723
  });
3672
3724
  }, [eligible, followup.assistant_entry_id, onInteraction, sessionId]);
3673
- useEffect10(() => {
3725
+ useEffect11(() => {
3674
3726
  if (!savedFeedback || latestChoice.current) return;
3675
3727
  setSaved(savedFeedback);
3676
3728
  setHelpful(savedFeedback.helpful);
@@ -3782,14 +3834,14 @@ function PostChatFollowupBlock({
3782
3834
  savedFeedback,
3783
3835
  onFeedbackSaved
3784
3836
  }) {
3785
- const [expanded, setExpanded] = useState12(false);
3786
- const adopted = useRef11(/* @__PURE__ */ new Set());
3787
- const reportedSuggestions = useRef11(false);
3788
- const reportedArtifacts = useRef11(/* @__PURE__ */ new Set());
3789
- const openedArtifacts = useRef11(/* @__PURE__ */ new Set());
3837
+ const [expanded, setExpanded] = useState13(false);
3838
+ const adopted = useRef12(/* @__PURE__ */ new Set());
3839
+ const reportedSuggestions = useRef12(false);
3840
+ const reportedArtifacts = useRef12(/* @__PURE__ */ new Set());
3841
+ const openedArtifacts = useRef12(/* @__PURE__ */ new Set());
3790
3842
  const artifacts = followup.final_artifacts ?? [];
3791
3843
  const visibleArtifacts = expanded ? artifacts : artifacts.slice(0, 3);
3792
- useEffect10(() => {
3844
+ useEffect11(() => {
3793
3845
  if (!reportedSuggestions.current && followup.suggestions.length > 0) {
3794
3846
  reportedSuggestions.current = true;
3795
3847
  emitInteraction(onInteraction, {
@@ -3842,7 +3894,7 @@ function PostChatFollowupBlock({
3842
3894
  "\u672C\u8F6E\u5C0F\u7ED3"
3843
3895
  ] }),
3844
3896
  followup.recaption ? /* @__PURE__ */ jsx14("p", { className: "text-[13px] leading-5", children: followup.recaption }) : null,
3845
- artifacts.length > 0 ? /* @__PURE__ */ jsxs12(Fragment2, { children: [
3897
+ artifacts.length > 0 ? /* @__PURE__ */ jsxs12(Fragment3, { children: [
3846
3898
  /* @__PURE__ */ jsx14("div", { className: "grid max-w-full grid-cols-3 gap-1.5", children: visibleArtifacts.map((artifact, artifactIndex) => /* @__PURE__ */ jsx14(
3847
3899
  ArtifactCard,
3848
3900
  {
@@ -4100,8 +4152,16 @@ function MessageList({
4100
4152
  resultFeedbackByEntry = /* @__PURE__ */ new Map(),
4101
4153
  onResultFeedbackSaved
4102
4154
  }) {
4103
- const userMessages = messages.filter((message) => isUserMessage(message));
4155
+ const visibleRootMessages = messages.filter((message) => {
4156
+ if ((message.loop_name ?? "root") !== "root") return false;
4157
+ if (isHiddenInternalMessage(message)) return false;
4158
+ if (message.kind === "context") return false;
4159
+ return message.role !== "tool" || getPlanningDividerKind(message) !== null;
4160
+ });
4161
+ const userMessages = visibleRootMessages.filter((message) => isUserMessage(message));
4104
4162
  const latestUserMessage = userMessages.at(-1);
4163
+ const latestPromptText = latestUserMessage ? (typeof latestUserMessage.content === "string" ? latestUserMessage.content : latestUserMessage.content.filter((part) => part.type === "text").map((part) => part.text).join("")).replace(/\s+/g, " ").trim() : "";
4164
+ const latestPromptPreview = latestPromptText.length > 80 ? `${latestPromptText.slice(0, 80)}\u2026` : latestPromptText;
4105
4165
  const shouldPinLatestUser = latestUserMessage != null && (latestUserMessage.entry_id == null || latestUserMessage.entry_id.startsWith("local-user-"));
4106
4166
  const renderBlocks = useMemo7(() => {
4107
4167
  const visible = messages.filter((message) => {
@@ -4186,78 +4246,90 @@ function MessageList({
4186
4246
  initial: "instant",
4187
4247
  resize: "instant",
4188
4248
  children: [
4189
- /* @__PURE__ */ jsx17(StickToBottom.Content, { className: "blade-chat-messages-scroll", children: /* @__PURE__ */ jsx17("div", { className: "blade-chat-messages-content mx-auto max-w-[748px]", children: /* @__PURE__ */ jsxs15("div", { className: "flex min-w-0 flex-col", children: [
4190
- renderBlocks.length === 0 ? emptyState ?? /* @__PURE__ */ jsxs15("div", { className: "blade-chat-empty", children: [
4191
- /* @__PURE__ */ jsx17(MessageSquare, { size: 40, strokeWidth: 1.5 }),
4192
- /* @__PURE__ */ jsx17("span", { className: "text-base font-medium", children: "\u5F00\u59CB\u5BF9\u8BDD" }),
4193
- /* @__PURE__ */ jsx17("span", { className: "text-sm opacity-60", children: "\u5728\u4E0B\u65B9\u8F93\u5165\u6D88\u606F\u5F00\u59CB\u804A\u5929" })
4194
- ] }) : renderBlocks.map((block) => {
4195
- if (block.type === "message") {
4196
- return /* @__PURE__ */ jsx17("div", { "data-entry-id": block.message.entry_id, children: isUserMessage(block.message) ? /* @__PURE__ */ jsx17(UserMessageBubble, { message: block.message }) : isErrorMessage(block.message) ? /* @__PURE__ */ jsx17(ErrorMessageBlock, { message: block.message }) : null }, block.key);
4197
- }
4198
- if (block.type === "assistant_turn") {
4199
- const blockFeedback = block.messages.map(
4200
- (message) => message.entry_id ? resultFeedbackByEntry.get(message.entry_id) : void 0
4201
- ).find((feedback) => feedback != null);
4202
- const hasActiveFollowup = Boolean(
4203
- postChatFollowup && block.messages.some(
4204
- (message) => message.entry_id === postChatFollowup.assistant_entry_id
4205
- )
4206
- );
4207
- return /* @__PURE__ */ jsx17("div", { "data-entry-id": block.messages[0]?.entry_id, children: /* @__PURE__ */ jsxs15(
4208
- RenderErrorBoundary,
4209
- {
4210
- label: "\u52A9\u624B\u6D88\u606F",
4211
- details: block.key,
4212
- resetKey: getMessageResetSignature(block.messages),
4213
- children: [
4214
- /* @__PURE__ */ jsx17(
4215
- AssistantTurnBlock,
4216
- {
4217
- messages: block.messages,
4218
- isStreaming: block.isStreaming,
4219
- askAnswers,
4220
- onAnswer,
4221
- sessionStatus,
4222
- toolCallRenderer,
4223
- hidePlanUpdateTools,
4224
- sessionId
4225
- }
4226
- ),
4227
- blockFeedback && !hasActiveFollowup ? /* @__PURE__ */ jsx17(HistoricalResultFeedback, { feedback: blockFeedback }) : null,
4228
- hasActiveFollowup && postChatFollowup ? /* @__PURE__ */ jsx17(
4229
- PostChatFollowupBlock,
4230
- {
4231
- followup: postChatFollowup,
4232
- sessionId,
4233
- onSuggestion,
4234
- isViewer,
4235
- onInteraction: onFollowupInteraction,
4236
- savedFeedback: blockFeedback,
4237
- onFeedbackSaved: onResultFeedbackSaved
4238
- }
4239
- ) : null
4240
- ]
4241
- }
4242
- ) }, block.key);
4243
- }
4244
- if (block.type === "compaction") {
4245
- return /* @__PURE__ */ jsxs15(
4246
- "div",
4247
- {
4248
- className: "flex items-center gap-2 text-xs text-[hsl(var(--muted-foreground))]",
4249
- children: [
4250
- /* @__PURE__ */ jsx17(Layers, { size: 12 }),
4251
- /* @__PURE__ */ jsx17("span", { children: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29" })
4252
- ]
4253
- },
4254
- block.key
4255
- );
4249
+ /* @__PURE__ */ jsx17(StickToBottom.Content, { className: "blade-chat-messages-scroll", children: /* @__PURE__ */ jsxs15("div", { className: "blade-chat-messages-content mx-auto max-w-[748px]", children: [
4250
+ isStreaming && latestUserMessage && latestPromptPreview ? /* @__PURE__ */ jsx17(
4251
+ "button",
4252
+ {
4253
+ type: "button",
4254
+ className: "sticky top-0 z-20 mb-4 w-full truncate rounded-2xl border border-[hsl(var(--primary)/0.2)] bg-[hsl(var(--background)/0.92)] px-4 py-3 text-left text-sm font-medium shadow-sm backdrop-blur",
4255
+ onClick: () => Array.from(document.querySelectorAll("[data-entry-id]")).find((el) => el.dataset.entryId === latestUserMessage.entry_id)?.scrollIntoView({ behavior: "smooth", block: "start" }),
4256
+ "aria-label": "\u8DF3\u8F6C\u5230\u5F53\u524D\u63D0\u95EE",
4257
+ children: latestPromptPreview
4256
4258
  }
4257
- return /* @__PURE__ */ jsx17(PlanningDivider, { kind: block.kind }, block.key);
4258
- }),
4259
- sessionStatus === "interrupted" && !isStreaming ? /* @__PURE__ */ jsx17("div", { className: "flex", children: /* @__PURE__ */ jsx17("div", { className: "rounded-full border border-amber-500/30 bg-amber-500/10 px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.12em] text-amber-300", children: "\u5DF2\u4E2D\u65AD" }) }) : null
4260
- ] }) }) }),
4259
+ ) : null,
4260
+ /* @__PURE__ */ jsxs15("div", { className: "flex min-w-0 flex-col", children: [
4261
+ renderBlocks.length === 0 ? emptyState ?? /* @__PURE__ */ jsxs15("div", { className: "blade-chat-empty", children: [
4262
+ /* @__PURE__ */ jsx17(MessageSquare, { size: 40, strokeWidth: 1.5 }),
4263
+ /* @__PURE__ */ jsx17("span", { className: "text-base font-medium", children: "\u5F00\u59CB\u5BF9\u8BDD" }),
4264
+ /* @__PURE__ */ jsx17("span", { className: "text-sm opacity-60", children: "\u5728\u4E0B\u65B9\u8F93\u5165\u6D88\u606F\u5F00\u59CB\u804A\u5929" })
4265
+ ] }) : renderBlocks.map((block) => {
4266
+ if (block.type === "message") {
4267
+ return /* @__PURE__ */ jsx17("div", { "data-entry-id": block.message.entry_id, children: isUserMessage(block.message) ? /* @__PURE__ */ jsx17(UserMessageBubble, { message: block.message }) : isErrorMessage(block.message) ? /* @__PURE__ */ jsx17(ErrorMessageBlock, { message: block.message }) : null }, block.key);
4268
+ }
4269
+ if (block.type === "assistant_turn") {
4270
+ const blockFeedback = block.messages.map(
4271
+ (message) => message.entry_id ? resultFeedbackByEntry.get(message.entry_id) : void 0
4272
+ ).find((feedback) => feedback != null);
4273
+ const hasActiveFollowup = Boolean(
4274
+ postChatFollowup && block.messages.some(
4275
+ (message) => message.entry_id === postChatFollowup.assistant_entry_id
4276
+ )
4277
+ );
4278
+ return /* @__PURE__ */ jsx17("div", { "data-entry-id": block.messages[0]?.entry_id, children: /* @__PURE__ */ jsxs15(
4279
+ RenderErrorBoundary,
4280
+ {
4281
+ label: "\u52A9\u624B\u6D88\u606F",
4282
+ details: block.key,
4283
+ resetKey: getMessageResetSignature(block.messages),
4284
+ children: [
4285
+ /* @__PURE__ */ jsx17(
4286
+ AssistantTurnBlock,
4287
+ {
4288
+ messages: block.messages,
4289
+ isStreaming: block.isStreaming,
4290
+ askAnswers,
4291
+ onAnswer,
4292
+ sessionStatus,
4293
+ toolCallRenderer,
4294
+ hidePlanUpdateTools,
4295
+ sessionId
4296
+ }
4297
+ ),
4298
+ blockFeedback && !hasActiveFollowup ? /* @__PURE__ */ jsx17(HistoricalResultFeedback, { feedback: blockFeedback }) : null,
4299
+ hasActiveFollowup && postChatFollowup ? /* @__PURE__ */ jsx17(
4300
+ PostChatFollowupBlock,
4301
+ {
4302
+ followup: postChatFollowup,
4303
+ sessionId,
4304
+ onSuggestion,
4305
+ isViewer,
4306
+ onInteraction: onFollowupInteraction,
4307
+ savedFeedback: blockFeedback,
4308
+ onFeedbackSaved: onResultFeedbackSaved
4309
+ }
4310
+ ) : null
4311
+ ]
4312
+ }
4313
+ ) }, block.key);
4314
+ }
4315
+ if (block.type === "compaction") {
4316
+ return /* @__PURE__ */ jsxs15(
4317
+ "div",
4318
+ {
4319
+ className: "flex items-center gap-2 text-xs text-[hsl(var(--muted-foreground))]",
4320
+ children: [
4321
+ /* @__PURE__ */ jsx17(Layers, { size: 12 }),
4322
+ /* @__PURE__ */ jsx17("span", { children: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29" })
4323
+ ]
4324
+ },
4325
+ block.key
4326
+ );
4327
+ }
4328
+ return /* @__PURE__ */ jsx17(PlanningDivider, { kind: block.kind }, block.key);
4329
+ }),
4330
+ sessionStatus === "interrupted" && !isStreaming ? /* @__PURE__ */ jsx17("div", { className: "flex", children: /* @__PURE__ */ jsx17("div", { className: "rounded-full border border-amber-500/30 bg-amber-500/10 px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.12em] text-amber-300", children: "\u5DF2\u4E2D\u65AD" }) }) : null
4331
+ ] })
4332
+ ] }) }),
4261
4333
  /* @__PURE__ */ jsx17(
4262
4334
  PinLatestUserMessage,
4263
4335
  {
@@ -4280,8 +4352,8 @@ function PinLatestUserMessage({
4280
4352
  targetKey
4281
4353
  }) {
4282
4354
  const { contentRef, scrollRef, scrollToBottom, stopScroll } = useStickToBottomContext();
4283
- const previousCountRef = useRef12(userMessageCount);
4284
- const spacerHeightRef = useRef12(0);
4355
+ const previousCountRef = useRef13(userMessageCount);
4356
+ const spacerHeightRef = useRef13(0);
4285
4357
  const getScrollElement = useCallback7(() => scrollRef.current, [scrollRef]);
4286
4358
  const getContentElement = useCallback7(() => contentRef.current, [contentRef]);
4287
4359
  const getTargetElement = useCallback7(() => {
@@ -4310,7 +4382,7 @@ function PinLatestUserMessage({
4310
4382
  stopAutoScroll: stopScroll,
4311
4383
  scrollToBottom
4312
4384
  });
4313
- useEffect11(() => {
4385
+ useEffect12(() => {
4314
4386
  if (userMessageCount > previousCountRef.current && !shouldPinLatestUser) {
4315
4387
  scrollToBottom("instant");
4316
4388
  }
@@ -4320,9 +4392,9 @@ function PinLatestUserMessage({
4320
4392
  }
4321
4393
  function ScrollToBottomButton() {
4322
4394
  const { isAtBottom, scrollToBottom } = useStickToBottomContext();
4323
- const [visible, setVisible] = useState13(false);
4324
- const hideTimerRef = useRef12(null);
4325
- useEffect11(() => {
4395
+ const [visible, setVisible] = useState14(false);
4396
+ const hideTimerRef = useRef13(null);
4397
+ useEffect12(() => {
4326
4398
  if (isAtBottom) {
4327
4399
  if (!hideTimerRef.current) {
4328
4400
  hideTimerRef.current = setTimeout(() => {
@@ -4399,6 +4471,7 @@ function ChatSurface({
4399
4471
  onInputChange,
4400
4472
  onSuggestion,
4401
4473
  onSend,
4474
+ onAppend,
4402
4475
  onStop,
4403
4476
  sessionStatus,
4404
4477
  askAnswers,
@@ -4467,10 +4540,12 @@ function ChatSurface({
4467
4540
  value: inputText,
4468
4541
  onValueChange: onInputChange,
4469
4542
  onSend,
4543
+ onAppend,
4470
4544
  onStop,
4471
4545
  isStreaming,
4472
4546
  isStopping,
4473
4547
  placeholder,
4548
+ queueKey: sessionId,
4474
4549
  className: classNames?.chatInput
4475
4550
  }
4476
4551
  ),
@@ -4481,13 +4556,13 @@ function ChatSurface({
4481
4556
  }
4482
4557
 
4483
4558
  // src/components/AgentChat.tsx
4484
- import { Fragment as Fragment3, jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
4559
+ import { Fragment as Fragment4, jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
4485
4560
  function isUnauthorizedError(error) {
4486
4561
  return error instanceof BladeApiError && error.status === 401;
4487
4562
  }
4488
4563
  function LoginCard({ client, onLoggedIn }) {
4489
- const [loggingIn, setLoggingIn] = useState14(false);
4490
- const [loginError, setLoginError] = useState14(null);
4564
+ const [loggingIn, setLoggingIn] = useState15(false);
4565
+ const [loginError, setLoginError] = useState15(null);
4491
4566
  const handleLogin = async () => {
4492
4567
  setLoggingIn(true);
4493
4568
  setLoginError(null);
@@ -4519,8 +4594,8 @@ function LoginCard({ client, onLoggedIn }) {
4519
4594
  }
4520
4595
  function AgentChat(props) {
4521
4596
  const client = useBladeClient();
4522
- const [attempt, setAttempt] = useState14(0);
4523
- const [needLogin, setNeedLogin] = useState14(() => !client.hasToken());
4597
+ const [attempt, setAttempt] = useState15(0);
4598
+ const [needLogin, setNeedLogin] = useState15(() => !client.hasToken());
4524
4599
  if (needLogin) {
4525
4600
  return /* @__PURE__ */ jsx19(
4526
4601
  "div",
@@ -4557,7 +4632,7 @@ function ChatSessionView({
4557
4632
  onUnauthorized
4558
4633
  }) {
4559
4634
  const client = useBladeClient();
4560
- const [planRevealRevisions, setPlanRevealRevisions] = useState14(
4635
+ const [planRevealRevisions, setPlanRevealRevisions] = useState15(
4561
4636
  () => /* @__PURE__ */ new Map()
4562
4637
  );
4563
4638
  const handleSessionConnected = useCallback8((connectedSession) => {
@@ -4578,12 +4653,12 @@ function ChatSessionView({
4578
4653
  onSessionConnected: handleSessionConnected
4579
4654
  });
4580
4655
  const replay = useReplay(session);
4581
- const [stopRequested, setStopRequested] = useState14(false);
4582
- const [inputText, setInputText] = useState14("");
4583
- const [resultFeedback, setResultFeedback] = useState14([]);
4656
+ const [stopRequested, setStopRequested] = useState15(false);
4657
+ const [inputText, setInputText] = useState15("");
4658
+ const [resultFeedback, setResultFeedback] = useState15([]);
4584
4659
  const resolvedSessionId = session?.sessionId;
4585
4660
  const isViewer = state?.viewerRole === "viewer";
4586
- useEffect12(() => {
4661
+ useEffect13(() => {
4587
4662
  setResultFeedback([]);
4588
4663
  if (!resolvedSessionId || isViewer) return;
4589
4664
  let cancelled = false;
@@ -4618,12 +4693,12 @@ function ChatSessionView({
4618
4693
  saved
4619
4694
  ]);
4620
4695
  }, []);
4621
- useEffect12(() => {
4696
+ useEffect13(() => {
4622
4697
  if (session) {
4623
4698
  onSessionReady?.(session);
4624
4699
  }
4625
4700
  }, [session, onSessionReady]);
4626
- useEffect12(() => {
4701
+ useEffect13(() => {
4627
4702
  if (!session) return;
4628
4703
  const offAttach = session.on("attachRequested", ({ label, content }) => {
4629
4704
  setInputText((prev) => `${prev ? `${prev}
@@ -4639,12 +4714,12 @@ ${content}`);
4639
4714
  offInsert();
4640
4715
  };
4641
4716
  }, [session]);
4642
- useEffect12(() => {
4717
+ useEffect13(() => {
4643
4718
  if (isUnauthorizedError(error)) {
4644
4719
  onUnauthorized();
4645
4720
  }
4646
4721
  }, [error, onUnauthorized]);
4647
- useEffect12(() => {
4722
+ useEffect13(() => {
4648
4723
  if (!session || !commands) return;
4649
4724
  const unsubscribes = Object.entries(commands).map(
4650
4725
  ([action, handler]) => session.onCommand(action, (payload) => handler(payload))
@@ -4676,7 +4751,7 @@ ${content}`);
4676
4751
  slots,
4677
4752
  placeholder,
4678
4753
  connection: state?.connection ?? "connecting",
4679
- banner: /* @__PURE__ */ jsxs17(Fragment3, { children: [
4754
+ banner: /* @__PURE__ */ jsxs17(Fragment4, { children: [
4680
4755
  /* @__PURE__ */ jsx19(
4681
4756
  ReplayBar,
4682
4757
  {
@@ -4702,6 +4777,9 @@ ${content}`);
4702
4777
  onInputChange: setInputText,
4703
4778
  onSuggestion: setInputText,
4704
4779
  onSend: handleSend,
4780
+ onAppend: (text) => {
4781
+ void session?.send(text, { mode: state?.mode ?? void 0 });
4782
+ },
4705
4783
  onStop: handleStop,
4706
4784
  sessionStatus: state?.status ?? void 0,
4707
4785
  askAnswers: state?.askAnswers,
@@ -4718,10 +4796,10 @@ ${content}`);
4718
4796
  }
4719
4797
 
4720
4798
  // src/components/LlmChat.tsx
4721
- import { useEffect as useEffect13, useMemo as useMemo9, useState as useState16 } from "react";
4799
+ import { useEffect as useEffect14, useMemo as useMemo9, useState as useState17 } from "react";
4722
4800
 
4723
4801
  // src/components/LlmAdvancedSettings.tsx
4724
- import { useState as useState15 } from "react";
4802
+ import { useState as useState16 } from "react";
4725
4803
  import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
4726
4804
  var FIELDS = [
4727
4805
  { id: "baseURL", label: "\u6A21\u578B\u670D\u52A1\u5730\u5740", placeholder: "http://\u5185\u7F51\u5730\u5740/v1" },
@@ -4768,8 +4846,8 @@ function writeOverride(settings, baseURL, override) {
4768
4846
  }
4769
4847
  function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
4770
4848
  const normalized = normalizeAdvanced(settings);
4771
- const [open, setOpen] = useState15(false);
4772
- const [draft, setDraft] = useState15(override);
4849
+ const [open, setOpen] = useState16(false);
4850
+ const [draft, setDraft] = useState16(override);
4773
4851
  if (!normalized) return null;
4774
4852
  const fields = FIELDS.filter((field) => normalized[field.id]);
4775
4853
  const dirty = Object.keys(override).length > 0;
@@ -4852,11 +4930,11 @@ function LlmChat({
4852
4930
  onOverrideChange,
4853
4931
  ...options
4854
4932
  }) {
4855
- const [override, setOverride] = useState16(() => readOverride(advanced, options.baseURL));
4933
+ const [override, setOverride] = useState17(() => readOverride(advanced, options.baseURL));
4856
4934
  const effective = { ...options, ...override };
4857
4935
  const { messages, isStreaming, error, send, stop, reset } = useLlmChat(effective);
4858
- const [inputText, setInputText] = useState16("");
4859
- const [stopRequested, setStopRequested] = useState16(false);
4936
+ const [inputText, setInputText] = useState17("");
4937
+ const [stopRequested, setStopRequested] = useState17(false);
4860
4938
  const handle = useMemo9(
4861
4939
  () => ({
4862
4940
  insertText: (text) => setInputText((prev) => prev ? `${prev}
@@ -4866,7 +4944,7 @@ ${text}` : text),
4866
4944
  }),
4867
4945
  [send, reset]
4868
4946
  );
4869
- useEffect13(() => {
4947
+ useEffect14(() => {
4870
4948
  onReady?.(handle);
4871
4949
  }, [handle, onReady]);
4872
4950
  return /* @__PURE__ */ jsx21(
@@ -5013,7 +5091,7 @@ function ContextGroupCard({ contexts, className }) {
5013
5091
  }
5014
5092
 
5015
5093
  // src/components/SessionMemoryToggle.tsx
5016
- import { useCallback as useCallback9, useEffect as useEffect14, useRef as useRef13, useState as useState17, useSyncExternalStore as useSyncExternalStore2 } from "react";
5094
+ import { useCallback as useCallback9, useEffect as useEffect15, useRef as useRef14, useState as useState18, useSyncExternalStore as useSyncExternalStore2 } from "react";
5017
5095
  import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
5018
5096
  var saveStates = /* @__PURE__ */ new WeakMap();
5019
5097
  function getSaveState(client, sessionId) {
@@ -5071,10 +5149,10 @@ function SessionMemoryToggle({
5071
5149
  getSaving,
5072
5150
  getSaving
5073
5151
  );
5074
- const [draftEnabled, setDraftEnabled] = useState17(enabled);
5075
- const activeSessionIdRef = useRef13(sessionId);
5152
+ const [draftEnabled, setDraftEnabled] = useState18(enabled);
5153
+ const activeSessionIdRef = useRef14(sessionId);
5076
5154
  activeSessionIdRef.current = sessionId;
5077
- useEffect14(() => {
5155
+ useEffect15(() => {
5078
5156
  setDraftEnabled(enabled);
5079
5157
  }, [enabled, sessionId]);
5080
5158
  const update = useCallback9(