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

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 useEffect12, 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 { 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,31 @@ 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
+ void queueKey;
1639
+ const canSend = trimmed.length > 0 && (!isStreaming || sendMode === "queue");
1635
1640
  const handleSend = async () => {
1636
1641
  if (!canSend) return;
1642
+ if (isStreaming && sendMode === "queue") {
1643
+ if (onAppend) onAppend(trimmed);
1644
+ onValueChange("");
1645
+ return;
1646
+ }
1647
+ if (isStreaming && sendMode === "direct") {
1648
+ if (!onAppend) return;
1649
+ onAppend(trimmed);
1650
+ onValueChange("");
1651
+ return;
1652
+ }
1637
1653
  const accepted = await onSend(trimmed);
1638
1654
  if (!accepted) return;
1639
1655
  onValueChange("");
@@ -1649,57 +1665,56 @@ function ChatInput({
1649
1665
  void handleSend();
1650
1666
  }
1651
1667
  };
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
- ] }) });
1668
+ return /* @__PURE__ */ jsxs4("div", { className: cn("blade-chat-input border-t border-[hsl(var(--border))] py-3", className), children: [
1669
+ /* @__PURE__ */ jsx5("div", { className: "mx-auto mb-2 flex max-w-[748px] items-center justify-between px-1 text-xs text-[hsl(var(--muted-foreground))]", children: /* @__PURE__ */ jsxs4("fieldset", { className: "flex items-center gap-1 rounded-md border border-[hsl(var(--border))] p-0.5", children: [
1670
+ /* @__PURE__ */ jsx5("legend", { className: "sr-only", children: "\u53D1\u9001\u65B9\u5F0F" }),
1671
+ /* @__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" }),
1672
+ /* @__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" })
1673
+ ] }) }),
1674
+ /* @__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: [
1675
+ /* @__PURE__ */ jsx5(
1676
+ "textarea",
1677
+ {
1678
+ value,
1679
+ onChange: (event) => onValueChange(event.target.value),
1680
+ onKeyDown: handleKeyDown,
1681
+ onInput: (event) => {
1682
+ const el = event.currentTarget;
1683
+ el.style.height = "auto";
1684
+ el.style.height = `${Math.min(el.scrollHeight, 192)}px`;
1685
+ },
1686
+ rows: 1,
1687
+ placeholder,
1688
+ "aria-label": "\u804A\u5929\u8F93\u5165",
1689
+ 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)]"
1690
+ }
1691
+ ),
1692
+ isStreaming ? /* @__PURE__ */ jsxs4(Fragment, { children: [
1693
+ 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,
1694
+ /* @__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" }) })
1695
+ ] }) : /* @__PURE__ */ jsx5(
1696
+ "button",
1697
+ {
1698
+ type: "button",
1699
+ onClick: handleSend,
1700
+ disabled: !canSend,
1701
+ "aria-label": "\u53D1\u9001\u6D88\u606F",
1702
+ title: "\u53D1\u9001\u6D88\u606F",
1703
+ 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",
1704
+ children: /* @__PURE__ */ jsx5(ArrowUp, { size: 15 })
1705
+ }
1706
+ )
1707
+ ] })
1708
+ ] });
1694
1709
  }
1695
1710
 
1696
1711
  // src/components/ConnectionBanner.tsx
1697
- import { useEffect as useEffect5, useRef as useRef5, useState as useState5 } from "react";
1712
+ import { useEffect as useEffect5, useRef as useRef5, useState as useState6 } from "react";
1698
1713
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1699
1714
  var CONNECTION_NOTICE_DELAY_MS = 3e3;
1700
1715
  var CONNECTION_ERROR_DELAY_MS = 15e3;
1701
1716
  function useConnectionNoticePhase(connected) {
1702
- const [phase, setPhase] = useState5("hidden");
1717
+ const [phase, setPhase] = useState6("hidden");
1703
1718
  const connectedRef = useRef5(connected);
1704
1719
  const timersRef = useRef5([]);
1705
1720
  connectedRef.current = connected;
@@ -1770,10 +1785,10 @@ function ConnectionBanner({ connection, className }) {
1770
1785
 
1771
1786
  // src/components/MessageList.tsx
1772
1787
  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";
1788
+ import { useCallback as useCallback7, useEffect as useEffect11, useMemo as useMemo7, useRef as useRef12, useState as useState14 } from "react";
1774
1789
 
1775
1790
  // ../../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";
1791
+ import { useCallback as useCallback4, useMemo as useMemo3, useRef as useRef6, useState as useState7 } from "react";
1777
1792
  var DEFAULT_SPRING_ANIMATION = {
1778
1793
  /**
1779
1794
  * A value from 0 to 1, on how much to damp the animation.
@@ -1810,9 +1825,9 @@ globalThis.document?.addEventListener("click", () => {
1810
1825
  mouseDown = false;
1811
1826
  });
1812
1827
  var useStickToBottom = (options = {}) => {
1813
- const [escapedFromLock, updateEscapedFromLock] = useState6(false);
1814
- const [isAtBottom, updateIsAtBottom] = useState6(options.initial !== false);
1815
- const [isNearBottom, setIsNearBottom] = useState6(false);
1828
+ const [escapedFromLock, updateEscapedFromLock] = useState7(false);
1829
+ const [isAtBottom, updateIsAtBottom] = useState7(options.initial !== false);
1830
+ const [isNearBottom, setIsNearBottom] = useState7(false);
1816
1831
  const optionsRef = useRef6(null);
1817
1832
  optionsRef.current = options;
1818
1833
  const isSelecting = useCallback4(() => {
@@ -2203,10 +2218,10 @@ import {
2203
2218
  getTextContent,
2204
2219
  normalizeMessageContent
2205
2220
  } from "@blade-hq/agent-client";
2206
- import { useEffect as useEffect9, useRef as useRef10, useState as useState11 } from "react";
2221
+ import { useEffect as useEffect9, useRef as useRef10, useState as useState12 } from "react";
2207
2222
 
2208
2223
  // src/components/AgentLoopBlock.tsx
2209
- import { useState as useState7 } from "react";
2224
+ import { useState as useState8 } from "react";
2210
2225
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2211
2226
  function parseAgentDescription(argumentsJson) {
2212
2227
  try {
@@ -2217,7 +2232,7 @@ function parseAgentDescription(argumentsJson) {
2217
2232
  }
2218
2233
  }
2219
2234
  function AgentLoopBlock({ toolCall }) {
2220
- const [expanded, setExpanded] = useState7(false);
2235
+ const [expanded, setExpanded] = useState8(false);
2221
2236
  const description = parseAgentDescription(toolCall.arguments);
2222
2237
  const running = toolCall.status === "pending" || toolCall.status === "awaiting_answer";
2223
2238
  const failed = toolCall.status === "error" || toolCall.status === "cancelled";
@@ -2271,7 +2286,7 @@ import {
2271
2286
  useEffect as useEffect7,
2272
2287
  useMemo as useMemo5,
2273
2288
  useRef as useRef8,
2274
- useState as useState8
2289
+ useState as useState9
2275
2290
  } from "react";
2276
2291
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2277
2292
  var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
@@ -2289,8 +2304,8 @@ function normalizeAdjacentUrlFormatting(value) {
2289
2304
  }
2290
2305
  function CodeBlockPre({ children, node: _node, ...props }) {
2291
2306
  const preRef = useRef8(null);
2292
- const [copied, setCopied] = useState8(false);
2293
- const [language, setLanguage] = useState8("");
2307
+ const [copied, setCopied] = useState9(false);
2308
+ const [language, setLanguage] = useState9("");
2294
2309
  useEffect7(() => {
2295
2310
  const codeEl = preRef.current?.querySelector("code");
2296
2311
  setLanguage(codeEl?.className.match(/language-(\S+)/)?.[1] ?? "");
@@ -2362,10 +2377,10 @@ function Shimmer({ children = "\u6B63\u5728\u601D\u8003...", className }) {
2362
2377
  }
2363
2378
 
2364
2379
  // src/components/ToolCallBlock.tsx
2365
- import { useState as useState10 } from "react";
2380
+ import { useState as useState11 } from "react";
2366
2381
 
2367
2382
  // src/components/AskUserQuestionBlock.tsx
2368
- import { useEffect as useEffect8, useMemo as useMemo6, useRef as useRef9, useState as useState9 } from "react";
2383
+ import { useEffect as useEffect8, useMemo as useMemo6, useRef as useRef9, useState as useState10 } from "react";
2369
2384
  import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
2370
2385
  var CUSTOM_TEXTAREA_MAX_HEIGHT = 160;
2371
2386
  function resizeCustomTextarea(textarea) {
@@ -2404,11 +2419,11 @@ function AskUserQuestionBlock({
2404
2419
  answerData,
2405
2420
  onAnswer
2406
2421
  }) {
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);
2422
+ const [selections, setSelections] = useState10(/* @__PURE__ */ new Map());
2423
+ const [customTexts, setCustomTexts] = useState10(/* @__PURE__ */ new Map());
2424
+ const [usingCustom, setUsingCustom] = useState10(/* @__PURE__ */ new Set());
2425
+ const [note, setNote] = useState10("");
2426
+ const [submitted, setSubmitted] = useState10(false);
2412
2427
  useEffect8(() => {
2413
2428
  if (sessionStatus === "failed" || sessionStatus === "interrupted") {
2414
2429
  setSubmitted(false);
@@ -2798,7 +2813,7 @@ function normalizeOptionItem(value) {
2798
2813
  }
2799
2814
 
2800
2815
  // src/components/ToolCallBlock.tsx
2801
- import { Fragment, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2816
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2802
2817
  function resolveAskQuestionState({
2803
2818
  toolStatus,
2804
2819
  hasAnswerData,
@@ -2820,12 +2835,12 @@ function ToolCallBlock({
2820
2835
  isActiveQuestion,
2821
2836
  renderer
2822
2837
  }) {
2823
- const [expanded, setExpanded] = useState10(false);
2838
+ const [expanded, setExpanded] = useState11(false);
2824
2839
  const normalizedName = formatToolName(toolCall.name);
2825
2840
  if (renderer) {
2826
2841
  const custom = renderer(toolCall);
2827
2842
  if (custom !== null && custom !== void 0) {
2828
- return /* @__PURE__ */ jsx11(Fragment, { children: custom });
2843
+ return /* @__PURE__ */ jsx11(Fragment2, { children: custom });
2829
2844
  }
2830
2845
  }
2831
2846
  if (normalizedName === "AskUserQuestion") {
@@ -2908,7 +2923,7 @@ function ToolCallBlock({
2908
2923
  /* @__PURE__ */ jsx11("div", { className: "mb-3 font-mono text-[11px] text-[hsl(var(--foreground))]", children: normalizedName }),
2909
2924
  /* @__PURE__ */ jsx11("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u53C2\u6570" }),
2910
2925
  /* @__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: [
2926
+ toolCall.result != null && /* @__PURE__ */ jsxs9(Fragment2, { children: [
2912
2927
  /* @__PURE__ */ jsx11("div", { className: "mb-1 mt-3 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u7ED3\u679C" }),
2913
2928
  /* @__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
2929
  ] })
@@ -2930,7 +2945,7 @@ function buildAskUserPayload(argumentsJson) {
2930
2945
  // src/components/AssistantTurnBlock.tsx
2931
2946
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
2932
2947
  function ThinkingBlock({ reasoning, isStreaming }) {
2933
- const [open, setOpen] = useState11(false);
2948
+ const [open, setOpen] = useState12(false);
2934
2949
  if (!isStreaming) return null;
2935
2950
  return /* @__PURE__ */ jsxs10("div", { className: "blade-chat-thinking text-xs", children: [
2936
2951
  /* @__PURE__ */ jsxs10(
@@ -3200,7 +3215,7 @@ function AssistantTurnBlock({
3200
3215
  )
3201
3216
  );
3202
3217
  const activeQuestionId = questionToolCalls.filter((toolCall) => toolCall.status === "pending").at(-1)?.id;
3203
- const [displayMode, setDisplayMode] = useState11(
3218
+ const [displayMode, setDisplayMode] = useState12(
3204
3219
  () => isStreaming || hasActionableToolCall ? "detail" : "compact"
3205
3220
  );
3206
3221
  const userSelectedDisplayModeRef = useRef10(false);
@@ -3213,7 +3228,7 @@ function AssistantTurnBlock({
3213
3228
  }, [hasActionableToolCall, isStreaming]);
3214
3229
  const effectiveMode = resolveTurnDisplayMode({ isStreaming, displayMode });
3215
3230
  const executionDurationMs = getExecutionDurationMs({ messages, isStreaming });
3216
- const [clock, setClock] = useState11(() => Date.now());
3231
+ const [clock, setClock] = useState12(() => Date.now());
3217
3232
  const hasLiveStartTime = messages.some(
3218
3233
  (message) => message.timestamp != null && Number.isFinite(Date.parse(message.timestamp))
3219
3234
  );
@@ -3392,7 +3407,7 @@ function collectMemoryRefs(messages) {
3392
3407
  return [...refs.values()];
3393
3408
  }
3394
3409
  function MemoryRefsHint({ refs }) {
3395
- const [expanded, setExpanded] = useState11(false);
3410
+ const [expanded, setExpanded] = useState12(false);
3396
3411
  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
3412
  return /* @__PURE__ */ jsxs10("div", { className: "blade-chat-memory-refs ml-1 w-full max-w-[680px]", children: [
3398
3413
  /* @__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 +3542,8 @@ var RenderErrorBoundary = class extends Component {
3527
3542
  };
3528
3543
 
3529
3544
  // 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";
3545
+ import { useCallback as useCallback6, useEffect as useEffect10, useRef as useRef11, useState as useState13 } from "react";
3546
+ import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
3532
3547
  function emitInteraction(callback, event) {
3533
3548
  try {
3534
3549
  callback?.(event);
@@ -3547,7 +3562,7 @@ function ArtifactCard({
3547
3562
  onArtifactOpened
3548
3563
  }) {
3549
3564
  const client = useBladeClient();
3550
- const [downloading, setDownloading] = useState12(false);
3565
+ const [downloading, setDownloading] = useState13(false);
3551
3566
  const name = artifact.label || basename(artifact.target);
3552
3567
  if (artifact.kind === "link") {
3553
3568
  return /* @__PURE__ */ jsxs12(
@@ -3653,11 +3668,11 @@ function ResultFeedback({
3653
3668
  onFeedbackSaved
3654
3669
  }) {
3655
3670
  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);
3671
+ const [saved, setSaved] = useState13(savedFeedback ?? null);
3672
+ const [helpful, setHelpful] = useState13(savedFeedback?.helpful ?? null);
3673
+ const [reason, setReason] = useState13(savedFeedback?.reason ?? null);
3674
+ const [saving, setSaving] = useState13(false);
3675
+ const [saveError, setSaveError] = useState13(false);
3661
3676
  const reportedShown = useRef11(false);
3662
3677
  const latestChoice = useRef11(null);
3663
3678
  const eligible = followup.feedback_eligible === true && Boolean(sessionId) && !isViewer;
@@ -3782,7 +3797,7 @@ function PostChatFollowupBlock({
3782
3797
  savedFeedback,
3783
3798
  onFeedbackSaved
3784
3799
  }) {
3785
- const [expanded, setExpanded] = useState12(false);
3800
+ const [expanded, setExpanded] = useState13(false);
3786
3801
  const adopted = useRef11(/* @__PURE__ */ new Set());
3787
3802
  const reportedSuggestions = useRef11(false);
3788
3803
  const reportedArtifacts = useRef11(/* @__PURE__ */ new Set());
@@ -3842,7 +3857,7 @@ function PostChatFollowupBlock({
3842
3857
  "\u672C\u8F6E\u5C0F\u7ED3"
3843
3858
  ] }),
3844
3859
  followup.recaption ? /* @__PURE__ */ jsx14("p", { className: "text-[13px] leading-5", children: followup.recaption }) : null,
3845
- artifacts.length > 0 ? /* @__PURE__ */ jsxs12(Fragment2, { children: [
3860
+ artifacts.length > 0 ? /* @__PURE__ */ jsxs12(Fragment3, { children: [
3846
3861
  /* @__PURE__ */ jsx14("div", { className: "grid max-w-full grid-cols-3 gap-1.5", children: visibleArtifacts.map((artifact, artifactIndex) => /* @__PURE__ */ jsx14(
3847
3862
  ArtifactCard,
3848
3863
  {
@@ -4098,10 +4113,19 @@ function MessageList({
4098
4113
  isViewer = false,
4099
4114
  onFollowupInteraction,
4100
4115
  resultFeedbackByEntry = /* @__PURE__ */ new Map(),
4101
- onResultFeedbackSaved
4116
+ onResultFeedbackSaved,
4117
+ historyPaging
4102
4118
  }) {
4103
- const userMessages = messages.filter((message) => isUserMessage(message));
4119
+ const visibleRootMessages = messages.filter((message) => {
4120
+ if ((message.loop_name ?? "root") !== "root") return false;
4121
+ if (isHiddenInternalMessage(message)) return false;
4122
+ if (message.kind === "context") return false;
4123
+ return message.role !== "tool" || getPlanningDividerKind(message) !== null;
4124
+ });
4125
+ const userMessages = visibleRootMessages.filter((message) => isUserMessage(message));
4104
4126
  const latestUserMessage = userMessages.at(-1);
4127
+ 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() : "";
4128
+ const latestPromptPreview = latestPromptText.length > 80 ? `${latestPromptText.slice(0, 80)}\u2026` : latestPromptText;
4105
4129
  const shouldPinLatestUser = latestUserMessage != null && (latestUserMessage.entry_id == null || latestUserMessage.entry_id.startsWith("local-user-"));
4106
4130
  const renderBlocks = useMemo7(() => {
4107
4131
  const visible = messages.filter((message) => {
@@ -4186,78 +4210,91 @@ function MessageList({
4186
4210
  initial: "instant",
4187
4211
  resize: "instant",
4188
4212
  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
- );
4213
+ /* @__PURE__ */ jsx17(StickToBottom.Content, { className: "blade-chat-messages-scroll", children: /* @__PURE__ */ jsxs15("div", { className: "blade-chat-messages-content mx-auto max-w-[748px]", children: [
4214
+ historyPaging && (historyPaging.hasOlder || historyPaging.loading) ? /* @__PURE__ */ jsx17(LoadOlderSentinel, { ...historyPaging }) : null,
4215
+ isStreaming && latestUserMessage && latestPromptPreview ? /* @__PURE__ */ jsx17(
4216
+ "button",
4217
+ {
4218
+ type: "button",
4219
+ 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",
4220
+ onClick: () => Array.from(document.querySelectorAll("[data-entry-id]")).find((el) => el.dataset.entryId === latestUserMessage.entry_id)?.scrollIntoView({ behavior: "smooth", block: "start" }),
4221
+ "aria-label": "\u8DF3\u8F6C\u5230\u5F53\u524D\u63D0\u95EE",
4222
+ children: latestPromptPreview
4256
4223
  }
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
- ] }) }) }),
4224
+ ) : null,
4225
+ /* @__PURE__ */ jsxs15("div", { className: "flex min-w-0 flex-col", children: [
4226
+ renderBlocks.length === 0 ? emptyState ?? /* @__PURE__ */ jsxs15("div", { className: "blade-chat-empty", children: [
4227
+ /* @__PURE__ */ jsx17(MessageSquare, { size: 40, strokeWidth: 1.5 }),
4228
+ /* @__PURE__ */ jsx17("span", { className: "text-base font-medium", children: "\u5F00\u59CB\u5BF9\u8BDD" }),
4229
+ /* @__PURE__ */ jsx17("span", { className: "text-sm opacity-60", children: "\u5728\u4E0B\u65B9\u8F93\u5165\u6D88\u606F\u5F00\u59CB\u804A\u5929" })
4230
+ ] }) : renderBlocks.map((block) => {
4231
+ if (block.type === "message") {
4232
+ 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);
4233
+ }
4234
+ if (block.type === "assistant_turn") {
4235
+ const blockFeedback = block.messages.map(
4236
+ (message) => message.entry_id ? resultFeedbackByEntry.get(message.entry_id) : void 0
4237
+ ).find((feedback) => feedback != null);
4238
+ const hasActiveFollowup = Boolean(
4239
+ postChatFollowup && block.messages.some(
4240
+ (message) => message.entry_id === postChatFollowup.assistant_entry_id
4241
+ )
4242
+ );
4243
+ return /* @__PURE__ */ jsx17("div", { "data-entry-id": block.messages[0]?.entry_id, children: /* @__PURE__ */ jsxs15(
4244
+ RenderErrorBoundary,
4245
+ {
4246
+ label: "\u52A9\u624B\u6D88\u606F",
4247
+ details: block.key,
4248
+ resetKey: getMessageResetSignature(block.messages),
4249
+ children: [
4250
+ /* @__PURE__ */ jsx17(
4251
+ AssistantTurnBlock,
4252
+ {
4253
+ messages: block.messages,
4254
+ isStreaming: block.isStreaming,
4255
+ askAnswers,
4256
+ onAnswer,
4257
+ sessionStatus,
4258
+ toolCallRenderer,
4259
+ hidePlanUpdateTools,
4260
+ sessionId
4261
+ }
4262
+ ),
4263
+ blockFeedback && !hasActiveFollowup ? /* @__PURE__ */ jsx17(HistoricalResultFeedback, { feedback: blockFeedback }) : null,
4264
+ hasActiveFollowup && postChatFollowup ? /* @__PURE__ */ jsx17(
4265
+ PostChatFollowupBlock,
4266
+ {
4267
+ followup: postChatFollowup,
4268
+ sessionId,
4269
+ onSuggestion,
4270
+ isViewer,
4271
+ onInteraction: onFollowupInteraction,
4272
+ savedFeedback: blockFeedback,
4273
+ onFeedbackSaved: onResultFeedbackSaved
4274
+ }
4275
+ ) : null
4276
+ ]
4277
+ }
4278
+ ) }, block.key);
4279
+ }
4280
+ if (block.type === "compaction") {
4281
+ return /* @__PURE__ */ jsxs15(
4282
+ "div",
4283
+ {
4284
+ className: "flex items-center gap-2 text-xs text-[hsl(var(--muted-foreground))]",
4285
+ children: [
4286
+ /* @__PURE__ */ jsx17(Layers, { size: 12 }),
4287
+ /* @__PURE__ */ jsx17("span", { children: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29" })
4288
+ ]
4289
+ },
4290
+ block.key
4291
+ );
4292
+ }
4293
+ return /* @__PURE__ */ jsx17(PlanningDivider, { kind: block.kind }, block.key);
4294
+ }),
4295
+ 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
4296
+ ] })
4297
+ ] }) }),
4261
4298
  /* @__PURE__ */ jsx17(
4262
4299
  PinLatestUserMessage,
4263
4300
  {
@@ -4274,6 +4311,76 @@ function MessageList({
4274
4311
  )
4275
4312
  ] });
4276
4313
  }
4314
+ function LoadOlderSentinel({
4315
+ hasOlder,
4316
+ loading,
4317
+ loadOlder
4318
+ }) {
4319
+ const { contentRef, scrollRef } = useStickToBottomContext();
4320
+ const sentinelRef = useRef12(null);
4321
+ const stateRef = useRef12({ hasOlder, loading, loadOlder });
4322
+ stateRef.current = { hasOlder, loading, loadOlder };
4323
+ useEffect11(() => {
4324
+ const sentinel = sentinelRef.current;
4325
+ const scroller = scrollRef.current;
4326
+ if (!sentinel || !scroller || typeof IntersectionObserver === "undefined") return;
4327
+ let entered = false;
4328
+ let disposed = false;
4329
+ const firstVisible = () => {
4330
+ const top = scroller.getBoundingClientRect().top;
4331
+ for (const row of contentRef.current?.querySelectorAll("[data-entry-id]") ?? []) {
4332
+ const rect = row.getBoundingClientRect();
4333
+ if (rect.bottom >= top) return { id: row.dataset.entryId, offset: rect.top - top };
4334
+ }
4335
+ return null;
4336
+ };
4337
+ const load = async () => {
4338
+ if (disposed || !stateRef.current.hasOlder || stateRef.current.loading) return;
4339
+ const commitSnapshot = { anchor: null, height: scroller.scrollHeight };
4340
+ let advanced = false;
4341
+ try {
4342
+ advanced = await stateRef.current.loadOlder(() => {
4343
+ commitSnapshot.anchor = firstVisible();
4344
+ commitSnapshot.height = scroller.scrollHeight;
4345
+ });
4346
+ } catch {
4347
+ return;
4348
+ }
4349
+ if (disposed) return;
4350
+ await new Promise((resolve) => requestAnimationFrame(() => resolve()));
4351
+ if (disposed) return;
4352
+ const anchor = commitSnapshot.anchor;
4353
+ const heightBefore = commitSnapshot.height;
4354
+ const anchored = anchor?.id ? Array.from(contentRef.current?.querySelectorAll("[data-entry-id]") ?? []).find((row) => row.dataset.entryId === anchor.id) : null;
4355
+ if (anchored && anchor) {
4356
+ scroller.scrollTop += anchored.getBoundingClientRect().top - scroller.getBoundingClientRect().top - anchor.offset;
4357
+ } else {
4358
+ scroller.scrollTop += scroller.scrollHeight - heightBefore;
4359
+ }
4360
+ if (!disposed && advanced && stateRef.current.hasOlder && !stateRef.current.loading && scroller.scrollHeight <= scroller.clientHeight) {
4361
+ await load();
4362
+ }
4363
+ };
4364
+ const observer = new IntersectionObserver(
4365
+ ([entry]) => {
4366
+ if (!entry?.isIntersecting) {
4367
+ entered = false;
4368
+ return;
4369
+ }
4370
+ if (entered) return;
4371
+ entered = true;
4372
+ void load();
4373
+ },
4374
+ { root: scroller, rootMargin: "160px 0px 0px" }
4375
+ );
4376
+ observer.observe(sentinel);
4377
+ return () => {
4378
+ disposed = true;
4379
+ observer.disconnect();
4380
+ };
4381
+ }, [contentRef, scrollRef]);
4382
+ return /* @__PURE__ */ jsx17("div", { ref: sentinelRef, "data-history-sentinel": true, className: "flex h-6 items-center justify-center", children: loading ? /* @__PURE__ */ jsx17("span", { className: "text-xs text-[hsl(var(--muted-foreground))]", children: "\u6B63\u5728\u52A0\u8F7D\u66F4\u65E9\u6D88\u606F\u2026" }) : null });
4383
+ }
4277
4384
  function PinLatestUserMessage({
4278
4385
  userMessageCount,
4279
4386
  shouldPinLatestUser,
@@ -4320,7 +4427,7 @@ function PinLatestUserMessage({
4320
4427
  }
4321
4428
  function ScrollToBottomButton() {
4322
4429
  const { isAtBottom, scrollToBottom } = useStickToBottomContext();
4323
- const [visible, setVisible] = useState13(false);
4430
+ const [visible, setVisible] = useState14(false);
4324
4431
  const hideTimerRef = useRef12(null);
4325
4432
  useEffect11(() => {
4326
4433
  if (isAtBottom) {
@@ -4399,6 +4506,7 @@ function ChatSurface({
4399
4506
  onInputChange,
4400
4507
  onSuggestion,
4401
4508
  onSend,
4509
+ onAppend,
4402
4510
  onStop,
4403
4511
  sessionStatus,
4404
4512
  askAnswers,
@@ -4411,6 +4519,7 @@ function ChatSurface({
4411
4519
  beforeInput,
4412
4520
  showPlanUpdates = false,
4413
4521
  planRevealRevision = 0,
4522
+ historyPaging,
4414
4523
  banner
4415
4524
  }) {
4416
4525
  return /* @__PURE__ */ jsxs16(
@@ -4447,7 +4556,8 @@ function ChatSurface({
4447
4556
  isViewer,
4448
4557
  resultFeedbackByEntry,
4449
4558
  onResultFeedbackSaved,
4450
- onFollowupInteraction
4559
+ onFollowupInteraction,
4560
+ historyPaging
4451
4561
  }
4452
4562
  ),
4453
4563
  showPlanUpdates ? /* @__PURE__ */ jsx18(
@@ -4467,10 +4577,12 @@ function ChatSurface({
4467
4577
  value: inputText,
4468
4578
  onValueChange: onInputChange,
4469
4579
  onSend,
4580
+ onAppend,
4470
4581
  onStop,
4471
4582
  isStreaming,
4472
4583
  isStopping,
4473
4584
  placeholder,
4585
+ queueKey: sessionId,
4474
4586
  className: classNames?.chatInput
4475
4587
  }
4476
4588
  ),
@@ -4481,13 +4593,13 @@ function ChatSurface({
4481
4593
  }
4482
4594
 
4483
4595
  // src/components/AgentChat.tsx
4484
- import { Fragment as Fragment3, jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
4596
+ import { Fragment as Fragment4, jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
4485
4597
  function isUnauthorizedError(error) {
4486
4598
  return error instanceof BladeApiError && error.status === 401;
4487
4599
  }
4488
4600
  function LoginCard({ client, onLoggedIn }) {
4489
- const [loggingIn, setLoggingIn] = useState14(false);
4490
- const [loginError, setLoginError] = useState14(null);
4601
+ const [loggingIn, setLoggingIn] = useState15(false);
4602
+ const [loginError, setLoginError] = useState15(null);
4491
4603
  const handleLogin = async () => {
4492
4604
  setLoggingIn(true);
4493
4605
  setLoginError(null);
@@ -4519,8 +4631,8 @@ function LoginCard({ client, onLoggedIn }) {
4519
4631
  }
4520
4632
  function AgentChat(props) {
4521
4633
  const client = useBladeClient();
4522
- const [attempt, setAttempt] = useState14(0);
4523
- const [needLogin, setNeedLogin] = useState14(() => !client.hasToken());
4634
+ const [attempt, setAttempt] = useState15(0);
4635
+ const [needLogin, setNeedLogin] = useState15(() => !client.hasToken());
4524
4636
  if (needLogin) {
4525
4637
  return /* @__PURE__ */ jsx19(
4526
4638
  "div",
@@ -4557,7 +4669,7 @@ function ChatSessionView({
4557
4669
  onUnauthorized
4558
4670
  }) {
4559
4671
  const client = useBladeClient();
4560
- const [planRevealRevisions, setPlanRevealRevisions] = useState14(
4672
+ const [planRevealRevisions, setPlanRevealRevisions] = useState15(
4561
4673
  () => /* @__PURE__ */ new Map()
4562
4674
  );
4563
4675
  const handleSessionConnected = useCallback8((connectedSession) => {
@@ -4578,9 +4690,9 @@ function ChatSessionView({
4578
4690
  onSessionConnected: handleSessionConnected
4579
4691
  });
4580
4692
  const replay = useReplay(session);
4581
- const [stopRequested, setStopRequested] = useState14(false);
4582
- const [inputText, setInputText] = useState14("");
4583
- const [resultFeedback, setResultFeedback] = useState14([]);
4693
+ const [stopRequested, setStopRequested] = useState15(false);
4694
+ const [inputText, setInputText] = useState15("");
4695
+ const [resultFeedback, setResultFeedback] = useState15([]);
4584
4696
  const resolvedSessionId = session?.sessionId;
4585
4697
  const isViewer = state?.viewerRole === "viewer";
4586
4698
  useEffect12(() => {
@@ -4676,7 +4788,7 @@ ${content}`);
4676
4788
  slots,
4677
4789
  placeholder,
4678
4790
  connection: state?.connection ?? "connecting",
4679
- banner: /* @__PURE__ */ jsxs17(Fragment3, { children: [
4791
+ banner: /* @__PURE__ */ jsxs17(Fragment4, { children: [
4680
4792
  /* @__PURE__ */ jsx19(
4681
4793
  ReplayBar,
4682
4794
  {
@@ -4697,11 +4809,23 @@ ${content}`);
4697
4809
  isStreaming,
4698
4810
  showPlanUpdates: true,
4699
4811
  planRevealRevision,
4812
+ historyPaging: session && state ? {
4813
+ hasOlder: session.hasOlderHistory,
4814
+ loading: state.loadingOlder,
4815
+ loadOlder: async (beforeCommit) => {
4816
+ const before = session.getState().nextBefore;
4817
+ await session.loadOlderHistory({ beforeCommit });
4818
+ return before !== session.getState().nextBefore;
4819
+ }
4820
+ } : void 0,
4700
4821
  isStopping,
4701
4822
  inputText,
4702
4823
  onInputChange: setInputText,
4703
4824
  onSuggestion: setInputText,
4704
4825
  onSend: handleSend,
4826
+ onAppend: (text) => {
4827
+ void session?.queue(text);
4828
+ },
4705
4829
  onStop: handleStop,
4706
4830
  sessionStatus: state?.status ?? void 0,
4707
4831
  askAnswers: state?.askAnswers,
@@ -4718,10 +4842,10 @@ ${content}`);
4718
4842
  }
4719
4843
 
4720
4844
  // src/components/LlmChat.tsx
4721
- import { useEffect as useEffect13, useMemo as useMemo9, useState as useState16 } from "react";
4845
+ import { useEffect as useEffect13, useMemo as useMemo9, useState as useState17 } from "react";
4722
4846
 
4723
4847
  // src/components/LlmAdvancedSettings.tsx
4724
- import { useState as useState15 } from "react";
4848
+ import { useState as useState16 } from "react";
4725
4849
  import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
4726
4850
  var FIELDS = [
4727
4851
  { id: "baseURL", label: "\u6A21\u578B\u670D\u52A1\u5730\u5740", placeholder: "http://\u5185\u7F51\u5730\u5740/v1" },
@@ -4768,8 +4892,8 @@ function writeOverride(settings, baseURL, override) {
4768
4892
  }
4769
4893
  function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
4770
4894
  const normalized = normalizeAdvanced(settings);
4771
- const [open, setOpen] = useState15(false);
4772
- const [draft, setDraft] = useState15(override);
4895
+ const [open, setOpen] = useState16(false);
4896
+ const [draft, setDraft] = useState16(override);
4773
4897
  if (!normalized) return null;
4774
4898
  const fields = FIELDS.filter((field) => normalized[field.id]);
4775
4899
  const dirty = Object.keys(override).length > 0;
@@ -4852,11 +4976,11 @@ function LlmChat({
4852
4976
  onOverrideChange,
4853
4977
  ...options
4854
4978
  }) {
4855
- const [override, setOverride] = useState16(() => readOverride(advanced, options.baseURL));
4979
+ const [override, setOverride] = useState17(() => readOverride(advanced, options.baseURL));
4856
4980
  const effective = { ...options, ...override };
4857
4981
  const { messages, isStreaming, error, send, stop, reset } = useLlmChat(effective);
4858
- const [inputText, setInputText] = useState16("");
4859
- const [stopRequested, setStopRequested] = useState16(false);
4982
+ const [inputText, setInputText] = useState17("");
4983
+ const [stopRequested, setStopRequested] = useState17(false);
4860
4984
  const handle = useMemo9(
4861
4985
  () => ({
4862
4986
  insertText: (text) => setInputText((prev) => prev ? `${prev}
@@ -5013,7 +5137,7 @@ function ContextGroupCard({ contexts, className }) {
5013
5137
  }
5014
5138
 
5015
5139
  // src/components/SessionMemoryToggle.tsx
5016
- import { useCallback as useCallback9, useEffect as useEffect14, useRef as useRef13, useState as useState17, useSyncExternalStore as useSyncExternalStore2 } from "react";
5140
+ import { useCallback as useCallback9, useEffect as useEffect14, useRef as useRef13, useState as useState18, useSyncExternalStore as useSyncExternalStore2 } from "react";
5017
5141
  import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
5018
5142
  var saveStates = /* @__PURE__ */ new WeakMap();
5019
5143
  function getSaveState(client, sessionId) {
@@ -5071,7 +5195,7 @@ function SessionMemoryToggle({
5071
5195
  getSaving,
5072
5196
  getSaving
5073
5197
  );
5074
- const [draftEnabled, setDraftEnabled] = useState17(enabled);
5198
+ const [draftEnabled, setDraftEnabled] = useState18(enabled);
5075
5199
  const activeSessionIdRef = useRef13(sessionId);
5076
5200
  activeSessionIdRef.current = sessionId;
5077
5201
  useEffect14(() => {