@blade-hq/agent-kit 0.0.0-placeholder.0 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -3
- package/dist/AskUserQuestionBlock-CjvG_pUY.d.ts +116 -0
- package/dist/SkillStatusBar-DItrW2vv.d.ts +203 -0
- package/dist/blade-client-nOsdVlb1.d.ts +1498 -0
- package/dist/client/index.d.ts +8036 -0
- package/dist/client/index.js +1057 -0
- package/dist/client/index.js.map +1 -0
- package/dist/licenses-Cxl1xGVy.d.ts +16 -0
- package/dist/projection-DIfyh6RK.d.ts +85 -0
- package/dist/react/api/licenses.d.ts +7 -0
- package/dist/react/api/licenses.js +1477 -0
- package/dist/react/api/licenses.js.map +1 -0
- package/dist/react/api/vibe-coding.d.ts +55 -0
- package/dist/react/api/vibe-coding.js +1503 -0
- package/dist/react/api/vibe-coding.js.map +1 -0
- package/dist/react/cards/register.d.ts +2 -0
- package/dist/react/cards/register.js +4367 -0
- package/dist/react/cards/register.js.map +1 -0
- package/dist/react/components/chat/index.d.ts +128 -0
- package/dist/react/components/chat/index.js +11389 -0
- package/dist/react/components/chat/index.js.map +1 -0
- package/dist/react/components/plan/index.d.ts +111 -0
- package/dist/react/components/plan/index.js +3490 -0
- package/dist/react/components/plan/index.js.map +1 -0
- package/dist/react/components/session/index.d.ts +53 -0
- package/dist/react/components/session/index.js +2175 -0
- package/dist/react/components/session/index.js.map +1 -0
- package/dist/react/components/workspace/index.d.ts +35 -0
- package/dist/react/components/workspace/index.js +2886 -0
- package/dist/react/components/workspace/index.js.map +1 -0
- package/dist/react/devtools/bridge-devtools/index.d.ts +36 -0
- package/dist/react/devtools/bridge-devtools/index.js +692 -0
- package/dist/react/devtools/bridge-devtools/index.js.map +1 -0
- package/dist/react/index.d.ts +1283 -0
- package/dist/react/index.js +14299 -0
- package/dist/react/index.js.map +1 -0
- package/dist/session-CDeiO81j.d.ts +128 -0
- package/package.json +73 -7
- package/index.js +0 -1
|
@@ -0,0 +1,3490 @@
|
|
|
1
|
+
// src/react/components/plan/debug-log.ts
|
|
2
|
+
var events = [];
|
|
3
|
+
var verbose = typeof window !== "undefined" && (window.location?.hostname === "localhost" || window.location?.hostname === "127.0.0.1");
|
|
4
|
+
function exportPlanLog() {
|
|
5
|
+
return [...events];
|
|
6
|
+
}
|
|
7
|
+
function downloadPlanLog() {
|
|
8
|
+
const blob = new Blob([JSON.stringify(events, null, 2)], { type: "application/json" });
|
|
9
|
+
const url = URL.createObjectURL(blob);
|
|
10
|
+
const a = document.createElement("a");
|
|
11
|
+
a.href = url;
|
|
12
|
+
a.download = `plan-timeline-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}.json`;
|
|
13
|
+
a.click();
|
|
14
|
+
URL.revokeObjectURL(url);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/react/components/plan/extract-plan-messages.ts
|
|
18
|
+
function parseModeChange(message) {
|
|
19
|
+
if (message.kind !== "mode_change" || typeof message.content !== "string") {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(message.content);
|
|
24
|
+
if (typeof parsed.from === "string" && typeof parsed.to === "string") {
|
|
25
|
+
return { from: parsed.from, to: parsed.to };
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
function isPlanningEnter(message) {
|
|
32
|
+
if (message.kind === "planning_enter") {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
const modeChange = parseModeChange(message);
|
|
36
|
+
return modeChange?.to === "planning";
|
|
37
|
+
}
|
|
38
|
+
function isPlanningExit(message) {
|
|
39
|
+
if (message.kind === "planning_exit") {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
const modeChange = parseModeChange(message);
|
|
43
|
+
return modeChange?.from === "planning";
|
|
44
|
+
}
|
|
45
|
+
function extractLatestPlanMessages(messages) {
|
|
46
|
+
const safeMessages = Array.isArray(messages) ? messages : [];
|
|
47
|
+
let enterIndex = -1;
|
|
48
|
+
let exitIndex = -1;
|
|
49
|
+
for (let i = safeMessages.length - 1; i >= 0; i -= 1) {
|
|
50
|
+
const message = safeMessages[i];
|
|
51
|
+
if (isPlanningExit(message) && exitIndex === -1) {
|
|
52
|
+
exitIndex = i;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (isPlanningEnter(message)) {
|
|
56
|
+
enterIndex = i;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (enterIndex === -1) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
return safeMessages.slice(enterIndex + 1).filter((message, offset) => {
|
|
64
|
+
const absoluteIndex = enterIndex + 1 + offset;
|
|
65
|
+
if (isPlanningEnter(message) || isPlanningExit(message)) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
if (exitIndex !== -1 && absoluteIndex > exitIndex) {
|
|
69
|
+
return message.kind === "plan_status";
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/react/components/plan/PlanSummaryCard.tsx
|
|
76
|
+
import { CheckCircle2, ChevronRight, PencilLine, Play, Sparkles } from "lucide-react";
|
|
77
|
+
import { useMemo as useMemo4, useState as useState4 } from "react";
|
|
78
|
+
|
|
79
|
+
// src/react/lib/utils.ts
|
|
80
|
+
import { clsx } from "clsx";
|
|
81
|
+
import { twMerge } from "tailwind-merge";
|
|
82
|
+
function cn(...inputs) {
|
|
83
|
+
return twMerge(clsx(inputs));
|
|
84
|
+
}
|
|
85
|
+
async function copyToClipboard(text) {
|
|
86
|
+
if (navigator.clipboard) {
|
|
87
|
+
try {
|
|
88
|
+
await navigator.clipboard.writeText(text);
|
|
89
|
+
return true;
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const textarea = document.createElement("textarea");
|
|
94
|
+
textarea.value = text;
|
|
95
|
+
textarea.style.position = "fixed";
|
|
96
|
+
textarea.style.opacity = "0";
|
|
97
|
+
document.body.appendChild(textarea);
|
|
98
|
+
textarea.select();
|
|
99
|
+
try {
|
|
100
|
+
return document.execCommand("copy");
|
|
101
|
+
} finally {
|
|
102
|
+
document.body.removeChild(textarea);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/react/stores/ui-store.ts
|
|
107
|
+
import { create } from "zustand";
|
|
108
|
+
|
|
109
|
+
// src/react/stores/client-aware.ts
|
|
110
|
+
function createClientActions(set) {
|
|
111
|
+
return {
|
|
112
|
+
_client: null,
|
|
113
|
+
setClient: (client) => set({ _client: client })
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/react/stores/ui-store.ts
|
|
118
|
+
var isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
119
|
+
function resolveEffectiveTheme(theme) {
|
|
120
|
+
if (theme !== "system") return theme;
|
|
121
|
+
return isBrowser && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
122
|
+
}
|
|
123
|
+
function applyTheme(theme) {
|
|
124
|
+
if (!isBrowser) return;
|
|
125
|
+
const effective = resolveEffectiveTheme(theme);
|
|
126
|
+
document.documentElement.setAttribute("data-theme", effective);
|
|
127
|
+
}
|
|
128
|
+
var storedTheme = isBrowser ? localStorage.getItem("blade-theme") ?? "light" : "light";
|
|
129
|
+
applyTheme(storedTheme);
|
|
130
|
+
if (isBrowser) {
|
|
131
|
+
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
|
|
132
|
+
const current = useUiStore?.getState?.()?.theme;
|
|
133
|
+
if (current === "system") applyTheme("system");
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function removeArtifactAtIndex(state, index) {
|
|
137
|
+
const next = state.artifacts.filter((_, i) => i !== index);
|
|
138
|
+
let nextActive = state.activeArtifactIndex;
|
|
139
|
+
if (next.length === 0) {
|
|
140
|
+
return { artifacts: [], activeArtifactIndex: -1, rightPanelCollapsed: true };
|
|
141
|
+
}
|
|
142
|
+
if (index < nextActive) {
|
|
143
|
+
nextActive -= 1;
|
|
144
|
+
} else if (index === nextActive) {
|
|
145
|
+
nextActive = Math.min(index, next.length - 1);
|
|
146
|
+
}
|
|
147
|
+
return { artifacts: next, activeArtifactIndex: nextActive };
|
|
148
|
+
}
|
|
149
|
+
function upsertArtifactState(state, target, options) {
|
|
150
|
+
const reveal = options?.reveal ?? true;
|
|
151
|
+
const activate = options?.activate ?? true;
|
|
152
|
+
const targetKey = target.key ?? target.title;
|
|
153
|
+
const applyUiState = (partial) => ({
|
|
154
|
+
...partial,
|
|
155
|
+
...reveal ? { rightPanelCollapsed: false, activeRightTab: "preview" } : {}
|
|
156
|
+
});
|
|
157
|
+
const existing = state.artifacts.findIndex((artifact) => targetKey && (artifact.key ?? artifact.title) === targetKey);
|
|
158
|
+
if (existing >= 0) {
|
|
159
|
+
const updated = [...state.artifacts];
|
|
160
|
+
updated[existing] = target;
|
|
161
|
+
return applyUiState({
|
|
162
|
+
artifacts: updated,
|
|
163
|
+
activeArtifactIndex: activate ? existing : state.activeArtifactIndex
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
const next = [...state.artifacts, target];
|
|
167
|
+
return applyUiState({
|
|
168
|
+
artifacts: next,
|
|
169
|
+
activeArtifactIndex: activate ? next.length - 1 : state.activeArtifactIndex >= 0 ? state.activeArtifactIndex : 0
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
var useUiStore = create()((set) => ({
|
|
173
|
+
...createClientActions(set),
|
|
174
|
+
leftPanelSize: 20,
|
|
175
|
+
rightPanelSize: 25,
|
|
176
|
+
leftPanelCollapsed: false,
|
|
177
|
+
rightPanelCollapsed: false,
|
|
178
|
+
activeRightTab: "situation",
|
|
179
|
+
artifacts: [],
|
|
180
|
+
activeArtifactIndex: -1,
|
|
181
|
+
theme: storedTheme,
|
|
182
|
+
setLeftPanelSize: (size) => set({ leftPanelSize: size }),
|
|
183
|
+
setRightPanelSize: (size) => set({ rightPanelSize: size }),
|
|
184
|
+
setLeftPanelCollapsed: (collapsed) => set({ leftPanelCollapsed: collapsed }),
|
|
185
|
+
setRightPanelCollapsed: (collapsed) => set({ rightPanelCollapsed: collapsed }),
|
|
186
|
+
toggleLeftPanel: () => set((s) => ({ leftPanelCollapsed: !s.leftPanelCollapsed })),
|
|
187
|
+
toggleRightPanel: () => set((s) => ({ rightPanelCollapsed: !s.rightPanelCollapsed })),
|
|
188
|
+
setActiveRightTab: (tab) => set({ activeRightTab: tab }),
|
|
189
|
+
pushArtifact: (target) => set((state) => upsertArtifactState(state, target)),
|
|
190
|
+
upsertArtifact: (target, options) => set((state) => upsertArtifactState(state, target, options)),
|
|
191
|
+
setActiveArtifact: (index) => set({ activeArtifactIndex: index }),
|
|
192
|
+
closeArtifact: (index) => set((state) => removeArtifactAtIndex(state, index)),
|
|
193
|
+
removeArtifactByKey: (key) => set((state) => {
|
|
194
|
+
const index = state.artifacts.findIndex((artifact) => (artifact.key ?? artifact.title) === key);
|
|
195
|
+
if (index < 0) return state;
|
|
196
|
+
return removeArtifactAtIndex(state, index);
|
|
197
|
+
}),
|
|
198
|
+
clearArtifacts: () => set({ artifacts: [], activeArtifactIndex: -1 }),
|
|
199
|
+
setPreviewTarget: (target) => set(() => {
|
|
200
|
+
if (target === null) {
|
|
201
|
+
return { artifacts: [], activeArtifactIndex: -1 };
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
artifacts: [target],
|
|
205
|
+
activeArtifactIndex: 0,
|
|
206
|
+
rightPanelCollapsed: false,
|
|
207
|
+
activeRightTab: "preview"
|
|
208
|
+
};
|
|
209
|
+
}),
|
|
210
|
+
setTheme: (theme) => {
|
|
211
|
+
localStorage.setItem("blade-theme", theme);
|
|
212
|
+
applyTheme(theme);
|
|
213
|
+
set({ theme });
|
|
214
|
+
}
|
|
215
|
+
}));
|
|
216
|
+
|
|
217
|
+
// src/react/components/chat/AskUserQuestionBlock.tsx
|
|
218
|
+
import { Check as Check2, Loader2, MessageSquareMore } from "lucide-react";
|
|
219
|
+
import { useEffect as useEffect3, useMemo as useMemo3, useState as useState3 } from "react";
|
|
220
|
+
|
|
221
|
+
// src/react/components/markdown/MarkdownContent.tsx
|
|
222
|
+
import { mermaid } from "@streamdown/mermaid";
|
|
223
|
+
import { Check, Copy, Download } from "lucide-react";
|
|
224
|
+
import {
|
|
225
|
+
useEffect as useEffect2,
|
|
226
|
+
useMemo as useMemo2,
|
|
227
|
+
useRef,
|
|
228
|
+
useState as useState2
|
|
229
|
+
} from "react";
|
|
230
|
+
import { Streamdown } from "streamdown";
|
|
231
|
+
|
|
232
|
+
// src/client/resources/models.ts
|
|
233
|
+
import { type } from "arktype";
|
|
234
|
+
var ModelOption = type({
|
|
235
|
+
id: "string",
|
|
236
|
+
label: "string"
|
|
237
|
+
});
|
|
238
|
+
var ModelsConfig = type({
|
|
239
|
+
default: "string",
|
|
240
|
+
models: ModelOption.array()
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// src/client/socket.ts
|
|
244
|
+
import { io } from "socket.io-client";
|
|
245
|
+
|
|
246
|
+
// src/react/schemas/partner-skill.ts
|
|
247
|
+
import { type as type2 } from "arktype";
|
|
248
|
+
var PartnerSkillName = type2("/^[a-z0-9-]+\\/[a-z0-9-]+$/");
|
|
249
|
+
var PartnerSkillFile = type2({
|
|
250
|
+
path: "string > 0",
|
|
251
|
+
content: "string"
|
|
252
|
+
});
|
|
253
|
+
var PartnerSkillInstallPayload = type2({
|
|
254
|
+
name: PartnerSkillName,
|
|
255
|
+
files: PartnerSkillFile.array().atLeastLength(1)
|
|
256
|
+
});
|
|
257
|
+
var PartnerSkillInstallResult = type2({
|
|
258
|
+
name: PartnerSkillName,
|
|
259
|
+
skill_dir: "string",
|
|
260
|
+
file_count: "number.integer >= 0",
|
|
261
|
+
overwritten: "boolean"
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// src/react/stores/ui-bridge-store.ts
|
|
265
|
+
import { create as create2 } from "zustand";
|
|
266
|
+
function buildSignalId() {
|
|
267
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
268
|
+
return crypto.randomUUID();
|
|
269
|
+
}
|
|
270
|
+
return `bridge-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
271
|
+
}
|
|
272
|
+
function clearSignalRecord(record, sessionId) {
|
|
273
|
+
if (!(sessionId in record)) {
|
|
274
|
+
return record;
|
|
275
|
+
}
|
|
276
|
+
const next = { ...record };
|
|
277
|
+
delete next[sessionId];
|
|
278
|
+
return next;
|
|
279
|
+
}
|
|
280
|
+
var useUiBridgeStore = create2()((set, get) => ({
|
|
281
|
+
...createClientActions(set),
|
|
282
|
+
pendingContexts: {},
|
|
283
|
+
draftAppends: {},
|
|
284
|
+
sendRequests: {},
|
|
285
|
+
addPendingContext: (sessionId, context) => set((state) => ({
|
|
286
|
+
pendingContexts: {
|
|
287
|
+
...state.pendingContexts,
|
|
288
|
+
[sessionId]: [
|
|
289
|
+
...state.pendingContexts[sessionId] ?? [],
|
|
290
|
+
{
|
|
291
|
+
id: buildSignalId(),
|
|
292
|
+
label: context.label,
|
|
293
|
+
content: context.content
|
|
294
|
+
}
|
|
295
|
+
]
|
|
296
|
+
}
|
|
297
|
+
})),
|
|
298
|
+
removePendingContext: (sessionId, contextId) => set((state) => {
|
|
299
|
+
const contexts = state.pendingContexts[sessionId];
|
|
300
|
+
if (!contexts?.length) {
|
|
301
|
+
return state;
|
|
302
|
+
}
|
|
303
|
+
const nextContexts = contexts.filter((context) => context.id !== contextId);
|
|
304
|
+
if (nextContexts.length === contexts.length) {
|
|
305
|
+
return state;
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
pendingContexts: nextContexts.length > 0 ? {
|
|
309
|
+
...state.pendingContexts,
|
|
310
|
+
[sessionId]: nextContexts
|
|
311
|
+
} : clearSignalRecord(state.pendingContexts, sessionId)
|
|
312
|
+
};
|
|
313
|
+
}),
|
|
314
|
+
consumePendingContexts: (sessionId) => {
|
|
315
|
+
const signals = get().pendingContexts[sessionId] ?? [];
|
|
316
|
+
if (signals.length === 0) return [];
|
|
317
|
+
set((state) => ({
|
|
318
|
+
pendingContexts: clearSignalRecord(state.pendingContexts, sessionId)
|
|
319
|
+
}));
|
|
320
|
+
return signals;
|
|
321
|
+
},
|
|
322
|
+
clearPendingContexts: (sessionId) => set((state) => ({
|
|
323
|
+
pendingContexts: clearSignalRecord(state.pendingContexts, sessionId)
|
|
324
|
+
})),
|
|
325
|
+
addDraftAppend: (sessionId, text) => set((state) => ({
|
|
326
|
+
draftAppends: {
|
|
327
|
+
...state.draftAppends,
|
|
328
|
+
[sessionId]: [
|
|
329
|
+
...state.draftAppends[sessionId] ?? [],
|
|
330
|
+
{
|
|
331
|
+
id: buildSignalId(),
|
|
332
|
+
text
|
|
333
|
+
}
|
|
334
|
+
]
|
|
335
|
+
}
|
|
336
|
+
})),
|
|
337
|
+
consumeDraftAppends: (sessionId) => {
|
|
338
|
+
const signals = get().draftAppends[sessionId] ?? [];
|
|
339
|
+
if (signals.length === 0) return [];
|
|
340
|
+
set((state) => ({
|
|
341
|
+
draftAppends: clearSignalRecord(state.draftAppends, sessionId)
|
|
342
|
+
}));
|
|
343
|
+
return signals;
|
|
344
|
+
},
|
|
345
|
+
clearDraftAppends: (sessionId) => set((state) => ({
|
|
346
|
+
draftAppends: clearSignalRecord(state.draftAppends, sessionId)
|
|
347
|
+
})),
|
|
348
|
+
addSendRequest: (sessionId) => set((state) => ({
|
|
349
|
+
sendRequests: {
|
|
350
|
+
...state.sendRequests,
|
|
351
|
+
[sessionId]: [
|
|
352
|
+
...state.sendRequests[sessionId] ?? [],
|
|
353
|
+
{
|
|
354
|
+
id: buildSignalId()
|
|
355
|
+
}
|
|
356
|
+
]
|
|
357
|
+
}
|
|
358
|
+
})),
|
|
359
|
+
consumeSendRequests: (sessionId) => {
|
|
360
|
+
const signals = get().sendRequests[sessionId] ?? [];
|
|
361
|
+
if (signals.length === 0) return [];
|
|
362
|
+
set((state) => ({
|
|
363
|
+
sendRequests: clearSignalRecord(state.sendRequests, sessionId)
|
|
364
|
+
}));
|
|
365
|
+
return signals;
|
|
366
|
+
},
|
|
367
|
+
clearSendRequests: (sessionId) => set((state) => ({
|
|
368
|
+
sendRequests: clearSignalRecord(state.sendRequests, sessionId)
|
|
369
|
+
})),
|
|
370
|
+
clearSession: (sessionId) => set((state) => ({
|
|
371
|
+
pendingContexts: clearSignalRecord(state.pendingContexts, sessionId),
|
|
372
|
+
draftAppends: clearSignalRecord(state.draftAppends, sessionId),
|
|
373
|
+
sendRequests: clearSignalRecord(state.sendRequests, sessionId)
|
|
374
|
+
}))
|
|
375
|
+
}));
|
|
376
|
+
|
|
377
|
+
// src/react/lib/chat.ts
|
|
378
|
+
function normalizeMessageContent(content) {
|
|
379
|
+
if (typeof content === "string") return content;
|
|
380
|
+
if (Array.isArray(content)) return content;
|
|
381
|
+
return String(content ?? "");
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/react/stores/auth-store.ts
|
|
385
|
+
import { create as create6 } from "zustand";
|
|
386
|
+
import { createJSONStorage, persist } from "zustand/middleware";
|
|
387
|
+
|
|
388
|
+
// src/react/api/auth.ts
|
|
389
|
+
var r = () => getClient().auth;
|
|
390
|
+
var getMe = (...args) => r().getMe(...args);
|
|
391
|
+
var logout = (...args) => r().logout(...args);
|
|
392
|
+
|
|
393
|
+
// src/react/sockets/socket-state.ts
|
|
394
|
+
var agentSocket = null;
|
|
395
|
+
|
|
396
|
+
// src/react/stores/session-store.ts
|
|
397
|
+
import { create as create5 } from "zustand";
|
|
398
|
+
|
|
399
|
+
// src/react/api/sessions.ts
|
|
400
|
+
var r2 = () => getClient().sessions;
|
|
401
|
+
var listSessions = (...args) => r2().listSessions(...args);
|
|
402
|
+
var createSession = (...args) => r2().createSession(...args);
|
|
403
|
+
var getSession = (...args) => r2().getSession(...args);
|
|
404
|
+
var pinSession = (...args) => r2().pinSession(...args);
|
|
405
|
+
var updateSharing = (...args) => r2().updateSharing(...args);
|
|
406
|
+
var getSessionTasks = (...args) => r2().getSessionTasks(...args);
|
|
407
|
+
var getSessionTurns = (...args) => r2().getSessionTurns(...args);
|
|
408
|
+
var deleteSession = (...args) => r2().deleteSession(...args);
|
|
409
|
+
|
|
410
|
+
// src/react/stores/chat-store.ts
|
|
411
|
+
import { create as create3 } from "zustand";
|
|
412
|
+
|
|
413
|
+
// src/react/components/chat/display-utils.ts
|
|
414
|
+
var TOOL_NAME_ALIASES = {
|
|
415
|
+
agent: "Agent",
|
|
416
|
+
ask_user_question: "AskUserQuestion",
|
|
417
|
+
bash: "Bash",
|
|
418
|
+
bg_bash: "BgBash",
|
|
419
|
+
edit: "Edit",
|
|
420
|
+
exit_plan_mode: "ExitPlanMode",
|
|
421
|
+
file_edit: "Edit",
|
|
422
|
+
file_read: "Read",
|
|
423
|
+
file_write: "Write",
|
|
424
|
+
finish_task: "FinishTask",
|
|
425
|
+
get_skill_content: "GetSkillContent",
|
|
426
|
+
glob: "Glob",
|
|
427
|
+
grep: "Grep",
|
|
428
|
+
list_skill_tools: "ListSkillTools",
|
|
429
|
+
load_skill_tools: "LoadSkillTools",
|
|
430
|
+
ls: "Ls",
|
|
431
|
+
read: "Read",
|
|
432
|
+
run_skill_tool: "RunSkillTool",
|
|
433
|
+
search_skills: "SearchSkills",
|
|
434
|
+
web_fetch: "WebFetch",
|
|
435
|
+
web_search: "WebSearch",
|
|
436
|
+
write: "Write"
|
|
437
|
+
};
|
|
438
|
+
var TOOL_DISPLAY_LABELS = {
|
|
439
|
+
Bash: "\u6267\u884C\u547D\u4EE4",
|
|
440
|
+
BgBash: "\u540E\u53F0\u6267\u884C\u547D\u4EE4",
|
|
441
|
+
Read: "\u8BFB\u53D6\u6587\u4EF6",
|
|
442
|
+
Write: "\u5199\u5165\u6587\u4EF6",
|
|
443
|
+
Edit: "\u7F16\u8F91\u6587\u4EF6",
|
|
444
|
+
Ls: "\u5217\u51FA\u76EE\u5F55",
|
|
445
|
+
Glob: "\u5339\u914D\u6587\u4EF6",
|
|
446
|
+
Grep: "\u641C\u7D22\u6587\u672C",
|
|
447
|
+
WebSearch: "\u641C\u7D22\u7F51\u9875",
|
|
448
|
+
WebFetch: "\u6574\u7406\u7F51\u9875\u5185\u5BB9",
|
|
449
|
+
Agent: "\u6D3E\u751F\u5B50\u667A\u80FD\u4F53",
|
|
450
|
+
AskUserQuestion: "\u5411\u7528\u6237\u63D0\u95EE",
|
|
451
|
+
SearchSkills: "\u641C\u7D22\u6280\u80FD",
|
|
452
|
+
GetSkillContent: "\u8BFB\u53D6\u6280\u80FD",
|
|
453
|
+
ListSkillTools: "\u67E5\u770B\u5DE5\u5177\u5217\u8868",
|
|
454
|
+
LoadSkillTools: "\u52A0\u8F7D\u6280\u80FD",
|
|
455
|
+
RunSkillTool: "\u6267\u884C\u6280\u80FD\u5DE5\u5177",
|
|
456
|
+
FinishTask: "\u4EFB\u52A1\u5B8C\u6210",
|
|
457
|
+
ExitPlanMode: "\u63D0\u4EA4\u8BA1\u5212",
|
|
458
|
+
ListSessions: "\u5217\u51FA\u5386\u53F2\u4F1A\u8BDD",
|
|
459
|
+
GetSessionHistory: "\u8BFB\u53D6\u4F1A\u8BDD\u5386\u53F2"
|
|
460
|
+
};
|
|
461
|
+
var GENERIC_DISPLAY_NAMES = new Set(Object.values(TOOL_DISPLAY_LABELS));
|
|
462
|
+
function formatToolName(name) {
|
|
463
|
+
const trimmed = name.trim();
|
|
464
|
+
if (!trimmed) return name;
|
|
465
|
+
const stripped = trimmed.split(":").pop()?.split("/").pop()?.split(".").pop()?.trim() || trimmed;
|
|
466
|
+
const normalized = stripped.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
467
|
+
return TOOL_NAME_ALIASES[normalized] ?? stripped;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// src/react/stores/chat-store.ts
|
|
471
|
+
var _getActiveSessionId = null;
|
|
472
|
+
function setChatStoreSessionAccessor(fn) {
|
|
473
|
+
_getActiveSessionId = fn;
|
|
474
|
+
}
|
|
475
|
+
function parseAgentDescription(argumentsJson) {
|
|
476
|
+
try {
|
|
477
|
+
return JSON.parse(argumentsJson)?.description ?? "\u5B50\u667A\u80FD\u4F53";
|
|
478
|
+
} catch {
|
|
479
|
+
return "\u5B50\u667A\u80FD\u4F53";
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function inferLoopStatusFromMessages(messages) {
|
|
483
|
+
const toolCalls = messages.flatMap((message) => message.tool_calls ?? []);
|
|
484
|
+
if (messages.some((message) => message.status === "streaming")) return "running";
|
|
485
|
+
if (toolCalls.some((toolCall) => toolCall.status === "awaiting_answer")) return "awaiting_answer";
|
|
486
|
+
if (toolCalls.some((toolCall) => toolCall.status === "error")) return "error";
|
|
487
|
+
if (toolCalls.some((toolCall) => toolCall.status === "cancelled")) return "cancelled";
|
|
488
|
+
if (toolCalls.some((toolCall) => toolCall.status === "pending")) return "running";
|
|
489
|
+
return "done";
|
|
490
|
+
}
|
|
491
|
+
function inferLoopStatusFromTurns(turns, messages) {
|
|
492
|
+
const latestAgentNotification = [...turns].reverse().flatMap((turn) => turn.blocks).find((block) => {
|
|
493
|
+
if (block.type !== "system_notification" || !isRecord(block.content)) return false;
|
|
494
|
+
return block.content.notification_type === "agent:start" || block.content.notification_type === "agent:end";
|
|
495
|
+
});
|
|
496
|
+
if (latestAgentNotification?.type === "system_notification" && isRecord(latestAgentNotification.content)) {
|
|
497
|
+
const notificationType = latestAgentNotification.content.notification_type;
|
|
498
|
+
const status = latestAgentNotification.content.status;
|
|
499
|
+
if (notificationType === "agent:start" || status === "running") return "running";
|
|
500
|
+
if (status === "error") return "error";
|
|
501
|
+
if (status === "cancelled") return "cancelled";
|
|
502
|
+
}
|
|
503
|
+
return inferLoopStatusFromMessages(messages);
|
|
504
|
+
}
|
|
505
|
+
function isRecord(value) {
|
|
506
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
507
|
+
}
|
|
508
|
+
function parentForkToolCallIdFromTurn(turn) {
|
|
509
|
+
if (typeof turn.parent_fork_tool_call_id === "string" && turn.parent_fork_tool_call_id.length > 0) {
|
|
510
|
+
return turn.parent_fork_tool_call_id;
|
|
511
|
+
}
|
|
512
|
+
for (const block of turn.blocks) {
|
|
513
|
+
if (block.type !== "system_notification" || !isRecord(block.content)) continue;
|
|
514
|
+
const metadata = block.content.metadata;
|
|
515
|
+
if (!isRecord(metadata)) continue;
|
|
516
|
+
const parentId = metadata.parent_fork_tool_call_id;
|
|
517
|
+
if (typeof parentId === "string" && parentId.length > 0) return parentId;
|
|
518
|
+
}
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
function buildMessageContent(turn) {
|
|
522
|
+
const textBlocks = turn.blocks.filter((block) => block.type === "text");
|
|
523
|
+
if (textBlocks.length === 0) return "";
|
|
524
|
+
if (textBlocks.length === 1) return normalizeMessageContent(textBlocks[0].content);
|
|
525
|
+
return textBlocks.map((block) => {
|
|
526
|
+
if (typeof block.content === "string") return block.content;
|
|
527
|
+
return JSON.stringify(block.content);
|
|
528
|
+
}).join("");
|
|
529
|
+
}
|
|
530
|
+
function buildReasoning(turn) {
|
|
531
|
+
const thinking = turn.blocks.filter((block) => block.type === "thinking").map((block) => typeof block.content === "string" ? block.content : JSON.stringify(block.content)).filter(Boolean);
|
|
532
|
+
if (thinking.length === 0) return void 0;
|
|
533
|
+
return thinking.join("\n\n");
|
|
534
|
+
}
|
|
535
|
+
function isModeChangeContent(value) {
|
|
536
|
+
return typeof value === "object" && value !== null && typeof value.from === "string" && typeof value.to === "string";
|
|
537
|
+
}
|
|
538
|
+
function extractModeFromBlocks(blocks) {
|
|
539
|
+
const modeBlock = blocks.find((block) => block.type === "mode_change");
|
|
540
|
+
if (modeBlock && isModeChangeContent(modeBlock.content)) {
|
|
541
|
+
return modeBlock.content.to === "planning" || modeBlock.content.to === "executing" ? modeBlock.content.to : null;
|
|
542
|
+
}
|
|
543
|
+
if (blocks.some((block) => block.type === "planning_enter")) return "planning";
|
|
544
|
+
if (blocks.some((block) => block.type === "planning_exit")) return "executing";
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
function projectionToMessage(turn) {
|
|
548
|
+
if (turn.kind === "compaction" && turn.compaction_id) {
|
|
549
|
+
return {
|
|
550
|
+
role: "assistant",
|
|
551
|
+
content: turn.summary_preview ?? "",
|
|
552
|
+
kind: "compaction",
|
|
553
|
+
loop_name: turn.loop_id,
|
|
554
|
+
entry_id: turn.turn_id,
|
|
555
|
+
status: turn.status,
|
|
556
|
+
compaction: {
|
|
557
|
+
compaction_id: turn.compaction_id,
|
|
558
|
+
summary_preview: turn.summary_preview,
|
|
559
|
+
summary_full: turn.summary_full,
|
|
560
|
+
archived_count: turn.archived_count,
|
|
561
|
+
archived_files: turn.archived_files,
|
|
562
|
+
archived_tool_calls: turn.archived_tool_calls,
|
|
563
|
+
tokens_before: turn.tokens_before,
|
|
564
|
+
tokens_after: turn.tokens_after,
|
|
565
|
+
saved_ratio: turn.saved_ratio,
|
|
566
|
+
trigger: turn.trigger,
|
|
567
|
+
failure_reason: turn.failure_reason,
|
|
568
|
+
fallback_applied: turn.fallback_applied
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
const planningBlock = turn.blocks.find(
|
|
573
|
+
(block) => block.type === "mode_change" || block.type === "planning_enter" || block.type === "planning_exit" || block.type === "plan_status"
|
|
574
|
+
);
|
|
575
|
+
if (planningBlock) {
|
|
576
|
+
if (planningBlock.type === "plan_status") {
|
|
577
|
+
return {
|
|
578
|
+
role: "tool",
|
|
579
|
+
content: typeof planningBlock.content === "string" ? planningBlock.content : JSON.stringify(planningBlock.content ?? {}, null, 2),
|
|
580
|
+
kind: "plan_status",
|
|
581
|
+
loop_name: turn.loop_id,
|
|
582
|
+
entry_id: turn.turn_id,
|
|
583
|
+
status: turn.status
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
return {
|
|
587
|
+
role: "assistant",
|
|
588
|
+
content: planningBlock.type === "mode_change" ? typeof planningBlock.content === "string" ? planningBlock.content : JSON.stringify(planningBlock.content ?? {}) : "",
|
|
589
|
+
kind: planningBlock.type,
|
|
590
|
+
loop_name: turn.loop_id,
|
|
591
|
+
entry_id: turn.turn_id,
|
|
592
|
+
status: turn.status
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
if (turn.blocks.some((block) => block.type === "ask_user_answer")) {
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
const content = buildMessageContent(turn);
|
|
599
|
+
const reasoning = buildReasoning(turn);
|
|
600
|
+
const toolCalls = turn.tool_calls.length > 0 ? turn.tool_calls.map((toolCall) => ({
|
|
601
|
+
id: toolCall.id,
|
|
602
|
+
name: toolCall.tool_name,
|
|
603
|
+
display_name: toolCall.display_name,
|
|
604
|
+
arguments: toolCall.arguments,
|
|
605
|
+
result: toolCall.result ?? void 0,
|
|
606
|
+
pending_question_ref: toolCall.pending_question_ref ?? void 0,
|
|
607
|
+
status: toolCall.status === "pending" || toolCall.status === "awaiting_answer" || toolCall.status === "done" || toolCall.status === "error" || toolCall.status === "cancelled" ? toolCall.status : "pending",
|
|
608
|
+
...typeof toolCall.duration_ms === "number" ? { duration_ms: toolCall.duration_ms } : {}
|
|
609
|
+
})) : void 0;
|
|
610
|
+
if (turn.role === "system" && !content && !toolCalls?.length) {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
role: turn.role === "system" ? "assistant" : turn.role,
|
|
615
|
+
content,
|
|
616
|
+
blocks: turn.blocks,
|
|
617
|
+
...reasoning ? { reasoning } : {},
|
|
618
|
+
...toolCalls ? { tool_calls: toolCalls } : {},
|
|
619
|
+
loop_name: turn.loop_id,
|
|
620
|
+
entry_id: turn.turn_id,
|
|
621
|
+
status: turn.status,
|
|
622
|
+
...typeof turn.duration_ms === "number" ? { duration_ms: turn.duration_ms } : {},
|
|
623
|
+
...turn.started_at ? { timestamp: turn.started_at } : {},
|
|
624
|
+
...turn.memory_refs?.length ? { memory_refs: turn.memory_refs } : {}
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
function rebuildAgentLoops(turns) {
|
|
628
|
+
const messages = turns.map(projectionToMessage).filter(Boolean);
|
|
629
|
+
const childLoopNames = [...new Set(turns.map((turn) => turn.loop_id).filter((name) => name !== "root"))];
|
|
630
|
+
if (childLoopNames.length === 0) return {};
|
|
631
|
+
const agentToolCalls = messages.filter((message) => message.role === "assistant" && (message.loop_name ?? "root") === "root").flatMap((message) => message.tool_calls ?? []).filter((toolCall) => formatToolName(toolCall.name) === "Agent");
|
|
632
|
+
const loops = {};
|
|
633
|
+
const agentToolCallsById = new Map(agentToolCalls.map((toolCall) => [toolCall.id, toolCall]));
|
|
634
|
+
const explicitParentToolCallIds = new Set(
|
|
635
|
+
turns.map(parentForkToolCallIdFromTurn).filter((id) => id !== null)
|
|
636
|
+
);
|
|
637
|
+
const usedToolCallIds = /* @__PURE__ */ new Set();
|
|
638
|
+
for (const loopName of childLoopNames) {
|
|
639
|
+
const loopMessages = messages.filter((message) => (message.loop_name ?? "root") === loopName);
|
|
640
|
+
const loopTurns = turns.filter((turn) => turn.loop_id === loopName);
|
|
641
|
+
const parentToolCallId = loopTurns.map(parentForkToolCallIdFromTurn).find(Boolean);
|
|
642
|
+
const explicitToolCall = parentToolCallId && !usedToolCallIds.has(parentToolCallId) ? agentToolCallsById.get(parentToolCallId) ?? null : null;
|
|
643
|
+
const fallbackToolCall = explicitToolCall === null ? agentToolCalls.find(
|
|
644
|
+
(toolCall2) => !usedToolCallIds.has(toolCall2.id) && !explicitParentToolCallIds.has(toolCall2.id)
|
|
645
|
+
) : null;
|
|
646
|
+
const toolCall = explicitToolCall ?? fallbackToolCall;
|
|
647
|
+
if (!toolCall) continue;
|
|
648
|
+
usedToolCallIds.add(toolCall.id);
|
|
649
|
+
loops[loopName] = {
|
|
650
|
+
toolCallId: toolCall.id,
|
|
651
|
+
description: parseAgentDescription(toolCall.arguments),
|
|
652
|
+
status: inferLoopStatusFromTurns(loopTurns, loopMessages)
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
return loops;
|
|
656
|
+
}
|
|
657
|
+
function applyPlanningSideEffects(sessionId, turns) {
|
|
658
|
+
const latestMode = [...turns].reverse().map((turn) => extractModeFromBlocks(turn.blocks)).find((mode) => mode !== null);
|
|
659
|
+
if (latestMode !== "planning") return;
|
|
660
|
+
if (_getActiveSessionId?.() !== sessionId) return;
|
|
661
|
+
const ui = useUiStore.getState();
|
|
662
|
+
ui.setActiveRightTab("situation");
|
|
663
|
+
if (ui.rightPanelCollapsed) {
|
|
664
|
+
ui.toggleRightPanel();
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
function materialize(turns) {
|
|
668
|
+
const messages = turns.map(projectionToMessage).filter((message) => message !== null);
|
|
669
|
+
const activeCompaction = [...turns].reverse().find(
|
|
670
|
+
(turn) => turn.kind === "compaction" && turn.status === "streaming" && typeof turn.compaction_id === "string"
|
|
671
|
+
);
|
|
672
|
+
return {
|
|
673
|
+
messages,
|
|
674
|
+
agentLoops: rebuildAgentLoops(turns),
|
|
675
|
+
activeCompaction: activeCompaction ? {
|
|
676
|
+
turn_id: activeCompaction.turn_id,
|
|
677
|
+
status: activeCompaction.status,
|
|
678
|
+
compaction_id: activeCompaction.compaction_id,
|
|
679
|
+
summary_preview: activeCompaction.summary_preview,
|
|
680
|
+
summary_full: activeCompaction.summary_full,
|
|
681
|
+
archived_count: activeCompaction.archived_count,
|
|
682
|
+
archived_files: activeCompaction.archived_files,
|
|
683
|
+
archived_tool_calls: activeCompaction.archived_tool_calls,
|
|
684
|
+
tokens_before: activeCompaction.tokens_before,
|
|
685
|
+
tokens_after: activeCompaction.tokens_after,
|
|
686
|
+
saved_ratio: activeCompaction.saved_ratio,
|
|
687
|
+
trigger: activeCompaction.trigger,
|
|
688
|
+
failure_reason: activeCompaction.failure_reason,
|
|
689
|
+
fallback_applied: activeCompaction.fallback_applied
|
|
690
|
+
} : null
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
var ERROR_ANCHOR_PREFIX = "error-anchor:";
|
|
694
|
+
function updateSessionState(state, sessionId, turns) {
|
|
695
|
+
const orderedTurns = [...turns].sort((left, right) => left.sequence - right.sequence);
|
|
696
|
+
const { messages, agentLoops, activeCompaction } = materialize(orderedTurns);
|
|
697
|
+
applyPlanningSideEffects(sessionId, orderedTurns);
|
|
698
|
+
const lastTurnId = orderedTurns[orderedTurns.length - 1]?.turn_id ?? null;
|
|
699
|
+
const preservedErrors = lastTurnId ? (state.messages[sessionId] ?? []).filter(
|
|
700
|
+
(m) => m.role === "error" && typeof m.entry_id === "string" && m.entry_id.startsWith(`${ERROR_ANCHOR_PREFIX}${lastTurnId}:`)
|
|
701
|
+
) : [];
|
|
702
|
+
const mergedMessages = preservedErrors.length > 0 ? [...messages, ...preservedErrors] : messages;
|
|
703
|
+
return {
|
|
704
|
+
turns: { ...state.turns, [sessionId]: orderedTurns },
|
|
705
|
+
messages: { ...state.messages, [sessionId]: mergedMessages },
|
|
706
|
+
agentLoops: { ...state.agentLoops, [sessionId]: agentLoops },
|
|
707
|
+
activeCompactions: { ...state.activeCompactions, [sessionId]: activeCompaction }
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
var useChatStore = create3()((set) => ({
|
|
711
|
+
...createClientActions(set),
|
|
712
|
+
turns: {},
|
|
713
|
+
messages: {},
|
|
714
|
+
askAnswers: {},
|
|
715
|
+
isStreaming: {},
|
|
716
|
+
agentLoops: {},
|
|
717
|
+
activeCompactions: {},
|
|
718
|
+
addUserMessage: (sessionId, content) => {
|
|
719
|
+
set((state) => {
|
|
720
|
+
const existing = state.turns[sessionId] ?? [];
|
|
721
|
+
const turnId = `local-user-${Date.now()}`;
|
|
722
|
+
const optimisticTurn = {
|
|
723
|
+
id: turnId,
|
|
724
|
+
sequence: Math.max(0, ...existing.map((turn) => turn.sequence)) + 1,
|
|
725
|
+
turn_id: turnId,
|
|
726
|
+
loop_id: "root",
|
|
727
|
+
role: "user",
|
|
728
|
+
status: "completed",
|
|
729
|
+
blocks: [{ type: "text", content }],
|
|
730
|
+
tool_calls: [],
|
|
731
|
+
model: null,
|
|
732
|
+
usage: null,
|
|
733
|
+
duration_ms: 0
|
|
734
|
+
};
|
|
735
|
+
return updateSessionState(state, sessionId, [...existing, optimisticTurn]);
|
|
736
|
+
});
|
|
737
|
+
},
|
|
738
|
+
setTurns: (sessionId, turns) => {
|
|
739
|
+
set((state) => updateSessionState(state, sessionId, [...turns]));
|
|
740
|
+
},
|
|
741
|
+
upsertTurn: (sessionId, turn) => {
|
|
742
|
+
set((state) => {
|
|
743
|
+
const existing = [...state.turns[sessionId] ?? []];
|
|
744
|
+
const index = existing.findIndex((item) => item.turn_id === turn.turn_id);
|
|
745
|
+
if (index >= 0) {
|
|
746
|
+
existing[index] = turn;
|
|
747
|
+
} else {
|
|
748
|
+
existing.push(turn);
|
|
749
|
+
}
|
|
750
|
+
return {
|
|
751
|
+
...updateSessionState(state, sessionId, existing)
|
|
752
|
+
};
|
|
753
|
+
});
|
|
754
|
+
},
|
|
755
|
+
applyTurnPatch: (sessionId, patch) => {
|
|
756
|
+
set((state) => {
|
|
757
|
+
const turn = patch.data.turn;
|
|
758
|
+
if (!turn) return state;
|
|
759
|
+
const existing = [...state.turns[sessionId] ?? []];
|
|
760
|
+
const index = existing.findIndex((item) => item.turn_id === turn.turn_id);
|
|
761
|
+
const lastSequence = index >= 0 ? existing[index].sequence : null;
|
|
762
|
+
if (lastSequence !== null && patch.sequence <= lastSequence) {
|
|
763
|
+
return state;
|
|
764
|
+
}
|
|
765
|
+
const nextTurn = {
|
|
766
|
+
...turn,
|
|
767
|
+
sequence: patch.sequence
|
|
768
|
+
};
|
|
769
|
+
if (index >= 0) {
|
|
770
|
+
existing[index] = nextTurn;
|
|
771
|
+
} else {
|
|
772
|
+
existing.push(nextTurn);
|
|
773
|
+
}
|
|
774
|
+
return {
|
|
775
|
+
...updateSessionState(state, sessionId, existing)
|
|
776
|
+
};
|
|
777
|
+
});
|
|
778
|
+
},
|
|
779
|
+
addErrorMessage: (sessionId, content) => {
|
|
780
|
+
set((state) => {
|
|
781
|
+
const turns = state.turns[sessionId] ?? [];
|
|
782
|
+
const anchorTurnId = turns[turns.length - 1]?.turn_id ?? null;
|
|
783
|
+
const entry_id = anchorTurnId ? `${ERROR_ANCHOR_PREFIX}${anchorTurnId}:${Date.now()}` : void 0;
|
|
784
|
+
return {
|
|
785
|
+
messages: {
|
|
786
|
+
...state.messages,
|
|
787
|
+
[sessionId]: [
|
|
788
|
+
...state.messages[sessionId] ?? [],
|
|
789
|
+
{ role: "error", content, loop_name: "root", entry_id }
|
|
790
|
+
]
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
});
|
|
794
|
+
},
|
|
795
|
+
markInterrupted: (sessionId) => {
|
|
796
|
+
set((state) => {
|
|
797
|
+
const turns = (state.turns[sessionId] ?? []).map((turn) => {
|
|
798
|
+
if (turn.status !== "streaming") return turn;
|
|
799
|
+
return {
|
|
800
|
+
...turn,
|
|
801
|
+
status: "interrupted",
|
|
802
|
+
tool_calls: turn.tool_calls.map(
|
|
803
|
+
(toolCall) => toolCall.status === "pending" || toolCall.status === "awaiting_answer" ? { ...toolCall, status: "cancelled" } : toolCall
|
|
804
|
+
)
|
|
805
|
+
};
|
|
806
|
+
});
|
|
807
|
+
return {
|
|
808
|
+
...updateSessionState(state, sessionId, turns)
|
|
809
|
+
};
|
|
810
|
+
});
|
|
811
|
+
},
|
|
812
|
+
markFailed: (sessionId) => {
|
|
813
|
+
set((state) => {
|
|
814
|
+
const turns = (state.turns[sessionId] ?? []).map((turn) => {
|
|
815
|
+
if (turn.status !== "streaming") return turn;
|
|
816
|
+
return {
|
|
817
|
+
...turn,
|
|
818
|
+
status: "failed",
|
|
819
|
+
tool_calls: turn.tool_calls.map(
|
|
820
|
+
(toolCall) => toolCall.status === "pending" || toolCall.status === "awaiting_answer" ? { ...toolCall, status: "error" } : toolCall
|
|
821
|
+
)
|
|
822
|
+
};
|
|
823
|
+
});
|
|
824
|
+
return {
|
|
825
|
+
...updateSessionState(state, sessionId, turns)
|
|
826
|
+
};
|
|
827
|
+
});
|
|
828
|
+
},
|
|
829
|
+
setStreaming: (sessionId, streaming) => {
|
|
830
|
+
set((state) => ({
|
|
831
|
+
isStreaming: { ...state.isStreaming, [sessionId]: streaming }
|
|
832
|
+
}));
|
|
833
|
+
},
|
|
834
|
+
setAskAnswers: (sessionId, answers) => {
|
|
835
|
+
set((state) => ({
|
|
836
|
+
askAnswers: {
|
|
837
|
+
...state.askAnswers,
|
|
838
|
+
[sessionId]: answers
|
|
839
|
+
}
|
|
840
|
+
}));
|
|
841
|
+
},
|
|
842
|
+
clearMessages: (sessionId) => {
|
|
843
|
+
set((state) => {
|
|
844
|
+
const { [sessionId]: _turns, ...restTurns } = state.turns;
|
|
845
|
+
const { [sessionId]: _messages, ...restMessages } = state.messages;
|
|
846
|
+
const { [sessionId]: _answers, ...restAnswers } = state.askAnswers;
|
|
847
|
+
const { [sessionId]: _loops, ...restLoops } = state.agentLoops;
|
|
848
|
+
const { [sessionId]: _compaction, ...restCompactions } = state.activeCompactions;
|
|
849
|
+
return {
|
|
850
|
+
turns: restTurns,
|
|
851
|
+
messages: restMessages,
|
|
852
|
+
askAnswers: restAnswers,
|
|
853
|
+
agentLoops: restLoops,
|
|
854
|
+
activeCompactions: restCompactions
|
|
855
|
+
};
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
}));
|
|
859
|
+
|
|
860
|
+
// src/react/stores/task-store.ts
|
|
861
|
+
import { create as create4 } from "zustand";
|
|
862
|
+
var EMPTY_TASKS = [];
|
|
863
|
+
var useTaskStore = create4()((set, get) => ({
|
|
864
|
+
...createClientActions(set),
|
|
865
|
+
tasks: {},
|
|
866
|
+
setTasks: (sessionId, tasks) => {
|
|
867
|
+
set((state) => ({
|
|
868
|
+
tasks: { ...state.tasks, [sessionId]: tasks }
|
|
869
|
+
}));
|
|
870
|
+
},
|
|
871
|
+
getTasks: (sessionId) => {
|
|
872
|
+
return get().tasks[sessionId] ?? EMPTY_TASKS;
|
|
873
|
+
}
|
|
874
|
+
}));
|
|
875
|
+
|
|
876
|
+
// src/react/stores/session-store.ts
|
|
877
|
+
var DEFAULT_SESSION_MODE = "executing";
|
|
878
|
+
var onSessionChange = null;
|
|
879
|
+
function invalidateHomeSidebarSessions() {
|
|
880
|
+
const queryClient = globalThis.__agentQueryClient;
|
|
881
|
+
if (!queryClient) return;
|
|
882
|
+
void queryClient.invalidateQueries({ queryKey: ["sessions", "home-sidebar"] });
|
|
883
|
+
}
|
|
884
|
+
function isSessionAccessRevoked(error) {
|
|
885
|
+
if (error instanceof Error) {
|
|
886
|
+
const msg = error.message;
|
|
887
|
+
return msg.includes("API 403") || msg.includes("API 404");
|
|
888
|
+
}
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
function removeSessionArtifacts(sessionId) {
|
|
892
|
+
useChatStore.getState().clearMessages(sessionId);
|
|
893
|
+
useTaskStore.getState().setTasks(sessionId, []);
|
|
894
|
+
}
|
|
895
|
+
function pruneSessionState(state, sessionId) {
|
|
896
|
+
const nextFresh = new Set(state._freshSessions);
|
|
897
|
+
nextFresh.delete(sessionId);
|
|
898
|
+
const { [sessionId]: _mode, ...modes } = state.modes;
|
|
899
|
+
const { [sessionId]: _rewindDraft, ...rewindDrafts } = state.rewindDrafts;
|
|
900
|
+
return {
|
|
901
|
+
sessions: state.sessions.filter((session) => session.id !== sessionId),
|
|
902
|
+
activeSessionId: state.activeSessionId === sessionId ? null : state.activeSessionId,
|
|
903
|
+
modes,
|
|
904
|
+
rewindDrafts,
|
|
905
|
+
_freshSessions: nextFresh
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
function navigateAwayFromSession(sessionId) {
|
|
909
|
+
if (typeof window === "undefined") return;
|
|
910
|
+
if (window.location.pathname !== `/chat/${sessionId}`) return;
|
|
911
|
+
window.history.replaceState(window.history.state, "", "/chat");
|
|
912
|
+
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
913
|
+
}
|
|
914
|
+
function handleUnreadableSession(set, get, sessionId) {
|
|
915
|
+
const wasActive = get().activeSessionId === sessionId;
|
|
916
|
+
removeSessionArtifacts(sessionId);
|
|
917
|
+
set((state) => pruneSessionState(state, sessionId));
|
|
918
|
+
if (wasActive) {
|
|
919
|
+
onSessionChange?.(null);
|
|
920
|
+
navigateAwayFromSession(sessionId);
|
|
921
|
+
}
|
|
922
|
+
invalidateHomeSidebarSessions();
|
|
923
|
+
}
|
|
924
|
+
function isPlainRecord(value) {
|
|
925
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
926
|
+
}
|
|
927
|
+
function extractModeFromBlocks2(blocks) {
|
|
928
|
+
const modeBlock = blocks.find((block) => block.type === "mode_change");
|
|
929
|
+
if (modeBlock && isPlainRecord(modeBlock.content) && (modeBlock.content.to === "planning" || modeBlock.content.to === "executing")) {
|
|
930
|
+
return modeBlock.content.to;
|
|
931
|
+
}
|
|
932
|
+
if (blocks.some((block) => block.type === "planning_enter")) return "planning";
|
|
933
|
+
if (blocks.some((block) => block.type === "planning_exit")) return "executing";
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
function toSelectionMap(value) {
|
|
937
|
+
if (!isPlainRecord(value)) return {};
|
|
938
|
+
const entries = Object.entries(value).map(([questionKey, optionIndexes]) => {
|
|
939
|
+
if (!Array.isArray(optionIndexes)) return null;
|
|
940
|
+
const parsedIndexes = optionIndexes.map((item) => typeof item === "number" ? item : Number(item)).filter((item) => Number.isInteger(item));
|
|
941
|
+
return [Number(questionKey), parsedIndexes];
|
|
942
|
+
}).filter((entry) => entry !== null);
|
|
943
|
+
return Object.fromEntries(entries);
|
|
944
|
+
}
|
|
945
|
+
function toCustomMap(value) {
|
|
946
|
+
if (!isPlainRecord(value)) return {};
|
|
947
|
+
const entries = Object.entries(value).filter(([, text]) => typeof text === "string").map(([questionKey, text]) => [Number(questionKey), text]);
|
|
948
|
+
return Object.fromEntries(entries);
|
|
949
|
+
}
|
|
950
|
+
function extractAskAnswers(turns) {
|
|
951
|
+
const answers = {};
|
|
952
|
+
for (const turn of turns) {
|
|
953
|
+
for (const block of turn.blocks) {
|
|
954
|
+
if (block.type !== "ask_user_answer" || typeof block.tool_call_id !== "string") continue;
|
|
955
|
+
answers[block.tool_call_id] = {
|
|
956
|
+
selections: toSelectionMap(isPlainRecord(block.content) ? block.content.selections : void 0),
|
|
957
|
+
custom: toCustomMap(isPlainRecord(block.content) ? block.content.custom : void 0)
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
return answers;
|
|
962
|
+
}
|
|
963
|
+
function registerCreatedSessionState(set, session, mode = DEFAULT_SESSION_MODE) {
|
|
964
|
+
useChatStore.getState().setTurns(session.id, []);
|
|
965
|
+
useTaskStore.getState().setTasks(session.id, []);
|
|
966
|
+
set((state) => ({
|
|
967
|
+
sessions: [session, ...state.sessions.filter((item) => item.id !== session.id)],
|
|
968
|
+
modes: { ...state.modes, [session.id]: state.modes[session.id] ?? mode },
|
|
969
|
+
_freshSessions: new Set(state._freshSessions).add(session.id)
|
|
970
|
+
}));
|
|
971
|
+
}
|
|
972
|
+
async function revalidateViewerSessions(existingSessions) {
|
|
973
|
+
const viewerSessions = existingSessions.filter((session) => session.viewer_role === "viewer");
|
|
974
|
+
if (viewerSessions.length === 0) {
|
|
975
|
+
return [];
|
|
976
|
+
}
|
|
977
|
+
const refreshed = await Promise.all(
|
|
978
|
+
viewerSessions.map(async (session) => {
|
|
979
|
+
try {
|
|
980
|
+
return await getSession(session.id);
|
|
981
|
+
} catch (error) {
|
|
982
|
+
if (isSessionAccessRevoked(error)) {
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
return session;
|
|
986
|
+
}
|
|
987
|
+
})
|
|
988
|
+
);
|
|
989
|
+
return refreshed.filter((session) => session !== null);
|
|
990
|
+
}
|
|
991
|
+
var useSessionStore = create5()((set, get) => ({
|
|
992
|
+
...createClientActions(set),
|
|
993
|
+
sessions: [],
|
|
994
|
+
activeSessionId: null,
|
|
995
|
+
loading: false,
|
|
996
|
+
modes: {},
|
|
997
|
+
rewindDrafts: {},
|
|
998
|
+
_freshSessions: /* @__PURE__ */ new Set(),
|
|
999
|
+
fetchSessions: async () => {
|
|
1000
|
+
set({ loading: true });
|
|
1001
|
+
try {
|
|
1002
|
+
const currentState = get();
|
|
1003
|
+
const [sessions, viewerSessions] = await Promise.all([
|
|
1004
|
+
listSessions(),
|
|
1005
|
+
revalidateViewerSessions(currentState.sessions)
|
|
1006
|
+
]);
|
|
1007
|
+
const knownSessionIds = new Set(sessions.map((session) => session.id));
|
|
1008
|
+
const mergedSessions = [
|
|
1009
|
+
...sessions,
|
|
1010
|
+
...viewerSessions.filter((session) => !knownSessionIds.has(session.id))
|
|
1011
|
+
];
|
|
1012
|
+
const removedSessionIds = currentState.sessions.filter(
|
|
1013
|
+
(session) => session.viewer_role === "viewer" && !mergedSessions.some((candidate) => candidate.id === session.id)
|
|
1014
|
+
).map((session) => session.id);
|
|
1015
|
+
const activeSessionId = currentState.activeSessionId;
|
|
1016
|
+
for (const sessionId of removedSessionIds) {
|
|
1017
|
+
removeSessionArtifacts(sessionId);
|
|
1018
|
+
}
|
|
1019
|
+
let nextState = {
|
|
1020
|
+
sessions: mergedSessions,
|
|
1021
|
+
activeSessionId: currentState.activeSessionId,
|
|
1022
|
+
loading: false,
|
|
1023
|
+
modes: currentState.modes,
|
|
1024
|
+
rewindDrafts: currentState.rewindDrafts,
|
|
1025
|
+
_freshSessions: currentState._freshSessions
|
|
1026
|
+
};
|
|
1027
|
+
for (const sessionId of removedSessionIds) {
|
|
1028
|
+
nextState = {
|
|
1029
|
+
...nextState,
|
|
1030
|
+
...pruneSessionState(
|
|
1031
|
+
{
|
|
1032
|
+
...currentState,
|
|
1033
|
+
sessions: nextState.sessions ?? currentState.sessions,
|
|
1034
|
+
activeSessionId: nextState.activeSessionId ?? currentState.activeSessionId,
|
|
1035
|
+
loading: nextState.loading ?? currentState.loading,
|
|
1036
|
+
modes: nextState.modes ?? currentState.modes,
|
|
1037
|
+
rewindDrafts: nextState.rewindDrafts ?? currentState.rewindDrafts,
|
|
1038
|
+
_freshSessions: nextState._freshSessions ?? currentState._freshSessions
|
|
1039
|
+
},
|
|
1040
|
+
sessionId
|
|
1041
|
+
)
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
set(nextState);
|
|
1045
|
+
if (activeSessionId && removedSessionIds.includes(activeSessionId)) {
|
|
1046
|
+
onSessionChange?.(null);
|
|
1047
|
+
navigateAwayFromSession(activeSessionId);
|
|
1048
|
+
}
|
|
1049
|
+
invalidateHomeSidebarSessions();
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
set({ loading: false });
|
|
1052
|
+
throw error;
|
|
1053
|
+
}
|
|
1054
|
+
},
|
|
1055
|
+
createSession: async (intent) => {
|
|
1056
|
+
const { session_id } = await createSession(intent);
|
|
1057
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1058
|
+
get().registerCreatedSession({
|
|
1059
|
+
id: session_id,
|
|
1060
|
+
intent: intent ?? "",
|
|
1061
|
+
status: "created",
|
|
1062
|
+
created_at: now,
|
|
1063
|
+
updated_at: now
|
|
1064
|
+
});
|
|
1065
|
+
get().setActiveSession(session_id);
|
|
1066
|
+
invalidateHomeSidebarSessions();
|
|
1067
|
+
get().fetchSessions().catch(() => {
|
|
1068
|
+
});
|
|
1069
|
+
return session_id;
|
|
1070
|
+
},
|
|
1071
|
+
registerCreatedSession: (session, mode = DEFAULT_SESSION_MODE) => {
|
|
1072
|
+
registerCreatedSessionState(set, session, mode);
|
|
1073
|
+
},
|
|
1074
|
+
upsertSession: (session) => {
|
|
1075
|
+
set((state) => ({
|
|
1076
|
+
sessions: state.sessions.some((item) => item.id === session.id) ? state.sessions.map((item) => item.id === session.id ? { ...item, ...session } : item) : [session, ...state.sessions]
|
|
1077
|
+
}));
|
|
1078
|
+
},
|
|
1079
|
+
isFreshSession: (sessionId) => get()._freshSessions.has(sessionId),
|
|
1080
|
+
setActiveSession: (id) => {
|
|
1081
|
+
set({ activeSessionId: id });
|
|
1082
|
+
onSessionChange?.(id);
|
|
1083
|
+
getSession(id).then((detail) => {
|
|
1084
|
+
set((state) => ({
|
|
1085
|
+
sessions: state.sessions.some((s) => s.id === id) ? state.sessions.map(
|
|
1086
|
+
(s) => s.id === id ? { ...s, status: detail.status, updated_at: detail.updated_at } : s
|
|
1087
|
+
) : [detail, ...state.sessions]
|
|
1088
|
+
}));
|
|
1089
|
+
}).catch(() => {
|
|
1090
|
+
});
|
|
1091
|
+
const tasksPromise = getSessionTasks(id).catch(() => null);
|
|
1092
|
+
Promise.all([getSessionTurns(id), tasksPromise]).then(([turns, tasks]) => {
|
|
1093
|
+
if (tasks) useTaskStore.getState().setTasks(id, tasks);
|
|
1094
|
+
useChatStore.getState().setAskAnswers(id, extractAskAnswers(turns));
|
|
1095
|
+
let inferredMode = DEFAULT_SESSION_MODE;
|
|
1096
|
+
for (const turn of turns) {
|
|
1097
|
+
const nextMode = extractModeFromBlocks2(turn.blocks);
|
|
1098
|
+
if (nextMode) {
|
|
1099
|
+
inferredMode = nextMode;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
set((state) => {
|
|
1103
|
+
if (state.modes[id] == null) {
|
|
1104
|
+
return { modes: { ...state.modes, [id]: inferredMode } };
|
|
1105
|
+
}
|
|
1106
|
+
return state;
|
|
1107
|
+
});
|
|
1108
|
+
const isFresh = get()._freshSessions.has(id);
|
|
1109
|
+
if (isFresh) {
|
|
1110
|
+
set((state) => {
|
|
1111
|
+
const next = new Set(state._freshSessions);
|
|
1112
|
+
next.delete(id);
|
|
1113
|
+
return { _freshSessions: next };
|
|
1114
|
+
});
|
|
1115
|
+
if (turns.length > 0) {
|
|
1116
|
+
useChatStore.getState().setTurns(id, turns);
|
|
1117
|
+
}
|
|
1118
|
+
} else {
|
|
1119
|
+
useChatStore.getState().setTurns(id, turns);
|
|
1120
|
+
}
|
|
1121
|
+
}).catch((error) => {
|
|
1122
|
+
if (isSessionAccessRevoked(error)) {
|
|
1123
|
+
handleUnreadableSession(set, get, id);
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
console.error("Failed to load session data", error);
|
|
1127
|
+
});
|
|
1128
|
+
},
|
|
1129
|
+
clearActiveSession: () => {
|
|
1130
|
+
set({ activeSessionId: null });
|
|
1131
|
+
onSessionChange?.(null);
|
|
1132
|
+
},
|
|
1133
|
+
deleteSession: async (id) => {
|
|
1134
|
+
await deleteSession(id);
|
|
1135
|
+
invalidateHomeSidebarSessions();
|
|
1136
|
+
const wasActive = get().activeSessionId === id;
|
|
1137
|
+
await get().fetchSessions();
|
|
1138
|
+
if (!wasActive) {
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
const nextId = get().sessions[0]?.id ?? null;
|
|
1142
|
+
if (nextId) {
|
|
1143
|
+
get().setActiveSession(nextId);
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
get().clearActiveSession();
|
|
1147
|
+
},
|
|
1148
|
+
updateSessionStatus: (sessionId, status) => {
|
|
1149
|
+
set((state) => ({
|
|
1150
|
+
sessions: state.sessions.map((s) => s.id === sessionId ? { ...s, status } : s)
|
|
1151
|
+
}));
|
|
1152
|
+
if (status === "failed" || status === "interrupted") {
|
|
1153
|
+
useChatStore.getState().setStreaming(sessionId, false);
|
|
1154
|
+
if (status === "interrupted") {
|
|
1155
|
+
useChatStore.getState().markInterrupted(sessionId);
|
|
1156
|
+
} else {
|
|
1157
|
+
useChatStore.getState().markFailed(sessionId);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
},
|
|
1161
|
+
updateSessionIntent: (sessionId, intent) => {
|
|
1162
|
+
set((state) => ({
|
|
1163
|
+
sessions: state.sessions.map((s) => s.id === sessionId ? { ...s, intent } : s)
|
|
1164
|
+
}));
|
|
1165
|
+
},
|
|
1166
|
+
patchSession: (sessionId, patch) => {
|
|
1167
|
+
set((state) => ({
|
|
1168
|
+
sessions: state.sessions.map(
|
|
1169
|
+
(s) => s.id === sessionId ? { ...s, ...patch } : s
|
|
1170
|
+
)
|
|
1171
|
+
}));
|
|
1172
|
+
},
|
|
1173
|
+
pinSession: async (sessionId, pinned) => {
|
|
1174
|
+
const updated = await pinSession(sessionId, pinned);
|
|
1175
|
+
set((state) => ({
|
|
1176
|
+
sessions: state.sessions.map(
|
|
1177
|
+
(s) => s.id === sessionId ? { ...s, is_pinned: updated.is_pinned, pinned_at: updated.pinned_at } : s
|
|
1178
|
+
)
|
|
1179
|
+
}));
|
|
1180
|
+
invalidateHomeSidebarSessions();
|
|
1181
|
+
},
|
|
1182
|
+
setRewindDraft: (sessionId, text) => {
|
|
1183
|
+
set((state) => {
|
|
1184
|
+
if (text == null) {
|
|
1185
|
+
const { [sessionId]: _, ...rest } = state.rewindDrafts;
|
|
1186
|
+
return { rewindDrafts: rest };
|
|
1187
|
+
}
|
|
1188
|
+
return {
|
|
1189
|
+
rewindDrafts: {
|
|
1190
|
+
...state.rewindDrafts,
|
|
1191
|
+
[sessionId]: text
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
});
|
|
1195
|
+
},
|
|
1196
|
+
setMode: (sessionId, mode) => {
|
|
1197
|
+
set((state) => ({ modes: { ...state.modes, [sessionId]: mode } }));
|
|
1198
|
+
},
|
|
1199
|
+
togglePlanningMode: (sessionId) => {
|
|
1200
|
+
const current = get().modes[sessionId] ?? DEFAULT_SESSION_MODE;
|
|
1201
|
+
const next = current === "executing" ? "planning" : "executing";
|
|
1202
|
+
set((state) => ({ modes: { ...state.modes, [sessionId]: next } }));
|
|
1203
|
+
},
|
|
1204
|
+
toggleSharing: async (sessionId) => {
|
|
1205
|
+
const session = get().sessions.find((s) => s.id === sessionId);
|
|
1206
|
+
if (!session) return;
|
|
1207
|
+
const newShared = !session.shared;
|
|
1208
|
+
const result = await updateSharing(sessionId, newShared);
|
|
1209
|
+
set((state) => ({
|
|
1210
|
+
sessions: state.sessions.map(
|
|
1211
|
+
(s) => s.id === sessionId ? { ...s, shared: result.shared } : s
|
|
1212
|
+
)
|
|
1213
|
+
}));
|
|
1214
|
+
},
|
|
1215
|
+
reset: () => {
|
|
1216
|
+
set({
|
|
1217
|
+
sessions: [],
|
|
1218
|
+
activeSessionId: null,
|
|
1219
|
+
loading: false,
|
|
1220
|
+
modes: {},
|
|
1221
|
+
rewindDrafts: {},
|
|
1222
|
+
_freshSessions: /* @__PURE__ */ new Set()
|
|
1223
|
+
});
|
|
1224
|
+
invalidateHomeSidebarSessions();
|
|
1225
|
+
}
|
|
1226
|
+
}));
|
|
1227
|
+
setChatStoreSessionAccessor(() => useSessionStore.getState().activeSessionId);
|
|
1228
|
+
|
|
1229
|
+
// src/react/stores/auth-store.ts
|
|
1230
|
+
var noopStorage = {
|
|
1231
|
+
getItem: () => null,
|
|
1232
|
+
setItem: () => {
|
|
1233
|
+
},
|
|
1234
|
+
removeItem: () => {
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
function shouldClearPersistedAuthState(value) {
|
|
1238
|
+
try {
|
|
1239
|
+
const parsed = JSON.parse(value);
|
|
1240
|
+
return parsed.state?.token == null && parsed.state?.user == null;
|
|
1241
|
+
} catch {
|
|
1242
|
+
return false;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
var authStorage = createJSONStorage(() => {
|
|
1246
|
+
if (typeof localStorage === "undefined") {
|
|
1247
|
+
return noopStorage;
|
|
1248
|
+
}
|
|
1249
|
+
return {
|
|
1250
|
+
getItem: (name) => localStorage.getItem(name),
|
|
1251
|
+
setItem: (name, value) => {
|
|
1252
|
+
if (shouldClearPersistedAuthState(value)) {
|
|
1253
|
+
localStorage.removeItem(name);
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
localStorage.setItem(name, value);
|
|
1257
|
+
},
|
|
1258
|
+
removeItem: (name) => localStorage.removeItem(name)
|
|
1259
|
+
};
|
|
1260
|
+
});
|
|
1261
|
+
function finishAuth(set, payload) {
|
|
1262
|
+
set({
|
|
1263
|
+
token: payload.token,
|
|
1264
|
+
socketAuthToken: null,
|
|
1265
|
+
user: payload.user,
|
|
1266
|
+
loading: false,
|
|
1267
|
+
error: null
|
|
1268
|
+
});
|
|
1269
|
+
agentSocket?.reconnect();
|
|
1270
|
+
useSessionStore.getState().fetchSessions().catch(() => {
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
function finishCookieHydration(set, payload) {
|
|
1274
|
+
set({
|
|
1275
|
+
token: null,
|
|
1276
|
+
socketAuthToken: payload.token,
|
|
1277
|
+
user: payload.user,
|
|
1278
|
+
loading: false,
|
|
1279
|
+
error: null
|
|
1280
|
+
});
|
|
1281
|
+
agentSocket?.reconnect();
|
|
1282
|
+
useSessionStore.getState().fetchSessions().catch(() => {
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
function toUser(info) {
|
|
1286
|
+
return {
|
|
1287
|
+
id: info.id,
|
|
1288
|
+
username: info.username,
|
|
1289
|
+
display_name: info.display_name,
|
|
1290
|
+
avatar_url: info.avatar_url,
|
|
1291
|
+
is_admin: info.is_admin
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
var useAuthStore = create6()(
|
|
1295
|
+
persist(
|
|
1296
|
+
(set, get) => ({
|
|
1297
|
+
...createClientActions(set),
|
|
1298
|
+
token: null,
|
|
1299
|
+
socketAuthToken: null,
|
|
1300
|
+
user: null,
|
|
1301
|
+
loading: false,
|
|
1302
|
+
error: null,
|
|
1303
|
+
logout: () => {
|
|
1304
|
+
void logout().catch(() => {
|
|
1305
|
+
});
|
|
1306
|
+
set({ token: null, socketAuthToken: null, user: null, error: null });
|
|
1307
|
+
agentSocket?.disconnect();
|
|
1308
|
+
useSessionStore.getState().reset();
|
|
1309
|
+
},
|
|
1310
|
+
checkAuth: async () => {
|
|
1311
|
+
const token = get().token;
|
|
1312
|
+
if (!token) return;
|
|
1313
|
+
try {
|
|
1314
|
+
const auth = await getMe();
|
|
1315
|
+
finishAuth(set, { token: auth.token, user: toUser(auth) });
|
|
1316
|
+
} catch {
|
|
1317
|
+
set({ token: null, socketAuthToken: null, user: null, error: null });
|
|
1318
|
+
useSessionStore.getState().reset();
|
|
1319
|
+
}
|
|
1320
|
+
},
|
|
1321
|
+
hydrateFromCookie: async () => {
|
|
1322
|
+
set({ loading: true, error: null });
|
|
1323
|
+
const previousToken = get().token;
|
|
1324
|
+
if (previousToken) {
|
|
1325
|
+
try {
|
|
1326
|
+
const auth = await getMe();
|
|
1327
|
+
finishAuth(set, { token: auth.token, user: toUser(auth) });
|
|
1328
|
+
return;
|
|
1329
|
+
} catch {
|
|
1330
|
+
set({ token: null, socketAuthToken: null, user: null, error: null });
|
|
1331
|
+
useSessionStore.getState().reset();
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
try {
|
|
1335
|
+
const auth = await getMe();
|
|
1336
|
+
finishCookieHydration(set, { token: auth.token, user: toUser(auth) });
|
|
1337
|
+
} catch {
|
|
1338
|
+
set({
|
|
1339
|
+
token: null,
|
|
1340
|
+
socketAuthToken: null,
|
|
1341
|
+
user: null,
|
|
1342
|
+
loading: false,
|
|
1343
|
+
error: null
|
|
1344
|
+
});
|
|
1345
|
+
useSessionStore.getState().reset();
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}),
|
|
1349
|
+
{
|
|
1350
|
+
name: "agent-auth",
|
|
1351
|
+
storage: authStorage,
|
|
1352
|
+
partialize: (state) => {
|
|
1353
|
+
if (state.socketAuthToken) {
|
|
1354
|
+
return { token: null, user: null };
|
|
1355
|
+
}
|
|
1356
|
+
return { token: state.token, user: state.user };
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
)
|
|
1360
|
+
);
|
|
1361
|
+
|
|
1362
|
+
// src/react/stores/background-store.ts
|
|
1363
|
+
import { create as create7 } from "zustand";
|
|
1364
|
+
var useBackgroundStore = create7()((set) => ({
|
|
1365
|
+
...createClientActions(set),
|
|
1366
|
+
tasks: {},
|
|
1367
|
+
selectedTaskId: {},
|
|
1368
|
+
setTasks: (sessionId, tasks) => set((state) => ({
|
|
1369
|
+
tasks: { ...state.tasks, [sessionId]: tasks },
|
|
1370
|
+
selectedTaskId: {
|
|
1371
|
+
...state.selectedTaskId,
|
|
1372
|
+
[sessionId]: state.selectedTaskId[sessionId] ?? tasks[0]?.id ?? null
|
|
1373
|
+
}
|
|
1374
|
+
})),
|
|
1375
|
+
upsertTask: (sessionId, task) => set((state) => {
|
|
1376
|
+
const existing = state.tasks[sessionId] ?? [];
|
|
1377
|
+
const index = existing.findIndex((item) => item.id === task.id);
|
|
1378
|
+
const next = [...existing];
|
|
1379
|
+
if (index >= 0) {
|
|
1380
|
+
next[index] = { ...next[index], ...task };
|
|
1381
|
+
} else {
|
|
1382
|
+
next.unshift(task);
|
|
1383
|
+
}
|
|
1384
|
+
return {
|
|
1385
|
+
tasks: { ...state.tasks, [sessionId]: next },
|
|
1386
|
+
selectedTaskId: {
|
|
1387
|
+
...state.selectedTaskId,
|
|
1388
|
+
[sessionId]: state.selectedTaskId[sessionId] ?? task.id
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
}),
|
|
1392
|
+
selectTask: (sessionId, taskId) => set((state) => ({
|
|
1393
|
+
selectedTaskId: { ...state.selectedTaskId, [sessionId]: taskId }
|
|
1394
|
+
}))
|
|
1395
|
+
}));
|
|
1396
|
+
|
|
1397
|
+
// src/react/stores/card-state-store.ts
|
|
1398
|
+
import { create as create8 } from "zustand";
|
|
1399
|
+
var useCardStateStore = create8((set, get) => ({
|
|
1400
|
+
...createClientActions(set),
|
|
1401
|
+
states: {},
|
|
1402
|
+
getCardState: (cardId) => {
|
|
1403
|
+
return get().states[cardId];
|
|
1404
|
+
},
|
|
1405
|
+
setCardState: (cardId, state) => {
|
|
1406
|
+
set((prev) => ({
|
|
1407
|
+
states: { ...prev.states, [cardId]: state }
|
|
1408
|
+
}));
|
|
1409
|
+
},
|
|
1410
|
+
removeCardState: (cardId) => {
|
|
1411
|
+
set((prev) => {
|
|
1412
|
+
const { [cardId]: _, ...rest } = prev.states;
|
|
1413
|
+
return { states: rest };
|
|
1414
|
+
});
|
|
1415
|
+
},
|
|
1416
|
+
clearAllStates: () => {
|
|
1417
|
+
set({ states: {} });
|
|
1418
|
+
}
|
|
1419
|
+
}));
|
|
1420
|
+
|
|
1421
|
+
// src/react/stores/connection-store.ts
|
|
1422
|
+
import { create as create9 } from "zustand";
|
|
1423
|
+
var initialConnectionState = {
|
|
1424
|
+
status: "disconnected",
|
|
1425
|
+
reconnectAttempt: 0,
|
|
1426
|
+
lastConnectedAt: null,
|
|
1427
|
+
lastDisconnectedAt: null,
|
|
1428
|
+
hasEverConnected: false
|
|
1429
|
+
};
|
|
1430
|
+
var useConnectionStore = create9()((set) => ({
|
|
1431
|
+
...createClientActions(set),
|
|
1432
|
+
...initialConnectionState,
|
|
1433
|
+
markConnecting: (attempt = 0) => set({
|
|
1434
|
+
status: "connecting",
|
|
1435
|
+
reconnectAttempt: attempt
|
|
1436
|
+
}),
|
|
1437
|
+
markConnected: () => set({
|
|
1438
|
+
status: "connected",
|
|
1439
|
+
reconnectAttempt: 0,
|
|
1440
|
+
lastConnectedAt: Date.now(),
|
|
1441
|
+
hasEverConnected: true
|
|
1442
|
+
}),
|
|
1443
|
+
markDisconnected: () => set({
|
|
1444
|
+
status: "disconnected",
|
|
1445
|
+
reconnectAttempt: 0,
|
|
1446
|
+
lastDisconnectedAt: Date.now()
|
|
1447
|
+
}),
|
|
1448
|
+
reset: () => set(initialConnectionState)
|
|
1449
|
+
}));
|
|
1450
|
+
|
|
1451
|
+
// src/react/stores/runtime-store.ts
|
|
1452
|
+
import { create as create10 } from "zustand";
|
|
1453
|
+
var useRuntimeStore = create10()((set) => ({
|
|
1454
|
+
...createClientActions(set),
|
|
1455
|
+
events: {},
|
|
1456
|
+
addEvent: (sessionId, event) => set((state) => {
|
|
1457
|
+
const current = state.events[sessionId] ?? [];
|
|
1458
|
+
const nextEvent = {
|
|
1459
|
+
...event,
|
|
1460
|
+
id: `${Date.now()}-${current.length}`,
|
|
1461
|
+
sessionId,
|
|
1462
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1463
|
+
};
|
|
1464
|
+
return {
|
|
1465
|
+
events: {
|
|
1466
|
+
...state.events,
|
|
1467
|
+
[sessionId]: [...current.slice(-59), nextEvent]
|
|
1468
|
+
}
|
|
1469
|
+
};
|
|
1470
|
+
}),
|
|
1471
|
+
clearSession: (sessionId) => set((state) => ({
|
|
1472
|
+
events: { ...state.events, [sessionId]: [] }
|
|
1473
|
+
}))
|
|
1474
|
+
}));
|
|
1475
|
+
|
|
1476
|
+
// src/react/stores/gis-store.ts
|
|
1477
|
+
import { create as create11 } from "zustand";
|
|
1478
|
+
var EMPTY_GOALS = [];
|
|
1479
|
+
var EMPTY_RESOURCES = [];
|
|
1480
|
+
var EMPTY_TARGETS = [];
|
|
1481
|
+
var EMPTY_COMMANDS = [];
|
|
1482
|
+
function newCommandId() {
|
|
1483
|
+
if (typeof globalThis !== "undefined" && "crypto" in globalThis) {
|
|
1484
|
+
return globalThis.crypto?.randomUUID?.() ?? `gis-map-${Date.now()}-${Math.random()}`;
|
|
1485
|
+
}
|
|
1486
|
+
return `gis-map-${Date.now()}-${Math.random()}`;
|
|
1487
|
+
}
|
|
1488
|
+
var useGisStore = create11()((set, get) => ({
|
|
1489
|
+
...createClientActions(set),
|
|
1490
|
+
goalsBySession: {},
|
|
1491
|
+
resourcesBySession: {},
|
|
1492
|
+
targetsBySession: {},
|
|
1493
|
+
pendingMapCommandsBySession: {},
|
|
1494
|
+
setGoals: (sessionId, goals) => {
|
|
1495
|
+
set((state) => ({
|
|
1496
|
+
goalsBySession: { ...state.goalsBySession, [sessionId]: goals }
|
|
1497
|
+
}));
|
|
1498
|
+
},
|
|
1499
|
+
setResources: (sessionId, resources) => {
|
|
1500
|
+
set((state) => ({
|
|
1501
|
+
resourcesBySession: { ...state.resourcesBySession, [sessionId]: resources }
|
|
1502
|
+
}));
|
|
1503
|
+
},
|
|
1504
|
+
setTargets: (sessionId, targets) => {
|
|
1505
|
+
set((state) => ({
|
|
1506
|
+
targetsBySession: { ...state.targetsBySession, [sessionId]: targets }
|
|
1507
|
+
}));
|
|
1508
|
+
},
|
|
1509
|
+
pushMapCommand: (sessionId, command) => {
|
|
1510
|
+
set((state) => ({
|
|
1511
|
+
pendingMapCommandsBySession: {
|
|
1512
|
+
...state.pendingMapCommandsBySession,
|
|
1513
|
+
[sessionId]: [
|
|
1514
|
+
...state.pendingMapCommandsBySession[sessionId] ?? EMPTY_COMMANDS,
|
|
1515
|
+
{
|
|
1516
|
+
...command,
|
|
1517
|
+
id: newCommandId(),
|
|
1518
|
+
createdAt: Date.now()
|
|
1519
|
+
}
|
|
1520
|
+
]
|
|
1521
|
+
}
|
|
1522
|
+
}));
|
|
1523
|
+
},
|
|
1524
|
+
consumeMapCommand: (sessionId, commandId) => {
|
|
1525
|
+
set((state) => ({
|
|
1526
|
+
pendingMapCommandsBySession: {
|
|
1527
|
+
...state.pendingMapCommandsBySession,
|
|
1528
|
+
[sessionId]: (state.pendingMapCommandsBySession[sessionId] ?? EMPTY_COMMANDS).filter(
|
|
1529
|
+
(command) => command.id !== commandId
|
|
1530
|
+
)
|
|
1531
|
+
}
|
|
1532
|
+
}));
|
|
1533
|
+
},
|
|
1534
|
+
getGoals: (sessionId) => get().goalsBySession[sessionId] ?? EMPTY_GOALS,
|
|
1535
|
+
getResources: (sessionId) => get().resourcesBySession[sessionId] ?? EMPTY_RESOURCES,
|
|
1536
|
+
getTargets: (sessionId) => get().targetsBySession[sessionId] ?? EMPTY_TARGETS,
|
|
1537
|
+
getPendingMapCommands: (sessionId) => get().pendingMapCommandsBySession[sessionId] ?? EMPTY_COMMANDS
|
|
1538
|
+
}));
|
|
1539
|
+
|
|
1540
|
+
// src/react/stores/answer-callback-store.ts
|
|
1541
|
+
import { create as create12 } from "zustand";
|
|
1542
|
+
var useAnswerCallbackStore = create12()((set) => ({
|
|
1543
|
+
...createClientActions(set),
|
|
1544
|
+
callbacks: {},
|
|
1545
|
+
setAnswerCallback: (sessionId, callback) => {
|
|
1546
|
+
set((state) => ({
|
|
1547
|
+
callbacks: {
|
|
1548
|
+
...state.callbacks,
|
|
1549
|
+
[sessionId]: callback
|
|
1550
|
+
}
|
|
1551
|
+
}));
|
|
1552
|
+
}
|
|
1553
|
+
}));
|
|
1554
|
+
|
|
1555
|
+
// src/react/stores/runtime-features-store.ts
|
|
1556
|
+
import { create as create13 } from "zustand";
|
|
1557
|
+
var useRuntimeFeaturesStore = create13((set) => ({
|
|
1558
|
+
...createClientActions(set),
|
|
1559
|
+
asrEnabled: false,
|
|
1560
|
+
asrProvider: "volcengine",
|
|
1561
|
+
publicSharingEnabled: false,
|
|
1562
|
+
memoryEnabled: false,
|
|
1563
|
+
setFeatures: (features) => set((prev) => ({
|
|
1564
|
+
asrEnabled: features.asrEnabled ?? prev.asrEnabled,
|
|
1565
|
+
asrProvider: features.asrProvider ?? prev.asrProvider,
|
|
1566
|
+
publicSharingEnabled: features.publicSharingEnabled ?? prev.publicSharingEnabled,
|
|
1567
|
+
memoryEnabled: features.memoryEnabled ?? prev.memoryEnabled
|
|
1568
|
+
}))
|
|
1569
|
+
}));
|
|
1570
|
+
|
|
1571
|
+
// src/react/bootstrap.ts
|
|
1572
|
+
var bootstrappedClient = null;
|
|
1573
|
+
function getBootstrappedClient() {
|
|
1574
|
+
if (!bootstrappedClient) {
|
|
1575
|
+
throw new Error("bootstrapBladeClient() must be called before any SDK usage");
|
|
1576
|
+
}
|
|
1577
|
+
return bootstrappedClient;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// src/react/api/client.ts
|
|
1581
|
+
function getAuthedUrl(path) {
|
|
1582
|
+
return getClient().buildAuthedUrl(path);
|
|
1583
|
+
}
|
|
1584
|
+
function getClient() {
|
|
1585
|
+
return getBootstrappedClient();
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
// src/react/components/card/CardCodeBlock.tsx
|
|
1589
|
+
import { useIsCodeFenceIncomplete } from "streamdown";
|
|
1590
|
+
|
|
1591
|
+
// src/react/lib/card-registry.ts
|
|
1592
|
+
var CardComponentRegistry = class {
|
|
1593
|
+
components;
|
|
1594
|
+
constructor(initial) {
|
|
1595
|
+
if (initial instanceof Map) {
|
|
1596
|
+
this.components = new Map(initial);
|
|
1597
|
+
} else if (initial) {
|
|
1598
|
+
this.components = new Map(Object.entries(initial));
|
|
1599
|
+
} else {
|
|
1600
|
+
this.components = /* @__PURE__ */ new Map();
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
register(type3, component) {
|
|
1604
|
+
this.components.set(type3, component);
|
|
1605
|
+
}
|
|
1606
|
+
get(type3) {
|
|
1607
|
+
return this.components.get(type3);
|
|
1608
|
+
}
|
|
1609
|
+
has(type3) {
|
|
1610
|
+
return this.components.has(type3);
|
|
1611
|
+
}
|
|
1612
|
+
keys() {
|
|
1613
|
+
return Array.from(this.components.keys());
|
|
1614
|
+
}
|
|
1615
|
+
clear() {
|
|
1616
|
+
this.components.clear();
|
|
1617
|
+
}
|
|
1618
|
+
get size() {
|
|
1619
|
+
return this.components.size;
|
|
1620
|
+
}
|
|
1621
|
+
};
|
|
1622
|
+
var cardRegistry = new CardComponentRegistry();
|
|
1623
|
+
var CardJSON = {
|
|
1624
|
+
safeParseJSON(text) {
|
|
1625
|
+
try {
|
|
1626
|
+
const value = JSON.parse(text);
|
|
1627
|
+
return { ok: true, value };
|
|
1628
|
+
} catch (error) {
|
|
1629
|
+
return {
|
|
1630
|
+
ok: false,
|
|
1631
|
+
error: error instanceof Error ? error.message : "Unknown parsing error"
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
},
|
|
1635
|
+
toCardArray(value) {
|
|
1636
|
+
if (value && typeof value === "object" && "type" in value && typeof value.type === "string") {
|
|
1637
|
+
return [value];
|
|
1638
|
+
}
|
|
1639
|
+
if (Array.isArray(value)) {
|
|
1640
|
+
const cards = value.filter(
|
|
1641
|
+
(item) => item && typeof item === "object" && "type" in item && typeof item.type === "string"
|
|
1642
|
+
);
|
|
1643
|
+
return cards.length > 0 ? cards : null;
|
|
1644
|
+
}
|
|
1645
|
+
return null;
|
|
1646
|
+
},
|
|
1647
|
+
isCardsLanguage(lang) {
|
|
1648
|
+
return lang === "card+json";
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
|
|
1652
|
+
// src/react/lib/code-highlight.ts
|
|
1653
|
+
import { useEffect, useMemo, useState } from "react";
|
|
1654
|
+
var DARK_THEME = "github-dark-default";
|
|
1655
|
+
var LIGHT_THEME = "github-light-default";
|
|
1656
|
+
var LANGUAGE_ALIASES = {
|
|
1657
|
+
bash: "bash",
|
|
1658
|
+
css: "css",
|
|
1659
|
+
go: "go",
|
|
1660
|
+
golang: "go",
|
|
1661
|
+
htm: "html",
|
|
1662
|
+
html: "html",
|
|
1663
|
+
javascript: "javascript",
|
|
1664
|
+
js: "javascript",
|
|
1665
|
+
json: "json",
|
|
1666
|
+
jsonc: "json",
|
|
1667
|
+
jsx: "jsx",
|
|
1668
|
+
markdown: "markdown",
|
|
1669
|
+
md: "markdown",
|
|
1670
|
+
py: "python",
|
|
1671
|
+
python: "python",
|
|
1672
|
+
rs: "rust",
|
|
1673
|
+
rust: "rust",
|
|
1674
|
+
scss: "css",
|
|
1675
|
+
sh: "bash",
|
|
1676
|
+
shell: "bash",
|
|
1677
|
+
ts: "typescript",
|
|
1678
|
+
tsx: "tsx",
|
|
1679
|
+
typescript: "typescript",
|
|
1680
|
+
yaml: "yaml",
|
|
1681
|
+
yml: "yaml",
|
|
1682
|
+
zsh: "bash"
|
|
1683
|
+
};
|
|
1684
|
+
var highlighterPromise = null;
|
|
1685
|
+
var CACHE_MAX_SIZE = 200;
|
|
1686
|
+
var highlightCache = /* @__PURE__ */ new Map();
|
|
1687
|
+
function normalizeCodeLanguage(language) {
|
|
1688
|
+
if (!language) return null;
|
|
1689
|
+
return LANGUAGE_ALIASES[language.trim().toLowerCase()] ?? null;
|
|
1690
|
+
}
|
|
1691
|
+
function useHighlightedCodeHtml(code, language) {
|
|
1692
|
+
const normalizedLanguage = useMemo(() => normalizeCodeLanguage(language), [language]);
|
|
1693
|
+
const [highlightedHtml, setHighlightedHtml] = useState(null);
|
|
1694
|
+
useEffect(() => {
|
|
1695
|
+
let cancelled = false;
|
|
1696
|
+
setHighlightedHtml(null);
|
|
1697
|
+
if (!normalizedLanguage) {
|
|
1698
|
+
return () => {
|
|
1699
|
+
cancelled = true;
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
void highlightCodeToInnerHtml(code, normalizedLanguage).then((result) => {
|
|
1703
|
+
if (!cancelled) {
|
|
1704
|
+
setHighlightedHtml(result);
|
|
1705
|
+
}
|
|
1706
|
+
});
|
|
1707
|
+
return () => {
|
|
1708
|
+
cancelled = true;
|
|
1709
|
+
};
|
|
1710
|
+
}, [code, normalizedLanguage]);
|
|
1711
|
+
return { highlightedHtml, language: normalizedLanguage };
|
|
1712
|
+
}
|
|
1713
|
+
async function highlightCodeToInnerHtml(code, language) {
|
|
1714
|
+
const cacheKey = `${language}\0${code}`;
|
|
1715
|
+
const cached = highlightCache.get(cacheKey);
|
|
1716
|
+
if (cached) {
|
|
1717
|
+
return cached;
|
|
1718
|
+
}
|
|
1719
|
+
const request = loadCodeHighlighter().then((highlighter) => {
|
|
1720
|
+
const html = highlighter.codeToHtml(code, {
|
|
1721
|
+
lang: language,
|
|
1722
|
+
themes: { light: LIGHT_THEME, dark: DARK_THEME },
|
|
1723
|
+
defaultColor: false
|
|
1724
|
+
});
|
|
1725
|
+
return extractInnerCodeHtml(html);
|
|
1726
|
+
}).catch(() => {
|
|
1727
|
+
highlightCache.delete(cacheKey);
|
|
1728
|
+
return null;
|
|
1729
|
+
});
|
|
1730
|
+
if (highlightCache.size >= CACHE_MAX_SIZE) {
|
|
1731
|
+
const oldest = highlightCache.keys().next().value;
|
|
1732
|
+
if (oldest !== void 0) highlightCache.delete(oldest);
|
|
1733
|
+
}
|
|
1734
|
+
highlightCache.set(cacheKey, request);
|
|
1735
|
+
return request;
|
|
1736
|
+
}
|
|
1737
|
+
async function loadCodeHighlighter() {
|
|
1738
|
+
if (!highlighterPromise) {
|
|
1739
|
+
highlighterPromise = Promise.all([
|
|
1740
|
+
import("shiki/core"),
|
|
1741
|
+
import("shiki/engine/javascript"),
|
|
1742
|
+
import("@shikijs/langs/bash"),
|
|
1743
|
+
import("@shikijs/langs/css"),
|
|
1744
|
+
import("@shikijs/langs/go"),
|
|
1745
|
+
import("@shikijs/langs/html"),
|
|
1746
|
+
import("@shikijs/langs/javascript"),
|
|
1747
|
+
import("@shikijs/langs/json"),
|
|
1748
|
+
import("@shikijs/langs/jsx"),
|
|
1749
|
+
import("@shikijs/langs/markdown"),
|
|
1750
|
+
import("@shikijs/langs/python"),
|
|
1751
|
+
import("@shikijs/langs/rust"),
|
|
1752
|
+
import("@shikijs/langs/tsx"),
|
|
1753
|
+
import("@shikijs/langs/typescript"),
|
|
1754
|
+
import("@shikijs/langs/yaml"),
|
|
1755
|
+
import("@shikijs/themes/github-dark-default"),
|
|
1756
|
+
import("@shikijs/themes/github-light-default")
|
|
1757
|
+
]).then(
|
|
1758
|
+
([
|
|
1759
|
+
core,
|
|
1760
|
+
engine,
|
|
1761
|
+
bash,
|
|
1762
|
+
css,
|
|
1763
|
+
go,
|
|
1764
|
+
html,
|
|
1765
|
+
javascript,
|
|
1766
|
+
json,
|
|
1767
|
+
jsx13,
|
|
1768
|
+
markdown,
|
|
1769
|
+
python,
|
|
1770
|
+
rust,
|
|
1771
|
+
tsx,
|
|
1772
|
+
typescript,
|
|
1773
|
+
yaml,
|
|
1774
|
+
darkTheme,
|
|
1775
|
+
lightTheme
|
|
1776
|
+
]) => core.createHighlighterCore({
|
|
1777
|
+
engine: engine.createJavaScriptRegexEngine(),
|
|
1778
|
+
langs: [
|
|
1779
|
+
bash.default,
|
|
1780
|
+
css.default,
|
|
1781
|
+
go.default,
|
|
1782
|
+
html.default,
|
|
1783
|
+
javascript.default,
|
|
1784
|
+
json.default,
|
|
1785
|
+
jsx13.default,
|
|
1786
|
+
markdown.default,
|
|
1787
|
+
python.default,
|
|
1788
|
+
rust.default,
|
|
1789
|
+
tsx.default,
|
|
1790
|
+
typescript.default,
|
|
1791
|
+
yaml.default
|
|
1792
|
+
],
|
|
1793
|
+
themes: [darkTheme.default, lightTheme.default]
|
|
1794
|
+
})
|
|
1795
|
+
).catch((err) => {
|
|
1796
|
+
highlighterPromise = null;
|
|
1797
|
+
throw err;
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
return highlighterPromise;
|
|
1801
|
+
}
|
|
1802
|
+
function extractInnerCodeHtml(html) {
|
|
1803
|
+
const match = html.match(/<code[^>]*>([\s\S]*)<\/code><\/pre>\s*$/);
|
|
1804
|
+
return match?.[1] ?? null;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
// src/react/components/card/CardRenderer.tsx
|
|
1808
|
+
import { Component } from "react";
|
|
1809
|
+
|
|
1810
|
+
// src/react/components/card/CardContext.tsx
|
|
1811
|
+
import { createContext, useContext } from "react";
|
|
1812
|
+
var CardContext = createContext({});
|
|
1813
|
+
var useCardContext = () => useContext(CardContext);
|
|
1814
|
+
|
|
1815
|
+
// src/react/components/card/CardStates.tsx
|
|
1816
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1817
|
+
function CardLoadingState({ content }) {
|
|
1818
|
+
return /* @__PURE__ */ jsxs("div", { className: "my-4 rounded-md border border-[hsl(var(--primary)/0.2)] bg-[hsl(var(--primary)/0.05)] p-4", children: [
|
|
1819
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center gap-2", children: [
|
|
1820
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-4 animate-spin rounded-full border-2 border-[hsl(var(--primary))] border-t-transparent" }),
|
|
1821
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-[hsl(var(--primary))]", children: "\u6B63\u5728\u52A0\u8F7D\u5361\u7247\u6570\u636E..." })
|
|
1822
|
+
] }),
|
|
1823
|
+
/* @__PURE__ */ jsxs("details", { className: "mt-2", children: [
|
|
1824
|
+
/* @__PURE__ */ jsx("summary", { className: "cursor-pointer text-xs text-[hsl(var(--primary)/0.7)]", children: "\u67E5\u770B\u63A5\u6536\u4E2D\u7684\u6570\u636E" }),
|
|
1825
|
+
/* @__PURE__ */ jsx("pre", { className: "mt-1 whitespace-pre-wrap rounded bg-[hsl(var(--primary)/0.1)] p-2 font-mono text-xs text-[hsl(var(--primary)/0.8)]", children: content })
|
|
1826
|
+
] })
|
|
1827
|
+
] });
|
|
1828
|
+
}
|
|
1829
|
+
function CardErrorState({ content, message }) {
|
|
1830
|
+
return /* @__PURE__ */ jsxs("div", { className: "my-4 rounded-md border border-red-500/20 bg-red-500/5 p-4", children: [
|
|
1831
|
+
/* @__PURE__ */ jsx("p", { className: "mb-2 text-sm text-red-400", children: message || "\u5361\u7247\u6570\u636E\u89E3\u6790\u5931\u8D25" }),
|
|
1832
|
+
/* @__PURE__ */ jsxs("details", { className: "mt-2", children: [
|
|
1833
|
+
/* @__PURE__ */ jsx("summary", { className: "cursor-pointer text-xs text-red-400/70 hover:underline", children: "\u67E5\u770B\u539F\u59CB\u4EE3\u7801\u5757" }),
|
|
1834
|
+
/* @__PURE__ */ jsx("pre", { className: "mt-2 overflow-x-auto whitespace-pre-wrap rounded bg-red-500/10 p-3 font-mono text-xs text-red-400/80", children: content })
|
|
1835
|
+
] })
|
|
1836
|
+
] });
|
|
1837
|
+
}
|
|
1838
|
+
function CardWarningState({ content, message }) {
|
|
1839
|
+
return /* @__PURE__ */ jsxs("div", { className: "my-4 rounded-md border border-yellow-500/20 bg-yellow-500/5 p-4", children: [
|
|
1840
|
+
/* @__PURE__ */ jsx("p", { className: "mb-2 text-sm text-yellow-400", children: message || "\u6570\u636E\u683C\u5F0F\u4E0D\u7B26\u5408\u5361\u7247\u8981\u6C42" }),
|
|
1841
|
+
/* @__PURE__ */ jsxs("details", { className: "mt-2", children: [
|
|
1842
|
+
/* @__PURE__ */ jsx("summary", { className: "cursor-pointer text-xs text-yellow-400/70", children: "\u67E5\u770B\u539F\u59CB\u6570\u636E" }),
|
|
1843
|
+
/* @__PURE__ */ jsx("pre", { className: "mt-1 whitespace-pre-wrap rounded bg-yellow-500/10 p-2 font-mono text-xs text-yellow-400/80", children: content })
|
|
1844
|
+
] })
|
|
1845
|
+
] });
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
// src/react/components/card/CardRenderer.tsx
|
|
1849
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1850
|
+
function looksIncomplete(text) {
|
|
1851
|
+
const t = text.trim();
|
|
1852
|
+
if (!t) return true;
|
|
1853
|
+
const openBraces = (t.match(/{/g) || []).length;
|
|
1854
|
+
const closeBraces = (t.match(/}/g) || []).length;
|
|
1855
|
+
const openBrackets = (t.match(/\[/g) || []).length;
|
|
1856
|
+
const closeBrackets = (t.match(/]/g) || []).length;
|
|
1857
|
+
const quotes = (t.match(/"/g) || []).length;
|
|
1858
|
+
return !t.endsWith("}") && !t.endsWith("]") || openBraces !== closeBraces || openBrackets !== closeBrackets || t.endsWith(",") || t.endsWith(":") || quotes % 2 !== 0;
|
|
1859
|
+
}
|
|
1860
|
+
var CardErrorBoundary = class extends Component {
|
|
1861
|
+
state = { hasError: false };
|
|
1862
|
+
static getDerivedStateFromError() {
|
|
1863
|
+
return { hasError: true };
|
|
1864
|
+
}
|
|
1865
|
+
componentDidCatch(error, info) {
|
|
1866
|
+
console.error("Card render error:", error, info);
|
|
1867
|
+
}
|
|
1868
|
+
render() {
|
|
1869
|
+
if (this.state.hasError) return this.props.fallback;
|
|
1870
|
+
return this.props.children;
|
|
1871
|
+
}
|
|
1872
|
+
};
|
|
1873
|
+
function OpenInPreviewButton({ card }) {
|
|
1874
|
+
const pushArtifact = useUiStore((s) => s.pushArtifact);
|
|
1875
|
+
return /* @__PURE__ */ jsx2(
|
|
1876
|
+
"button",
|
|
1877
|
+
{
|
|
1878
|
+
type: "button",
|
|
1879
|
+
onClick: () => pushArtifact({
|
|
1880
|
+
type: "card",
|
|
1881
|
+
content: JSON.stringify(card, null, 2),
|
|
1882
|
+
title: card.title || card.type,
|
|
1883
|
+
key: `card-${card.id}`
|
|
1884
|
+
}),
|
|
1885
|
+
className: "absolute right-2 top-2 rounded bg-[hsl(var(--accent))] px-1.5 py-0.5 text-[10px] text-[hsl(var(--muted-foreground))] opacity-0 transition-opacity group-hover/card:opacity-100 hover:text-[hsl(var(--foreground))]",
|
|
1886
|
+
children: "\u9884\u89C8"
|
|
1887
|
+
}
|
|
1888
|
+
);
|
|
1889
|
+
}
|
|
1890
|
+
function CardRenderer({ raw, blockPosition, isCodeFenceIncomplete }) {
|
|
1891
|
+
const { sessionId, messageId, sendMessage } = useCardContext();
|
|
1892
|
+
const trimmed = raw.trim();
|
|
1893
|
+
if (!trimmed) return /* @__PURE__ */ jsx2(CardLoadingState, { content: "" });
|
|
1894
|
+
if (isCodeFenceIncomplete) {
|
|
1895
|
+
return /* @__PURE__ */ jsx2(CardLoadingState, { content: trimmed });
|
|
1896
|
+
}
|
|
1897
|
+
const parsed = CardJSON.safeParseJSON(trimmed);
|
|
1898
|
+
if (!parsed.ok) {
|
|
1899
|
+
if (looksIncomplete(trimmed)) {
|
|
1900
|
+
return /* @__PURE__ */ jsx2(CardLoadingState, { content: trimmed });
|
|
1901
|
+
}
|
|
1902
|
+
return /* @__PURE__ */ jsx2(CardErrorState, { content: trimmed, message: parsed.error });
|
|
1903
|
+
}
|
|
1904
|
+
const cards = CardJSON.toCardArray(parsed.value);
|
|
1905
|
+
if (!cards) {
|
|
1906
|
+
return /* @__PURE__ */ jsx2(CardWarningState, { content: trimmed, message: "JSON \u683C\u5F0F\u6B63\u786E\u4F46\u4E0D\u7B26\u5408\u5361\u7247\u7ED3\u6784" });
|
|
1907
|
+
}
|
|
1908
|
+
const cardsWithIds = cards.map((c, i) => {
|
|
1909
|
+
if (c.id) return c;
|
|
1910
|
+
return {
|
|
1911
|
+
...c,
|
|
1912
|
+
id: `${messageId || "msg"}-block-${blockPosition ?? 0}-card-${i}`
|
|
1913
|
+
};
|
|
1914
|
+
});
|
|
1915
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: cardsWithIds.map((card) => {
|
|
1916
|
+
const CardComponent = cardRegistry.get(card.type);
|
|
1917
|
+
if (!CardComponent) {
|
|
1918
|
+
return /* @__PURE__ */ jsx2(
|
|
1919
|
+
CardWarningState,
|
|
1920
|
+
{
|
|
1921
|
+
content: JSON.stringify(card, null, 2),
|
|
1922
|
+
message: `\u672A\u77E5\u7684\u5361\u7247\u7C7B\u578B: ${card.type}`
|
|
1923
|
+
},
|
|
1924
|
+
card.id
|
|
1925
|
+
);
|
|
1926
|
+
}
|
|
1927
|
+
return /* @__PURE__ */ jsx2("div", { className: "group/card relative my-4", children: /* @__PURE__ */ jsxs2(
|
|
1928
|
+
CardErrorBoundary,
|
|
1929
|
+
{
|
|
1930
|
+
fallback: /* @__PURE__ */ jsx2(
|
|
1931
|
+
CardErrorState,
|
|
1932
|
+
{
|
|
1933
|
+
content: JSON.stringify(card, null, 2),
|
|
1934
|
+
message: "\u5361\u7247\u6E32\u67D3\u51FA\u9519"
|
|
1935
|
+
}
|
|
1936
|
+
),
|
|
1937
|
+
children: [
|
|
1938
|
+
/* @__PURE__ */ jsx2(CardComponent, { card, sendMessage, sessionId }),
|
|
1939
|
+
/* @__PURE__ */ jsx2(OpenInPreviewButton, { card })
|
|
1940
|
+
]
|
|
1941
|
+
}
|
|
1942
|
+
) }, card.id);
|
|
1943
|
+
}) });
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// src/react/components/card/CardCodeBlock.tsx
|
|
1947
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
1948
|
+
function CardCodeBlock({ className, children, node, ...props }) {
|
|
1949
|
+
const isIncomplete = useIsCodeFenceIncomplete();
|
|
1950
|
+
const match = /language-(\S+)/.exec(className || "");
|
|
1951
|
+
const lang = match?.[1];
|
|
1952
|
+
const isInline = !className;
|
|
1953
|
+
const raw = String(children ?? "");
|
|
1954
|
+
const normalizedLang = normalizeCodeLanguage(lang);
|
|
1955
|
+
const { highlightedHtml } = useHighlightedCodeHtml(raw, normalizedLang);
|
|
1956
|
+
if (!isInline && CardJSON.isCardsLanguage(lang)) {
|
|
1957
|
+
return /* @__PURE__ */ jsx3(
|
|
1958
|
+
CardRenderer,
|
|
1959
|
+
{
|
|
1960
|
+
raw,
|
|
1961
|
+
blockPosition: node?.position?.start?.line,
|
|
1962
|
+
isCodeFenceIncomplete: isIncomplete
|
|
1963
|
+
}
|
|
1964
|
+
);
|
|
1965
|
+
}
|
|
1966
|
+
if (!isInline && highlightedHtml) {
|
|
1967
|
+
return /* @__PURE__ */ jsx3(
|
|
1968
|
+
"code",
|
|
1969
|
+
{
|
|
1970
|
+
className,
|
|
1971
|
+
...props,
|
|
1972
|
+
dangerouslySetInnerHTML: { __html: highlightedHtml }
|
|
1973
|
+
}
|
|
1974
|
+
);
|
|
1975
|
+
}
|
|
1976
|
+
return /* @__PURE__ */ jsx3("code", { className, ...props, children });
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
// src/react/components/markdown/MarkdownContent.tsx
|
|
1980
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1981
|
+
var STREAMDOWN_PLUGINS = { mermaid };
|
|
1982
|
+
var SYSTEM_REMINDER_TAG = "system-reminder";
|
|
1983
|
+
var HIDDEN_SYSTEM_REMINDER_TAGS = {
|
|
1984
|
+
[SYSTEM_REMINDER_TAG]: []
|
|
1985
|
+
};
|
|
1986
|
+
var HIDDEN_SYSTEM_REMINDER_COMPONENTS = {
|
|
1987
|
+
[SYSTEM_REMINDER_TAG]: () => null
|
|
1988
|
+
};
|
|
1989
|
+
function CodeBlockPre({ children, node: _node, ...props }) {
|
|
1990
|
+
const preRef = useRef(null);
|
|
1991
|
+
const [copied, setCopied] = useState2(false);
|
|
1992
|
+
const [hasCodeNode, setHasCodeNode] = useState2(false);
|
|
1993
|
+
useEffect2(() => {
|
|
1994
|
+
setHasCodeNode(!!preRef.current?.querySelector("code"));
|
|
1995
|
+
}, []);
|
|
1996
|
+
const getRawCode = () => preRef.current?.querySelector("code")?.textContent ?? "";
|
|
1997
|
+
const handleCopy = async () => {
|
|
1998
|
+
const ok = await copyToClipboard(getRawCode());
|
|
1999
|
+
if (ok) {
|
|
2000
|
+
setCopied(true);
|
|
2001
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
2002
|
+
}
|
|
2003
|
+
};
|
|
2004
|
+
const handleDownload = () => {
|
|
2005
|
+
const codeEl = preRef.current?.querySelector("code");
|
|
2006
|
+
const text = codeEl?.textContent ?? "";
|
|
2007
|
+
const ext = codeEl?.className.match(/language-(\S+)/)?.[1] ?? "txt";
|
|
2008
|
+
const blob = new Blob([text], { type: "text/plain" });
|
|
2009
|
+
const url = URL.createObjectURL(blob);
|
|
2010
|
+
const a = document.createElement("a");
|
|
2011
|
+
a.href = url;
|
|
2012
|
+
a.download = `code.${ext}`;
|
|
2013
|
+
a.click();
|
|
2014
|
+
URL.revokeObjectURL(url);
|
|
2015
|
+
};
|
|
2016
|
+
return /* @__PURE__ */ jsxs3("div", { className: "relative group", children: [
|
|
2017
|
+
/* @__PURE__ */ jsx4("pre", { ref: preRef, ...props, children }),
|
|
2018
|
+
hasCodeNode && /* @__PURE__ */ jsxs3("div", { className: "absolute top-2 right-2 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity", children: [
|
|
2019
|
+
/* @__PURE__ */ jsxs3(
|
|
2020
|
+
"button",
|
|
2021
|
+
{
|
|
2022
|
+
type: "button",
|
|
2023
|
+
onClick: handleCopy,
|
|
2024
|
+
className: cn(
|
|
2025
|
+
"flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[11px] transition-colors",
|
|
2026
|
+
copied ? "text-[hsl(var(--primary))]" : "text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))] hover:bg-[hsl(var(--accent))]"
|
|
2027
|
+
),
|
|
2028
|
+
children: [
|
|
2029
|
+
copied ? /* @__PURE__ */ jsx4(Check, { size: 12 }) : /* @__PURE__ */ jsx4(Copy, { size: 12 }),
|
|
2030
|
+
/* @__PURE__ */ jsx4("span", { children: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236" })
|
|
2031
|
+
]
|
|
2032
|
+
}
|
|
2033
|
+
),
|
|
2034
|
+
/* @__PURE__ */ jsxs3(
|
|
2035
|
+
"button",
|
|
2036
|
+
{
|
|
2037
|
+
type: "button",
|
|
2038
|
+
onClick: handleDownload,
|
|
2039
|
+
className: "flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[11px] transition-colors text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))] hover:bg-[hsl(var(--accent))]",
|
|
2040
|
+
children: [
|
|
2041
|
+
/* @__PURE__ */ jsx4(Download, { size: 12 }),
|
|
2042
|
+
/* @__PURE__ */ jsx4("span", { children: "\u4E0B\u8F7D" })
|
|
2043
|
+
]
|
|
2044
|
+
}
|
|
2045
|
+
)
|
|
2046
|
+
] })
|
|
2047
|
+
] });
|
|
2048
|
+
}
|
|
2049
|
+
function isExternalImageSrc(src) {
|
|
2050
|
+
if (!src) return false;
|
|
2051
|
+
if (src.startsWith("data:") || src.startsWith("blob:")) return true;
|
|
2052
|
+
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(src)) return true;
|
|
2053
|
+
if (src.startsWith("/")) return true;
|
|
2054
|
+
return false;
|
|
2055
|
+
}
|
|
2056
|
+
function decodeMarkdownPath(src) {
|
|
2057
|
+
try {
|
|
2058
|
+
return decodeURIComponent(src);
|
|
2059
|
+
} catch {
|
|
2060
|
+
return src;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
var MARKDOWN_IMAGE_PATTERN = /!\[((?:\\.|[^\]\\])*)\]\(\s*(?:<([^>\n]+)>|([^\s)]+))(\s+(?:"[^"]*"|'[^']*'|\([^)]*\)))?\s*\)/g;
|
|
2064
|
+
function resolveSessionImageMarkdown(children, sessionId) {
|
|
2065
|
+
return children.replace(
|
|
2066
|
+
MARKDOWN_IMAGE_PATTERN,
|
|
2067
|
+
(match, alt, angleSrc, plainSrc, title = "") => {
|
|
2068
|
+
const src = angleSrc ?? plainSrc;
|
|
2069
|
+
if (!src || isExternalImageSrc(src)) {
|
|
2070
|
+
return match;
|
|
2071
|
+
}
|
|
2072
|
+
const resolved = getAuthedUrl(
|
|
2073
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/files/${encodeURIComponent(decodeMarkdownPath(src))}`
|
|
2074
|
+
);
|
|
2075
|
+
return ``;
|
|
2076
|
+
}
|
|
2077
|
+
);
|
|
2078
|
+
}
|
|
2079
|
+
function MarkdownContent({
|
|
2080
|
+
allowedTags,
|
|
2081
|
+
children,
|
|
2082
|
+
components,
|
|
2083
|
+
mode,
|
|
2084
|
+
plugins,
|
|
2085
|
+
sessionId,
|
|
2086
|
+
...props
|
|
2087
|
+
}) {
|
|
2088
|
+
const resolvedChildren = useMemo2(
|
|
2089
|
+
() => sessionId && typeof children === "string" ? resolveSessionImageMarkdown(children, sessionId) : children,
|
|
2090
|
+
[children, sessionId]
|
|
2091
|
+
);
|
|
2092
|
+
return /* @__PURE__ */ jsx4(
|
|
2093
|
+
Streamdown,
|
|
2094
|
+
{
|
|
2095
|
+
...props,
|
|
2096
|
+
mode: mode ?? "static",
|
|
2097
|
+
allowedTags: {
|
|
2098
|
+
...allowedTags ?? {},
|
|
2099
|
+
...HIDDEN_SYSTEM_REMINDER_TAGS
|
|
2100
|
+
},
|
|
2101
|
+
components: {
|
|
2102
|
+
code: CardCodeBlock,
|
|
2103
|
+
pre: CodeBlockPre,
|
|
2104
|
+
...components ?? {},
|
|
2105
|
+
...HIDDEN_SYSTEM_REMINDER_COMPONENTS
|
|
2106
|
+
},
|
|
2107
|
+
plugins: {
|
|
2108
|
+
...STREAMDOWN_PLUGINS,
|
|
2109
|
+
...plugins ?? {}
|
|
2110
|
+
},
|
|
2111
|
+
children: resolvedChildren
|
|
2112
|
+
}
|
|
2113
|
+
);
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
// src/react/components/chat/AskUserQuestionBlock.tsx
|
|
2117
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2118
|
+
function AskUserQuestionBlock({
|
|
2119
|
+
data,
|
|
2120
|
+
answered,
|
|
2121
|
+
toolCallId,
|
|
2122
|
+
sessionStatus,
|
|
2123
|
+
answerData,
|
|
2124
|
+
onAnswer
|
|
2125
|
+
}) {
|
|
2126
|
+
const [selections, setSelections] = useState3(/* @__PURE__ */ new Map());
|
|
2127
|
+
const [customTexts, setCustomTexts] = useState3(/* @__PURE__ */ new Map());
|
|
2128
|
+
const [usingCustom, setUsingCustom] = useState3(/* @__PURE__ */ new Set());
|
|
2129
|
+
const [submitted, setSubmitted] = useState3(false);
|
|
2130
|
+
useEffect3(() => {
|
|
2131
|
+
if (sessionStatus === "failed" || sessionStatus === "interrupted") {
|
|
2132
|
+
setSubmitted(false);
|
|
2133
|
+
}
|
|
2134
|
+
}, [sessionStatus]);
|
|
2135
|
+
const displayAnswerState = useMemo3(() => {
|
|
2136
|
+
if (!(answered && answerData)) {
|
|
2137
|
+
return {
|
|
2138
|
+
selections,
|
|
2139
|
+
customTexts,
|
|
2140
|
+
usingCustom
|
|
2141
|
+
};
|
|
2142
|
+
}
|
|
2143
|
+
const nextSelections = /* @__PURE__ */ new Map();
|
|
2144
|
+
const nextCustomTexts = /* @__PURE__ */ new Map();
|
|
2145
|
+
const nextUsingCustom = /* @__PURE__ */ new Set();
|
|
2146
|
+
for (const [questionKey, optionIndexes] of Object.entries(answerData.selections)) {
|
|
2147
|
+
nextSelections.set(Number(questionKey), new Set(optionIndexes));
|
|
2148
|
+
}
|
|
2149
|
+
for (const [questionKey, text] of Object.entries(answerData.custom)) {
|
|
2150
|
+
const qIdx = Number(questionKey);
|
|
2151
|
+
nextCustomTexts.set(qIdx, text);
|
|
2152
|
+
nextUsingCustom.add(qIdx);
|
|
2153
|
+
}
|
|
2154
|
+
return {
|
|
2155
|
+
selections: nextSelections,
|
|
2156
|
+
customTexts: nextCustomTexts,
|
|
2157
|
+
usingCustom: nextUsingCustom
|
|
2158
|
+
};
|
|
2159
|
+
}, [answerData, answered, customTexts, selections, usingCustom]);
|
|
2160
|
+
const displaySelections = displayAnswerState.selections;
|
|
2161
|
+
const displayCustomTexts = displayAnswerState.customTexts;
|
|
2162
|
+
const displayUsingCustom = displayAnswerState.usingCustom;
|
|
2163
|
+
const toggleOption = (qIdx, optIdx, multi) => {
|
|
2164
|
+
if (answered || submitted) return;
|
|
2165
|
+
setSelections((prev) => {
|
|
2166
|
+
const next = new Map(prev);
|
|
2167
|
+
const current = new Set(next.get(qIdx) ?? []);
|
|
2168
|
+
if (multi) {
|
|
2169
|
+
if (current.has(optIdx)) current.delete(optIdx);
|
|
2170
|
+
else current.add(optIdx);
|
|
2171
|
+
} else {
|
|
2172
|
+
current.clear();
|
|
2173
|
+
current.add(optIdx);
|
|
2174
|
+
}
|
|
2175
|
+
next.set(qIdx, current);
|
|
2176
|
+
return next;
|
|
2177
|
+
});
|
|
2178
|
+
setUsingCustom((prev) => {
|
|
2179
|
+
const next = new Set(prev);
|
|
2180
|
+
next.delete(qIdx);
|
|
2181
|
+
return next;
|
|
2182
|
+
});
|
|
2183
|
+
};
|
|
2184
|
+
const handleCustomFocus = (qIdx) => {
|
|
2185
|
+
if (answered || submitted) return;
|
|
2186
|
+
setSelections((prev) => {
|
|
2187
|
+
const next = new Map(prev);
|
|
2188
|
+
next.delete(qIdx);
|
|
2189
|
+
return next;
|
|
2190
|
+
});
|
|
2191
|
+
setUsingCustom((prev) => new Set(prev).add(qIdx));
|
|
2192
|
+
};
|
|
2193
|
+
const setCustomText = (qIdx, text) => {
|
|
2194
|
+
if (answered || submitted) return;
|
|
2195
|
+
setCustomTexts((prev) => new Map(prev).set(qIdx, text));
|
|
2196
|
+
};
|
|
2197
|
+
const getAnswer = (qIdx) => {
|
|
2198
|
+
const q = data.questions[qIdx];
|
|
2199
|
+
if (usingCustom.has(qIdx)) {
|
|
2200
|
+
const text = (customTexts.get(qIdx) ?? "").trim();
|
|
2201
|
+
return text || null;
|
|
2202
|
+
}
|
|
2203
|
+
const sel = selections.get(qIdx);
|
|
2204
|
+
if (!sel || sel.size === 0) return null;
|
|
2205
|
+
return [...sel].sort().map((i) => q.options[i].label).join(", ");
|
|
2206
|
+
};
|
|
2207
|
+
const allAnswered = data.questions.every((_, i) => getAnswer(i) !== null);
|
|
2208
|
+
const handleSubmit = () => {
|
|
2209
|
+
if (answered || submitted || !allAnswered || !onAnswer) return;
|
|
2210
|
+
const nextAnswerData = {
|
|
2211
|
+
selections: Object.fromEntries(
|
|
2212
|
+
Array.from(selections.entries()).map(([qIdx, optionIndexes]) => [
|
|
2213
|
+
qIdx,
|
|
2214
|
+
Array.from(optionIndexes).sort((a, b) => a - b)
|
|
2215
|
+
])
|
|
2216
|
+
),
|
|
2217
|
+
custom: Object.fromEntries(
|
|
2218
|
+
Array.from(usingCustom).map((qIdx) => [qIdx, (customTexts.get(qIdx) ?? "").trim()]).filter(([, text2]) => text2.length > 0)
|
|
2219
|
+
)
|
|
2220
|
+
};
|
|
2221
|
+
const parts = data.questions.map((q, i) => `- ${q.question} -> ${getAnswer(i)}`);
|
|
2222
|
+
const text = `\u5173\u4E8E\u9700\u8981\u786E\u8BA4\u7684\u95EE\u9898\uFF0C\u7528\u6237\u7684\u56DE\u7B54\u5982\u4E0B\uFF1A
|
|
2223
|
+
${parts.join("\n")}`;
|
|
2224
|
+
setSubmitted(true);
|
|
2225
|
+
onAnswer(text, toolCallId, nextAnswerData);
|
|
2226
|
+
};
|
|
2227
|
+
return /* @__PURE__ */ jsxs4(
|
|
2228
|
+
"div",
|
|
2229
|
+
{
|
|
2230
|
+
className: cn(
|
|
2231
|
+
"ml-4 rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))]",
|
|
2232
|
+
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"
|
|
2233
|
+
),
|
|
2234
|
+
children: [
|
|
2235
|
+
data.source_loop?.description && /* @__PURE__ */ jsxs4("div", { className: "rounded-lg bg-[hsl(var(--muted)/0.35)] px-3 py-2 text-xs text-[hsl(var(--muted-foreground))]", children: [
|
|
2236
|
+
"\u5B50\u667A\u80FD\u4F53\u300C",
|
|
2237
|
+
data.source_loop.description,
|
|
2238
|
+
"\u300D\u5728\u7B49\u5F85\u4F60\u7684\u56DE\u7B54"
|
|
2239
|
+
] }),
|
|
2240
|
+
data.questions.map((q, qIdx) => /* @__PURE__ */ jsx5(
|
|
2241
|
+
QuestionCard,
|
|
2242
|
+
{
|
|
2243
|
+
question: q,
|
|
2244
|
+
qIdx,
|
|
2245
|
+
answered,
|
|
2246
|
+
selected: displaySelections.get(qIdx) ?? /* @__PURE__ */ new Set(),
|
|
2247
|
+
isCustom: displayUsingCustom.has(qIdx),
|
|
2248
|
+
customText: displayCustomTexts.get(qIdx) ?? "",
|
|
2249
|
+
onToggle: toggleOption,
|
|
2250
|
+
onCustomFocus: handleCustomFocus,
|
|
2251
|
+
onCustomChange: setCustomText
|
|
2252
|
+
},
|
|
2253
|
+
q.question
|
|
2254
|
+
)),
|
|
2255
|
+
!answered && !submitted && onAnswer && /* @__PURE__ */ jsx5(
|
|
2256
|
+
"button",
|
|
2257
|
+
{
|
|
2258
|
+
type: "button",
|
|
2259
|
+
onClick: handleSubmit,
|
|
2260
|
+
disabled: !allAnswered,
|
|
2261
|
+
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",
|
|
2262
|
+
children: allAnswered ? "\u786E\u8BA4" : "\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u9009\u9879"
|
|
2263
|
+
}
|
|
2264
|
+
),
|
|
2265
|
+
submitted && !answered && /* @__PURE__ */ jsxs4(
|
|
2266
|
+
"button",
|
|
2267
|
+
{
|
|
2268
|
+
type: "button",
|
|
2269
|
+
disabled: true,
|
|
2270
|
+
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",
|
|
2271
|
+
children: [
|
|
2272
|
+
/* @__PURE__ */ jsx5(Loader2, { size: 14, className: "animate-spin" }),
|
|
2273
|
+
"\u786E\u8BA4\u4E2D"
|
|
2274
|
+
]
|
|
2275
|
+
}
|
|
2276
|
+
)
|
|
2277
|
+
]
|
|
2278
|
+
}
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2281
|
+
function QuestionCard({
|
|
2282
|
+
question,
|
|
2283
|
+
qIdx,
|
|
2284
|
+
answered,
|
|
2285
|
+
selected,
|
|
2286
|
+
isCustom,
|
|
2287
|
+
customText,
|
|
2288
|
+
onToggle,
|
|
2289
|
+
onCustomFocus,
|
|
2290
|
+
onCustomChange
|
|
2291
|
+
}) {
|
|
2292
|
+
const multi = question.multiSelect ?? false;
|
|
2293
|
+
return /* @__PURE__ */ jsxs4("div", { children: [
|
|
2294
|
+
/* @__PURE__ */ jsxs4("div", { className: cn("flex items-start gap-2", answered ? "mb-2" : "mb-3"), children: [
|
|
2295
|
+
/* @__PURE__ */ jsx5(
|
|
2296
|
+
MessageSquareMore,
|
|
2297
|
+
{
|
|
2298
|
+
size: answered ? 12 : 13,
|
|
2299
|
+
className: "mt-0.5 shrink-0 text-[hsl(var(--primary))]"
|
|
2300
|
+
}
|
|
2301
|
+
),
|
|
2302
|
+
/* @__PURE__ */ jsx5(
|
|
2303
|
+
"div",
|
|
2304
|
+
{
|
|
2305
|
+
className: cn(
|
|
2306
|
+
"min-w-0 flex-1 font-medium text-[hsl(var(--foreground))]",
|
|
2307
|
+
answered ? "text-xs" : "text-sm"
|
|
2308
|
+
),
|
|
2309
|
+
children: /* @__PURE__ */ jsx5(
|
|
2310
|
+
MarkdownContent,
|
|
2311
|
+
{
|
|
2312
|
+
className: cn(
|
|
2313
|
+
"prose prose-sm prose-invert max-w-none [&_li>p]:inline [&_ol]:my-1 [&_p]:my-1 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0 [&_ul]:my-1",
|
|
2314
|
+
answered ? "text-xs" : "text-sm"
|
|
2315
|
+
),
|
|
2316
|
+
controls: { code: true },
|
|
2317
|
+
children: question.question
|
|
2318
|
+
}
|
|
2319
|
+
)
|
|
2320
|
+
}
|
|
2321
|
+
)
|
|
2322
|
+
] }),
|
|
2323
|
+
/* @__PURE__ */ jsxs4("div", { className: cn("flex flex-col pl-5", answered ? "gap-1" : "gap-1.5"), children: [
|
|
2324
|
+
question.options.map((opt, optIdx) => {
|
|
2325
|
+
const isSel = selected.has(optIdx);
|
|
2326
|
+
return /* @__PURE__ */ jsxs4(
|
|
2327
|
+
"button",
|
|
2328
|
+
{
|
|
2329
|
+
type: "button",
|
|
2330
|
+
disabled: answered,
|
|
2331
|
+
onClick: () => onToggle(qIdx, optIdx, multi),
|
|
2332
|
+
className: cn(
|
|
2333
|
+
"flex items-start gap-2.5 rounded-lg border text-left transition-all",
|
|
2334
|
+
answered ? "px-2.5 py-1.5" : "px-3 py-2.5",
|
|
2335
|
+
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)]",
|
|
2336
|
+
!answered && !isSel && "hover:border-[hsl(var(--ring)/0.4)] hover:bg-[hsl(var(--accent))]",
|
|
2337
|
+
answered && "cursor-default opacity-70"
|
|
2338
|
+
),
|
|
2339
|
+
children: [
|
|
2340
|
+
multi && /* @__PURE__ */ jsx5(
|
|
2341
|
+
"div",
|
|
2342
|
+
{
|
|
2343
|
+
className: cn(
|
|
2344
|
+
"mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded border transition-colors",
|
|
2345
|
+
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))]"
|
|
2346
|
+
),
|
|
2347
|
+
children: isSel && /* @__PURE__ */ jsx5(
|
|
2348
|
+
Check2,
|
|
2349
|
+
{
|
|
2350
|
+
size: 9,
|
|
2351
|
+
className: answered ? "text-[hsl(var(--primary))]" : "text-[hsl(var(--primary-foreground))]"
|
|
2352
|
+
}
|
|
2353
|
+
)
|
|
2354
|
+
}
|
|
2355
|
+
),
|
|
2356
|
+
/* @__PURE__ */ jsxs4("div", { className: "min-w-0", children: [
|
|
2357
|
+
/* @__PURE__ */ jsx5("div", { className: cn("font-medium", answered ? "text-xs" : "text-[13px]"), children: opt.label }),
|
|
2358
|
+
opt.description && /* @__PURE__ */ jsx5(
|
|
2359
|
+
"div",
|
|
2360
|
+
{
|
|
2361
|
+
className: cn(
|
|
2362
|
+
"mt-0.5",
|
|
2363
|
+
answered ? "text-[11px]" : "text-xs",
|
|
2364
|
+
isSel && !answered ? "opacity-75" : "text-[hsl(var(--muted-foreground))]"
|
|
2365
|
+
),
|
|
2366
|
+
children: opt.description
|
|
2367
|
+
}
|
|
2368
|
+
)
|
|
2369
|
+
] })
|
|
2370
|
+
]
|
|
2371
|
+
},
|
|
2372
|
+
opt.label
|
|
2373
|
+
);
|
|
2374
|
+
}),
|
|
2375
|
+
answered && !isCustom ? null : /* @__PURE__ */ jsxs4(
|
|
2376
|
+
"div",
|
|
2377
|
+
{
|
|
2378
|
+
className: cn(
|
|
2379
|
+
"flex items-center gap-2 rounded-lg border transition-all",
|
|
2380
|
+
answered ? "px-2.5 py-1.5" : "px-3 py-2.5",
|
|
2381
|
+
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))]",
|
|
2382
|
+
answered && "cursor-default opacity-70"
|
|
2383
|
+
),
|
|
2384
|
+
children: [
|
|
2385
|
+
/* @__PURE__ */ jsx5("span", { className: "shrink-0 text-xs text-[hsl(var(--muted-foreground))]", children: "\u5176\u4ED6\uFF1A" }),
|
|
2386
|
+
/* @__PURE__ */ jsx5(
|
|
2387
|
+
"input",
|
|
2388
|
+
{
|
|
2389
|
+
type: "text",
|
|
2390
|
+
value: customText,
|
|
2391
|
+
disabled: answered,
|
|
2392
|
+
onChange: (e) => onCustomChange(qIdx, e.target.value),
|
|
2393
|
+
onFocus: () => onCustomFocus(qIdx),
|
|
2394
|
+
"aria-label": "\u81EA\u5B9A\u4E49\u56DE\u7B54",
|
|
2395
|
+
placeholder: "\u8F93\u5165\u4F60\u7684\u7B54\u6848...",
|
|
2396
|
+
className: cn(
|
|
2397
|
+
"min-w-0 flex-1 bg-transparent text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground)/0.5)]",
|
|
2398
|
+
answered ? "text-xs" : "text-sm"
|
|
2399
|
+
)
|
|
2400
|
+
}
|
|
2401
|
+
)
|
|
2402
|
+
]
|
|
2403
|
+
}
|
|
2404
|
+
)
|
|
2405
|
+
] })
|
|
2406
|
+
] });
|
|
2407
|
+
}
|
|
2408
|
+
function parseAskUserQuestion(toolResult) {
|
|
2409
|
+
if (!toolResult) return null;
|
|
2410
|
+
try {
|
|
2411
|
+
const parsed = JSON.parse(toolResult);
|
|
2412
|
+
let questions = parsed?.questions;
|
|
2413
|
+
if (typeof questions === "string") {
|
|
2414
|
+
try {
|
|
2415
|
+
questions = JSON.parse(questions);
|
|
2416
|
+
} catch {
|
|
2417
|
+
return null;
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
if (Array.isArray(questions) && questions.every(isQuestionItem)) {
|
|
2421
|
+
return { ...parsed, questions };
|
|
2422
|
+
}
|
|
2423
|
+
} catch {
|
|
2424
|
+
}
|
|
2425
|
+
return null;
|
|
2426
|
+
}
|
|
2427
|
+
function isQuestionItem(value) {
|
|
2428
|
+
if (!value || typeof value !== "object") return false;
|
|
2429
|
+
const question = value.question;
|
|
2430
|
+
const options = value.options;
|
|
2431
|
+
return typeof question === "string" && Array.isArray(options) && options.every(isOptionItem);
|
|
2432
|
+
}
|
|
2433
|
+
function isOptionItem(value) {
|
|
2434
|
+
if (!value || typeof value !== "object") return false;
|
|
2435
|
+
const option = value;
|
|
2436
|
+
return typeof option.label === "string" && typeof option.description === "string";
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
// src/react/components/plan/parse-plan-tree.ts
|
|
2440
|
+
import { load } from "js-yaml";
|
|
2441
|
+
function isNonEmptyString(value) {
|
|
2442
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
2443
|
+
}
|
|
2444
|
+
function isPlanStep(value) {
|
|
2445
|
+
if (!value || typeof value !== "object") return false;
|
|
2446
|
+
const step = value;
|
|
2447
|
+
if (!isNonEmptyString(step.label)) return false;
|
|
2448
|
+
if ("skill" in step && step.skill !== void 0 && !isNonEmptyString(step.skill)) return false;
|
|
2449
|
+
if ("children" in step && !Array.isArray(step.children)) return false;
|
|
2450
|
+
const children = Array.isArray(step.children) ? step.children : [];
|
|
2451
|
+
if (children.length > 0 && !isNonEmptyString(step.skill)) return false;
|
|
2452
|
+
return children.every((child) => isPlanStep(child));
|
|
2453
|
+
}
|
|
2454
|
+
function isPlanDocument(value) {
|
|
2455
|
+
if (!value || typeof value !== "object") return false;
|
|
2456
|
+
const doc = value;
|
|
2457
|
+
if (!isNonEmptyString(doc.title) || !isNonEmptyString(doc.objective)) return false;
|
|
2458
|
+
if (!Array.isArray(doc.steps) || doc.steps.length === 0) return false;
|
|
2459
|
+
if (!doc.steps.every((step) => isPlanStep(step))) return false;
|
|
2460
|
+
if ("notes" in doc) {
|
|
2461
|
+
if (!Array.isArray(doc.notes)) return false;
|
|
2462
|
+
if (!doc.notes.every((note) => isNonEmptyString(note))) return false;
|
|
2463
|
+
}
|
|
2464
|
+
return true;
|
|
2465
|
+
}
|
|
2466
|
+
function getPlanDocumentError(value) {
|
|
2467
|
+
if (!value || typeof value !== "object") return "PLAN.yaml \u9876\u5C42\u7ED3\u6784\u5FC5\u987B\u662F\u5BF9\u8C61";
|
|
2468
|
+
const doc = value;
|
|
2469
|
+
if (!isNonEmptyString(doc.title)) return "PLAN.yaml \u7F3A\u5C11\u975E\u7A7A title";
|
|
2470
|
+
if (!isNonEmptyString(doc.objective)) return "PLAN.yaml \u7F3A\u5C11\u975E\u7A7A objective";
|
|
2471
|
+
if (!Array.isArray(doc.steps) || doc.steps.length === 0) return "PLAN.yaml \u7F3A\u5C11\u975E\u7A7A steps";
|
|
2472
|
+
if (!doc.steps.every((step) => isPlanStep(step))) {
|
|
2473
|
+
return "PLAN.yaml \u6B65\u9AA4\u7ED3\u6784\u65E0\u6548\uFF0C\u8BF7\u68C0\u67E5 label / skill / children";
|
|
2474
|
+
}
|
|
2475
|
+
if ("notes" in doc) {
|
|
2476
|
+
if (!Array.isArray(doc.notes) || !doc.notes.every((note) => isNonEmptyString(note))) {
|
|
2477
|
+
return "PLAN.yaml notes \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\u5217\u8868";
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
return "PLAN.yaml \u7ED3\u6784\u65E0\u6548";
|
|
2481
|
+
}
|
|
2482
|
+
function toPlanStatus(value) {
|
|
2483
|
+
if (!isNonEmptyString(value)) return "pending";
|
|
2484
|
+
const normalized = value.trim().toLowerCase();
|
|
2485
|
+
return normalized === "pending" || normalized === "running" || normalized === "done" || normalized === "failed" ? normalized : "pending";
|
|
2486
|
+
}
|
|
2487
|
+
function applyPlanStatusesById(root, statuses) {
|
|
2488
|
+
const nextStatus = statuses[root.id] ?? root.status;
|
|
2489
|
+
const children = Array.isArray(root.children) ? root.children : [];
|
|
2490
|
+
return {
|
|
2491
|
+
...root,
|
|
2492
|
+
status: nextStatus,
|
|
2493
|
+
children: children.map((child) => applyPlanStatusesById(child, statuses))
|
|
2494
|
+
};
|
|
2495
|
+
}
|
|
2496
|
+
function parsePlanTreeResult(content) {
|
|
2497
|
+
if (!content.trim()) {
|
|
2498
|
+
return { plan: null, error: null };
|
|
2499
|
+
}
|
|
2500
|
+
let parsed;
|
|
2501
|
+
try {
|
|
2502
|
+
parsed = load(content);
|
|
2503
|
+
} catch (error) {
|
|
2504
|
+
return {
|
|
2505
|
+
plan: null,
|
|
2506
|
+
error: error instanceof Error ? `PLAN.yaml YAML \u8BED\u6CD5\u9519\u8BEF\uFF1A${error.message}` : "PLAN.yaml YAML \u8BED\u6CD5\u9519\u8BEF"
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
if (!isPlanDocument(parsed)) {
|
|
2510
|
+
return { plan: null, error: getPlanDocumentError(parsed) };
|
|
2511
|
+
}
|
|
2512
|
+
const createNode = (step, depth, fallbackId) => {
|
|
2513
|
+
const children = Array.isArray(step.children) ? step.children : [];
|
|
2514
|
+
return {
|
|
2515
|
+
id: isNonEmptyString(step.id) ? step.id.trim() : fallbackId,
|
|
2516
|
+
label: step.label.trim(),
|
|
2517
|
+
skillRef: isNonEmptyString(step.skill) ? step.skill.trim() : null,
|
|
2518
|
+
status: toPlanStatus(step.status),
|
|
2519
|
+
children: children.map((child, index) => createNode(child, depth + 1, `${fallbackId}.${index + 1}`)),
|
|
2520
|
+
depth
|
|
2521
|
+
};
|
|
2522
|
+
};
|
|
2523
|
+
const root = {
|
|
2524
|
+
id: "plan-root",
|
|
2525
|
+
label: parsed.title.trim(),
|
|
2526
|
+
skillRef: null,
|
|
2527
|
+
status: "pending",
|
|
2528
|
+
children: parsed.steps.map((step, index) => createNode(step, 1, `${index + 1}`)),
|
|
2529
|
+
depth: 0
|
|
2530
|
+
};
|
|
2531
|
+
const notes = [parsed.objective.trim(), ...(parsed.notes ?? []).map((note) => note.trim())];
|
|
2532
|
+
return { plan: { root, notes }, error: null };
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
// src/react/components/plan/parse-plan-messages.ts
|
|
2536
|
+
function resultAsString(result) {
|
|
2537
|
+
if (typeof result === "string") return result;
|
|
2538
|
+
return null;
|
|
2539
|
+
}
|
|
2540
|
+
var PAUSE_TOOLS = /* @__PURE__ */ new Set(["AskUserQuestion", "ExitPlanMode"]);
|
|
2541
|
+
var READ_FILE_TOOLS = /* @__PURE__ */ new Set(["Read"]);
|
|
2542
|
+
var PLAN_WRITE_TOOLS = /* @__PURE__ */ new Set(["Write"]);
|
|
2543
|
+
var PLAN_EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit"]);
|
|
2544
|
+
function getSkillId(payload) {
|
|
2545
|
+
if (!payload) {
|
|
2546
|
+
return "";
|
|
2547
|
+
}
|
|
2548
|
+
if (typeof payload.skill_id === "string" && payload.skill_id) {
|
|
2549
|
+
return payload.skill_id;
|
|
2550
|
+
}
|
|
2551
|
+
if (typeof payload.name === "string" && payload.name) {
|
|
2552
|
+
return payload.name;
|
|
2553
|
+
}
|
|
2554
|
+
return "";
|
|
2555
|
+
}
|
|
2556
|
+
function getSkillDisplayName(payload) {
|
|
2557
|
+
if (!payload || typeof payload.display_name !== "string") {
|
|
2558
|
+
return void 0;
|
|
2559
|
+
}
|
|
2560
|
+
const displayName = payload.display_name.trim();
|
|
2561
|
+
if (!displayName) {
|
|
2562
|
+
return void 0;
|
|
2563
|
+
}
|
|
2564
|
+
const skillId = getSkillId(payload);
|
|
2565
|
+
return displayName === skillId ? void 0 : displayName;
|
|
2566
|
+
}
|
|
2567
|
+
function parseModeChange2(message) {
|
|
2568
|
+
if (message.kind !== "mode_change" || typeof message.content !== "string") {
|
|
2569
|
+
return null;
|
|
2570
|
+
}
|
|
2571
|
+
try {
|
|
2572
|
+
const parsed = JSON.parse(message.content);
|
|
2573
|
+
if (typeof parsed.from === "string" && typeof parsed.to === "string") {
|
|
2574
|
+
return { from: parsed.from, to: parsed.to };
|
|
2575
|
+
}
|
|
2576
|
+
} catch {
|
|
2577
|
+
}
|
|
2578
|
+
return null;
|
|
2579
|
+
}
|
|
2580
|
+
function isPlanningBoundaryMessage(message) {
|
|
2581
|
+
if (message.kind === "planning_enter" || message.kind === "planning_exit") {
|
|
2582
|
+
return true;
|
|
2583
|
+
}
|
|
2584
|
+
const modeChange = parseModeChange2(message);
|
|
2585
|
+
if (!modeChange) {
|
|
2586
|
+
return false;
|
|
2587
|
+
}
|
|
2588
|
+
return modeChange.to === "planning" || modeChange.from === "planning";
|
|
2589
|
+
}
|
|
2590
|
+
function parsePlanMessages(allMessages) {
|
|
2591
|
+
const messages = Array.isArray(allMessages) ? allMessages : [];
|
|
2592
|
+
let intent = "";
|
|
2593
|
+
for (const msg of messages) {
|
|
2594
|
+
if (!msg.tool_calls) continue;
|
|
2595
|
+
const searchTc = msg.tool_calls.find(
|
|
2596
|
+
(tc) => tc.name === "search_skills" || tc.name === "SearchSkills"
|
|
2597
|
+
);
|
|
2598
|
+
if (searchTc) {
|
|
2599
|
+
try {
|
|
2600
|
+
intent = JSON.parse(searchTc.arguments).query ?? "";
|
|
2601
|
+
} catch {
|
|
2602
|
+
}
|
|
2603
|
+
if (intent) break;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
const plannerMsgs = messages.filter(
|
|
2607
|
+
(message) => !isPlanningBoundaryMessage(message)
|
|
2608
|
+
);
|
|
2609
|
+
const searchResults = [];
|
|
2610
|
+
const searchMap = /* @__PURE__ */ new Map();
|
|
2611
|
+
const selectedSkills = [];
|
|
2612
|
+
const selectedSet = /* @__PURE__ */ new Set();
|
|
2613
|
+
const askQuestions = [];
|
|
2614
|
+
const readFiles = [];
|
|
2615
|
+
let lastPauseTool = null;
|
|
2616
|
+
let planContent = "";
|
|
2617
|
+
let planContentPriority = 0;
|
|
2618
|
+
let latestStatuses = {};
|
|
2619
|
+
const setPlanContent = (nextContent, priority) => {
|
|
2620
|
+
if (typeof nextContent !== "string" || !nextContent.trim()) {
|
|
2621
|
+
return;
|
|
2622
|
+
}
|
|
2623
|
+
if (priority < planContentPriority) {
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2626
|
+
planContent = nextContent;
|
|
2627
|
+
planContentPriority = priority;
|
|
2628
|
+
};
|
|
2629
|
+
const applyPlanEdit = (args) => {
|
|
2630
|
+
const oldString = typeof args.old_string === "string" ? args.old_string : "";
|
|
2631
|
+
const newString = typeof args.new_string === "string" ? args.new_string : "";
|
|
2632
|
+
if (!planContent || !oldString || !planContent.includes(oldString)) {
|
|
2633
|
+
return;
|
|
2634
|
+
}
|
|
2635
|
+
planContent = planContent.replace(oldString, newString);
|
|
2636
|
+
};
|
|
2637
|
+
const maybeParsePlanStatus = (msg) => {
|
|
2638
|
+
if (msg.kind !== "plan_status") return;
|
|
2639
|
+
if (typeof msg.content !== "string" || !msg.content.trim()) return;
|
|
2640
|
+
try {
|
|
2641
|
+
const data = JSON.parse(msg.content);
|
|
2642
|
+
setPlanContent(data.plan_yaml, 3);
|
|
2643
|
+
if (data.statuses && typeof data.statuses === "object" && !Array.isArray(data.statuses)) {
|
|
2644
|
+
latestStatuses = Object.fromEntries(
|
|
2645
|
+
Object.entries(data.statuses).filter((entry) => typeof entry[0] === "string" && typeof entry[1] === "string").map(([stepId, status]) => [stepId, normalizePlanStatus(status)])
|
|
2646
|
+
);
|
|
2647
|
+
}
|
|
2648
|
+
} catch {
|
|
2649
|
+
}
|
|
2650
|
+
};
|
|
2651
|
+
for (const msg of plannerMsgs) {
|
|
2652
|
+
maybeParsePlanStatus(msg);
|
|
2653
|
+
if (!msg.tool_calls) continue;
|
|
2654
|
+
for (const tc of msg.tool_calls) {
|
|
2655
|
+
if ((tc.name === "search_skills" || tc.name === "SearchSkills") && resultAsString(tc.result)) {
|
|
2656
|
+
try {
|
|
2657
|
+
const parsed = JSON.parse(resultAsString(tc.result));
|
|
2658
|
+
const results = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.results) ? parsed.results : [];
|
|
2659
|
+
for (const r3 of results) {
|
|
2660
|
+
const skillId = getSkillId(r3);
|
|
2661
|
+
if (!skillId || searchMap.has(skillId)) continue;
|
|
2662
|
+
const skill = {
|
|
2663
|
+
skillId,
|
|
2664
|
+
displayName: getSkillDisplayName(r3),
|
|
2665
|
+
description: r3.description ?? ""
|
|
2666
|
+
};
|
|
2667
|
+
searchMap.set(skillId, skill);
|
|
2668
|
+
searchResults.push(skill);
|
|
2669
|
+
}
|
|
2670
|
+
} catch {
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
if ((tc.name === "get_skill_content" || tc.name === "GetSkillContent") && resultAsString(tc.result)) {
|
|
2674
|
+
try {
|
|
2675
|
+
const data = JSON.parse(resultAsString(tc.result));
|
|
2676
|
+
const skillId = getSkillId(data);
|
|
2677
|
+
if (!skillId || selectedSet.has(skillId)) continue;
|
|
2678
|
+
let existing = searchMap.get(skillId);
|
|
2679
|
+
if (!existing) {
|
|
2680
|
+
existing = {
|
|
2681
|
+
skillId,
|
|
2682
|
+
displayName: getSkillDisplayName(data),
|
|
2683
|
+
description: data.description ?? ""
|
|
2684
|
+
};
|
|
2685
|
+
searchMap.set(skillId, existing);
|
|
2686
|
+
searchResults.push(existing);
|
|
2687
|
+
}
|
|
2688
|
+
existing.displayName ??= getSkillDisplayName(data);
|
|
2689
|
+
existing.content = data.content;
|
|
2690
|
+
existing.references = extractRefs(data.content ?? "");
|
|
2691
|
+
selectedSkills.push(existing);
|
|
2692
|
+
selectedSet.add(skillId);
|
|
2693
|
+
} catch {
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
if (tc.name === "AskUserQuestion" && resultAsString(tc.result)) {
|
|
2697
|
+
const data = parseAskUserQuestion(resultAsString(tc.result));
|
|
2698
|
+
if (data) {
|
|
2699
|
+
askQuestions.push({ toolCallId: tc.id, data });
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
if (READ_FILE_TOOLS.has(tc.name) && tc.arguments) {
|
|
2703
|
+
try {
|
|
2704
|
+
const args = JSON.parse(tc.arguments);
|
|
2705
|
+
if (typeof args.file_path === "string" && args.file_path) {
|
|
2706
|
+
readFiles.push({ path: args.file_path, status: tc.status ?? "done" });
|
|
2707
|
+
}
|
|
2708
|
+
} catch {
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
if (PLAN_WRITE_TOOLS.has(tc.name) && tc.arguments && tc.status === "done" && !(typeof tc.result === "string" && tc.result.startsWith("\u9519\u8BEF"))) {
|
|
2712
|
+
try {
|
|
2713
|
+
const args = JSON.parse(tc.arguments);
|
|
2714
|
+
if (typeof args.file_path === "string" && (args.file_path === "PLAN.yaml" || args.file_path.endsWith("/PLAN.yaml")) && typeof args.content === "string") {
|
|
2715
|
+
setPlanContent(args.content, 1);
|
|
2716
|
+
}
|
|
2717
|
+
} catch {
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
if (PLAN_EDIT_TOOLS.has(tc.name) && tc.arguments && tc.status === "done" && !(typeof tc.result === "string" && tc.result.startsWith("\u9519\u8BEF"))) {
|
|
2721
|
+
try {
|
|
2722
|
+
const args = JSON.parse(tc.arguments);
|
|
2723
|
+
if (typeof args.file_path === "string" && (args.file_path === "PLAN.yaml" || args.file_path.endsWith("/PLAN.yaml"))) {
|
|
2724
|
+
applyPlanEdit(args);
|
|
2725
|
+
}
|
|
2726
|
+
} catch {
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
if (PAUSE_TOOLS.has(tc.name) && tc.status === "done") {
|
|
2730
|
+
lastPauseTool = tc.name;
|
|
2731
|
+
}
|
|
2732
|
+
if (tc.name === "ExitPlanMode" && resultAsString(tc.result)) {
|
|
2733
|
+
try {
|
|
2734
|
+
const data = JSON.parse(resultAsString(tc.result));
|
|
2735
|
+
setPlanContent(data.plan, 2);
|
|
2736
|
+
} catch {
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
const { plan: parsedPlan, error: parseError } = parsePlanTreeResult(planContent);
|
|
2742
|
+
const plan = parsedPlan == null ? null : {
|
|
2743
|
+
...parsedPlan,
|
|
2744
|
+
root: applyPlanStatusesById(parsedPlan.root, latestStatuses)
|
|
2745
|
+
};
|
|
2746
|
+
return {
|
|
2747
|
+
intent,
|
|
2748
|
+
searchResults,
|
|
2749
|
+
selectedSkills,
|
|
2750
|
+
plan,
|
|
2751
|
+
hasPlanContent: planContent.trim().length > 0,
|
|
2752
|
+
parseError,
|
|
2753
|
+
askQuestions,
|
|
2754
|
+
readFiles,
|
|
2755
|
+
lastPauseTool
|
|
2756
|
+
};
|
|
2757
|
+
}
|
|
2758
|
+
function normalizePlanStatus(value) {
|
|
2759
|
+
const normalized = value.trim().toLowerCase();
|
|
2760
|
+
return normalized === "running" || normalized === "done" || normalized === "failed" ? normalized : "pending";
|
|
2761
|
+
}
|
|
2762
|
+
function extractRefs(content) {
|
|
2763
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2764
|
+
const refs = [];
|
|
2765
|
+
for (const m of content.matchAll(/\[\[([^\]]+)\]\]/g)) {
|
|
2766
|
+
const ref = m[1].trim();
|
|
2767
|
+
if (ref && !seen.has(ref)) {
|
|
2768
|
+
seen.add(ref);
|
|
2769
|
+
refs.push(ref);
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
return refs;
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
// src/react/components/plan/PlanSummaryCard.tsx
|
|
2776
|
+
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2777
|
+
function PlanSummaryCard({
|
|
2778
|
+
messages,
|
|
2779
|
+
sessionStatus,
|
|
2780
|
+
onConfirmPlan
|
|
2781
|
+
}) {
|
|
2782
|
+
const data = useMemo4(() => parsePlanMessages(messages), [messages]);
|
|
2783
|
+
const rightPanelCollapsed = useUiStore((state) => state.rightPanelCollapsed);
|
|
2784
|
+
const toggleRightPanel = useUiStore((state) => state.toggleRightPanel);
|
|
2785
|
+
const setActiveRightTab = useUiStore((state) => state.setActiveRightTab);
|
|
2786
|
+
const summaryState = useMemo4(() => getSummaryState(data, sessionStatus), [data, sessionStatus]);
|
|
2787
|
+
const stepCount = useMemo4(() => countPlanSteps(data.plan), [data.plan]);
|
|
2788
|
+
const isWaitingForInput = sessionStatus === "waiting_for_input";
|
|
2789
|
+
const canConfirm = Boolean(onConfirmPlan) && isWaitingForInput && data.lastPauseTool === "ExitPlanMode";
|
|
2790
|
+
const openPlanPanel = () => {
|
|
2791
|
+
if (rightPanelCollapsed) {
|
|
2792
|
+
toggleRightPanel();
|
|
2793
|
+
}
|
|
2794
|
+
setActiveRightTab("situation");
|
|
2795
|
+
};
|
|
2796
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex w-full flex-col gap-4 rounded-2xl border border-[hsl(var(--border))] bg-[hsl(var(--card)/0.55)] p-4", children: [
|
|
2797
|
+
/* @__PURE__ */ jsxs5(
|
|
2798
|
+
"button",
|
|
2799
|
+
{
|
|
2800
|
+
type: "button",
|
|
2801
|
+
onClick: openPlanPanel,
|
|
2802
|
+
className: "flex w-full flex-col gap-4 text-left transition-colors hover:text-[hsl(var(--foreground))]",
|
|
2803
|
+
children: [
|
|
2804
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-start justify-between gap-3", children: [
|
|
2805
|
+
/* @__PURE__ */ jsxs5("div", { className: "min-w-0 space-y-2", children: [
|
|
2806
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 text-xs text-[hsl(var(--muted-foreground))]", children: [
|
|
2807
|
+
/* @__PURE__ */ jsx6(Sparkles, { size: 12 }),
|
|
2808
|
+
/* @__PURE__ */ jsx6("span", { children: "\u89C4\u5212\u6458\u8981" })
|
|
2809
|
+
] }),
|
|
2810
|
+
/* @__PURE__ */ jsx6("div", { className: "truncate text-base font-medium text-[hsl(var(--foreground))]", children: data.intent || "\u5F53\u524D\u89C4\u5212" }),
|
|
2811
|
+
/* @__PURE__ */ jsx6("div", { className: "flex flex-wrap items-center gap-2 text-xs", children: summaryState.kind === "progress" ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
2812
|
+
/* @__PURE__ */ jsx6("span", { className: "rounded-full bg-[hsl(var(--accent))] px-2.5 py-1 text-[hsl(var(--foreground))]", children: summaryState.label }),
|
|
2813
|
+
/* @__PURE__ */ jsx6("span", { className: "font-mono tracking-[0.25em] text-[hsl(var(--muted-foreground))]", children: summaryState.dots })
|
|
2814
|
+
] }) : /* @__PURE__ */ jsx6(
|
|
2815
|
+
"span",
|
|
2816
|
+
{
|
|
2817
|
+
className: cn(
|
|
2818
|
+
"rounded-full px-2.5 py-1",
|
|
2819
|
+
summaryState.kind === "complete" && "bg-[hsl(var(--primary)/0.12)] text-[hsl(var(--primary))]",
|
|
2820
|
+
summaryState.kind === "failed" && "bg-[hsl(var(--destructive)/0.12)] text-[hsl(var(--destructive))]",
|
|
2821
|
+
summaryState.kind === "interrupted" && "bg-orange-500/12 text-orange-300"
|
|
2822
|
+
),
|
|
2823
|
+
children: summaryState.label
|
|
2824
|
+
}
|
|
2825
|
+
) })
|
|
2826
|
+
] }),
|
|
2827
|
+
/* @__PURE__ */ jsx6(
|
|
2828
|
+
ChevronRight,
|
|
2829
|
+
{
|
|
2830
|
+
size: 16,
|
|
2831
|
+
className: "mt-1 shrink-0 text-[hsl(var(--muted-foreground))]",
|
|
2832
|
+
"aria-hidden": "true"
|
|
2833
|
+
}
|
|
2834
|
+
)
|
|
2835
|
+
] }),
|
|
2836
|
+
summaryState.kind === "complete" && /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-2 text-xs text-[hsl(var(--muted-foreground))]", children: [
|
|
2837
|
+
/* @__PURE__ */ jsxs5("span", { className: "rounded-full border border-[hsl(var(--border))] px-2.5 py-1", children: [
|
|
2838
|
+
data.selectedSkills.length,
|
|
2839
|
+
" \u4E2A\u6280\u80FD"
|
|
2840
|
+
] }),
|
|
2841
|
+
/* @__PURE__ */ jsxs5("span", { className: "rounded-full border border-[hsl(var(--border))] px-2.5 py-1", children: [
|
|
2842
|
+
stepCount,
|
|
2843
|
+
" \u4E2A\u6B65\u9AA4"
|
|
2844
|
+
] }),
|
|
2845
|
+
/* @__PURE__ */ jsxs5("span", { className: "inline-flex items-center gap-1 text-[hsl(var(--muted-foreground))]", children: [
|
|
2846
|
+
/* @__PURE__ */ jsx6(CheckCircle2, { size: 12 }),
|
|
2847
|
+
"\u53F3\u4FA7\u67E5\u770B\u8BE6\u60C5"
|
|
2848
|
+
] })
|
|
2849
|
+
] })
|
|
2850
|
+
]
|
|
2851
|
+
}
|
|
2852
|
+
),
|
|
2853
|
+
canConfirm && /* @__PURE__ */ jsx6(PlanConfirmationPanel, { onConfirmPlan })
|
|
2854
|
+
] });
|
|
2855
|
+
}
|
|
2856
|
+
function PlanConfirmationPanel({
|
|
2857
|
+
onConfirmPlan
|
|
2858
|
+
}) {
|
|
2859
|
+
const [isEditing, setIsEditing] = useState4(false);
|
|
2860
|
+
const [revisionText, setRevisionText] = useState4("");
|
|
2861
|
+
const submitRevision = () => {
|
|
2862
|
+
const trimmed = revisionText.trim();
|
|
2863
|
+
if (!trimmed) return;
|
|
2864
|
+
onConfirmPlan?.("revise", trimmed);
|
|
2865
|
+
setRevisionText("");
|
|
2866
|
+
setIsEditing(false);
|
|
2867
|
+
};
|
|
2868
|
+
return /* @__PURE__ */ jsxs5("div", { className: "space-y-3 rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--background)/0.45)] p-3", children: [
|
|
2869
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap gap-2", children: [
|
|
2870
|
+
/* @__PURE__ */ jsxs5(
|
|
2871
|
+
"button",
|
|
2872
|
+
{
|
|
2873
|
+
type: "button",
|
|
2874
|
+
onClick: () => onConfirmPlan?.("execute"),
|
|
2875
|
+
className: "inline-flex items-center gap-1.5 rounded-lg bg-[hsl(var(--primary))] px-3 py-2 text-sm font-medium text-[hsl(var(--primary-foreground))] transition-opacity hover:opacity-90",
|
|
2876
|
+
children: [
|
|
2877
|
+
/* @__PURE__ */ jsx6(Play, { size: 14 }),
|
|
2878
|
+
"\u5F00\u59CB\u6267\u884C"
|
|
2879
|
+
]
|
|
2880
|
+
}
|
|
2881
|
+
),
|
|
2882
|
+
/* @__PURE__ */ jsxs5(
|
|
2883
|
+
"button",
|
|
2884
|
+
{
|
|
2885
|
+
type: "button",
|
|
2886
|
+
onClick: () => setIsEditing((value) => !value),
|
|
2887
|
+
className: "inline-flex items-center gap-1.5 rounded-lg border border-[hsl(var(--border))] px-3 py-2 text-sm text-[hsl(var(--foreground))] transition-colors hover:bg-[hsl(var(--accent))]",
|
|
2888
|
+
children: [
|
|
2889
|
+
/* @__PURE__ */ jsx6(PencilLine, { size: 14 }),
|
|
2890
|
+
"\u4FEE\u6539"
|
|
2891
|
+
]
|
|
2892
|
+
}
|
|
2893
|
+
)
|
|
2894
|
+
] }),
|
|
2895
|
+
isEditing && /* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
|
|
2896
|
+
/* @__PURE__ */ jsx6(
|
|
2897
|
+
"textarea",
|
|
2898
|
+
{
|
|
2899
|
+
value: revisionText,
|
|
2900
|
+
onChange: (event) => setRevisionText(event.target.value),
|
|
2901
|
+
placeholder: "\u8F93\u5165\u4FEE\u6539\u5EFA\u8BAE...",
|
|
2902
|
+
className: "min-h-24 w-full resize-y rounded-lg border border-[hsl(var(--border))] bg-transparent px-3 py-2 text-sm text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground))]"
|
|
2903
|
+
}
|
|
2904
|
+
),
|
|
2905
|
+
/* @__PURE__ */ jsx6("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx6(
|
|
2906
|
+
"button",
|
|
2907
|
+
{
|
|
2908
|
+
type: "button",
|
|
2909
|
+
disabled: !revisionText.trim(),
|
|
2910
|
+
onClick: submitRevision,
|
|
2911
|
+
className: "rounded-lg bg-[hsl(var(--primary))] px-3 py-1.5 text-xs font-medium text-[hsl(var(--primary-foreground))] transition-opacity hover:opacity-90 disabled:opacity-30",
|
|
2912
|
+
children: "\u63D0\u4EA4\u4FEE\u6539\u610F\u89C1"
|
|
2913
|
+
}
|
|
2914
|
+
) })
|
|
2915
|
+
] })
|
|
2916
|
+
] });
|
|
2917
|
+
}
|
|
2918
|
+
function getSummaryState(data, sessionStatus) {
|
|
2919
|
+
if (sessionStatus === "failed") {
|
|
2920
|
+
return { kind: "failed", label: "\u51FA\u9519" };
|
|
2921
|
+
}
|
|
2922
|
+
if (sessionStatus === "interrupted") {
|
|
2923
|
+
return { kind: "interrupted", label: "\u5DF2\u4E2D\u65AD" };
|
|
2924
|
+
}
|
|
2925
|
+
if (data.parseError) {
|
|
2926
|
+
return { kind: "failed", label: "\u89E3\u6790\u5931\u8D25" };
|
|
2927
|
+
}
|
|
2928
|
+
if (data.hasPlanContent || data.plan != null) {
|
|
2929
|
+
return { kind: "complete", label: "\u5DF2\u751F\u6210" };
|
|
2930
|
+
}
|
|
2931
|
+
if (data.selectedSkills.length > 0) {
|
|
2932
|
+
return { kind: "progress", label: "\u6280\u80FD\u5206\u6790\u4E2D", dots: "\u25CF\u25CF\u25CB" };
|
|
2933
|
+
}
|
|
2934
|
+
if (data.searchResults.length > 0) {
|
|
2935
|
+
return { kind: "progress", label: "\u6280\u80FD\u53D1\u73B0\u4E2D", dots: "\u25CF\u25CB\u25CB" };
|
|
2936
|
+
}
|
|
2937
|
+
return { kind: "progress", label: "\u89C4\u5212\u51C6\u5907\u4E2D", dots: "\u25CB\u25CB\u25CB" };
|
|
2938
|
+
}
|
|
2939
|
+
function countPlanSteps(plan) {
|
|
2940
|
+
if (!plan) return 0;
|
|
2941
|
+
const countNode = (node) => {
|
|
2942
|
+
const children = Array.isArray(node.children) ? node.children : [];
|
|
2943
|
+
return 1 + children.reduce((total, child) => total + countNode(child), 0);
|
|
2944
|
+
};
|
|
2945
|
+
if (plan.root.id === "plan-root") {
|
|
2946
|
+
const children = Array.isArray(plan.root.children) ? plan.root.children : [];
|
|
2947
|
+
return children.reduce((total, child) => total + countNode(child), 0);
|
|
2948
|
+
}
|
|
2949
|
+
return countNode(plan.root);
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
// src/react/components/plan/PlanVisualization.tsx
|
|
2953
|
+
import { BookOpen as BookOpen2, GitBranch as GitBranch2, Search as Search2 } from "lucide-react";
|
|
2954
|
+
import { useMemo as useMemo7, useRef as useRef2, useState as useState6 } from "react";
|
|
2955
|
+
|
|
2956
|
+
// src/react/components/plan/phases/PlanTree.tsx
|
|
2957
|
+
import {
|
|
2958
|
+
CheckSquare,
|
|
2959
|
+
ChevronRight as ChevronRight3,
|
|
2960
|
+
GitBranch,
|
|
2961
|
+
List,
|
|
2962
|
+
Workflow as Workflow2
|
|
2963
|
+
} from "lucide-react";
|
|
2964
|
+
import { useCallback, useMemo as useMemo5, useState as useState5 } from "react";
|
|
2965
|
+
|
|
2966
|
+
// src/react/components/plan/phases/PlanMindMap.tsx
|
|
2967
|
+
import { ChevronRight as ChevronRight2, Workflow } from "lucide-react";
|
|
2968
|
+
|
|
2969
|
+
// src/react/components/plan/phases/PlanStatusIcon.tsx
|
|
2970
|
+
import { CheckCircle2 as CheckCircle22, Circle, Loader2 as Loader22, XCircle } from "lucide-react";
|
|
2971
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
2972
|
+
function PlanStatusIcon({
|
|
2973
|
+
status,
|
|
2974
|
+
size = 16
|
|
2975
|
+
}) {
|
|
2976
|
+
if (status === "pending") {
|
|
2977
|
+
return /* @__PURE__ */ jsx7(
|
|
2978
|
+
Circle,
|
|
2979
|
+
{
|
|
2980
|
+
size,
|
|
2981
|
+
className: "shrink-0 text-[hsl(var(--muted-foreground))] transition-transform duration-300"
|
|
2982
|
+
}
|
|
2983
|
+
);
|
|
2984
|
+
}
|
|
2985
|
+
if (status === "running") {
|
|
2986
|
+
return /* @__PURE__ */ jsx7(
|
|
2987
|
+
Loader22,
|
|
2988
|
+
{
|
|
2989
|
+
size,
|
|
2990
|
+
className: "shrink-0 animate-spin text-[hsl(var(--primary))] transition-transform duration-300"
|
|
2991
|
+
}
|
|
2992
|
+
);
|
|
2993
|
+
}
|
|
2994
|
+
if (status === "done") {
|
|
2995
|
+
return /* @__PURE__ */ jsx7(
|
|
2996
|
+
CheckCircle22,
|
|
2997
|
+
{
|
|
2998
|
+
size,
|
|
2999
|
+
className: "shrink-0 scale-105 text-green-500 transition-transform duration-300"
|
|
3000
|
+
}
|
|
3001
|
+
);
|
|
3002
|
+
}
|
|
3003
|
+
return /* @__PURE__ */ jsx7(
|
|
3004
|
+
XCircle,
|
|
3005
|
+
{
|
|
3006
|
+
size,
|
|
3007
|
+
className: "shrink-0 text-destructive transition-transform duration-300"
|
|
3008
|
+
}
|
|
3009
|
+
);
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
// src/react/components/plan/phases/PlanMindMap.tsx
|
|
3013
|
+
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
3014
|
+
function PlanMindMap({ root, expandedIds, onToggle }) {
|
|
3015
|
+
return /* @__PURE__ */ jsx8("div", { className: "overflow-x-auto py-2", children: /* @__PURE__ */ jsx8(MindMapSubtree, { node: root, expandedIds, onToggle, isRoot: true }) });
|
|
3016
|
+
}
|
|
3017
|
+
function MindMapSubtree({
|
|
3018
|
+
node,
|
|
3019
|
+
expandedIds,
|
|
3020
|
+
onToggle,
|
|
3021
|
+
isRoot = false
|
|
3022
|
+
}) {
|
|
3023
|
+
const children = node.children ?? [];
|
|
3024
|
+
const hasChildren = children.length > 0;
|
|
3025
|
+
const isExpanded = expandedIds.has(node.id);
|
|
3026
|
+
const visibleChildren = hasChildren && isExpanded ? children : [];
|
|
3027
|
+
return /* @__PURE__ */ jsxs6("div", { className: "flex items-center", children: [
|
|
3028
|
+
/* @__PURE__ */ jsx8(
|
|
3029
|
+
"div",
|
|
3030
|
+
{
|
|
3031
|
+
role: hasChildren ? "button" : void 0,
|
|
3032
|
+
tabIndex: hasChildren ? 0 : void 0,
|
|
3033
|
+
"aria-expanded": hasChildren ? isExpanded : void 0,
|
|
3034
|
+
onClick: hasChildren ? () => onToggle(node.id) : void 0,
|
|
3035
|
+
onKeyDown: hasChildren ? (e) => {
|
|
3036
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
3037
|
+
e.preventDefault();
|
|
3038
|
+
onToggle(node.id);
|
|
3039
|
+
}
|
|
3040
|
+
} : void 0,
|
|
3041
|
+
className: cn(
|
|
3042
|
+
"relative z-10 shrink-0 rounded-lg border px-3 py-1.5 text-sm font-medium whitespace-nowrap select-none",
|
|
3043
|
+
hasChildren && "cursor-pointer",
|
|
3044
|
+
isRoot ? "border-[hsl(var(--primary))] bg-[hsl(var(--primary)/0.15)] text-[hsl(var(--primary))]" : node.skillRef ? "border-[hsl(var(--primary)/0.4)] bg-[hsl(var(--primary)/0.08)] text-[hsl(var(--foreground))]" : "border-[hsl(var(--border))] bg-[hsl(var(--card))] text-[hsl(var(--foreground))]"
|
|
3045
|
+
),
|
|
3046
|
+
children: /* @__PURE__ */ jsxs6("span", { className: "flex items-center gap-1.5", children: [
|
|
3047
|
+
/* @__PURE__ */ jsx8(PlanStatusIcon, { status: node.status, size: 14 }),
|
|
3048
|
+
node.skillRef && /* @__PURE__ */ jsx8(Workflow, { size: 12, className: "text-[hsl(var(--primary))]" }),
|
|
3049
|
+
node.label,
|
|
3050
|
+
hasChildren && /* @__PURE__ */ jsx8(
|
|
3051
|
+
ChevronRight2,
|
|
3052
|
+
{
|
|
3053
|
+
size: 14,
|
|
3054
|
+
className: cn(
|
|
3055
|
+
"text-[hsl(var(--muted-foreground))] transition-transform duration-150",
|
|
3056
|
+
isExpanded && "rotate-90"
|
|
3057
|
+
)
|
|
3058
|
+
}
|
|
3059
|
+
),
|
|
3060
|
+
hasChildren && !isExpanded && /* @__PURE__ */ jsxs6("span", { className: "ml-0.5 rounded-full bg-[hsl(var(--muted))] px-1.5 py-px text-[10px] text-[hsl(var(--muted-foreground))]", children: [
|
|
3061
|
+
"+",
|
|
3062
|
+
children.length
|
|
3063
|
+
] })
|
|
3064
|
+
] })
|
|
3065
|
+
}
|
|
3066
|
+
),
|
|
3067
|
+
visibleChildren.length > 0 && /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
3068
|
+
/* @__PURE__ */ jsx8("div", { className: "h-px w-6 shrink-0 bg-[hsl(var(--border))]" }),
|
|
3069
|
+
/* @__PURE__ */ jsx8(VerticalRail, { count: visibleChildren.length, children: visibleChildren.map((child) => /* @__PURE__ */ jsxs6("div", { className: "flex items-center", children: [
|
|
3070
|
+
/* @__PURE__ */ jsx8("div", { className: "h-px w-4 shrink-0 bg-[hsl(var(--border))]" }),
|
|
3071
|
+
/* @__PURE__ */ jsx8(MindMapSubtree, { node: child, expandedIds, onToggle })
|
|
3072
|
+
] }, child.id)) })
|
|
3073
|
+
] })
|
|
3074
|
+
] });
|
|
3075
|
+
}
|
|
3076
|
+
function VerticalRail({ count, children }) {
|
|
3077
|
+
if (count <= 1) {
|
|
3078
|
+
return /* @__PURE__ */ jsx8("div", { className: "flex flex-col", children });
|
|
3079
|
+
}
|
|
3080
|
+
return /* @__PURE__ */ jsx8("div", { className: "flex flex-col", children: children.map((child, i) => /* @__PURE__ */ jsxs6("div", { className: cn("relative", i > 0 && "pt-1.5"), children: [
|
|
3081
|
+
/* @__PURE__ */ jsx8(
|
|
3082
|
+
"div",
|
|
3083
|
+
{
|
|
3084
|
+
className: "absolute left-0 w-px bg-[hsl(var(--border))]",
|
|
3085
|
+
style: {
|
|
3086
|
+
top: i === 0 ? "50%" : 0,
|
|
3087
|
+
bottom: i === count - 1 ? "50%" : 0
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
),
|
|
3091
|
+
child
|
|
3092
|
+
] }, i)) });
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
// src/react/components/plan/phases/PlanTree.tsx
|
|
3096
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3097
|
+
function collectIdsToDepth(node, maxDepth, out) {
|
|
3098
|
+
if (node.depth > maxDepth) return;
|
|
3099
|
+
out.add(node.id);
|
|
3100
|
+
for (const child of node.children ?? []) collectIdsToDepth(child, maxDepth, out);
|
|
3101
|
+
}
|
|
3102
|
+
function treeFinger(node) {
|
|
3103
|
+
const children = node.children ?? [];
|
|
3104
|
+
if (children.length === 0) return node.label;
|
|
3105
|
+
return `${node.label}[${children.map(treeFinger).join(",")}]`;
|
|
3106
|
+
}
|
|
3107
|
+
function PlanTree({ root, notes, active }) {
|
|
3108
|
+
return /* @__PURE__ */ jsx9(PlanTreeContent, { root, notes: notes ?? [], active }, treeFinger(root));
|
|
3109
|
+
}
|
|
3110
|
+
function PlanTreeContent({ root, notes, active }) {
|
|
3111
|
+
const [viewMode, setViewMode] = useState5("mindmap");
|
|
3112
|
+
const defaultExpanded = useMemo5(() => {
|
|
3113
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3114
|
+
collectIdsToDepth(root, 1, ids);
|
|
3115
|
+
return ids;
|
|
3116
|
+
}, [root]);
|
|
3117
|
+
const [expandedIds, setExpandedIds] = useState5(defaultExpanded);
|
|
3118
|
+
const toggle = useCallback((id) => {
|
|
3119
|
+
setExpandedIds((prev) => {
|
|
3120
|
+
const next = new Set(prev);
|
|
3121
|
+
if (next.has(id)) next.delete(id);
|
|
3122
|
+
else next.add(id);
|
|
3123
|
+
return next;
|
|
3124
|
+
});
|
|
3125
|
+
}, []);
|
|
3126
|
+
return /* @__PURE__ */ jsx9("div", { className: cn(!active && "pointer-events-none opacity-0"), children: /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-3", children: [
|
|
3127
|
+
/* @__PURE__ */ jsx9("div", { className: "flex items-center justify-end", children: /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-0.5 rounded-lg border border-[hsl(var(--border))] p-0.5", children: [
|
|
3128
|
+
/* @__PURE__ */ jsx9(
|
|
3129
|
+
"button",
|
|
3130
|
+
{
|
|
3131
|
+
type: "button",
|
|
3132
|
+
onClick: () => setViewMode("mindmap"),
|
|
3133
|
+
className: cn(
|
|
3134
|
+
"rounded-md p-1.5 transition-colors",
|
|
3135
|
+
viewMode === "mindmap" ? "bg-[hsl(var(--accent))] text-[hsl(var(--foreground))]" : "text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]"
|
|
3136
|
+
),
|
|
3137
|
+
title: "\u601D\u7EF4\u5BFC\u56FE",
|
|
3138
|
+
children: /* @__PURE__ */ jsx9(GitBranch, { size: 14 })
|
|
3139
|
+
}
|
|
3140
|
+
),
|
|
3141
|
+
/* @__PURE__ */ jsx9(
|
|
3142
|
+
"button",
|
|
3143
|
+
{
|
|
3144
|
+
type: "button",
|
|
3145
|
+
onClick: () => setViewMode("tree"),
|
|
3146
|
+
className: cn(
|
|
3147
|
+
"rounded-md p-1.5 transition-colors",
|
|
3148
|
+
viewMode === "tree" ? "bg-[hsl(var(--accent))] text-[hsl(var(--foreground))]" : "text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]"
|
|
3149
|
+
),
|
|
3150
|
+
title: "\u5217\u8868\u89C6\u56FE",
|
|
3151
|
+
children: /* @__PURE__ */ jsx9(List, { size: 14 })
|
|
3152
|
+
}
|
|
3153
|
+
)
|
|
3154
|
+
] }) }),
|
|
3155
|
+
/* @__PURE__ */ jsx9("div", { className: "rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card)/0.5)] px-3 py-3", children: viewMode === "mindmap" ? /* @__PURE__ */ jsx9(PlanMindMap, { root, expandedIds, onToggle: toggle }) : /* @__PURE__ */ jsx9(TreeNode, { node: root }) }),
|
|
3156
|
+
notes.length > 0 && /* @__PURE__ */ jsx9("div", { className: "rounded-xl border border-[hsl(var(--border)/0.5)] bg-[hsl(var(--card)/0.5)] px-4 py-3 text-sm text-[hsl(var(--muted-foreground))]", children: notes.map((note, i) => /* @__PURE__ */ jsxs7("p", { className: "py-0.5", children: [
|
|
3157
|
+
/* @__PURE__ */ jsxs7("span", { className: "mr-1.5 text-[hsl(var(--primary)/0.6)]", children: [
|
|
3158
|
+
i + 1,
|
|
3159
|
+
"."
|
|
3160
|
+
] }),
|
|
3161
|
+
note
|
|
3162
|
+
] }, i)) })
|
|
3163
|
+
] }) });
|
|
3164
|
+
}
|
|
3165
|
+
function TreeNode({ node }) {
|
|
3166
|
+
const children = node.children ?? [];
|
|
3167
|
+
const hasChildren = children.length > 0;
|
|
3168
|
+
return /* @__PURE__ */ jsxs7("div", { children: [
|
|
3169
|
+
/* @__PURE__ */ jsxs7(
|
|
3170
|
+
"div",
|
|
3171
|
+
{
|
|
3172
|
+
className: "group flex min-w-0 items-center gap-2 rounded-md px-2 py-2 hover:bg-[hsl(var(--accent)/0.5)]",
|
|
3173
|
+
style: { paddingLeft: `${node.depth * 24 + 10}px` },
|
|
3174
|
+
children: [
|
|
3175
|
+
hasChildren ? /* @__PURE__ */ jsx9(
|
|
3176
|
+
ChevronRight3,
|
|
3177
|
+
{
|
|
3178
|
+
size: 16,
|
|
3179
|
+
className: "shrink-0 rotate-90 text-[hsl(var(--muted-foreground)/0.6)]"
|
|
3180
|
+
}
|
|
3181
|
+
) : /* @__PURE__ */ jsx9("span", { className: "w-4 shrink-0" }),
|
|
3182
|
+
/* @__PURE__ */ jsx9(PlanStatusIcon, { status: node.status }),
|
|
3183
|
+
node.skillRef ? /* @__PURE__ */ jsx9(Workflow2, { size: 15, className: "shrink-0 text-[hsl(var(--primary))]" }) : /* @__PURE__ */ jsx9(CheckSquare, { size: 15, className: "shrink-0 text-[hsl(var(--muted-foreground))]" }),
|
|
3184
|
+
/* @__PURE__ */ jsx9("span", { className: "min-w-0 flex-1 truncate text-[15px] font-medium text-[hsl(var(--foreground))]", children: node.label }),
|
|
3185
|
+
node.skillRef && /* @__PURE__ */ jsx9("span", { className: "ml-auto max-w-[160px] shrink-0 truncate rounded bg-[hsl(var(--primary)/0.1)] px-2 py-0.5 text-sm font-medium text-[hsl(var(--primary)/0.8)]", children: node.skillRef })
|
|
3186
|
+
]
|
|
3187
|
+
}
|
|
3188
|
+
),
|
|
3189
|
+
hasChildren && /* @__PURE__ */ jsxs7("div", { className: "relative", children: [
|
|
3190
|
+
/* @__PURE__ */ jsx9(
|
|
3191
|
+
"div",
|
|
3192
|
+
{
|
|
3193
|
+
className: "absolute bottom-1 top-0 w-px bg-[hsl(var(--border)/0.4)]",
|
|
3194
|
+
style: { left: `${node.depth * 24 + 18}px` }
|
|
3195
|
+
}
|
|
3196
|
+
),
|
|
3197
|
+
children.map((child) => /* @__PURE__ */ jsx9(TreeNode, { node: child }, child.id))
|
|
3198
|
+
] })
|
|
3199
|
+
] });
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
// src/react/components/plan/phases/SkillAnalysis.tsx
|
|
3203
|
+
import { BookOpen, Link2 } from "lucide-react";
|
|
3204
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
3205
|
+
function planSkillDisplayName(skill) {
|
|
3206
|
+
return skill.displayName?.trim() || skill.skillId;
|
|
3207
|
+
}
|
|
3208
|
+
function extractSteps(content) {
|
|
3209
|
+
return content.split("\n").filter((line) => /^\d+\.\s/.test(line.trim())).map((line) => line.trim().replace(/^\d+\.\s*/, "")).slice(0, 5);
|
|
3210
|
+
}
|
|
3211
|
+
function SkillAnalysis({ skills: rawSkills, active }) {
|
|
3212
|
+
const skills = Array.isArray(rawSkills) ? rawSkills : [];
|
|
3213
|
+
return /* @__PURE__ */ jsx10(
|
|
3214
|
+
"div",
|
|
3215
|
+
{
|
|
3216
|
+
className: cn(
|
|
3217
|
+
"transition-opacity duration-500",
|
|
3218
|
+
active ? "opacity-100" : "pointer-events-none opacity-0"
|
|
3219
|
+
),
|
|
3220
|
+
children: /* @__PURE__ */ jsx10("div", { className: "columns-2 gap-4", children: skills.map((skill) => {
|
|
3221
|
+
const steps = skill.content ? extractSteps(skill.content) : [];
|
|
3222
|
+
return /* @__PURE__ */ jsxs8(
|
|
3223
|
+
"div",
|
|
3224
|
+
{
|
|
3225
|
+
className: "mb-4 break-inside-avoid overflow-hidden rounded-xl border border-[hsl(var(--primary)/0.4)] bg-[hsl(var(--card))] shadow-lg shadow-[hsl(var(--primary)/0.05)] transition-all duration-500",
|
|
3226
|
+
children: [
|
|
3227
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2 px-4 py-3", children: [
|
|
3228
|
+
/* @__PURE__ */ jsx10(BookOpen, { size: 15, className: "shrink-0 text-[hsl(var(--primary))]" }),
|
|
3229
|
+
/* @__PURE__ */ jsx10("span", { className: "min-w-0 truncate text-[15px] font-semibold text-[hsl(var(--foreground))]", children: planSkillDisplayName(skill) })
|
|
3230
|
+
] }),
|
|
3231
|
+
/* @__PURE__ */ jsx10("div", { className: "border-t border-[hsl(var(--border)/0.5)] px-4 py-2.5", children: /* @__PURE__ */ jsx10("p", { className: "text-sm leading-relaxed text-[hsl(var(--muted-foreground))]", children: skill.description }) }),
|
|
3232
|
+
/* @__PURE__ */ jsxs8("div", { className: "border-t border-[hsl(var(--border)/0.5)] px-4 py-2.5", children: [
|
|
3233
|
+
skill.references && skill.references.length > 0 && /* @__PURE__ */ jsx10("div", { className: "mb-2 flex flex-wrap gap-1.5", children: skill.references.map((ref, refIdx) => /* @__PURE__ */ jsxs8(
|
|
3234
|
+
"span",
|
|
3235
|
+
{
|
|
3236
|
+
className: "inline-flex items-center gap-1 rounded-md bg-[hsl(var(--primary)/0.1)] px-2 py-0.5 text-xs text-[hsl(var(--primary))]",
|
|
3237
|
+
children: [
|
|
3238
|
+
/* @__PURE__ */ jsx10(Link2, { size: 10 }),
|
|
3239
|
+
ref
|
|
3240
|
+
]
|
|
3241
|
+
},
|
|
3242
|
+
`${refIdx}-${ref}`
|
|
3243
|
+
)) }),
|
|
3244
|
+
steps.length > 0 && /* @__PURE__ */ jsx10("ol", { className: "list-inside list-decimal space-y-0.5 text-sm leading-relaxed text-[hsl(var(--muted-foreground)/0.8)]", children: steps.map((step, idx) => /* @__PURE__ */ jsx10("li", { children: step }, idx)) })
|
|
3245
|
+
] })
|
|
3246
|
+
]
|
|
3247
|
+
},
|
|
3248
|
+
skill.skillId
|
|
3249
|
+
);
|
|
3250
|
+
}) })
|
|
3251
|
+
}
|
|
3252
|
+
);
|
|
3253
|
+
}
|
|
3254
|
+
|
|
3255
|
+
// src/react/components/plan/phases/SkillDiscovery.tsx
|
|
3256
|
+
import { CheckCircle2 as CheckCircle23, Search, Sparkles as Sparkles2 } from "lucide-react";
|
|
3257
|
+
import { useMemo as useMemo6 } from "react";
|
|
3258
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3259
|
+
function sizeClass(name) {
|
|
3260
|
+
const hash = name.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0);
|
|
3261
|
+
const sizes = ["text-xs", "text-sm", "text-[15px]"];
|
|
3262
|
+
return sizes[hash % sizes.length];
|
|
3263
|
+
}
|
|
3264
|
+
function planSkillDisplayName2(skill) {
|
|
3265
|
+
return skill.displayName?.trim() || skill.skillId;
|
|
3266
|
+
}
|
|
3267
|
+
function SkillDiscovery({
|
|
3268
|
+
intent,
|
|
3269
|
+
searchResults: rawSearchResults,
|
|
3270
|
+
selectedSkills: rawSelectedSkills,
|
|
3271
|
+
allSkillNames: rawAllSkillNames,
|
|
3272
|
+
active
|
|
3273
|
+
}) {
|
|
3274
|
+
const searchResults = Array.isArray(rawSearchResults) ? rawSearchResults : [];
|
|
3275
|
+
const selectedSkills = Array.isArray(rawSelectedSkills) ? rawSelectedSkills : [];
|
|
3276
|
+
const allSkillNames = Array.isArray(rawAllSkillNames) ? rawAllSkillNames : [];
|
|
3277
|
+
const searchNames = useMemo6(() => new Set(searchResults.map((s) => s.skillId)), [searchResults]);
|
|
3278
|
+
const selectedNames = useMemo6(
|
|
3279
|
+
() => new Set(selectedSkills.map((s) => s.skillId)),
|
|
3280
|
+
[selectedSkills]
|
|
3281
|
+
);
|
|
3282
|
+
const cloudItems = useMemo6(() => {
|
|
3283
|
+
const bgItems = allSkillNames.filter((skill) => !searchNames.has(skill.skillId)).map((skill) => ({
|
|
3284
|
+
name: planSkillDisplayName2(skill),
|
|
3285
|
+
key: `bg:${skill.skillId}`,
|
|
3286
|
+
isSearch: false,
|
|
3287
|
+
isSelected: false
|
|
3288
|
+
}));
|
|
3289
|
+
const searchItems = searchResults.map((s) => ({
|
|
3290
|
+
name: planSkillDisplayName2(s),
|
|
3291
|
+
key: `search:${s.skillId}`,
|
|
3292
|
+
isSearch: true,
|
|
3293
|
+
isSelected: selectedNames.has(s.skillId)
|
|
3294
|
+
}));
|
|
3295
|
+
const result = [...bgItems];
|
|
3296
|
+
for (let i = 0; i < searchItems.length; i++) {
|
|
3297
|
+
const insertAt = Math.round((i + 1) / (searchItems.length + 1) * result.length);
|
|
3298
|
+
result.splice(insertAt, 0, searchItems[i]);
|
|
3299
|
+
}
|
|
3300
|
+
return result;
|
|
3301
|
+
}, [allSkillNames, searchNames, searchResults, selectedNames]);
|
|
3302
|
+
return /* @__PURE__ */ jsxs9(
|
|
3303
|
+
"div",
|
|
3304
|
+
{
|
|
3305
|
+
className: cn(
|
|
3306
|
+
"flex h-full flex-col gap-6 transition-opacity duration-500",
|
|
3307
|
+
active ? "opacity-100" : "pointer-events-none opacity-0"
|
|
3308
|
+
),
|
|
3309
|
+
children: [
|
|
3310
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex shrink-0 max-w-md self-center flex-col items-center gap-2.5 rounded-2xl border border-[hsl(var(--primary)/0.4)] bg-[hsl(var(--primary)/0.08)] px-6 py-5 text-center shadow-lg shadow-[hsl(var(--primary)/0.1)]", children: [
|
|
3311
|
+
/* @__PURE__ */ jsx11(Sparkles2, { size: 20, className: "text-[hsl(var(--primary))]" }),
|
|
3312
|
+
/* @__PURE__ */ jsx11("p", { className: "break-words text-[15px] leading-relaxed text-[hsl(var(--foreground))]", children: intent })
|
|
3313
|
+
] }),
|
|
3314
|
+
/* @__PURE__ */ jsx11("div", { className: "flex min-h-0 flex-1 flex-wrap content-start items-start justify-center gap-x-4 gap-y-3 overflow-y-auto px-6 pb-6", children: cloudItems.map((item) => {
|
|
3315
|
+
if (item.isSearch && item.isSelected) {
|
|
3316
|
+
return /* @__PURE__ */ jsxs9(
|
|
3317
|
+
"span",
|
|
3318
|
+
{
|
|
3319
|
+
className: "inline-flex items-center gap-1.5 rounded-lg border border-[hsl(var(--primary)/0.5)] bg-[hsl(var(--primary)/0.12)] px-3 py-1.5 text-[15px] font-medium text-[hsl(var(--primary))] shadow-md shadow-[hsl(var(--primary)/0.15)] transition-all duration-500",
|
|
3320
|
+
children: [
|
|
3321
|
+
/* @__PURE__ */ jsx11(CheckCircle23, { size: 14, className: "shrink-0" }),
|
|
3322
|
+
item.name
|
|
3323
|
+
]
|
|
3324
|
+
},
|
|
3325
|
+
item.key
|
|
3326
|
+
);
|
|
3327
|
+
}
|
|
3328
|
+
if (item.isSearch) {
|
|
3329
|
+
return /* @__PURE__ */ jsxs9(
|
|
3330
|
+
"span",
|
|
3331
|
+
{
|
|
3332
|
+
className: "inline-flex items-center gap-1.5 rounded-lg border border-[hsl(var(--foreground)/0.3)] bg-[hsl(var(--foreground)/0.08)] px-3 py-1.5 text-[15px] font-medium text-[hsl(var(--foreground))] transition-all duration-500",
|
|
3333
|
+
children: [
|
|
3334
|
+
/* @__PURE__ */ jsx11(Search, { size: 14, className: "shrink-0" }),
|
|
3335
|
+
item.name
|
|
3336
|
+
]
|
|
3337
|
+
},
|
|
3338
|
+
item.key
|
|
3339
|
+
);
|
|
3340
|
+
}
|
|
3341
|
+
return /* @__PURE__ */ jsx11(
|
|
3342
|
+
"span",
|
|
3343
|
+
{
|
|
3344
|
+
className: cn(
|
|
3345
|
+
"select-none font-medium text-[hsl(var(--foreground)/0.45)] transition-all duration-500",
|
|
3346
|
+
sizeClass(item.name)
|
|
3347
|
+
),
|
|
3348
|
+
children: item.name
|
|
3349
|
+
},
|
|
3350
|
+
item.key
|
|
3351
|
+
);
|
|
3352
|
+
}) })
|
|
3353
|
+
]
|
|
3354
|
+
}
|
|
3355
|
+
);
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
// src/react/components/plan/PlanVisualization.tsx
|
|
3359
|
+
import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3360
|
+
var PHASE_LABELS = [
|
|
3361
|
+
{ key: "discovering", label: "\u6280\u80FD\u53D1\u73B0", icon: Search2 },
|
|
3362
|
+
{ key: "analyzing", label: "\u6280\u80FD\u5206\u6790", icon: BookOpen2 },
|
|
3363
|
+
{ key: "generating", label: "\u8BA1\u5212\u751F\u6210", icon: GitBranch2 }
|
|
3364
|
+
];
|
|
3365
|
+
function getLatestPhaseIndex(data) {
|
|
3366
|
+
if (data.plan != null || data.hasPlanContent) return 2;
|
|
3367
|
+
if (data.selectedSkills.length > 0) return 1;
|
|
3368
|
+
if (data.searchResults.length > 0) return 0;
|
|
3369
|
+
return -1;
|
|
3370
|
+
}
|
|
3371
|
+
function PlanVisualization({
|
|
3372
|
+
messages: rawMessages,
|
|
3373
|
+
allSkillNames: rawAllSkillNames = [],
|
|
3374
|
+
onAnswer,
|
|
3375
|
+
sessionStatus,
|
|
3376
|
+
isStreaming = false
|
|
3377
|
+
}) {
|
|
3378
|
+
const messages = Array.isArray(rawMessages) ? rawMessages : [];
|
|
3379
|
+
const allSkillNames = Array.isArray(rawAllSkillNames) ? rawAllSkillNames : [];
|
|
3380
|
+
const data = useMemo7(() => parsePlanMessages(messages), [messages]);
|
|
3381
|
+
const latestIdx = getLatestPhaseIndex(data);
|
|
3382
|
+
const isWaitingForInput = sessionStatus === "waiting_for_input";
|
|
3383
|
+
const hasError = sessionStatus === "failed";
|
|
3384
|
+
const isInterrupted = sessionStatus === "interrupted";
|
|
3385
|
+
const isSessionActive = isStreaming || sessionStatus === "created" || sessionStatus === "running" || sessionStatus === "waiting_for_input";
|
|
3386
|
+
const activeStatusIdx = latestIdx >= 0 ? latestIdx : 0;
|
|
3387
|
+
const latestAskQuestion = data.askQuestions[data.askQuestions.length - 1];
|
|
3388
|
+
const hasPhaseData = latestIdx >= 0;
|
|
3389
|
+
const showAskQuestion = latestAskQuestion && isWaitingForInput && data.lastPauseTool === "AskUserQuestion" && onAnswer;
|
|
3390
|
+
const [selectedTab, setSelectedTab] = useState6(null);
|
|
3391
|
+
const prevLatestRef = useRef2(latestIdx);
|
|
3392
|
+
if (latestIdx < prevLatestRef.current) {
|
|
3393
|
+
setSelectedTab(null);
|
|
3394
|
+
}
|
|
3395
|
+
prevLatestRef.current = latestIdx;
|
|
3396
|
+
const activeSelectedTab = selectedTab != null && selectedTab <= latestIdx ? selectedTab : null;
|
|
3397
|
+
const viewIdx = activeSelectedTab ?? latestIdx;
|
|
3398
|
+
return /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-4 rounded-2xl border border-[hsl(var(--border))] bg-[hsl(var(--card)/0.4)] p-5", children: [
|
|
3399
|
+
hasPhaseData && /* @__PURE__ */ jsxs10(Fragment4, { children: [
|
|
3400
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between", children: [
|
|
3401
|
+
/* @__PURE__ */ jsx12("div", { className: "flex items-center gap-1", children: PHASE_LABELS.map((p, i) => {
|
|
3402
|
+
const Icon = p.icon;
|
|
3403
|
+
const isViewing = viewIdx === i;
|
|
3404
|
+
const isDone = latestIdx > i;
|
|
3405
|
+
const isStatusPhase = i === activeStatusIdx && (hasError || isInterrupted);
|
|
3406
|
+
const tabHasData = i === 0 && data.searchResults.length > 0 || i === 1 && data.selectedSkills.length > 0 || i === 2 && (data.plan != null || data.hasPlanContent);
|
|
3407
|
+
const isReachable = i <= latestIdx || tabHasData;
|
|
3408
|
+
return /* @__PURE__ */ jsxs10("div", { className: "flex items-center", children: [
|
|
3409
|
+
i > 0 && /* @__PURE__ */ jsx12(
|
|
3410
|
+
"div",
|
|
3411
|
+
{
|
|
3412
|
+
className: cn(
|
|
3413
|
+
"mx-1.5 h-px w-6 transition-colors duration-300",
|
|
3414
|
+
isDone && !isStatusPhase ? "bg-[hsl(var(--primary))]" : "bg-[hsl(var(--border))]"
|
|
3415
|
+
)
|
|
3416
|
+
}
|
|
3417
|
+
),
|
|
3418
|
+
/* @__PURE__ */ jsxs10(
|
|
3419
|
+
"button",
|
|
3420
|
+
{
|
|
3421
|
+
type: "button",
|
|
3422
|
+
disabled: !isReachable,
|
|
3423
|
+
onClick: () => isReachable && setSelectedTab(i === latestIdx ? null : i),
|
|
3424
|
+
className: cn(
|
|
3425
|
+
"flex items-center gap-2 rounded-full px-3.5 py-2 text-sm font-medium transition-all duration-300",
|
|
3426
|
+
isViewing && "bg-[hsl(var(--primary)/0.15)] text-[hsl(var(--primary))]",
|
|
3427
|
+
!isViewing && isDone && "text-[hsl(var(--primary)/0.6)] hover:bg-[hsl(var(--primary)/0.08)]",
|
|
3428
|
+
!isReachable && "cursor-default text-[hsl(var(--muted-foreground))]",
|
|
3429
|
+
isReachable && !isViewing && !isDone && tabHasData && "text-[hsl(var(--primary)/0.5)] hover:bg-[hsl(var(--primary)/0.08)]",
|
|
3430
|
+
isReachable && !isViewing && !isDone && !tabHasData && "text-[hsl(var(--muted-foreground))]"
|
|
3431
|
+
),
|
|
3432
|
+
children: [
|
|
3433
|
+
/* @__PURE__ */ jsx12(Icon, { size: 12 }),
|
|
3434
|
+
p.label,
|
|
3435
|
+
latestIdx === i && isSessionActive && activeSelectedTab === null && /* @__PURE__ */ jsx12(
|
|
3436
|
+
"span",
|
|
3437
|
+
{
|
|
3438
|
+
className: cn(
|
|
3439
|
+
"h-1.5 w-1.5 animate-pulse rounded-full",
|
|
3440
|
+
hasError ? "bg-[hsl(var(--destructive))]" : isInterrupted ? "bg-orange-400" : "bg-[hsl(var(--primary))]"
|
|
3441
|
+
)
|
|
3442
|
+
}
|
|
3443
|
+
),
|
|
3444
|
+
isStatusPhase && latestIdx !== i && /* @__PURE__ */ jsx12(
|
|
3445
|
+
"span",
|
|
3446
|
+
{
|
|
3447
|
+
className: cn(
|
|
3448
|
+
"h-1.5 w-1.5 rounded-full",
|
|
3449
|
+
hasError ? "bg-[hsl(var(--destructive))]" : "bg-orange-400"
|
|
3450
|
+
)
|
|
3451
|
+
}
|
|
3452
|
+
)
|
|
3453
|
+
]
|
|
3454
|
+
}
|
|
3455
|
+
)
|
|
3456
|
+
] }, p.key);
|
|
3457
|
+
}) }),
|
|
3458
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1", children: [
|
|
3459
|
+
hasError && /* @__PURE__ */ jsx12("span", { className: "rounded-full bg-[hsl(var(--destructive)/0.12)] px-2.5 py-1 text-xs font-medium text-[hsl(var(--destructive))]", children: "\u51FA\u9519" }),
|
|
3460
|
+
isInterrupted && /* @__PURE__ */ jsx12("span", { className: "rounded-full bg-orange-500/10 px-2.5 py-1 text-xs font-medium text-orange-300", children: "\u5DF2\u4E2D\u65AD" })
|
|
3461
|
+
] })
|
|
3462
|
+
] }),
|
|
3463
|
+
/* @__PURE__ */ jsxs10("div", { className: "min-h-0", children: [
|
|
3464
|
+
viewIdx === 0 && /* @__PURE__ */ jsx12(
|
|
3465
|
+
SkillDiscovery,
|
|
3466
|
+
{
|
|
3467
|
+
intent: data.intent,
|
|
3468
|
+
searchResults: data.searchResults,
|
|
3469
|
+
selectedSkills: data.selectedSkills,
|
|
3470
|
+
allSkillNames,
|
|
3471
|
+
active: true
|
|
3472
|
+
}
|
|
3473
|
+
),
|
|
3474
|
+
viewIdx === 1 && /* @__PURE__ */ jsx12(SkillAnalysis, { skills: data.selectedSkills, active: true }),
|
|
3475
|
+
viewIdx === 2 && (data.plan != null ? /* @__PURE__ */ jsx12(PlanTree, { root: data.plan.root, notes: data.plan.notes, active: true }) : data.parseError ? /* @__PURE__ */ jsx12("div", { className: "flex min-h-[240px] items-center justify-center rounded-xl border border-[hsl(var(--destructive)/0.35)] bg-[hsl(var(--destructive)/0.08)] px-6 text-sm text-[hsl(var(--destructive))]", children: data.parseError }) : data.hasPlanContent ? /* @__PURE__ */ jsx12("div", { className: "flex min-h-[240px] items-center justify-center rounded-xl border border-dashed border-[hsl(var(--border))] bg-[hsl(var(--card)/0.3)] px-6 text-sm text-[hsl(var(--muted-foreground))]", children: "\u8BA1\u5212\u89E3\u6790\u4E2D..." }) : null)
|
|
3476
|
+
] })
|
|
3477
|
+
] }),
|
|
3478
|
+
!hasPhaseData && showAskQuestion && /* @__PURE__ */ jsx12(AskUserQuestionBlock, { data: latestAskQuestion.data, answered: false, toolCallId: latestAskQuestion.toolCallId, sessionStatus: sessionStatus ?? "", onAnswer }),
|
|
3479
|
+
hasPhaseData && showAskQuestion && /* @__PURE__ */ jsx12(AskUserQuestionBlock, { data: latestAskQuestion.data, answered: false, toolCallId: latestAskQuestion.toolCallId, sessionStatus: sessionStatus ?? "", onAnswer })
|
|
3480
|
+
] });
|
|
3481
|
+
}
|
|
3482
|
+
export {
|
|
3483
|
+
PlanSummaryCard,
|
|
3484
|
+
PlanVisualization,
|
|
3485
|
+
downloadPlanLog,
|
|
3486
|
+
exportPlanLog,
|
|
3487
|
+
extractLatestPlanMessages,
|
|
3488
|
+
parsePlanMessages
|
|
3489
|
+
};
|
|
3490
|
+
//# sourceMappingURL=index.js.map
|