@blade-hq/agent-react 2610.0.0-beta.34 → 2610.0.0-beta.36
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/components/ChatSurface.d.ts +7 -1
- package/dist/components/MessageList.d.ts +7 -1
- package/dist/embed/entry.d.ts +5 -0
- package/dist/index.js +145 -99
- package/dist/index.js.map +1 -1
- package/dist/style.full.css +1 -1
- package/package.json +2 -2
- package/public-api.md +69 -1
|
@@ -60,6 +60,12 @@ interface ChatSurfaceProps extends ChatPresentationProps {
|
|
|
60
60
|
showPlanUpdates?: boolean;
|
|
61
61
|
/** 仅实时成功的 update_plan 递增;历史 hydration 不得改变。 */
|
|
62
62
|
planRevealRevision?: number;
|
|
63
|
+
historyPaging?: {
|
|
64
|
+
hasOlder: boolean;
|
|
65
|
+
loading: boolean;
|
|
66
|
+
/** Resolves true only when the paging cursor advanced. */
|
|
67
|
+
loadOlder: (beforeCommit?: () => void) => Promise<boolean>;
|
|
68
|
+
};
|
|
63
69
|
/** 连接状态条下方的横幅(回放模式条用)。不传时不渲染任何节点。 */
|
|
64
70
|
banner?: ReactNode;
|
|
65
71
|
}
|
|
@@ -69,5 +75,5 @@ interface ChatSurfaceProps extends ChatPresentationProps {
|
|
|
69
75
|
* 这里的 DOM 结构受 tests/chat-view-compat.test.tsx 的快照保护——接入方拿
|
|
70
76
|
* .blade-chat-* 类名写了自己的样式,结构一动他们的页面就歪。
|
|
71
77
|
*/
|
|
72
|
-
export declare function ChatSurface({ theme, classNames, renderers, slots, placeholder, connection, errorMessage, messages, postChatFollowup, isStreaming, isStopping, inputText, onInputChange, onSuggestion, onSend, onAppend, onStop, sessionStatus, askAnswers, onAnswer, sessionId, isViewer, resultFeedbackByEntry, onResultFeedbackSaved, onFollowupInteraction, beforeInput, showPlanUpdates, planRevealRevision, banner, }: ChatSurfaceProps): import("react/jsx-runtime").JSX.Element;
|
|
78
|
+
export declare function ChatSurface({ theme, classNames, renderers, slots, placeholder, connection, errorMessage, messages, postChatFollowup, isStreaming, isStopping, inputText, onInputChange, onSuggestion, onSend, onAppend, onStop, sessionStatus, askAnswers, onAnswer, sessionId, isViewer, resultFeedbackByEntry, onResultFeedbackSaved, onFollowupInteraction, beforeInput, showPlanUpdates, planRevealRevision, historyPaging, banner, }: ChatSurfaceProps): import("react/jsx-runtime").JSX.Element;
|
|
73
79
|
export {};
|
|
@@ -20,10 +20,16 @@ interface Props {
|
|
|
20
20
|
onFollowupInteraction?: (event: FollowupInteractionEvent) => void;
|
|
21
21
|
resultFeedbackByEntry?: ReadonlyMap<string, ResultFeedback>;
|
|
22
22
|
onResultFeedbackSaved?: (feedback: ResultFeedback) => void;
|
|
23
|
+
historyPaging?: {
|
|
24
|
+
hasOlder: boolean;
|
|
25
|
+
loading: boolean;
|
|
26
|
+
/** Resolves true only when the paging cursor advanced. */
|
|
27
|
+
loadOlder: (beforeCommit?: () => void) => Promise<boolean>;
|
|
28
|
+
};
|
|
23
29
|
}
|
|
24
30
|
/**
|
|
25
31
|
* 消息列表:use-stick-to-bottom 自动滚动 + 按轮次分组渲染。
|
|
26
32
|
* 相比第一方版本去掉了轮次导航栏、吸顶状态条与规划摘要卡片。
|
|
27
33
|
*/
|
|
28
|
-
export declare function MessageList({ messages, postChatFollowup, onSuggestion, isStreaming, sessionStatus, askAnswers, onAnswer, toolCallRenderer, hidePlanUpdateTools, emptyState, className, sessionId, isViewer, onFollowupInteraction, resultFeedbackByEntry, onResultFeedbackSaved, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
export declare function MessageList({ messages, postChatFollowup, onSuggestion, isStreaming, sessionStatus, askAnswers, onAnswer, toolCallRenderer, hidePlanUpdateTools, emptyState, className, sessionId, isViewer, onFollowupInteraction, resultFeedbackByEntry, onResultFeedbackSaved, historyPaging, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
29
35
|
export {};
|
package/dist/embed/entry.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ declare const BladeAgent: {
|
|
|
27
27
|
SessionHub: typeof agentClient.SessionHub;
|
|
28
28
|
SessionSetupError: typeof agentClient.SessionSetupError;
|
|
29
29
|
createInitialSessionState: typeof agentClient.createInitialSessionState;
|
|
30
|
+
reconcileHistoricalUserTurns: typeof agentClient.reconcileHistoricalUserTurns;
|
|
30
31
|
reconcileOptimisticUserTurns: typeof agentClient.reconcileOptimisticUserTurns;
|
|
31
32
|
toReplaySnapshot: typeof agentClient.toReplaySnapshot;
|
|
32
33
|
connectEmbedded: typeof agentClient.connectEmbedded;
|
|
@@ -37,6 +38,7 @@ declare const BladeAgent: {
|
|
|
37
38
|
computerState: typeof agentClient.computerState;
|
|
38
39
|
sortComputers: typeof agentClient.sortComputers;
|
|
39
40
|
ModelsResource: typeof agentClient.ModelsResource;
|
|
41
|
+
hasChatRunEvent: typeof agentClient.hasChatRunEvent;
|
|
40
42
|
buildMessageContent: typeof agentClient.buildMessageContent;
|
|
41
43
|
chatErrorForDisplay: typeof agentClient.chatErrorForDisplay;
|
|
42
44
|
contentPreview: typeof agentClient.contentPreview;
|
|
@@ -53,6 +55,8 @@ declare const BladeAgent: {
|
|
|
53
55
|
getContextGroupDisplayState: typeof agentClient.getContextGroupDisplayState;
|
|
54
56
|
groupAdjacentContextRuns: typeof agentClient.groupAdjacentContextRuns;
|
|
55
57
|
latestPostChatFollowup: typeof agentClient.latestPostChatFollowup;
|
|
58
|
+
prependOlder: typeof agentClient.prependOlder;
|
|
59
|
+
replaceWindow: typeof agentClient.replaceWindow;
|
|
56
60
|
DEFAULT_REPLAY_SPEED: agentClient.ReplaySpeed;
|
|
57
61
|
SessionInfo: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
58
62
|
id: string;
|
|
@@ -100,6 +104,7 @@ declare const BladeAgent: {
|
|
|
100
104
|
}, {}>;
|
|
101
105
|
TaskStatus: import("arktype/internal/variants/string.ts").StringType<"done" | "failed" | "in_progress" | "pending" | "skipped", {}>;
|
|
102
106
|
acceptedPostChatFollowupCompletesLatestRun: typeof agentClient.acceptedPostChatFollowupCompletesLatestRun;
|
|
107
|
+
acknowledgeTurnEvents: typeof agentClient.acknowledgeTurnEvents;
|
|
103
108
|
ClientProjectionBuilder: typeof agentClient.ClientProjectionBuilder;
|
|
104
109
|
createSocket: typeof agentClient.createSocket;
|
|
105
110
|
getChat: typeof getChat;
|
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
|
|
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,7 @@ function CurrentPlanPanel({
|
|
|
1613
1613
|
import { chatErrorForDisplay as chatErrorForDisplay2 } from "@blade-hq/agent-client";
|
|
1614
1614
|
|
|
1615
1615
|
// src/components/ChatInput.tsx
|
|
1616
|
-
import {
|
|
1616
|
+
import { useState as useState5 } from "react";
|
|
1617
1617
|
import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1618
1618
|
function isImeCompositionKey(event) {
|
|
1619
1619
|
return event.isComposing || event.keyCode === 229;
|
|
@@ -1635,36 +1635,12 @@ function ChatInput({
|
|
|
1635
1635
|
}) {
|
|
1636
1636
|
const trimmed = value.trim();
|
|
1637
1637
|
const [sendMode, setSendMode] = useState5("direct");
|
|
1638
|
-
|
|
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]);
|
|
1638
|
+
void queueKey;
|
|
1648
1639
|
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]);
|
|
1664
1640
|
const handleSend = async () => {
|
|
1665
1641
|
if (!canSend) return;
|
|
1666
1642
|
if (isStreaming && sendMode === "queue") {
|
|
1667
|
-
|
|
1643
|
+
if (onAppend) onAppend(trimmed);
|
|
1668
1644
|
onValueChange("");
|
|
1669
1645
|
return;
|
|
1670
1646
|
}
|
|
@@ -1690,24 +1666,11 @@ function ChatInput({
|
|
|
1690
1666
|
}
|
|
1691
1667
|
};
|
|
1692
1668
|
return /* @__PURE__ */ jsxs4("div", { className: cn("blade-chat-input border-t border-[hsl(var(--border))] py-3", className), children: [
|
|
1693
|
-
/* @__PURE__ */
|
|
1694
|
-
/* @__PURE__ */
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
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
|
-
] }),
|
|
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
|
+
] }) }),
|
|
1711
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: [
|
|
1712
1675
|
/* @__PURE__ */ jsx5(
|
|
1713
1676
|
"textarea",
|
|
@@ -1746,16 +1709,16 @@ function ChatInput({
|
|
|
1746
1709
|
}
|
|
1747
1710
|
|
|
1748
1711
|
// src/components/ConnectionBanner.tsx
|
|
1749
|
-
import { useEffect as
|
|
1712
|
+
import { useEffect as useEffect5, useRef as useRef5, useState as useState6 } from "react";
|
|
1750
1713
|
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1751
1714
|
var CONNECTION_NOTICE_DELAY_MS = 3e3;
|
|
1752
1715
|
var CONNECTION_ERROR_DELAY_MS = 15e3;
|
|
1753
1716
|
function useConnectionNoticePhase(connected) {
|
|
1754
1717
|
const [phase, setPhase] = useState6("hidden");
|
|
1755
|
-
const connectedRef =
|
|
1756
|
-
const timersRef =
|
|
1718
|
+
const connectedRef = useRef5(connected);
|
|
1719
|
+
const timersRef = useRef5([]);
|
|
1757
1720
|
connectedRef.current = connected;
|
|
1758
|
-
|
|
1721
|
+
useEffect5(() => {
|
|
1759
1722
|
const clearTimers = () => {
|
|
1760
1723
|
for (const timer of timersRef.current) clearTimeout(timer);
|
|
1761
1724
|
timersRef.current = [];
|
|
@@ -1795,7 +1758,7 @@ function useConnectionNoticePhase(connected) {
|
|
|
1795
1758
|
return phase;
|
|
1796
1759
|
}
|
|
1797
1760
|
function ConnectionBanner({ connection, className }) {
|
|
1798
|
-
const hasConnectedRef =
|
|
1761
|
+
const hasConnectedRef = useRef5(connection === "connected" || connection === "reconnecting");
|
|
1799
1762
|
if (connection === "connected") hasConnectedRef.current = true;
|
|
1800
1763
|
const connected = connection === "connected";
|
|
1801
1764
|
const phase = useConnectionNoticePhase(connected);
|
|
@@ -1822,10 +1785,10 @@ function ConnectionBanner({ connection, className }) {
|
|
|
1822
1785
|
|
|
1823
1786
|
// src/components/MessageList.tsx
|
|
1824
1787
|
import { isHiddenInternalMessage } from "@blade-hq/agent-client";
|
|
1825
|
-
import { useCallback as useCallback7, useEffect as
|
|
1788
|
+
import { useCallback as useCallback7, useEffect as useEffect11, useMemo as useMemo7, useRef as useRef12, useState as useState14 } from "react";
|
|
1826
1789
|
|
|
1827
1790
|
// ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/useStickToBottom.js
|
|
1828
|
-
import { useCallback as useCallback4, useMemo as useMemo3, useRef as
|
|
1791
|
+
import { useCallback as useCallback4, useMemo as useMemo3, useRef as useRef6, useState as useState7 } from "react";
|
|
1829
1792
|
var DEFAULT_SPRING_ANIMATION = {
|
|
1830
1793
|
/**
|
|
1831
1794
|
* A value from 0 to 1, on how much to damp the animation.
|
|
@@ -1865,7 +1828,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
1865
1828
|
const [escapedFromLock, updateEscapedFromLock] = useState7(false);
|
|
1866
1829
|
const [isAtBottom, updateIsAtBottom] = useState7(options.initial !== false);
|
|
1867
1830
|
const [isNearBottom, setIsNearBottom] = useState7(false);
|
|
1868
|
-
const optionsRef =
|
|
1831
|
+
const optionsRef = useRef6(null);
|
|
1869
1832
|
optionsRef.current = options;
|
|
1870
1833
|
const isSelecting = useCallback4(() => {
|
|
1871
1834
|
if (!mouseDown) {
|
|
@@ -2169,11 +2132,11 @@ function mergeAnimations(...animations) {
|
|
|
2169
2132
|
|
|
2170
2133
|
// ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/StickToBottom.js
|
|
2171
2134
|
import * as React from "react";
|
|
2172
|
-
import { createContext as createContext2, useContext as useContext2, useEffect as
|
|
2135
|
+
import { createContext as createContext2, useContext as useContext2, useEffect as useEffect6, useImperativeHandle, useLayoutEffect, useMemo as useMemo4, useRef as useRef7 } from "react";
|
|
2173
2136
|
var StickToBottomContext = createContext2(null);
|
|
2174
|
-
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect :
|
|
2137
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect6;
|
|
2175
2138
|
function StickToBottom({ instance, children, resize, initial, mass, damping, stiffness, targetScrollTop: currentTargetScrollTop, contextRef, ...props }) {
|
|
2176
|
-
const customTargetScrollTop =
|
|
2139
|
+
const customTargetScrollTop = useRef7(null);
|
|
2177
2140
|
const targetScrollTop = React.useCallback((target, elements) => {
|
|
2178
2141
|
const get = context?.targetScrollTop ?? currentTargetScrollTop;
|
|
2179
2142
|
return get?.(target, elements) ?? target;
|
|
@@ -2255,7 +2218,7 @@ import {
|
|
|
2255
2218
|
getTextContent,
|
|
2256
2219
|
normalizeMessageContent
|
|
2257
2220
|
} from "@blade-hq/agent-client";
|
|
2258
|
-
import { useEffect as
|
|
2221
|
+
import { useEffect as useEffect9, useRef as useRef10, useState as useState12 } from "react";
|
|
2259
2222
|
|
|
2260
2223
|
// src/components/AgentLoopBlock.tsx
|
|
2261
2224
|
import { useState as useState8 } from "react";
|
|
@@ -2320,9 +2283,9 @@ function AgentLoopBlock({ toolCall }) {
|
|
|
2320
2283
|
|
|
2321
2284
|
// src/components/MarkdownContent.tsx
|
|
2322
2285
|
import {
|
|
2323
|
-
useEffect as
|
|
2286
|
+
useEffect as useEffect7,
|
|
2324
2287
|
useMemo as useMemo5,
|
|
2325
|
-
useRef as
|
|
2288
|
+
useRef as useRef8,
|
|
2326
2289
|
useState as useState9
|
|
2327
2290
|
} from "react";
|
|
2328
2291
|
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
@@ -2340,10 +2303,10 @@ function normalizeAdjacentUrlFormatting(value) {
|
|
|
2340
2303
|
return normalized.replace(/blade-url-protected-(\d+)-marker/g, (_, index) => protectedSegments[Number(index)]);
|
|
2341
2304
|
}
|
|
2342
2305
|
function CodeBlockPre({ children, node: _node, ...props }) {
|
|
2343
|
-
const preRef =
|
|
2306
|
+
const preRef = useRef8(null);
|
|
2344
2307
|
const [copied, setCopied] = useState9(false);
|
|
2345
2308
|
const [language, setLanguage] = useState9("");
|
|
2346
|
-
|
|
2309
|
+
useEffect7(() => {
|
|
2347
2310
|
const codeEl = preRef.current?.querySelector("code");
|
|
2348
2311
|
setLanguage(codeEl?.className.match(/language-(\S+)/)?.[1] ?? "");
|
|
2349
2312
|
}, []);
|
|
@@ -2417,7 +2380,7 @@ function Shimmer({ children = "\u6B63\u5728\u601D\u8003...", className }) {
|
|
|
2417
2380
|
import { useState as useState11 } from "react";
|
|
2418
2381
|
|
|
2419
2382
|
// src/components/AskUserQuestionBlock.tsx
|
|
2420
|
-
import { useEffect as
|
|
2383
|
+
import { useEffect as useEffect8, useMemo as useMemo6, useRef as useRef9, useState as useState10 } from "react";
|
|
2421
2384
|
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2422
2385
|
var CUSTOM_TEXTAREA_MAX_HEIGHT = 160;
|
|
2423
2386
|
function resizeCustomTextarea(textarea) {
|
|
@@ -2426,12 +2389,12 @@ function resizeCustomTextarea(textarea) {
|
|
|
2426
2389
|
textarea.style.overflowY = textarea.scrollHeight > CUSTOM_TEXTAREA_MAX_HEIGHT ? "auto" : "hidden";
|
|
2427
2390
|
}
|
|
2428
2391
|
function useAutoResizeTextarea(value) {
|
|
2429
|
-
const textareaRef =
|
|
2430
|
-
|
|
2392
|
+
const textareaRef = useRef9(null);
|
|
2393
|
+
useEffect8(() => {
|
|
2431
2394
|
const textarea = textareaRef.current;
|
|
2432
2395
|
if (textarea?.value === value) resizeCustomTextarea(textarea);
|
|
2433
2396
|
}, [value]);
|
|
2434
|
-
|
|
2397
|
+
useEffect8(() => {
|
|
2435
2398
|
const textarea = textareaRef.current;
|
|
2436
2399
|
if (!textarea || typeof ResizeObserver === "undefined") return;
|
|
2437
2400
|
let previousWidth = textarea.clientWidth;
|
|
@@ -2461,7 +2424,7 @@ function AskUserQuestionBlock({
|
|
|
2461
2424
|
const [usingCustom, setUsingCustom] = useState10(/* @__PURE__ */ new Set());
|
|
2462
2425
|
const [note, setNote] = useState10("");
|
|
2463
2426
|
const [submitted, setSubmitted] = useState10(false);
|
|
2464
|
-
|
|
2427
|
+
useEffect8(() => {
|
|
2465
2428
|
if (sessionStatus === "failed" || sessionStatus === "interrupted") {
|
|
2466
2429
|
setSubmitted(false);
|
|
2467
2430
|
}
|
|
@@ -3255,9 +3218,9 @@ function AssistantTurnBlock({
|
|
|
3255
3218
|
const [displayMode, setDisplayMode] = useState12(
|
|
3256
3219
|
() => isStreaming || hasActionableToolCall ? "detail" : "compact"
|
|
3257
3220
|
);
|
|
3258
|
-
const userSelectedDisplayModeRef =
|
|
3259
|
-
const wasStreamingRef =
|
|
3260
|
-
|
|
3221
|
+
const userSelectedDisplayModeRef = useRef10(false);
|
|
3222
|
+
const wasStreamingRef = useRef10(isStreaming);
|
|
3223
|
+
useEffect9(() => {
|
|
3261
3224
|
if (wasStreamingRef.current && !isStreaming && !userSelectedDisplayModeRef.current) {
|
|
3262
3225
|
setDisplayMode(hasActionableToolCall ? "detail" : "compact");
|
|
3263
3226
|
}
|
|
@@ -3269,7 +3232,7 @@ function AssistantTurnBlock({
|
|
|
3269
3232
|
const hasLiveStartTime = messages.some(
|
|
3270
3233
|
(message) => message.timestamp != null && Number.isFinite(Date.parse(message.timestamp))
|
|
3271
3234
|
);
|
|
3272
|
-
|
|
3235
|
+
useEffect9(() => {
|
|
3273
3236
|
if (!isStreaming || !hasLiveStartTime) return;
|
|
3274
3237
|
const timer = window.setInterval(() => setClock(Date.now()), 1e3);
|
|
3275
3238
|
return () => window.clearInterval(timer);
|
|
@@ -3579,7 +3542,7 @@ var RenderErrorBoundary = class extends Component {
|
|
|
3579
3542
|
};
|
|
3580
3543
|
|
|
3581
3544
|
// src/components/PostChatFollowupBlock.tsx
|
|
3582
|
-
import { useCallback as useCallback6, useEffect as
|
|
3545
|
+
import { useCallback as useCallback6, useEffect as useEffect10, useRef as useRef11, useState as useState13 } from "react";
|
|
3583
3546
|
import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3584
3547
|
function emitInteraction(callback, event) {
|
|
3585
3548
|
try {
|
|
@@ -3710,10 +3673,10 @@ function ResultFeedback({
|
|
|
3710
3673
|
const [reason, setReason] = useState13(savedFeedback?.reason ?? null);
|
|
3711
3674
|
const [saving, setSaving] = useState13(false);
|
|
3712
3675
|
const [saveError, setSaveError] = useState13(false);
|
|
3713
|
-
const reportedShown =
|
|
3714
|
-
const latestChoice =
|
|
3676
|
+
const reportedShown = useRef11(false);
|
|
3677
|
+
const latestChoice = useRef11(null);
|
|
3715
3678
|
const eligible = followup.feedback_eligible === true && Boolean(sessionId) && !isViewer;
|
|
3716
|
-
|
|
3679
|
+
useEffect10(() => {
|
|
3717
3680
|
if (!eligible || reportedShown.current) return;
|
|
3718
3681
|
reportedShown.current = true;
|
|
3719
3682
|
emitInteraction(onInteraction, {
|
|
@@ -3722,7 +3685,7 @@ function ResultFeedback({
|
|
|
3722
3685
|
assistantEntryId: followup.assistant_entry_id
|
|
3723
3686
|
});
|
|
3724
3687
|
}, [eligible, followup.assistant_entry_id, onInteraction, sessionId]);
|
|
3725
|
-
|
|
3688
|
+
useEffect10(() => {
|
|
3726
3689
|
if (!savedFeedback || latestChoice.current) return;
|
|
3727
3690
|
setSaved(savedFeedback);
|
|
3728
3691
|
setHelpful(savedFeedback.helpful);
|
|
@@ -3835,13 +3798,13 @@ function PostChatFollowupBlock({
|
|
|
3835
3798
|
onFeedbackSaved
|
|
3836
3799
|
}) {
|
|
3837
3800
|
const [expanded, setExpanded] = useState13(false);
|
|
3838
|
-
const adopted =
|
|
3839
|
-
const reportedSuggestions =
|
|
3840
|
-
const reportedArtifacts =
|
|
3841
|
-
const openedArtifacts =
|
|
3801
|
+
const adopted = useRef11(/* @__PURE__ */ new Set());
|
|
3802
|
+
const reportedSuggestions = useRef11(false);
|
|
3803
|
+
const reportedArtifacts = useRef11(/* @__PURE__ */ new Set());
|
|
3804
|
+
const openedArtifacts = useRef11(/* @__PURE__ */ new Set());
|
|
3842
3805
|
const artifacts = followup.final_artifacts ?? [];
|
|
3843
3806
|
const visibleArtifacts = expanded ? artifacts : artifacts.slice(0, 3);
|
|
3844
|
-
|
|
3807
|
+
useEffect10(() => {
|
|
3845
3808
|
if (!reportedSuggestions.current && followup.suggestions.length > 0) {
|
|
3846
3809
|
reportedSuggestions.current = true;
|
|
3847
3810
|
emitInteraction(onInteraction, {
|
|
@@ -4150,7 +4113,8 @@ function MessageList({
|
|
|
4150
4113
|
isViewer = false,
|
|
4151
4114
|
onFollowupInteraction,
|
|
4152
4115
|
resultFeedbackByEntry = /* @__PURE__ */ new Map(),
|
|
4153
|
-
onResultFeedbackSaved
|
|
4116
|
+
onResultFeedbackSaved,
|
|
4117
|
+
historyPaging
|
|
4154
4118
|
}) {
|
|
4155
4119
|
const visibleRootMessages = messages.filter((message) => {
|
|
4156
4120
|
if ((message.loop_name ?? "root") !== "root") return false;
|
|
@@ -4247,6 +4211,7 @@ function MessageList({
|
|
|
4247
4211
|
resize: "instant",
|
|
4248
4212
|
children: [
|
|
4249
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,
|
|
4250
4215
|
isStreaming && latestUserMessage && latestPromptPreview ? /* @__PURE__ */ jsx17(
|
|
4251
4216
|
"button",
|
|
4252
4217
|
{
|
|
@@ -4346,14 +4311,84 @@ function MessageList({
|
|
|
4346
4311
|
)
|
|
4347
4312
|
] });
|
|
4348
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
|
+
}
|
|
4349
4384
|
function PinLatestUserMessage({
|
|
4350
4385
|
userMessageCount,
|
|
4351
4386
|
shouldPinLatestUser,
|
|
4352
4387
|
targetKey
|
|
4353
4388
|
}) {
|
|
4354
4389
|
const { contentRef, scrollRef, scrollToBottom, stopScroll } = useStickToBottomContext();
|
|
4355
|
-
const previousCountRef =
|
|
4356
|
-
const spacerHeightRef =
|
|
4390
|
+
const previousCountRef = useRef12(userMessageCount);
|
|
4391
|
+
const spacerHeightRef = useRef12(0);
|
|
4357
4392
|
const getScrollElement = useCallback7(() => scrollRef.current, [scrollRef]);
|
|
4358
4393
|
const getContentElement = useCallback7(() => contentRef.current, [contentRef]);
|
|
4359
4394
|
const getTargetElement = useCallback7(() => {
|
|
@@ -4382,7 +4417,7 @@ function PinLatestUserMessage({
|
|
|
4382
4417
|
stopAutoScroll: stopScroll,
|
|
4383
4418
|
scrollToBottom
|
|
4384
4419
|
});
|
|
4385
|
-
|
|
4420
|
+
useEffect11(() => {
|
|
4386
4421
|
if (userMessageCount > previousCountRef.current && !shouldPinLatestUser) {
|
|
4387
4422
|
scrollToBottom("instant");
|
|
4388
4423
|
}
|
|
@@ -4393,8 +4428,8 @@ function PinLatestUserMessage({
|
|
|
4393
4428
|
function ScrollToBottomButton() {
|
|
4394
4429
|
const { isAtBottom, scrollToBottom } = useStickToBottomContext();
|
|
4395
4430
|
const [visible, setVisible] = useState14(false);
|
|
4396
|
-
const hideTimerRef =
|
|
4397
|
-
|
|
4431
|
+
const hideTimerRef = useRef12(null);
|
|
4432
|
+
useEffect11(() => {
|
|
4398
4433
|
if (isAtBottom) {
|
|
4399
4434
|
if (!hideTimerRef.current) {
|
|
4400
4435
|
hideTimerRef.current = setTimeout(() => {
|
|
@@ -4484,6 +4519,7 @@ function ChatSurface({
|
|
|
4484
4519
|
beforeInput,
|
|
4485
4520
|
showPlanUpdates = false,
|
|
4486
4521
|
planRevealRevision = 0,
|
|
4522
|
+
historyPaging,
|
|
4487
4523
|
banner
|
|
4488
4524
|
}) {
|
|
4489
4525
|
return /* @__PURE__ */ jsxs16(
|
|
@@ -4520,7 +4556,8 @@ function ChatSurface({
|
|
|
4520
4556
|
isViewer,
|
|
4521
4557
|
resultFeedbackByEntry,
|
|
4522
4558
|
onResultFeedbackSaved,
|
|
4523
|
-
onFollowupInteraction
|
|
4559
|
+
onFollowupInteraction,
|
|
4560
|
+
historyPaging
|
|
4524
4561
|
}
|
|
4525
4562
|
),
|
|
4526
4563
|
showPlanUpdates ? /* @__PURE__ */ jsx18(
|
|
@@ -4658,7 +4695,7 @@ function ChatSessionView({
|
|
|
4658
4695
|
const [resultFeedback, setResultFeedback] = useState15([]);
|
|
4659
4696
|
const resolvedSessionId = session?.sessionId;
|
|
4660
4697
|
const isViewer = state?.viewerRole === "viewer";
|
|
4661
|
-
|
|
4698
|
+
useEffect12(() => {
|
|
4662
4699
|
setResultFeedback([]);
|
|
4663
4700
|
if (!resolvedSessionId || isViewer) return;
|
|
4664
4701
|
let cancelled = false;
|
|
@@ -4693,12 +4730,12 @@ function ChatSessionView({
|
|
|
4693
4730
|
saved
|
|
4694
4731
|
]);
|
|
4695
4732
|
}, []);
|
|
4696
|
-
|
|
4733
|
+
useEffect12(() => {
|
|
4697
4734
|
if (session) {
|
|
4698
4735
|
onSessionReady?.(session);
|
|
4699
4736
|
}
|
|
4700
4737
|
}, [session, onSessionReady]);
|
|
4701
|
-
|
|
4738
|
+
useEffect12(() => {
|
|
4702
4739
|
if (!session) return;
|
|
4703
4740
|
const offAttach = session.on("attachRequested", ({ label, content }) => {
|
|
4704
4741
|
setInputText((prev) => `${prev ? `${prev}
|
|
@@ -4714,12 +4751,12 @@ ${content}`);
|
|
|
4714
4751
|
offInsert();
|
|
4715
4752
|
};
|
|
4716
4753
|
}, [session]);
|
|
4717
|
-
|
|
4754
|
+
useEffect12(() => {
|
|
4718
4755
|
if (isUnauthorizedError(error)) {
|
|
4719
4756
|
onUnauthorized();
|
|
4720
4757
|
}
|
|
4721
4758
|
}, [error, onUnauthorized]);
|
|
4722
|
-
|
|
4759
|
+
useEffect12(() => {
|
|
4723
4760
|
if (!session || !commands) return;
|
|
4724
4761
|
const unsubscribes = Object.entries(commands).map(
|
|
4725
4762
|
([action, handler]) => session.onCommand(action, (payload) => handler(payload))
|
|
@@ -4772,13 +4809,22 @@ ${content}`);
|
|
|
4772
4809
|
isStreaming,
|
|
4773
4810
|
showPlanUpdates: true,
|
|
4774
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,
|
|
4775
4821
|
isStopping,
|
|
4776
4822
|
inputText,
|
|
4777
4823
|
onInputChange: setInputText,
|
|
4778
4824
|
onSuggestion: setInputText,
|
|
4779
4825
|
onSend: handleSend,
|
|
4780
4826
|
onAppend: (text) => {
|
|
4781
|
-
void session?.
|
|
4827
|
+
void session?.queue(text);
|
|
4782
4828
|
},
|
|
4783
4829
|
onStop: handleStop,
|
|
4784
4830
|
sessionStatus: state?.status ?? void 0,
|
|
@@ -4796,7 +4842,7 @@ ${content}`);
|
|
|
4796
4842
|
}
|
|
4797
4843
|
|
|
4798
4844
|
// src/components/LlmChat.tsx
|
|
4799
|
-
import { useEffect as
|
|
4845
|
+
import { useEffect as useEffect13, useMemo as useMemo9, useState as useState17 } from "react";
|
|
4800
4846
|
|
|
4801
4847
|
// src/components/LlmAdvancedSettings.tsx
|
|
4802
4848
|
import { useState as useState16 } from "react";
|
|
@@ -4944,7 +4990,7 @@ ${text}` : text),
|
|
|
4944
4990
|
}),
|
|
4945
4991
|
[send, reset]
|
|
4946
4992
|
);
|
|
4947
|
-
|
|
4993
|
+
useEffect13(() => {
|
|
4948
4994
|
onReady?.(handle);
|
|
4949
4995
|
}, [handle, onReady]);
|
|
4950
4996
|
return /* @__PURE__ */ jsx21(
|
|
@@ -5091,7 +5137,7 @@ function ContextGroupCard({ contexts, className }) {
|
|
|
5091
5137
|
}
|
|
5092
5138
|
|
|
5093
5139
|
// src/components/SessionMemoryToggle.tsx
|
|
5094
|
-
import { useCallback as useCallback9, useEffect as
|
|
5140
|
+
import { useCallback as useCallback9, useEffect as useEffect14, useRef as useRef13, useState as useState18, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
5095
5141
|
import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
5096
5142
|
var saveStates = /* @__PURE__ */ new WeakMap();
|
|
5097
5143
|
function getSaveState(client, sessionId) {
|
|
@@ -5150,9 +5196,9 @@ function SessionMemoryToggle({
|
|
|
5150
5196
|
getSaving
|
|
5151
5197
|
);
|
|
5152
5198
|
const [draftEnabled, setDraftEnabled] = useState18(enabled);
|
|
5153
|
-
const activeSessionIdRef =
|
|
5199
|
+
const activeSessionIdRef = useRef13(sessionId);
|
|
5154
5200
|
activeSessionIdRef.current = sessionId;
|
|
5155
|
-
|
|
5201
|
+
useEffect14(() => {
|
|
5156
5202
|
setDraftEnabled(enabled);
|
|
5157
5203
|
}, [enabled, sessionId]);
|
|
5158
5204
|
const update = useCallback9(
|