@blade-hq/agent-react 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -0
- package/dist/components/AgentLoopBlock.d.ts +10 -0
- package/dist/components/AskUserQuestionBlock.d.ts +31 -0
- package/dist/components/AssistantTurnBlock.d.ts +16 -0
- package/dist/components/ChatInput.d.ts +18 -0
- package/dist/components/ChatView.d.ts +41 -0
- package/dist/components/ConnectionBanner.d.ts +8 -0
- package/dist/components/MarkdownContent.d.ts +16 -0
- package/dist/components/MessageList.d.ts +19 -0
- package/dist/components/RenderErrorBoundary.d.ts +20 -0
- package/dist/components/Shimmer.d.ts +7 -0
- package/dist/components/ToolCallBlock.d.ts +18 -0
- package/dist/components/UserMessageBubble.d.ts +21 -0
- package/dist/components/display-utils.d.ts +16 -0
- package/dist/context.d.ts +13 -0
- package/dist/embed/blade-chat-element.d.ts +33 -0
- package/dist/embed/entry.d.ts +94 -0
- package/dist/hooks/use-agent-session.d.ts +29 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1527 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/utils.d.ts +5 -0
- package/dist/style.css +391 -0
- package/package.json +37 -0
- package/public-api.md +1206 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1527 @@
|
|
|
1
|
+
// src/context.tsx
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
var BladeClientContext = createContext(null);
|
|
5
|
+
function BladeProvider({ client, children }) {
|
|
6
|
+
return /* @__PURE__ */ jsx(BladeClientContext.Provider, { value: client, children });
|
|
7
|
+
}
|
|
8
|
+
function useBladeClient() {
|
|
9
|
+
const client = useContext(BladeClientContext);
|
|
10
|
+
if (!client) {
|
|
11
|
+
throw new Error("useBladeClient \u5FC5\u987B\u5728 <BladeProvider> \u5185\u4F7F\u7528");
|
|
12
|
+
}
|
|
13
|
+
return client;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/hooks/use-agent-session.ts
|
|
17
|
+
import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
18
|
+
var EMPTY_SUBSCRIBE = () => () => {
|
|
19
|
+
};
|
|
20
|
+
var DISPOSE_DELAY_MS = 1e3;
|
|
21
|
+
function useAgentSession(sessionId, options = {}) {
|
|
22
|
+
const client = useBladeClient();
|
|
23
|
+
const [session, setSession] = useState(null);
|
|
24
|
+
const [error, setError] = useState(null);
|
|
25
|
+
const connRef = useRef({
|
|
26
|
+
id: null,
|
|
27
|
+
session: null,
|
|
28
|
+
gen: 0
|
|
29
|
+
});
|
|
30
|
+
const createdIdPromiseRef = useRef(null);
|
|
31
|
+
const onCreatedRef = useRef(options.onSessionCreated);
|
|
32
|
+
onCreatedRef.current = options.onSessionCreated;
|
|
33
|
+
const createOptionsRef = useRef(options.createOptions);
|
|
34
|
+
createOptionsRef.current = options.createOptions;
|
|
35
|
+
const sessionIdRef = useRef(sessionId);
|
|
36
|
+
sessionIdRef.current = sessionId;
|
|
37
|
+
const connect = useMemo(() => {
|
|
38
|
+
return (targetId) => {
|
|
39
|
+
const gen = ++connRef.current.gen;
|
|
40
|
+
const idPromise = targetId ? Promise.resolve(targetId) : (
|
|
41
|
+
// biome-ignore lint/suspicious/noAssignInExpressions: ??= 挂 ref 是 StrictMode 下"只创建一次"的关键
|
|
42
|
+
createdIdPromiseRef.current ??= client.sessions.create(createOptionsRef.current ?? {}).then((created) => {
|
|
43
|
+
const id = created.sessionId;
|
|
44
|
+
created.dispose();
|
|
45
|
+
onCreatedRef.current?.(id);
|
|
46
|
+
return id;
|
|
47
|
+
})
|
|
48
|
+
);
|
|
49
|
+
idPromise.then((id) => client.hub.connect(id)).then((next) => {
|
|
50
|
+
if (connRef.current.gen !== gen) {
|
|
51
|
+
next.dispose();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
connRef.current.id = next.sessionId;
|
|
55
|
+
connRef.current.session = next;
|
|
56
|
+
setSession(next);
|
|
57
|
+
setError(null);
|
|
58
|
+
}).catch((err) => {
|
|
59
|
+
if (connRef.current.gen !== gen) return;
|
|
60
|
+
createdIdPromiseRef.current = null;
|
|
61
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
}, [client]);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
connect(sessionIdRef.current);
|
|
67
|
+
return () => {
|
|
68
|
+
connRef.current.gen++;
|
|
69
|
+
const toRelease = connRef.current.session;
|
|
70
|
+
connRef.current.id = null;
|
|
71
|
+
connRef.current.session = null;
|
|
72
|
+
setSession(null);
|
|
73
|
+
if (toRelease) setTimeout(() => toRelease.dispose(), DISPOSE_DELAY_MS);
|
|
74
|
+
};
|
|
75
|
+
}, [connect]);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (!sessionId) return;
|
|
78
|
+
if (connRef.current.id === null) return;
|
|
79
|
+
if (sessionId === connRef.current.id) return;
|
|
80
|
+
const previous = connRef.current.session;
|
|
81
|
+
connRef.current.id = null;
|
|
82
|
+
connRef.current.session = null;
|
|
83
|
+
if (previous) setTimeout(() => previous.dispose(), DISPOSE_DELAY_MS);
|
|
84
|
+
connect(sessionId);
|
|
85
|
+
}, [sessionId, connect]);
|
|
86
|
+
const store = useMemo(
|
|
87
|
+
() => ({
|
|
88
|
+
subscribe: session ? (listener) => session.subscribe(listener) : EMPTY_SUBSCRIBE,
|
|
89
|
+
getSnapshot: () => session ? session.getState() : null
|
|
90
|
+
}),
|
|
91
|
+
[session]
|
|
92
|
+
);
|
|
93
|
+
const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
94
|
+
return { session, state, error };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/components/ChatView.tsx
|
|
98
|
+
import { BladeApiError, VersionMismatchError } from "@blade-hq/agent-client";
|
|
99
|
+
import { CircleAlert, LockKeyhole } from "lucide-react";
|
|
100
|
+
import { useEffect as useEffect5, useState as useState7 } from "react";
|
|
101
|
+
|
|
102
|
+
// src/lib/utils.ts
|
|
103
|
+
import { clsx } from "clsx";
|
|
104
|
+
function cn(...inputs) {
|
|
105
|
+
return clsx(inputs);
|
|
106
|
+
}
|
|
107
|
+
async function copyToClipboard(text) {
|
|
108
|
+
try {
|
|
109
|
+
await navigator.clipboard.writeText(text);
|
|
110
|
+
return true;
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/components/ChatInput.tsx
|
|
117
|
+
import { ArrowUp, Loader2, Square } from "lucide-react";
|
|
118
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
119
|
+
function ChatInput({
|
|
120
|
+
value,
|
|
121
|
+
onValueChange,
|
|
122
|
+
onSend,
|
|
123
|
+
onStop,
|
|
124
|
+
isStreaming,
|
|
125
|
+
isStopping = false,
|
|
126
|
+
placeholder = "\u8F93\u5165\u6D88\u606F\u2026",
|
|
127
|
+
className
|
|
128
|
+
}) {
|
|
129
|
+
const trimmed = value.trim();
|
|
130
|
+
const canSend = trimmed.length > 0 && !isStreaming;
|
|
131
|
+
const handleSend = () => {
|
|
132
|
+
if (!canSend) return;
|
|
133
|
+
onSend(trimmed);
|
|
134
|
+
onValueChange("");
|
|
135
|
+
};
|
|
136
|
+
const handleKeyDown = (event) => {
|
|
137
|
+
if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
|
|
138
|
+
event.preventDefault();
|
|
139
|
+
handleSend();
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
return /* @__PURE__ */ jsx2("div", { className: cn("blade-chat-input border-t border-[hsl(var(--border))] px-4 py-3", className), children: /* @__PURE__ */ jsxs("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: [
|
|
143
|
+
/* @__PURE__ */ jsx2(
|
|
144
|
+
"textarea",
|
|
145
|
+
{
|
|
146
|
+
value,
|
|
147
|
+
onChange: (event) => onValueChange(event.target.value),
|
|
148
|
+
onKeyDown: handleKeyDown,
|
|
149
|
+
onInput: (event) => {
|
|
150
|
+
const el = event.currentTarget;
|
|
151
|
+
el.style.height = "auto";
|
|
152
|
+
el.style.height = `${Math.min(el.scrollHeight, 192)}px`;
|
|
153
|
+
},
|
|
154
|
+
rows: 1,
|
|
155
|
+
placeholder,
|
|
156
|
+
"aria-label": "\u804A\u5929\u8F93\u5165",
|
|
157
|
+
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)]"
|
|
158
|
+
}
|
|
159
|
+
),
|
|
160
|
+
isStreaming ? /* @__PURE__ */ jsx2(
|
|
161
|
+
"button",
|
|
162
|
+
{
|
|
163
|
+
type: "button",
|
|
164
|
+
onClick: onStop,
|
|
165
|
+
disabled: isStopping,
|
|
166
|
+
"aria-label": isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D",
|
|
167
|
+
title: isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D",
|
|
168
|
+
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",
|
|
169
|
+
children: isStopping ? /* @__PURE__ */ jsx2(Loader2, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx2(Square, { size: 12, fill: "currentColor" })
|
|
170
|
+
}
|
|
171
|
+
) : /* @__PURE__ */ jsx2(
|
|
172
|
+
"button",
|
|
173
|
+
{
|
|
174
|
+
type: "button",
|
|
175
|
+
onClick: handleSend,
|
|
176
|
+
disabled: !canSend,
|
|
177
|
+
"aria-label": "\u53D1\u9001\u6D88\u606F",
|
|
178
|
+
title: "\u53D1\u9001\u6D88\u606F",
|
|
179
|
+
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",
|
|
180
|
+
children: /* @__PURE__ */ jsx2(ArrowUp, { size: 15 })
|
|
181
|
+
}
|
|
182
|
+
)
|
|
183
|
+
] }) });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/components/ConnectionBanner.tsx
|
|
187
|
+
import { AlertTriangle, LoaderCircle } from "lucide-react";
|
|
188
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
189
|
+
function ConnectionBanner({ connection, className }) {
|
|
190
|
+
if (connection === "connected" || connection === "connecting") {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
const reconnecting = connection === "reconnecting";
|
|
194
|
+
return /* @__PURE__ */ jsx3("div", { className: cn("blade-chat-banner bg-[hsl(var(--background))] px-5 pt-3", className), children: /* @__PURE__ */ jsxs2(
|
|
195
|
+
"div",
|
|
196
|
+
{
|
|
197
|
+
className: cn(
|
|
198
|
+
"mx-auto flex max-w-3xl items-start gap-3 rounded-2xl border px-4 py-3",
|
|
199
|
+
reconnecting ? "border-amber-500/25 bg-amber-500/10 text-amber-100" : "border-rose-500/25 bg-rose-500/10 text-rose-100"
|
|
200
|
+
),
|
|
201
|
+
children: [
|
|
202
|
+
/* @__PURE__ */ jsx3("span", { className: "mt-0.5 shrink-0", children: reconnecting ? /* @__PURE__ */ jsx3(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx3(AlertTriangle, { size: 14 }) }),
|
|
203
|
+
/* @__PURE__ */ jsxs2("div", { className: "min-w-0", children: [
|
|
204
|
+
/* @__PURE__ */ jsx3("div", { className: "text-sm font-medium", children: reconnecting ? "\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF0C\u6B63\u5728\u91CD\u8FDE\u2026" : "\u8FDE\u63A5\u5DF2\u65AD\u5F00" }),
|
|
205
|
+
/* @__PURE__ */ jsx3("div", { className: "text-xs opacity-80", children: "\u6D88\u606F\u540C\u6B65\u53EF\u80FD\u4F1A\u5EF6\u8FDF\uFF0C\u7CFB\u7EDF\u4F1A\u7EE7\u7EED\u81EA\u52A8\u91CD\u8BD5" })
|
|
206
|
+
] })
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
) });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/components/MessageList.tsx
|
|
213
|
+
import { isHiddenInternalMessage } from "@blade-hq/agent-client";
|
|
214
|
+
import { ChevronDown as ChevronDown2, Layers, Lightbulb, MessageSquare } from "lucide-react";
|
|
215
|
+
import { useEffect as useEffect4, useMemo as useMemo4, useRef as useRef3 } from "react";
|
|
216
|
+
import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
|
|
217
|
+
|
|
218
|
+
// src/components/AssistantTurnBlock.tsx
|
|
219
|
+
import { getTextContent, normalizeMessageContent } from "@blade-hq/agent-client";
|
|
220
|
+
import { Brain, ChevronDown } from "lucide-react";
|
|
221
|
+
import { useState as useState6 } from "react";
|
|
222
|
+
|
|
223
|
+
// src/components/AgentLoopBlock.tsx
|
|
224
|
+
import { Bot, Check, ChevronRight, Loader2 as Loader22, X } from "lucide-react";
|
|
225
|
+
import { useState as useState2 } from "react";
|
|
226
|
+
|
|
227
|
+
// src/components/display-utils.ts
|
|
228
|
+
var TOOL_NAME_ALIASES = {
|
|
229
|
+
agent: "Agent",
|
|
230
|
+
ask_user_question: "AskUserQuestion",
|
|
231
|
+
bash: "Bash",
|
|
232
|
+
bg_bash: "BgBash",
|
|
233
|
+
edit: "Edit",
|
|
234
|
+
exit_plan_mode: "ExitPlanMode",
|
|
235
|
+
file_edit: "Edit",
|
|
236
|
+
file_read: "Read",
|
|
237
|
+
file_write: "Write",
|
|
238
|
+
finish_task: "FinishTask",
|
|
239
|
+
glob: "Glob",
|
|
240
|
+
grep: "Grep",
|
|
241
|
+
ls: "Ls",
|
|
242
|
+
read: "Read",
|
|
243
|
+
read_skill: "ReadSkill",
|
|
244
|
+
web_fetch: "WebFetch",
|
|
245
|
+
web_search: "WebSearch",
|
|
246
|
+
write: "Write"
|
|
247
|
+
};
|
|
248
|
+
var TOOL_DISPLAY_LABELS = {
|
|
249
|
+
Bash: "\u6267\u884C\u547D\u4EE4",
|
|
250
|
+
BgBash: "\u540E\u53F0\u6267\u884C\u547D\u4EE4",
|
|
251
|
+
Read: "\u8BFB\u53D6\u6587\u4EF6",
|
|
252
|
+
Write: "\u5199\u5165\u6587\u4EF6",
|
|
253
|
+
Edit: "\u7F16\u8F91\u6587\u4EF6",
|
|
254
|
+
Ls: "\u5217\u51FA\u76EE\u5F55",
|
|
255
|
+
Glob: "\u5339\u914D\u6587\u4EF6",
|
|
256
|
+
Grep: "\u641C\u7D22\u6587\u672C",
|
|
257
|
+
WebSearch: "\u641C\u7D22\u7F51\u9875",
|
|
258
|
+
WebFetch: "\u6574\u7406\u7F51\u9875\u5185\u5BB9",
|
|
259
|
+
Agent: "\u6D3E\u751F\u5B50\u667A\u80FD\u4F53",
|
|
260
|
+
AskUserQuestion: "\u5411\u7528\u6237\u63D0\u95EE",
|
|
261
|
+
ReadSkill: "\u8BFB\u53D6\u6280\u80FD",
|
|
262
|
+
FinishTask: "\u4EFB\u52A1\u5B8C\u6210",
|
|
263
|
+
ExitPlanMode: "\u63D0\u4EA4\u8BA1\u5212",
|
|
264
|
+
ListSessions: "\u5217\u51FA\u5386\u53F2\u4F1A\u8BDD",
|
|
265
|
+
GetSessionHistory: "\u8BFB\u53D6\u4F1A\u8BDD\u5386\u53F2"
|
|
266
|
+
};
|
|
267
|
+
function safeParseJson(value) {
|
|
268
|
+
if (!value) return null;
|
|
269
|
+
try {
|
|
270
|
+
return JSON.parse(value);
|
|
271
|
+
} catch {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function getStringArgValue(args, key) {
|
|
276
|
+
const value = args?.[key];
|
|
277
|
+
return typeof value === "string" ? value.trim() : "";
|
|
278
|
+
}
|
|
279
|
+
function formatToolName(name) {
|
|
280
|
+
const trimmed = name.trim();
|
|
281
|
+
if (!trimmed) return name;
|
|
282
|
+
const stripped = trimmed.split(":").pop()?.split("/").pop()?.split(".").pop()?.trim() || trimmed;
|
|
283
|
+
const normalized = stripped.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
284
|
+
return TOOL_NAME_ALIASES[normalized] ?? stripped;
|
|
285
|
+
}
|
|
286
|
+
function getToolDisplayLabel(toolCall) {
|
|
287
|
+
const normalized = formatToolName(toolCall.name);
|
|
288
|
+
const args = safeParseJson(toolCall.arguments);
|
|
289
|
+
const displayName = toolCall.display_name?.trim() ?? "";
|
|
290
|
+
const baseLabel = displayName || TOOL_DISPLAY_LABELS[normalized] || normalized;
|
|
291
|
+
const metaDisplayName = getStringArgValue(args, "_meta_display_name");
|
|
292
|
+
if (metaDisplayName) {
|
|
293
|
+
return metaDisplayName;
|
|
294
|
+
}
|
|
295
|
+
const description = getStringArgValue(args, "description");
|
|
296
|
+
if (normalized === "BgBash") {
|
|
297
|
+
return description ? `\u540E\u53F0\u6267\u884C\uFF1A${description}` : "\u540E\u53F0\u6267\u884C\u547D\u4EE4";
|
|
298
|
+
}
|
|
299
|
+
if (normalized === "ReadSkill") {
|
|
300
|
+
const skillName = getStringArgValue(args, "skill") || getStringArgValue(args, "skill_name");
|
|
301
|
+
return skillName ? `${baseLabel}\u300C${skillName}\u300D` : baseLabel;
|
|
302
|
+
}
|
|
303
|
+
if (normalized === "FinishTask") {
|
|
304
|
+
const title = getStringArgValue(args, "title");
|
|
305
|
+
return title ? `${baseLabel}\uFF1A${title}` : baseLabel;
|
|
306
|
+
}
|
|
307
|
+
return description || baseLabel;
|
|
308
|
+
}
|
|
309
|
+
function getToolTone(status) {
|
|
310
|
+
if (status === "error" || status === "cancelled") return "red";
|
|
311
|
+
if (status === "awaiting_answer") return "amber";
|
|
312
|
+
if (status === "pending") return "blue";
|
|
313
|
+
return "emerald";
|
|
314
|
+
}
|
|
315
|
+
function getToolStatusLabel(status) {
|
|
316
|
+
if (status === "pending") return "\u8FD0\u884C\u4E2D";
|
|
317
|
+
if (status === "awaiting_answer") return "\u7B49\u5F85\u56DE\u7B54";
|
|
318
|
+
if (status === "error") return "\u9519\u8BEF";
|
|
319
|
+
if (status === "cancelled") return "\u5DF2\u53D6\u6D88";
|
|
320
|
+
return "\u5B8C\u6210";
|
|
321
|
+
}
|
|
322
|
+
function formatToolDuration(ms) {
|
|
323
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
324
|
+
const seconds = ms / 1e3;
|
|
325
|
+
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
326
|
+
const minutes = Math.floor(seconds / 60);
|
|
327
|
+
const remainingSeconds = Math.round(seconds % 60);
|
|
328
|
+
return remainingSeconds > 0 ? `${minutes}m${remainingSeconds}s` : `${minutes}m`;
|
|
329
|
+
}
|
|
330
|
+
function formatToolArgs(args) {
|
|
331
|
+
try {
|
|
332
|
+
return JSON.stringify(JSON.parse(args), null, 2);
|
|
333
|
+
} catch {
|
|
334
|
+
return args;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
var RESULT_PREVIEW_LIMIT = 4e3;
|
|
338
|
+
function formatToolResult(result) {
|
|
339
|
+
const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
340
|
+
if (text == null) return "";
|
|
341
|
+
if (text.length <= RESULT_PREVIEW_LIMIT) return text;
|
|
342
|
+
return `${text.slice(0, RESULT_PREVIEW_LIMIT)}
|
|
343
|
+
\u2026\uFF08\u7ED3\u679C\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\uFF09`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/components/AgentLoopBlock.tsx
|
|
347
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
348
|
+
function parseAgentDescription(argumentsJson) {
|
|
349
|
+
try {
|
|
350
|
+
const parsed = JSON.parse(argumentsJson);
|
|
351
|
+
return typeof parsed?.description === "string" && parsed.description ? parsed.description : "\u5B50\u667A\u80FD\u4F53";
|
|
352
|
+
} catch {
|
|
353
|
+
return "\u5B50\u667A\u80FD\u4F53";
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function AgentLoopBlock({ toolCall }) {
|
|
357
|
+
const [expanded, setExpanded] = useState2(false);
|
|
358
|
+
const description = parseAgentDescription(toolCall.arguments);
|
|
359
|
+
const running = toolCall.status === "pending" || toolCall.status === "awaiting_answer";
|
|
360
|
+
const failed = toolCall.status === "error" || toolCall.status === "cancelled";
|
|
361
|
+
return /* @__PURE__ */ jsxs3("div", { className: "blade-chat-agent-loop ml-4 text-xs", children: [
|
|
362
|
+
/* @__PURE__ */ jsxs3(
|
|
363
|
+
"div",
|
|
364
|
+
{
|
|
365
|
+
className: cn(
|
|
366
|
+
"border-l-[3px] flex items-center gap-2 px-3 py-2",
|
|
367
|
+
failed ? "border-l-[hsl(var(--muted-foreground)/0.5)]" : running ? "border-l-blue-500" : "border-l-[hsl(var(--primary))]"
|
|
368
|
+
),
|
|
369
|
+
children: [
|
|
370
|
+
/* @__PURE__ */ jsxs3(
|
|
371
|
+
"button",
|
|
372
|
+
{
|
|
373
|
+
type: "button",
|
|
374
|
+
onClick: () => setExpanded(!expanded),
|
|
375
|
+
className: "flex min-w-0 flex-1 items-center gap-2 text-left transition-colors hover:bg-white/3 focus-visible:ring-1 focus-visible:ring-[hsl(var(--ring))] focus:outline-none",
|
|
376
|
+
"aria-expanded": expanded,
|
|
377
|
+
children: [
|
|
378
|
+
/* @__PURE__ */ jsx4(
|
|
379
|
+
ChevronRight,
|
|
380
|
+
{
|
|
381
|
+
size: 11,
|
|
382
|
+
className: cn(
|
|
383
|
+
"shrink-0 text-[hsl(var(--muted-foreground))] transition-transform",
|
|
384
|
+
expanded && "rotate-90"
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
),
|
|
388
|
+
/* @__PURE__ */ jsx4(Bot, { size: 12, className: "shrink-0 text-[hsl(var(--muted-foreground))]" }),
|
|
389
|
+
/* @__PURE__ */ jsxs3(
|
|
390
|
+
"span",
|
|
391
|
+
{
|
|
392
|
+
className: cn(
|
|
393
|
+
"flex shrink-0 items-center gap-1 text-[10px]",
|
|
394
|
+
failed ? "text-[hsl(var(--muted-foreground))]" : running ? "text-blue-300" : "text-[hsl(var(--primary))]"
|
|
395
|
+
),
|
|
396
|
+
children: [
|
|
397
|
+
running ? /* @__PURE__ */ jsx4(Loader22, { size: 11, className: "animate-spin" }) : failed ? /* @__PURE__ */ jsx4(X, { size: 11 }) : /* @__PURE__ */ jsx4(Check, { size: 11 }),
|
|
398
|
+
/* @__PURE__ */ jsx4("span", { children: running ? "\u6267\u884C\u4E2D" : failed ? "\u5DF2\u7EC8\u6B62" : "\u5B8C\u6210" })
|
|
399
|
+
]
|
|
400
|
+
}
|
|
401
|
+
),
|
|
402
|
+
/* @__PURE__ */ jsxs3("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: [
|
|
403
|
+
"\u5B50\u667A\u80FD\u4F53\uFF1A",
|
|
404
|
+
description
|
|
405
|
+
] })
|
|
406
|
+
]
|
|
407
|
+
}
|
|
408
|
+
),
|
|
409
|
+
typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 && /* @__PURE__ */ jsx4("span", { className: "shrink-0 font-mono text-[10px] text-[hsl(var(--muted-foreground))]", children: formatToolDuration(toolCall.duration_ms) })
|
|
410
|
+
]
|
|
411
|
+
}
|
|
412
|
+
),
|
|
413
|
+
expanded && toolCall.result != null && /* @__PURE__ */ jsxs3("div", { className: "ml-4 mt-1 rounded-xl bg-[hsl(var(--card))] px-3 py-3", children: [
|
|
414
|
+
/* @__PURE__ */ jsx4("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u7ED3\u679C" }),
|
|
415
|
+
/* @__PURE__ */ jsx4("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) })
|
|
416
|
+
] })
|
|
417
|
+
] });
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/components/MarkdownContent.tsx
|
|
421
|
+
import { Check as Check2, Copy } from "lucide-react";
|
|
422
|
+
import {
|
|
423
|
+
useEffect as useEffect2,
|
|
424
|
+
useMemo as useMemo2,
|
|
425
|
+
useRef as useRef2,
|
|
426
|
+
useState as useState3
|
|
427
|
+
} from "react";
|
|
428
|
+
import { Streamdown } from "streamdown";
|
|
429
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
430
|
+
var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
|
|
431
|
+
function CodeBlockPre({ children, node: _node, ...props }) {
|
|
432
|
+
const preRef = useRef2(null);
|
|
433
|
+
const [copied, setCopied] = useState3(false);
|
|
434
|
+
const [language, setLanguage] = useState3("");
|
|
435
|
+
useEffect2(() => {
|
|
436
|
+
const codeEl = preRef.current?.querySelector("code");
|
|
437
|
+
setLanguage(codeEl?.className.match(/language-(\S+)/)?.[1] ?? "");
|
|
438
|
+
}, []);
|
|
439
|
+
const handleCopy = async () => {
|
|
440
|
+
const ok = await copyToClipboard(preRef.current?.querySelector("code")?.textContent ?? "");
|
|
441
|
+
if (ok) {
|
|
442
|
+
setCopied(true);
|
|
443
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
return /* @__PURE__ */ jsxs4("div", { className: "blade-chat-codeblock not-prose my-3 overflow-hidden rounded-xl border border-[hsl(var(--border))]", children: [
|
|
447
|
+
/* @__PURE__ */ jsxs4("div", { className: "blade-chat-codeblock-header flex h-[34px] items-center justify-between border-b border-[hsl(var(--border))] bg-[hsl(var(--muted))/0.5] pl-3.5 pr-1.5", children: [
|
|
448
|
+
/* @__PURE__ */ jsx5("span", { className: "font-mono text-[12px] text-[hsl(var(--muted-foreground))]", children: language || "code" }),
|
|
449
|
+
/* @__PURE__ */ jsxs4(
|
|
450
|
+
"button",
|
|
451
|
+
{
|
|
452
|
+
type: "button",
|
|
453
|
+
onClick: handleCopy,
|
|
454
|
+
className: cn(
|
|
455
|
+
"flex h-6 items-center gap-1 rounded-md px-1.5 text-[11px] transition-colors",
|
|
456
|
+
copied ? "text-[hsl(var(--primary))]" : "text-[hsl(var(--muted-foreground))] hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--foreground))]"
|
|
457
|
+
),
|
|
458
|
+
children: [
|
|
459
|
+
copied ? /* @__PURE__ */ jsx5(Check2, { size: 12 }) : /* @__PURE__ */ jsx5(Copy, { size: 12 }),
|
|
460
|
+
/* @__PURE__ */ jsx5("span", { children: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236" })
|
|
461
|
+
]
|
|
462
|
+
}
|
|
463
|
+
)
|
|
464
|
+
] }),
|
|
465
|
+
/* @__PURE__ */ jsx5(
|
|
466
|
+
"pre",
|
|
467
|
+
{
|
|
468
|
+
ref: preRef,
|
|
469
|
+
...props,
|
|
470
|
+
className: cn(props.className, "blade-chat-codeblock-body m-0 overflow-x-auto"),
|
|
471
|
+
children
|
|
472
|
+
}
|
|
473
|
+
)
|
|
474
|
+
] });
|
|
475
|
+
}
|
|
476
|
+
function ExternalAnchor({ node: _node, children, ...props }) {
|
|
477
|
+
return /* @__PURE__ */ jsx5("a", { ...props, target: "_blank", rel: "noopener noreferrer", children });
|
|
478
|
+
}
|
|
479
|
+
var MARKDOWN_COMPONENTS = {
|
|
480
|
+
pre: CodeBlockPre,
|
|
481
|
+
a: ExternalAnchor
|
|
482
|
+
};
|
|
483
|
+
function MarkdownContent({ children, className, mode }) {
|
|
484
|
+
const resolvedChildren = useMemo2(() => children.replace(SYSTEM_REMINDER_RE, ""), [children]);
|
|
485
|
+
return /* @__PURE__ */ jsx5(
|
|
486
|
+
Streamdown,
|
|
487
|
+
{
|
|
488
|
+
className: cn("blade-chat-markdown break-words", className),
|
|
489
|
+
mode: mode ?? "static",
|
|
490
|
+
components: MARKDOWN_COMPONENTS,
|
|
491
|
+
children: resolvedChildren
|
|
492
|
+
}
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// src/components/Shimmer.tsx
|
|
497
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
498
|
+
function Shimmer({ children = "\u6B63\u5728\u601D\u8003...", className }) {
|
|
499
|
+
return /* @__PURE__ */ jsx6("span", { className: cn("blade-shimmer-text text-sm font-medium", className), children });
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// src/components/ToolCallBlock.tsx
|
|
503
|
+
import { Check as Check4, ChevronRight as ChevronRight2, Loader2 as Loader24, MessageSquareMore as MessageSquareMore2, X as X2 } from "lucide-react";
|
|
504
|
+
import { useState as useState5 } from "react";
|
|
505
|
+
|
|
506
|
+
// src/components/AskUserQuestionBlock.tsx
|
|
507
|
+
import { Check as Check3, Loader2 as Loader23, MessageSquareMore } from "lucide-react";
|
|
508
|
+
import { useEffect as useEffect3, useMemo as useMemo3, useState as useState4 } from "react";
|
|
509
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
510
|
+
function AskUserQuestionBlock({
|
|
511
|
+
data,
|
|
512
|
+
answered,
|
|
513
|
+
toolCallId,
|
|
514
|
+
sessionStatus,
|
|
515
|
+
answerData,
|
|
516
|
+
onAnswer
|
|
517
|
+
}) {
|
|
518
|
+
const [selections, setSelections] = useState4(/* @__PURE__ */ new Map());
|
|
519
|
+
const [customTexts, setCustomTexts] = useState4(/* @__PURE__ */ new Map());
|
|
520
|
+
const [usingCustom, setUsingCustom] = useState4(/* @__PURE__ */ new Set());
|
|
521
|
+
const [submitted, setSubmitted] = useState4(false);
|
|
522
|
+
useEffect3(() => {
|
|
523
|
+
if (sessionStatus === "failed" || sessionStatus === "interrupted") {
|
|
524
|
+
setSubmitted(false);
|
|
525
|
+
}
|
|
526
|
+
}, [sessionStatus]);
|
|
527
|
+
const displayAnswerState = useMemo3(() => {
|
|
528
|
+
if (!(answered && answerData)) {
|
|
529
|
+
return { selections, customTexts, usingCustom };
|
|
530
|
+
}
|
|
531
|
+
const nextSelections = /* @__PURE__ */ new Map();
|
|
532
|
+
const nextCustomTexts = /* @__PURE__ */ new Map();
|
|
533
|
+
const nextUsingCustom = /* @__PURE__ */ new Set();
|
|
534
|
+
for (const [questionKey, optionIndexes] of Object.entries(answerData.selections)) {
|
|
535
|
+
nextSelections.set(Number(questionKey), new Set(optionIndexes));
|
|
536
|
+
}
|
|
537
|
+
for (const [questionKey, text] of Object.entries(answerData.custom)) {
|
|
538
|
+
const qIdx = Number(questionKey);
|
|
539
|
+
nextCustomTexts.set(qIdx, text);
|
|
540
|
+
nextUsingCustom.add(qIdx);
|
|
541
|
+
}
|
|
542
|
+
return {
|
|
543
|
+
selections: nextSelections,
|
|
544
|
+
customTexts: nextCustomTexts,
|
|
545
|
+
usingCustom: nextUsingCustom
|
|
546
|
+
};
|
|
547
|
+
}, [answerData, answered, customTexts, selections, usingCustom]);
|
|
548
|
+
const toggleOption = (qIdx, optIdx, multi) => {
|
|
549
|
+
if (answered || submitted) return;
|
|
550
|
+
setSelections((prev) => {
|
|
551
|
+
const next = new Map(prev);
|
|
552
|
+
const current = new Set(next.get(qIdx) ?? []);
|
|
553
|
+
if (multi) {
|
|
554
|
+
if (current.has(optIdx)) current.delete(optIdx);
|
|
555
|
+
else current.add(optIdx);
|
|
556
|
+
} else {
|
|
557
|
+
current.clear();
|
|
558
|
+
current.add(optIdx);
|
|
559
|
+
}
|
|
560
|
+
next.set(qIdx, current);
|
|
561
|
+
return next;
|
|
562
|
+
});
|
|
563
|
+
setUsingCustom((prev) => {
|
|
564
|
+
const next = new Set(prev);
|
|
565
|
+
next.delete(qIdx);
|
|
566
|
+
return next;
|
|
567
|
+
});
|
|
568
|
+
};
|
|
569
|
+
const handleCustomFocus = (qIdx) => {
|
|
570
|
+
if (answered || submitted) return;
|
|
571
|
+
setSelections((prev) => {
|
|
572
|
+
const next = new Map(prev);
|
|
573
|
+
next.delete(qIdx);
|
|
574
|
+
return next;
|
|
575
|
+
});
|
|
576
|
+
setUsingCustom((prev) => new Set(prev).add(qIdx));
|
|
577
|
+
};
|
|
578
|
+
const setCustomText = (qIdx, text) => {
|
|
579
|
+
if (answered || submitted) return;
|
|
580
|
+
setCustomTexts((prev) => new Map(prev).set(qIdx, text));
|
|
581
|
+
};
|
|
582
|
+
const getAnswer = (qIdx) => {
|
|
583
|
+
const q = data.questions[qIdx];
|
|
584
|
+
if (usingCustom.has(qIdx)) {
|
|
585
|
+
const text = (customTexts.get(qIdx) ?? "").trim();
|
|
586
|
+
return text || null;
|
|
587
|
+
}
|
|
588
|
+
const sel = selections.get(qIdx);
|
|
589
|
+
if (!sel || sel.size === 0) return null;
|
|
590
|
+
return [...sel].sort().map((i) => q.options[i].label).join(", ");
|
|
591
|
+
};
|
|
592
|
+
const allAnswered = data.questions.every((_, i) => getAnswer(i) !== null);
|
|
593
|
+
const handleSubmit = () => {
|
|
594
|
+
if (answered || submitted || !allAnswered || !onAnswer) return;
|
|
595
|
+
const nextAnswerData = {
|
|
596
|
+
selections: Object.fromEntries(
|
|
597
|
+
Array.from(selections.entries()).map(([qIdx, optionIndexes]) => [
|
|
598
|
+
qIdx,
|
|
599
|
+
Array.from(optionIndexes).sort((a, b) => a - b)
|
|
600
|
+
])
|
|
601
|
+
),
|
|
602
|
+
custom: Object.fromEntries(
|
|
603
|
+
Array.from(usingCustom).map((qIdx) => [qIdx, (customTexts.get(qIdx) ?? "").trim()]).filter(([, text2]) => text2.length > 0)
|
|
604
|
+
)
|
|
605
|
+
};
|
|
606
|
+
const parts = data.questions.map((q, i) => `- ${q.question} -> ${getAnswer(i)}`);
|
|
607
|
+
const text = `\u5173\u4E8E\u9700\u8981\u786E\u8BA4\u7684\u95EE\u9898\uFF0C\u7528\u6237\u7684\u56DE\u7B54\u5982\u4E0B\uFF1A
|
|
608
|
+
${parts.join("\n")}`;
|
|
609
|
+
setSubmitted(true);
|
|
610
|
+
onAnswer(text, toolCallId, nextAnswerData);
|
|
611
|
+
};
|
|
612
|
+
return /* @__PURE__ */ jsxs5(
|
|
613
|
+
"div",
|
|
614
|
+
{
|
|
615
|
+
className: cn(
|
|
616
|
+
"blade-chat-ask-card ml-4 rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))]",
|
|
617
|
+
answered ? "max-w-2xl space-y-3 p-3 text-xs text-[hsl(var(--muted-foreground))] opacity-80" : "max-w-lg space-y-5 p-4 text-sm"
|
|
618
|
+
),
|
|
619
|
+
children: [
|
|
620
|
+
data.source_loop?.description && /* @__PURE__ */ jsxs5("div", { className: "rounded-lg bg-[hsl(var(--muted)/0.35)] px-3 py-2 text-xs text-[hsl(var(--muted-foreground))]", children: [
|
|
621
|
+
"\u5B50\u667A\u80FD\u4F53\u300C",
|
|
622
|
+
data.source_loop.description,
|
|
623
|
+
"\u300D\u5728\u7B49\u5F85\u4F60\u7684\u56DE\u7B54"
|
|
624
|
+
] }),
|
|
625
|
+
data.questions.map((q, qIdx) => /* @__PURE__ */ jsx7(
|
|
626
|
+
QuestionCard,
|
|
627
|
+
{
|
|
628
|
+
question: q,
|
|
629
|
+
qIdx,
|
|
630
|
+
answered,
|
|
631
|
+
selected: displayAnswerState.selections.get(qIdx) ?? /* @__PURE__ */ new Set(),
|
|
632
|
+
isCustom: displayAnswerState.usingCustom.has(qIdx),
|
|
633
|
+
customText: displayAnswerState.customTexts.get(qIdx) ?? "",
|
|
634
|
+
onToggle: toggleOption,
|
|
635
|
+
onCustomFocus: handleCustomFocus,
|
|
636
|
+
onCustomChange: setCustomText
|
|
637
|
+
},
|
|
638
|
+
q.question
|
|
639
|
+
)),
|
|
640
|
+
!answered && !submitted && onAnswer && /* @__PURE__ */ jsx7(
|
|
641
|
+
"button",
|
|
642
|
+
{
|
|
643
|
+
type: "button",
|
|
644
|
+
onClick: handleSubmit,
|
|
645
|
+
disabled: !allAnswered,
|
|
646
|
+
className: "w-full rounded-lg bg-[hsl(var(--primary))] px-4 py-2 text-xs font-semibold text-[hsl(var(--primary-foreground))] transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60",
|
|
647
|
+
children: allAnswered ? "\u786E\u8BA4" : "\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u9009\u9879"
|
|
648
|
+
}
|
|
649
|
+
),
|
|
650
|
+
submitted && !answered && /* @__PURE__ */ jsxs5(
|
|
651
|
+
"button",
|
|
652
|
+
{
|
|
653
|
+
type: "button",
|
|
654
|
+
disabled: true,
|
|
655
|
+
className: "flex w-full items-center justify-center gap-2 rounded-lg bg-[hsl(var(--primary))] px-4 py-2 text-xs font-semibold text-[hsl(var(--primary-foreground))] opacity-80",
|
|
656
|
+
children: [
|
|
657
|
+
/* @__PURE__ */ jsx7(Loader23, { size: 14, className: "animate-spin" }),
|
|
658
|
+
"\u786E\u8BA4\u4E2D"
|
|
659
|
+
]
|
|
660
|
+
}
|
|
661
|
+
)
|
|
662
|
+
]
|
|
663
|
+
}
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
function QuestionCard({
|
|
667
|
+
question,
|
|
668
|
+
qIdx,
|
|
669
|
+
answered,
|
|
670
|
+
selected,
|
|
671
|
+
isCustom,
|
|
672
|
+
customText,
|
|
673
|
+
onToggle,
|
|
674
|
+
onCustomFocus,
|
|
675
|
+
onCustomChange
|
|
676
|
+
}) {
|
|
677
|
+
const multi = question.multiSelect ?? false;
|
|
678
|
+
return /* @__PURE__ */ jsxs5("div", { children: [
|
|
679
|
+
/* @__PURE__ */ jsxs5("div", { className: cn("flex items-start gap-2", answered ? "mb-2" : "mb-3"), children: [
|
|
680
|
+
/* @__PURE__ */ jsx7(
|
|
681
|
+
MessageSquareMore,
|
|
682
|
+
{
|
|
683
|
+
size: answered ? 12 : 13,
|
|
684
|
+
className: "mt-0.5 shrink-0 text-[hsl(var(--primary))]"
|
|
685
|
+
}
|
|
686
|
+
),
|
|
687
|
+
/* @__PURE__ */ jsx7(
|
|
688
|
+
"div",
|
|
689
|
+
{
|
|
690
|
+
className: cn(
|
|
691
|
+
"min-w-0 flex-1 font-medium text-[hsl(var(--foreground))]",
|
|
692
|
+
answered ? "text-xs" : "text-sm"
|
|
693
|
+
),
|
|
694
|
+
children: /* @__PURE__ */ jsx7(MarkdownContent, { className: "blade-chat-prose", children: question.question })
|
|
695
|
+
}
|
|
696
|
+
)
|
|
697
|
+
] }),
|
|
698
|
+
/* @__PURE__ */ jsxs5("div", { className: cn("flex flex-col pl-5", answered ? "gap-1" : "gap-1.5"), children: [
|
|
699
|
+
question.options.map((opt, optIdx) => {
|
|
700
|
+
const isSel = selected.has(optIdx);
|
|
701
|
+
return /* @__PURE__ */ jsxs5(
|
|
702
|
+
"button",
|
|
703
|
+
{
|
|
704
|
+
type: "button",
|
|
705
|
+
disabled: answered,
|
|
706
|
+
onClick: () => onToggle(qIdx, optIdx, multi),
|
|
707
|
+
className: cn(
|
|
708
|
+
"flex items-start gap-2.5 rounded-lg border text-left transition-all",
|
|
709
|
+
answered ? "px-2.5 py-1.5" : "px-3 py-2.5",
|
|
710
|
+
isSel && !answered ? "border-[hsl(var(--primary)/0.8)] bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]" : isSel ? "border-[hsl(var(--primary)/0.45)] bg-[hsl(var(--primary)/0.08)] text-[hsl(var(--foreground))]" : "border-[hsl(var(--border))] bg-[hsl(var(--muted)/0.2)]",
|
|
711
|
+
!answered && !isSel && "hover:border-[hsl(var(--ring)/0.4)] hover:bg-[hsl(var(--accent))]",
|
|
712
|
+
answered && "cursor-default opacity-70"
|
|
713
|
+
),
|
|
714
|
+
children: [
|
|
715
|
+
multi && /* @__PURE__ */ jsx7(
|
|
716
|
+
"div",
|
|
717
|
+
{
|
|
718
|
+
className: cn(
|
|
719
|
+
"mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded border transition-colors",
|
|
720
|
+
isSel && !answered ? "border-[hsl(var(--primary-foreground)/0.6)] bg-[hsl(var(--primary-foreground)/0.2)]" : isSel ? "border-[hsl(var(--primary)/0.45)] bg-[hsl(var(--primary)/0.12)]" : "border-[hsl(var(--border))]"
|
|
721
|
+
),
|
|
722
|
+
children: isSel && /* @__PURE__ */ jsx7(
|
|
723
|
+
Check3,
|
|
724
|
+
{
|
|
725
|
+
size: 9,
|
|
726
|
+
className: answered ? "text-[hsl(var(--primary))]" : "text-[hsl(var(--primary-foreground))]"
|
|
727
|
+
}
|
|
728
|
+
)
|
|
729
|
+
}
|
|
730
|
+
),
|
|
731
|
+
/* @__PURE__ */ jsxs5("div", { className: "min-w-0", children: [
|
|
732
|
+
/* @__PURE__ */ jsx7("div", { className: cn("font-medium", answered ? "text-xs" : "text-[13px]"), children: opt.label }),
|
|
733
|
+
opt.description && /* @__PURE__ */ jsx7(
|
|
734
|
+
"div",
|
|
735
|
+
{
|
|
736
|
+
className: cn(
|
|
737
|
+
"mt-0.5",
|
|
738
|
+
answered ? "text-[11px]" : "text-xs",
|
|
739
|
+
isSel && !answered ? "opacity-75" : "text-[hsl(var(--muted-foreground))]"
|
|
740
|
+
),
|
|
741
|
+
children: opt.description
|
|
742
|
+
}
|
|
743
|
+
)
|
|
744
|
+
] })
|
|
745
|
+
]
|
|
746
|
+
},
|
|
747
|
+
opt.label
|
|
748
|
+
);
|
|
749
|
+
}),
|
|
750
|
+
answered && !isCustom ? null : /* @__PURE__ */ jsxs5(
|
|
751
|
+
"div",
|
|
752
|
+
{
|
|
753
|
+
className: cn(
|
|
754
|
+
"flex items-center gap-2 rounded-lg border transition-all",
|
|
755
|
+
answered ? "px-2.5 py-1.5" : "px-3 py-2.5",
|
|
756
|
+
isCustom ? "border-[hsl(var(--ring)/0.6)] bg-[hsl(var(--accent))]" : "border-[hsl(var(--border))] hover:border-[hsl(var(--ring)/0.3)] hover:bg-[hsl(var(--accent))]",
|
|
757
|
+
answered && "cursor-default opacity-70"
|
|
758
|
+
),
|
|
759
|
+
children: [
|
|
760
|
+
/* @__PURE__ */ jsx7("span", { className: "shrink-0 text-xs text-[hsl(var(--muted-foreground))]", children: "\u5176\u4ED6\uFF1A" }),
|
|
761
|
+
/* @__PURE__ */ jsx7(
|
|
762
|
+
"input",
|
|
763
|
+
{
|
|
764
|
+
type: "text",
|
|
765
|
+
value: customText,
|
|
766
|
+
disabled: answered,
|
|
767
|
+
onChange: (e) => onCustomChange(qIdx, e.target.value),
|
|
768
|
+
onFocus: () => onCustomFocus(qIdx),
|
|
769
|
+
"aria-label": "\u81EA\u5B9A\u4E49\u56DE\u7B54",
|
|
770
|
+
placeholder: "\u8F93\u5165\u4F60\u7684\u7B54\u6848...",
|
|
771
|
+
className: cn(
|
|
772
|
+
"min-w-0 flex-1 bg-transparent text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground)/0.5)]",
|
|
773
|
+
answered ? "text-xs" : "text-sm"
|
|
774
|
+
)
|
|
775
|
+
}
|
|
776
|
+
)
|
|
777
|
+
]
|
|
778
|
+
}
|
|
779
|
+
)
|
|
780
|
+
] })
|
|
781
|
+
] });
|
|
782
|
+
}
|
|
783
|
+
function parseAskUserQuestion(toolResult) {
|
|
784
|
+
if (!toolResult) return null;
|
|
785
|
+
try {
|
|
786
|
+
const parsed = JSON.parse(toolResult);
|
|
787
|
+
let questions = parsed?.questions;
|
|
788
|
+
if (typeof questions === "string") {
|
|
789
|
+
try {
|
|
790
|
+
questions = JSON.parse(questions);
|
|
791
|
+
} catch {
|
|
792
|
+
return null;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (Array.isArray(questions) && questions.length > 0) {
|
|
796
|
+
const normalized = questions.map(normalizeQuestionItem);
|
|
797
|
+
if (normalized.every((question) => question !== null)) {
|
|
798
|
+
return { ...parsed, questions: normalized };
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
} catch {
|
|
802
|
+
}
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
805
|
+
function normalizeQuestionItem(value) {
|
|
806
|
+
if (!value || typeof value !== "object") return null;
|
|
807
|
+
const item = value;
|
|
808
|
+
if (typeof item.question !== "string" || !Array.isArray(item.options)) return null;
|
|
809
|
+
const options = item.options.map(normalizeOptionItem);
|
|
810
|
+
if (!options.every((option) => option !== null)) return null;
|
|
811
|
+
return {
|
|
812
|
+
question: item.question,
|
|
813
|
+
options,
|
|
814
|
+
multiSelect: item.multiSelect === true || item.multi_select === true
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
function normalizeOptionItem(value) {
|
|
818
|
+
if (typeof value === "string") return { label: value, description: "" };
|
|
819
|
+
if (!value || typeof value !== "object") return null;
|
|
820
|
+
const option = value;
|
|
821
|
+
if (typeof option.label !== "string") return null;
|
|
822
|
+
return {
|
|
823
|
+
label: option.label,
|
|
824
|
+
description: typeof option.description === "string" ? option.description : ""
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// src/components/ToolCallBlock.tsx
|
|
829
|
+
import { Fragment, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
830
|
+
function ToolCallBlock({
|
|
831
|
+
toolCall,
|
|
832
|
+
onAnswer,
|
|
833
|
+
answered,
|
|
834
|
+
answerData,
|
|
835
|
+
sessionStatus,
|
|
836
|
+
renderer
|
|
837
|
+
}) {
|
|
838
|
+
const [expanded, setExpanded] = useState5(false);
|
|
839
|
+
const normalizedName = formatToolName(toolCall.name);
|
|
840
|
+
if (renderer) {
|
|
841
|
+
const custom = renderer(toolCall);
|
|
842
|
+
if (custom !== null && custom !== void 0) {
|
|
843
|
+
return /* @__PURE__ */ jsx8(Fragment, { children: custom });
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
if (normalizedName === "AskUserQuestion") {
|
|
847
|
+
const askData = parseAskUserQuestion(typeof toolCall.result === "string" ? toolCall.result : null) ?? parseAskUserQuestion(buildAskUserPayload(toolCall.arguments));
|
|
848
|
+
const canAnswer = toolCall.status === "awaiting_answer" && sessionStatus === "waiting_for_input" && Boolean(onAnswer);
|
|
849
|
+
if (askData) {
|
|
850
|
+
return /* @__PURE__ */ jsx8(
|
|
851
|
+
AskUserQuestionBlock,
|
|
852
|
+
{
|
|
853
|
+
data: askData,
|
|
854
|
+
answered: Boolean(answered) || toolCall.status === "done",
|
|
855
|
+
toolCallId: toolCall.id,
|
|
856
|
+
sessionStatus: sessionStatus ?? "",
|
|
857
|
+
answerData,
|
|
858
|
+
onAnswer: canAnswer ? onAnswer : void 0
|
|
859
|
+
}
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
if (toolCall.status === "pending") {
|
|
863
|
+
return /* @__PURE__ */ jsxs6("div", { className: "ml-4 flex max-w-lg items-center gap-2 rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] p-4 text-sm text-[hsl(var(--muted-foreground))]", children: [
|
|
864
|
+
/* @__PURE__ */ jsx8(Loader24, { size: 14, className: "animate-spin" }),
|
|
865
|
+
/* @__PURE__ */ jsx8("span", { children: "\u6B63\u5728\u51C6\u5907\u95EE\u9898\u2026" })
|
|
866
|
+
] });
|
|
867
|
+
}
|
|
868
|
+
return /* @__PURE__ */ jsxs6("div", { className: "ml-4 max-w-lg rounded-xl border border-amber-500/35 bg-amber-500/10 p-4 text-sm text-[hsl(var(--foreground))]", children: [
|
|
869
|
+
/* @__PURE__ */ jsx8("div", { className: "font-semibold", children: "\u9009\u62E9\u9898\u5185\u5BB9\u6682\u65F6\u65E0\u6CD5\u663E\u793A" }),
|
|
870
|
+
/* @__PURE__ */ jsx8("div", { className: "mt-1 text-xs leading-5 text-[hsl(var(--muted-foreground))]", children: "\u6536\u5230\u7684\u4EA4\u4E92\u6570\u636E\u4E0D\u5B8C\u6574\u3002\u8BF7\u8BA9\u667A\u80FD\u4F53\u91CD\u65B0\u63D0\u95EE\u3002" })
|
|
871
|
+
] });
|
|
872
|
+
}
|
|
873
|
+
const tone = getToolTone(toolCall.status);
|
|
874
|
+
const displayName = getToolDisplayLabel(toolCall);
|
|
875
|
+
const toneClass = tone === "red" ? "border-l-[hsl(var(--muted-foreground)/0.5)]" : tone === "amber" ? "border-l-amber-400" : tone === "blue" ? "border-l-blue-500" : "border-l-[hsl(var(--primary))]";
|
|
876
|
+
const statusIcon = toolCall.status === "pending" ? /* @__PURE__ */ jsx8(Loader24, { size: 11, className: "animate-spin" }) : toolCall.status === "awaiting_answer" ? /* @__PURE__ */ jsx8(MessageSquareMore2, { size: 11 }) : toolCall.status === "cancelled" || toolCall.status === "error" ? /* @__PURE__ */ jsx8(X2, { size: 11 }) : /* @__PURE__ */ jsx8(Check4, { size: 11 });
|
|
877
|
+
const statusTextClass = tone === "red" ? "text-[hsl(var(--muted-foreground))]" : tone === "amber" ? "text-amber-300" : tone === "blue" ? "text-blue-300" : "text-[hsl(var(--primary))]";
|
|
878
|
+
return /* @__PURE__ */ jsxs6("div", { className: "blade-chat-tool ml-4 text-xs", children: [
|
|
879
|
+
/* @__PURE__ */ jsxs6("div", { className: cn("border-l-[3px] flex items-center gap-2 px-3 py-2", toneClass), children: [
|
|
880
|
+
/* @__PURE__ */ jsxs6(
|
|
881
|
+
"button",
|
|
882
|
+
{
|
|
883
|
+
type: "button",
|
|
884
|
+
onClick: () => setExpanded(!expanded),
|
|
885
|
+
className: "flex min-w-0 flex-1 items-center gap-2 text-left transition-colors hover:bg-white/3 focus-visible:ring-1 focus-visible:ring-[hsl(var(--ring))] focus:outline-none",
|
|
886
|
+
"aria-expanded": expanded,
|
|
887
|
+
children: [
|
|
888
|
+
/* @__PURE__ */ jsx8(
|
|
889
|
+
ChevronRight2,
|
|
890
|
+
{
|
|
891
|
+
size: 11,
|
|
892
|
+
className: cn(
|
|
893
|
+
"shrink-0 text-[hsl(var(--muted-foreground))] transition-transform",
|
|
894
|
+
expanded && "rotate-90"
|
|
895
|
+
)
|
|
896
|
+
}
|
|
897
|
+
),
|
|
898
|
+
/* @__PURE__ */ jsxs6("span", { className: cn("flex shrink-0 items-center gap-1 text-[10px]", statusTextClass), children: [
|
|
899
|
+
statusIcon,
|
|
900
|
+
/* @__PURE__ */ jsx8("span", { children: getToolStatusLabel(toolCall.status) })
|
|
901
|
+
] }),
|
|
902
|
+
/* @__PURE__ */ jsx8("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: displayName })
|
|
903
|
+
]
|
|
904
|
+
}
|
|
905
|
+
),
|
|
906
|
+
typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 && /* @__PURE__ */ jsx8("span", { className: "shrink-0 font-mono text-[10px] text-[hsl(var(--muted-foreground))]", children: formatToolDuration(toolCall.duration_ms) })
|
|
907
|
+
] }),
|
|
908
|
+
expanded && /* @__PURE__ */ jsxs6("div", { className: "blade-chat-tool-detail ml-4 mt-1 rounded-xl bg-[hsl(var(--card))] px-3 py-3", children: [
|
|
909
|
+
/* @__PURE__ */ jsx8("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u5DE5\u5177" }),
|
|
910
|
+
/* @__PURE__ */ jsx8("div", { className: "mb-3 font-mono text-[11px] text-[hsl(var(--foreground))]", children: normalizedName }),
|
|
911
|
+
/* @__PURE__ */ jsx8("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u53C2\u6570" }),
|
|
912
|
+
/* @__PURE__ */ jsx8("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) }),
|
|
913
|
+
toolCall.result != null && /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
914
|
+
/* @__PURE__ */ jsx8("div", { className: "mb-1 mt-3 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u7ED3\u679C" }),
|
|
915
|
+
/* @__PURE__ */ jsx8("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) })
|
|
916
|
+
] })
|
|
917
|
+
] })
|
|
918
|
+
] });
|
|
919
|
+
}
|
|
920
|
+
function buildAskUserPayload(argumentsJson) {
|
|
921
|
+
try {
|
|
922
|
+
const parsed = JSON.parse(argumentsJson);
|
|
923
|
+
if (parsed && typeof parsed === "object" && Array.isArray(parsed.questions)) {
|
|
924
|
+
return JSON.stringify({ questions: parsed.questions });
|
|
925
|
+
}
|
|
926
|
+
} catch {
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// src/components/AssistantTurnBlock.tsx
|
|
933
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
934
|
+
function ThinkingBlock({ reasoning, isStreaming }) {
|
|
935
|
+
const [open, setOpen] = useState6(false);
|
|
936
|
+
return /* @__PURE__ */ jsxs7("div", { className: "blade-chat-thinking ml-4 text-sm", children: [
|
|
937
|
+
/* @__PURE__ */ jsxs7(
|
|
938
|
+
"button",
|
|
939
|
+
{
|
|
940
|
+
type: "button",
|
|
941
|
+
onClick: () => setOpen(!open),
|
|
942
|
+
"aria-expanded": open,
|
|
943
|
+
className: "inline-flex items-center gap-1.5 text-[hsl(var(--muted-foreground))] transition-colors hover:text-[hsl(var(--foreground))]",
|
|
944
|
+
children: [
|
|
945
|
+
/* @__PURE__ */ jsx9(Brain, { size: 12, className: "shrink-0" }),
|
|
946
|
+
isStreaming ? /* @__PURE__ */ jsx9(Shimmer, { className: "text-xs", children: "\u6B63\u5728\u601D\u8003..." }) : /* @__PURE__ */ jsx9("span", { children: "\u601D\u8003\u8FC7\u7A0B" }),
|
|
947
|
+
/* @__PURE__ */ jsx9(
|
|
948
|
+
ChevronDown,
|
|
949
|
+
{
|
|
950
|
+
size: 12,
|
|
951
|
+
className: cn("shrink-0 transition-transform", open && "rotate-180")
|
|
952
|
+
}
|
|
953
|
+
)
|
|
954
|
+
]
|
|
955
|
+
}
|
|
956
|
+
),
|
|
957
|
+
open && /* @__PURE__ */ jsx9("div", { className: "mt-1.5 whitespace-pre-wrap border-l-2 border-[hsl(var(--border))] pl-3 text-[11px] leading-5 text-[hsl(var(--muted-foreground))]", children: reasoning })
|
|
958
|
+
] });
|
|
959
|
+
}
|
|
960
|
+
function getMessageText(message) {
|
|
961
|
+
return getTextContent(normalizeMessageContent(message.content)).trim();
|
|
962
|
+
}
|
|
963
|
+
function AssistantTurnBlock({
|
|
964
|
+
messages,
|
|
965
|
+
isStreaming = false,
|
|
966
|
+
askAnswers,
|
|
967
|
+
onAnswer,
|
|
968
|
+
sessionStatus,
|
|
969
|
+
toolCallRenderer
|
|
970
|
+
}) {
|
|
971
|
+
const hasInterrupted = messages.some((message) => message.status === "interrupted");
|
|
972
|
+
const hasAnyContent = messages.some(
|
|
973
|
+
(message) => getMessageText(message) || message.reasoning || (message.tool_calls?.length ?? 0) > 0
|
|
974
|
+
);
|
|
975
|
+
return /* @__PURE__ */ jsxs7("div", { className: "blade-chat-assistant-turn flex flex-col gap-3", children: [
|
|
976
|
+
hasInterrupted && /* @__PURE__ */ jsx9("div", { className: "ml-4 w-fit 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" }),
|
|
977
|
+
messages.map((message, index) => {
|
|
978
|
+
const isLast = index === messages.length - 1;
|
|
979
|
+
const streamingThis = isStreaming && isLast;
|
|
980
|
+
const text = getMessageText(message);
|
|
981
|
+
const toolCalls = message.tool_calls ?? [];
|
|
982
|
+
return /* @__PURE__ */ jsxs7(
|
|
983
|
+
"div",
|
|
984
|
+
{
|
|
985
|
+
className: "flex flex-col gap-3",
|
|
986
|
+
children: [
|
|
987
|
+
message.reasoning && /* @__PURE__ */ jsx9(ThinkingBlock, { reasoning: message.reasoning, isStreaming: streamingThis && !text }),
|
|
988
|
+
text && /* @__PURE__ */ jsx9("div", { className: "blade-chat-assistant-text text-[15px] leading-8 text-[hsl(var(--foreground))]", children: /* @__PURE__ */ jsx9(
|
|
989
|
+
MarkdownContent,
|
|
990
|
+
{
|
|
991
|
+
mode: streamingThis ? "streaming" : "static",
|
|
992
|
+
className: "blade-chat-prose",
|
|
993
|
+
children: text
|
|
994
|
+
}
|
|
995
|
+
) }),
|
|
996
|
+
toolCalls.length > 0 && /* @__PURE__ */ jsx9("div", { className: "flex flex-col gap-2", children: toolCalls.map(
|
|
997
|
+
(toolCall) => formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */ jsx9(AgentLoopBlock, { toolCall }, toolCall.id) : /* @__PURE__ */ jsx9(
|
|
998
|
+
ToolCallBlock,
|
|
999
|
+
{
|
|
1000
|
+
toolCall,
|
|
1001
|
+
answerData: askAnswers?.[toolCall.id],
|
|
1002
|
+
onAnswer,
|
|
1003
|
+
answered: sessionStatus !== "waiting_for_input",
|
|
1004
|
+
sessionStatus,
|
|
1005
|
+
renderer: toolCallRenderer
|
|
1006
|
+
},
|
|
1007
|
+
toolCall.id
|
|
1008
|
+
)
|
|
1009
|
+
) })
|
|
1010
|
+
]
|
|
1011
|
+
},
|
|
1012
|
+
message.entry_id ?? `${message.timestamp ?? "assistant"}-${index}`
|
|
1013
|
+
);
|
|
1014
|
+
}),
|
|
1015
|
+
isStreaming && !hasAnyContent && /* @__PURE__ */ jsx9(Shimmer, { className: "ml-4", children: "\u6B63\u5728\u751F\u6210..." })
|
|
1016
|
+
] });
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/components/RenderErrorBoundary.tsx
|
|
1020
|
+
import { AlertTriangle as AlertTriangle2 } from "lucide-react";
|
|
1021
|
+
import { Component } from "react";
|
|
1022
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1023
|
+
function getFirstComponentName(componentStack) {
|
|
1024
|
+
const match = componentStack.match(/\n\s+at\s+([^\s(]+)/);
|
|
1025
|
+
return match?.[1] ?? null;
|
|
1026
|
+
}
|
|
1027
|
+
var RenderErrorBoundary = class extends Component {
|
|
1028
|
+
state = {
|
|
1029
|
+
componentStack: "",
|
|
1030
|
+
error: null
|
|
1031
|
+
};
|
|
1032
|
+
static getDerivedStateFromError(error) {
|
|
1033
|
+
return {
|
|
1034
|
+
componentStack: "",
|
|
1035
|
+
error
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
componentDidCatch(error, info) {
|
|
1039
|
+
const componentStack = info.componentStack ?? "";
|
|
1040
|
+
console.error(`${this.props.label} render error:`, error, info);
|
|
1041
|
+
this.setState({ componentStack });
|
|
1042
|
+
}
|
|
1043
|
+
componentDidUpdate(prevProps) {
|
|
1044
|
+
if (this.state.error && prevProps.resetKey !== this.props.resetKey) {
|
|
1045
|
+
this.setState({ componentStack: "", error: null });
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
render() {
|
|
1049
|
+
const { children, details, label } = this.props;
|
|
1050
|
+
const { componentStack, error } = this.state;
|
|
1051
|
+
if (!error) {
|
|
1052
|
+
return children;
|
|
1053
|
+
}
|
|
1054
|
+
const componentName = getFirstComponentName(componentStack);
|
|
1055
|
+
return /* @__PURE__ */ jsx10("div", { className: "blade-chat-render-error rounded-xl border border-amber-500/30 bg-amber-500/8 px-4 py-3 text-sm text-amber-100", children: /* @__PURE__ */ jsxs8("div", { className: "flex items-start gap-2", children: [
|
|
1056
|
+
/* @__PURE__ */ jsx10(AlertTriangle2, { className: "mt-0.5 h-4 w-4 shrink-0 text-amber-300" }),
|
|
1057
|
+
/* @__PURE__ */ jsxs8("div", { className: "min-w-0 flex-1", children: [
|
|
1058
|
+
/* @__PURE__ */ jsxs8("div", { className: "font-medium", children: [
|
|
1059
|
+
label,
|
|
1060
|
+
"\u6E32\u67D3\u5931\u8D25"
|
|
1061
|
+
] }),
|
|
1062
|
+
/* @__PURE__ */ jsxs8("div", { className: "mt-1 break-words text-xs leading-5 text-amber-100/75", children: [
|
|
1063
|
+
componentName ? `\u7EC4\u4EF6\uFF1A${componentName}\u3002` : null,
|
|
1064
|
+
error.message || "\u53D1\u751F\u4E86\u672A\u9884\u671F\u7684\u6E32\u67D3\u9519\u8BEF\u3002"
|
|
1065
|
+
] }),
|
|
1066
|
+
details ? /* @__PURE__ */ jsx10("div", { className: "mt-1 truncate text-xs text-amber-100/55", children: details }) : null
|
|
1067
|
+
] })
|
|
1068
|
+
] }) });
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
|
|
1072
|
+
// src/components/UserMessageBubble.tsx
|
|
1073
|
+
import { getFileParts, getImageParts, getTextContent as getTextContent2 } from "@blade-hq/agent-client";
|
|
1074
|
+
import { FileText, Loader2 as Loader25 } from "lucide-react";
|
|
1075
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1076
|
+
function isUserMessage(message) {
|
|
1077
|
+
return message.role === "user";
|
|
1078
|
+
}
|
|
1079
|
+
function isErrorMessage(message) {
|
|
1080
|
+
return message.role === "error";
|
|
1081
|
+
}
|
|
1082
|
+
var isSending = (message) => message.status === "streaming";
|
|
1083
|
+
function UserMessageBubble({ message, className }) {
|
|
1084
|
+
const text = getTextContent2(message.content).trim();
|
|
1085
|
+
const fileParts = getFileParts(message.content);
|
|
1086
|
+
const imageParts = getImageParts(message.content);
|
|
1087
|
+
return /* @__PURE__ */ jsx11("div", { className: cn("blade-chat-user-row flex justify-end", className), children: /* @__PURE__ */ jsxs9("div", { className: "blade-chat-user-col flex max-w-[72%] flex-col items-end gap-3", children: [
|
|
1088
|
+
imageParts.length > 0 && /* @__PURE__ */ jsx11("div", { className: "grid gap-2", children: imageParts.map((part) => /* @__PURE__ */ jsx11(
|
|
1089
|
+
"img",
|
|
1090
|
+
{
|
|
1091
|
+
src: part.image_url.url,
|
|
1092
|
+
alt: "\u7528\u6237\u4E0A\u4F20\u7684\u56FE\u7247",
|
|
1093
|
+
className: "max-h-64 rounded-xl border border-[hsl(var(--user-msg-border))] object-cover"
|
|
1094
|
+
},
|
|
1095
|
+
part.image_url.url
|
|
1096
|
+
)) }),
|
|
1097
|
+
fileParts.length > 0 && /* @__PURE__ */ jsx11("div", { className: "flex flex-col items-end gap-1.5", children: fileParts.map((part) => /* @__PURE__ */ jsxs9(
|
|
1098
|
+
"div",
|
|
1099
|
+
{
|
|
1100
|
+
className: "flex items-center gap-1.5 rounded-lg border border-[hsl(var(--user-msg-border))] bg-[hsl(var(--muted)/0.3)] px-2.5 py-1.5 text-xs text-[hsl(var(--muted-foreground))]",
|
|
1101
|
+
children: [
|
|
1102
|
+
/* @__PURE__ */ jsx11(FileText, { size: 12, className: "shrink-0" }),
|
|
1103
|
+
/* @__PURE__ */ jsx11("span", { className: "max-w-56 truncate", title: part.name, children: part.name })
|
|
1104
|
+
]
|
|
1105
|
+
},
|
|
1106
|
+
`${part.name}-${part.data.length}`
|
|
1107
|
+
)) }),
|
|
1108
|
+
text && /* @__PURE__ */ jsx11("div", { className: "blade-chat-user-bubble max-w-full rounded-[20px] rounded-br-[6px] border border-[hsl(var(--user-msg-border))] bg-[hsl(var(--user-msg-bg))] px-[18px] py-[13px] text-sm leading-[1.65] text-[hsl(var(--user-msg-fg))]", children: /* @__PURE__ */ jsx11(MarkdownContent, { className: "blade-chat-prose", children: text }) }),
|
|
1109
|
+
text && isSending(message) && /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1 pr-1 text-[11px] font-medium text-[hsl(var(--muted-foreground))/0.85]", children: [
|
|
1110
|
+
/* @__PURE__ */ jsx11(Loader25, { size: 11, className: "animate-spin", "aria-hidden": "true" }),
|
|
1111
|
+
/* @__PURE__ */ jsx11("span", { children: "\u53D1\u9001\u4E2D" })
|
|
1112
|
+
] })
|
|
1113
|
+
] }) });
|
|
1114
|
+
}
|
|
1115
|
+
function ErrorMessageBlock({
|
|
1116
|
+
message,
|
|
1117
|
+
className
|
|
1118
|
+
}) {
|
|
1119
|
+
const text = getTextContent2(message.content);
|
|
1120
|
+
return /* @__PURE__ */ jsx11("div", { className: cn("blade-chat-error-row flex justify-center", className), children: /* @__PURE__ */ jsx11("div", { className: "blade-chat-error-block max-w-[85%] border-l-[3px] border-[hsl(var(--destructive))] px-4 py-1 text-sm leading-7 text-[hsl(var(--destructive))]", children: text }) });
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// src/components/MessageList.tsx
|
|
1124
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1125
|
+
function parseModeChange(message) {
|
|
1126
|
+
if (message.kind !== "mode_change" || typeof message.content !== "string") {
|
|
1127
|
+
return null;
|
|
1128
|
+
}
|
|
1129
|
+
try {
|
|
1130
|
+
const parsed = JSON.parse(message.content);
|
|
1131
|
+
if (typeof parsed.from === "string" && typeof parsed.to === "string") {
|
|
1132
|
+
return { from: parsed.from, to: parsed.to };
|
|
1133
|
+
}
|
|
1134
|
+
} catch {
|
|
1135
|
+
}
|
|
1136
|
+
return null;
|
|
1137
|
+
}
|
|
1138
|
+
function getPlanningDividerKind(message) {
|
|
1139
|
+
const modeChange = parseModeChange(message);
|
|
1140
|
+
if (modeChange?.to === "planning") return "enter";
|
|
1141
|
+
if (modeChange?.from === "planning") return "exit";
|
|
1142
|
+
return null;
|
|
1143
|
+
}
|
|
1144
|
+
function getMessageResetSignature(messages) {
|
|
1145
|
+
return messages.map((message) => {
|
|
1146
|
+
const contentSize = typeof message.content === "string" ? message.content.length : message.content.length;
|
|
1147
|
+
const toolSignature = message.tool_calls?.map((toolCall) => `${toolCall.id}:${toolCall.status ?? ""}`).join(",") ?? "";
|
|
1148
|
+
return [
|
|
1149
|
+
message.entry_id ?? message.timestamp ?? message.role,
|
|
1150
|
+
message.status ?? "",
|
|
1151
|
+
contentSize,
|
|
1152
|
+
toolSignature
|
|
1153
|
+
].join(":");
|
|
1154
|
+
}).join("|");
|
|
1155
|
+
}
|
|
1156
|
+
function MessageList({
|
|
1157
|
+
messages,
|
|
1158
|
+
isStreaming,
|
|
1159
|
+
sessionStatus,
|
|
1160
|
+
askAnswers,
|
|
1161
|
+
onAnswer,
|
|
1162
|
+
toolCallRenderer,
|
|
1163
|
+
emptyState,
|
|
1164
|
+
className
|
|
1165
|
+
}) {
|
|
1166
|
+
const renderBlocks = useMemo4(() => {
|
|
1167
|
+
const visible = messages.filter((message) => {
|
|
1168
|
+
if ((message.loop_name ?? "root") !== "root") return false;
|
|
1169
|
+
if (isHiddenInternalMessage(message)) return false;
|
|
1170
|
+
if (message.kind === "compaction") return true;
|
|
1171
|
+
return message.role !== "tool" || getPlanningDividerKind(message) !== null;
|
|
1172
|
+
});
|
|
1173
|
+
const blocks = [];
|
|
1174
|
+
let assistantBuffer = [];
|
|
1175
|
+
let assistantTurnCount = 0;
|
|
1176
|
+
const flushAssistant = () => {
|
|
1177
|
+
if (assistantBuffer.length === 0) return;
|
|
1178
|
+
assistantTurnCount += 1;
|
|
1179
|
+
const first = assistantBuffer[0];
|
|
1180
|
+
blocks.push({
|
|
1181
|
+
type: "assistant_turn",
|
|
1182
|
+
messages: assistantBuffer,
|
|
1183
|
+
key: first.entry_id ?? `assistant-${assistantTurnCount}`,
|
|
1184
|
+
isStreaming: assistantBuffer.some((message) => message.status === "streaming")
|
|
1185
|
+
});
|
|
1186
|
+
assistantBuffer = [];
|
|
1187
|
+
};
|
|
1188
|
+
for (const message of visible) {
|
|
1189
|
+
const dividerKind = getPlanningDividerKind(message);
|
|
1190
|
+
if (dividerKind) {
|
|
1191
|
+
flushAssistant();
|
|
1192
|
+
blocks.push({
|
|
1193
|
+
type: "planning_divider",
|
|
1194
|
+
kind: dividerKind,
|
|
1195
|
+
key: message.entry_id ?? `${dividerKind}-${blocks.length}`
|
|
1196
|
+
});
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
if (message.kind === "compaction") {
|
|
1200
|
+
flushAssistant();
|
|
1201
|
+
blocks.push({ type: "compaction", key: message.entry_id ?? `compaction-${blocks.length}` });
|
|
1202
|
+
continue;
|
|
1203
|
+
}
|
|
1204
|
+
if (message.role === "assistant") {
|
|
1205
|
+
assistantBuffer.push(message);
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1208
|
+
flushAssistant();
|
|
1209
|
+
blocks.push({
|
|
1210
|
+
type: "message",
|
|
1211
|
+
message,
|
|
1212
|
+
key: message.entry_id ?? `${message.role}-${blocks.length}`
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
flushAssistant();
|
|
1216
|
+
const last = blocks[blocks.length - 1];
|
|
1217
|
+
if (isStreaming) {
|
|
1218
|
+
if (last?.type === "assistant_turn") {
|
|
1219
|
+
last.isStreaming = true;
|
|
1220
|
+
} else if (last?.type === "message" && isUserMessage(last.message)) {
|
|
1221
|
+
blocks.push({
|
|
1222
|
+
type: "assistant_turn",
|
|
1223
|
+
messages: [
|
|
1224
|
+
{
|
|
1225
|
+
role: "assistant",
|
|
1226
|
+
content: "",
|
|
1227
|
+
entry_id: `pending-assistant-${last.key}`,
|
|
1228
|
+
status: "streaming",
|
|
1229
|
+
loop_name: "root"
|
|
1230
|
+
}
|
|
1231
|
+
],
|
|
1232
|
+
key: `pending-assistant-${last.key}`,
|
|
1233
|
+
isStreaming: true
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
return blocks;
|
|
1238
|
+
}, [messages, isStreaming]);
|
|
1239
|
+
return /* @__PURE__ */ jsx12("div", { className: cn("blade-chat-messages relative min-h-0 flex-1", className), children: /* @__PURE__ */ jsxs10(StickToBottom, { className: "h-full overflow-y-hidden", initial: "instant", resize: "instant", children: [
|
|
1240
|
+
/* @__PURE__ */ jsx12(StickToBottom.Content, { className: "px-5 pb-7 pt-8 sm:px-10 sm:pt-10", children: /* @__PURE__ */ jsx12("div", { className: "blade-chat-messages-content mx-auto max-w-[748px]", children: /* @__PURE__ */ jsxs10("div", { className: "flex min-w-0 flex-col gap-9", children: [
|
|
1241
|
+
renderBlocks.length === 0 ? emptyState ?? /* @__PURE__ */ jsxs10("div", { className: "flex flex-col items-center justify-center gap-3 py-24 text-[hsl(var(--muted-foreground))]", children: [
|
|
1242
|
+
/* @__PURE__ */ jsx12(MessageSquare, { size: 40, strokeWidth: 1.5 }),
|
|
1243
|
+
/* @__PURE__ */ jsx12("span", { className: "text-base font-medium", children: "\u5F00\u59CB\u5BF9\u8BDD" }),
|
|
1244
|
+
/* @__PURE__ */ jsx12("span", { className: "text-sm opacity-60", children: "\u5728\u4E0B\u65B9\u8F93\u5165\u6D88\u606F\u5F00\u59CB\u804A\u5929" })
|
|
1245
|
+
] }) : renderBlocks.map((block) => {
|
|
1246
|
+
if (block.type === "message") {
|
|
1247
|
+
return /* @__PURE__ */ jsx12("div", { "data-entry-id": block.message.entry_id, children: isUserMessage(block.message) ? /* @__PURE__ */ jsx12(UserMessageBubble, { message: block.message }) : isErrorMessage(block.message) ? /* @__PURE__ */ jsx12(ErrorMessageBlock, { message: block.message }) : null }, block.key);
|
|
1248
|
+
}
|
|
1249
|
+
if (block.type === "assistant_turn") {
|
|
1250
|
+
return /* @__PURE__ */ jsx12("div", { "data-entry-id": block.messages[0]?.entry_id, children: /* @__PURE__ */ jsx12(
|
|
1251
|
+
RenderErrorBoundary,
|
|
1252
|
+
{
|
|
1253
|
+
label: "\u52A9\u624B\u6D88\u606F",
|
|
1254
|
+
details: block.key,
|
|
1255
|
+
resetKey: getMessageResetSignature(block.messages),
|
|
1256
|
+
children: /* @__PURE__ */ jsx12(
|
|
1257
|
+
AssistantTurnBlock,
|
|
1258
|
+
{
|
|
1259
|
+
messages: block.messages,
|
|
1260
|
+
isStreaming: block.isStreaming,
|
|
1261
|
+
askAnswers,
|
|
1262
|
+
onAnswer,
|
|
1263
|
+
sessionStatus,
|
|
1264
|
+
toolCallRenderer
|
|
1265
|
+
}
|
|
1266
|
+
)
|
|
1267
|
+
}
|
|
1268
|
+
) }, block.key);
|
|
1269
|
+
}
|
|
1270
|
+
if (block.type === "compaction") {
|
|
1271
|
+
return /* @__PURE__ */ jsxs10(
|
|
1272
|
+
"div",
|
|
1273
|
+
{
|
|
1274
|
+
className: "flex items-center gap-2 text-xs text-[hsl(var(--muted-foreground))]",
|
|
1275
|
+
children: [
|
|
1276
|
+
/* @__PURE__ */ jsx12(Layers, { size: 12 }),
|
|
1277
|
+
/* @__PURE__ */ jsx12("span", { children: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29" })
|
|
1278
|
+
]
|
|
1279
|
+
},
|
|
1280
|
+
block.key
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
return /* @__PURE__ */ jsx12(PlanningDivider, { kind: block.kind }, block.key);
|
|
1284
|
+
}),
|
|
1285
|
+
sessionStatus === "interrupted" && !isStreaming ? /* @__PURE__ */ jsx12("div", { className: "flex", children: /* @__PURE__ */ jsx12("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
|
|
1286
|
+
] }) }) }),
|
|
1287
|
+
/* @__PURE__ */ jsx12(AutoScrollOnUserSend, { userMessageCount: messages.filter((m) => isUserMessage(m)).length }),
|
|
1288
|
+
/* @__PURE__ */ jsx12(ScrollToBottomButton, {})
|
|
1289
|
+
] }) });
|
|
1290
|
+
}
|
|
1291
|
+
function AutoScrollOnUserSend({ userMessageCount }) {
|
|
1292
|
+
const { scrollToBottom } = useStickToBottomContext();
|
|
1293
|
+
const previousCountRef = useRef3(userMessageCount);
|
|
1294
|
+
useEffect4(() => {
|
|
1295
|
+
if (userMessageCount > previousCountRef.current) {
|
|
1296
|
+
scrollToBottom("instant");
|
|
1297
|
+
}
|
|
1298
|
+
previousCountRef.current = userMessageCount;
|
|
1299
|
+
}, [userMessageCount, scrollToBottom]);
|
|
1300
|
+
return null;
|
|
1301
|
+
}
|
|
1302
|
+
function ScrollToBottomButton() {
|
|
1303
|
+
const { isAtBottom, scrollToBottom } = useStickToBottomContext();
|
|
1304
|
+
if (isAtBottom) return null;
|
|
1305
|
+
return /* @__PURE__ */ jsxs10(
|
|
1306
|
+
"button",
|
|
1307
|
+
{
|
|
1308
|
+
type: "button",
|
|
1309
|
+
onClick: () => scrollToBottom(),
|
|
1310
|
+
className: "absolute bottom-4 right-4 flex items-center gap-1 rounded-full border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-3 py-1.5 text-xs text-[hsl(var(--muted-foreground))] shadow-lg transition-colors hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--foreground))]",
|
|
1311
|
+
children: [
|
|
1312
|
+
/* @__PURE__ */ jsx12(ChevronDown2, { size: 14 }),
|
|
1313
|
+
/* @__PURE__ */ jsx12("span", { children: "\u6EDA\u52A8\u5230\u5E95\u90E8" })
|
|
1314
|
+
]
|
|
1315
|
+
}
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
function PlanningDivider({ kind }) {
|
|
1319
|
+
return /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-3 py-1", children: [
|
|
1320
|
+
/* @__PURE__ */ jsx12("div", { className: "h-px flex-1 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent" }),
|
|
1321
|
+
/* @__PURE__ */ jsxs10("div", { className: "inline-flex items-center gap-1.5 rounded-full border border-amber-500/30 bg-amber-500/10 px-3 py-1 text-[11px] text-amber-300", children: [
|
|
1322
|
+
/* @__PURE__ */ jsx12(Lightbulb, { size: 12 }),
|
|
1323
|
+
/* @__PURE__ */ jsx12("span", { children: kind === "enter" ? "\u8FDB\u5165\u89C4\u5212\u6A21\u5F0F" : "\u89C4\u5212\u5B8C\u6210" })
|
|
1324
|
+
] }),
|
|
1325
|
+
/* @__PURE__ */ jsx12("div", { className: "h-px flex-1 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent" })
|
|
1326
|
+
] });
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
// src/components/ChatView.tsx
|
|
1330
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1331
|
+
function isUnauthorizedError(error) {
|
|
1332
|
+
return error instanceof BladeApiError && error.status === 401;
|
|
1333
|
+
}
|
|
1334
|
+
function LoginCard({ client, onLoggedIn }) {
|
|
1335
|
+
const [loggingIn, setLoggingIn] = useState7(false);
|
|
1336
|
+
const [loginError, setLoginError] = useState7(null);
|
|
1337
|
+
const handleLogin = async () => {
|
|
1338
|
+
setLoggingIn(true);
|
|
1339
|
+
setLoginError(null);
|
|
1340
|
+
try {
|
|
1341
|
+
await client.login();
|
|
1342
|
+
onLoggedIn();
|
|
1343
|
+
} catch (error) {
|
|
1344
|
+
setLoginError(error instanceof Error && error.message ? error.message : "\u767B\u5F55\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5");
|
|
1345
|
+
} finally {
|
|
1346
|
+
setLoggingIn(false);
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
return /* @__PURE__ */ jsx13("div", { className: "blade-chat-login flex flex-1 items-center justify-center p-6", children: /* @__PURE__ */ jsxs11("div", { className: "flex w-full max-w-sm flex-col items-center gap-4 rounded-2xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-6 py-8 text-center", children: [
|
|
1350
|
+
/* @__PURE__ */ jsx13(LockKeyhole, { size: 28, className: "text-[hsl(var(--muted-foreground))]" }),
|
|
1351
|
+
/* @__PURE__ */ jsx13("div", { className: "text-base font-medium text-[hsl(var(--foreground))]", children: "\u9700\u8981\u767B\u5F55\u540E\u4F7F\u7528" }),
|
|
1352
|
+
/* @__PURE__ */ jsx13("div", { className: "text-sm text-[hsl(var(--muted-foreground))]", children: "\u767B\u5F55\u540E\u5373\u53EF\u4E0E\u667A\u80FD\u4F53\u5BF9\u8BDD\uFF0C\u4F60\u7684\u4F1A\u8BDD\u5185\u5BB9\u4EC5\u81EA\u5DF1\u53EF\u89C1\u3002" }),
|
|
1353
|
+
/* @__PURE__ */ jsx13(
|
|
1354
|
+
"button",
|
|
1355
|
+
{
|
|
1356
|
+
type: "button",
|
|
1357
|
+
onClick: handleLogin,
|
|
1358
|
+
disabled: loggingIn,
|
|
1359
|
+
className: "w-full rounded-lg bg-[hsl(var(--primary))] px-4 py-2 text-sm font-semibold text-[hsl(var(--primary-foreground))] transition-opacity hover:opacity-90 disabled:opacity-60",
|
|
1360
|
+
children: loggingIn ? "\u767B\u5F55\u4E2D\u2026" : "\u767B\u5F55"
|
|
1361
|
+
}
|
|
1362
|
+
),
|
|
1363
|
+
loginError && /* @__PURE__ */ jsx13("div", { className: "text-xs text-[hsl(var(--destructive))]", children: loginError })
|
|
1364
|
+
] }) });
|
|
1365
|
+
}
|
|
1366
|
+
function VersionMismatchCard({ error }) {
|
|
1367
|
+
return /* @__PURE__ */ jsx13("div", { className: "blade-chat-version-mismatch flex flex-1 items-center justify-center p-6", children: /* @__PURE__ */ jsxs11("div", { className: "flex w-full max-w-md flex-col gap-3 rounded-2xl border border-amber-500/30 bg-amber-500/10 px-6 py-6", children: [
|
|
1368
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2 text-sm font-medium text-amber-300", children: [
|
|
1369
|
+
/* @__PURE__ */ jsx13(CircleAlert, { size: 16 }),
|
|
1370
|
+
/* @__PURE__ */ jsx13("span", { children: "\u7248\u672C\u4E0D\u5339\u914D" })
|
|
1371
|
+
] }),
|
|
1372
|
+
/* @__PURE__ */ jsx13("div", { className: "break-words text-sm leading-6 text-[hsl(var(--foreground))]", children: error.message })
|
|
1373
|
+
] }) });
|
|
1374
|
+
}
|
|
1375
|
+
function ChatView(props) {
|
|
1376
|
+
const client = useBladeClient();
|
|
1377
|
+
const [attempt, setAttempt] = useState7(0);
|
|
1378
|
+
const [needLogin, setNeedLogin] = useState7(() => !client.hasToken());
|
|
1379
|
+
if (needLogin) {
|
|
1380
|
+
return /* @__PURE__ */ jsx13("div", { className: cn("blade-chat flex min-h-0 flex-1 flex-col", props.classNames?.root), children: /* @__PURE__ */ jsx13(
|
|
1381
|
+
LoginCard,
|
|
1382
|
+
{
|
|
1383
|
+
client,
|
|
1384
|
+
onLoggedIn: () => {
|
|
1385
|
+
setNeedLogin(false);
|
|
1386
|
+
setAttempt((value) => value + 1);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
) });
|
|
1390
|
+
}
|
|
1391
|
+
return /* @__PURE__ */ jsx13(ChatSessionView, { ...props, onUnauthorized: () => setNeedLogin(true) }, attempt);
|
|
1392
|
+
}
|
|
1393
|
+
function ChatSessionView({
|
|
1394
|
+
sessionId,
|
|
1395
|
+
createOptions,
|
|
1396
|
+
onSessionCreated,
|
|
1397
|
+
onSessionReady,
|
|
1398
|
+
commands,
|
|
1399
|
+
classNames,
|
|
1400
|
+
renderers,
|
|
1401
|
+
slots,
|
|
1402
|
+
placeholder,
|
|
1403
|
+
onUnauthorized
|
|
1404
|
+
}) {
|
|
1405
|
+
const { session, state, error } = useAgentSession(sessionId, {
|
|
1406
|
+
createOptions,
|
|
1407
|
+
onSessionCreated
|
|
1408
|
+
});
|
|
1409
|
+
const [stopRequested, setStopRequested] = useState7(false);
|
|
1410
|
+
const [inputText, setInputText] = useState7("");
|
|
1411
|
+
useEffect5(() => {
|
|
1412
|
+
if (session) {
|
|
1413
|
+
onSessionReady?.(session);
|
|
1414
|
+
}
|
|
1415
|
+
}, [session, onSessionReady]);
|
|
1416
|
+
useEffect5(() => {
|
|
1417
|
+
if (!session) return;
|
|
1418
|
+
const offAttach = session.on("attachRequested", ({ label, content }) => {
|
|
1419
|
+
setInputText((prev) => `${prev ? `${prev}
|
|
1420
|
+
|
|
1421
|
+
` : ""}[${label}]
|
|
1422
|
+
${content}`);
|
|
1423
|
+
});
|
|
1424
|
+
const offInsert = session.on("insertTextRequested", ({ text }) => {
|
|
1425
|
+
setInputText((prev) => prev + text);
|
|
1426
|
+
});
|
|
1427
|
+
return () => {
|
|
1428
|
+
offAttach();
|
|
1429
|
+
offInsert();
|
|
1430
|
+
};
|
|
1431
|
+
}, [session]);
|
|
1432
|
+
useEffect5(() => {
|
|
1433
|
+
if (isUnauthorizedError(error)) {
|
|
1434
|
+
onUnauthorized();
|
|
1435
|
+
}
|
|
1436
|
+
}, [error, onUnauthorized]);
|
|
1437
|
+
useEffect5(() => {
|
|
1438
|
+
if (!session || !commands) return;
|
|
1439
|
+
const unsubscribes = Object.entries(commands).map(
|
|
1440
|
+
([action, handler]) => session.onCommand(action, (payload) => handler(payload))
|
|
1441
|
+
);
|
|
1442
|
+
return () => {
|
|
1443
|
+
for (const unsubscribe of unsubscribes) unsubscribe();
|
|
1444
|
+
};
|
|
1445
|
+
}, [session, commands]);
|
|
1446
|
+
if (error instanceof VersionMismatchError) {
|
|
1447
|
+
return /* @__PURE__ */ jsx13("div", { className: cn("blade-chat flex min-h-0 flex-1 flex-col", classNames?.root), children: /* @__PURE__ */ jsx13(VersionMismatchCard, { error }) });
|
|
1448
|
+
}
|
|
1449
|
+
const isStreaming = state?.isStreaming ?? false;
|
|
1450
|
+
const isStopping = stopRequested && isStreaming;
|
|
1451
|
+
const connectError = error && !isUnauthorizedError(error) ? error.message || "\u8FDE\u63A5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" : null;
|
|
1452
|
+
const errorMessage = connectError ?? state?.errorMessage ?? null;
|
|
1453
|
+
const handleSend = (text) => {
|
|
1454
|
+
setStopRequested(false);
|
|
1455
|
+
void session?.send(text, { mode: state?.mode ?? void 0 });
|
|
1456
|
+
};
|
|
1457
|
+
const handleStop = () => {
|
|
1458
|
+
setStopRequested(true);
|
|
1459
|
+
void session?.stop();
|
|
1460
|
+
};
|
|
1461
|
+
return /* @__PURE__ */ jsxs11(
|
|
1462
|
+
"div",
|
|
1463
|
+
{
|
|
1464
|
+
className: cn(
|
|
1465
|
+
"blade-chat flex min-h-0 flex-1 flex-col overflow-hidden",
|
|
1466
|
+
classNames?.root
|
|
1467
|
+
),
|
|
1468
|
+
children: [
|
|
1469
|
+
/* @__PURE__ */ jsx13(
|
|
1470
|
+
ConnectionBanner,
|
|
1471
|
+
{
|
|
1472
|
+
connection: state?.connection ?? "connecting",
|
|
1473
|
+
className: classNames?.banner
|
|
1474
|
+
}
|
|
1475
|
+
),
|
|
1476
|
+
errorMessage && /* @__PURE__ */ jsxs11("div", { className: "blade-chat-error-bar flex items-start gap-2 border-b border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800 dark:border-red-800/30 dark:bg-red-950/30 dark:text-red-300", children: [
|
|
1477
|
+
/* @__PURE__ */ jsx13(CircleAlert, { size: 16, className: "mt-0.5 shrink-0" }),
|
|
1478
|
+
/* @__PURE__ */ jsx13("span", { children: errorMessage })
|
|
1479
|
+
] }),
|
|
1480
|
+
slots?.header,
|
|
1481
|
+
/* @__PURE__ */ jsx13(
|
|
1482
|
+
MessageList,
|
|
1483
|
+
{
|
|
1484
|
+
messages: state?.messages ?? [],
|
|
1485
|
+
isStreaming,
|
|
1486
|
+
sessionStatus: state?.status ?? void 0,
|
|
1487
|
+
askAnswers: state?.askAnswers,
|
|
1488
|
+
onAnswer: (answer, toolCallId, answerData) => {
|
|
1489
|
+
void session?.send(answer, {
|
|
1490
|
+
mode: state?.mode ?? void 0,
|
|
1491
|
+
askUserAnswer: { tool_call_id: toolCallId, ...answerData }
|
|
1492
|
+
});
|
|
1493
|
+
},
|
|
1494
|
+
toolCallRenderer: renderers?.toolCall,
|
|
1495
|
+
emptyState: slots?.emptyState,
|
|
1496
|
+
className: classNames?.messageList
|
|
1497
|
+
}
|
|
1498
|
+
),
|
|
1499
|
+
/* @__PURE__ */ jsx13(
|
|
1500
|
+
ChatInput,
|
|
1501
|
+
{
|
|
1502
|
+
value: inputText,
|
|
1503
|
+
onValueChange: setInputText,
|
|
1504
|
+
onSend: handleSend,
|
|
1505
|
+
onStop: handleStop,
|
|
1506
|
+
isStreaming,
|
|
1507
|
+
isStopping,
|
|
1508
|
+
placeholder,
|
|
1509
|
+
className: classNames?.chatInput
|
|
1510
|
+
}
|
|
1511
|
+
),
|
|
1512
|
+
slots?.footer
|
|
1513
|
+
]
|
|
1514
|
+
}
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// src/index.ts
|
|
1519
|
+
export * from "@blade-hq/agent-client";
|
|
1520
|
+
export {
|
|
1521
|
+
BladeProvider,
|
|
1522
|
+
ChatView,
|
|
1523
|
+
MarkdownContent,
|
|
1524
|
+
useAgentSession,
|
|
1525
|
+
useBladeClient
|
|
1526
|
+
};
|
|
1527
|
+
//# sourceMappingURL=index.js.map
|