@yumiai/chat-widget 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +119 -22
  3. package/dist/ExcelCore-DJOIVQMI.js +11 -0
  4. package/dist/ExcelCore-DJOIVQMI.js.map +1 -0
  5. package/dist/ExcelViewer-3YLLYYIQ.js +65 -0
  6. package/dist/ExcelViewer-3YLLYYIQ.js.map +1 -0
  7. package/dist/GerberViewerA2UI-7CNT7HX4.css +693 -0
  8. package/dist/GerberViewerA2UI-7CNT7HX4.css.map +1 -0
  9. package/dist/GerberViewerA2UI-X5FWAD5M.js +57 -0
  10. package/dist/GerberViewerA2UI-X5FWAD5M.js.map +1 -0
  11. package/dist/GraphStatsLegend-D5bPeXB_.d.cts +607 -0
  12. package/dist/GraphStatsLegend-D5bPeXB_.d.ts +607 -0
  13. package/dist/JsonRenderStandalone-EIZM62JU.js +18 -0
  14. package/dist/JsonRenderStandalone-EIZM62JU.js.map +1 -0
  15. package/dist/JsonRenderStandalone-POB4Q3N3.css +2384 -0
  16. package/dist/JsonRenderStandalone-POB4Q3N3.css.map +1 -0
  17. package/dist/JsonRenderStandalone-UsTcST4G.d.cts +23 -0
  18. package/dist/JsonRenderStandalone-UsTcST4G.d.ts +23 -0
  19. package/dist/KicadViewer-GV6ZC4AQ.js +124 -0
  20. package/dist/KicadViewer-GV6ZC4AQ.js.map +1 -0
  21. package/dist/KicadViewerCore-U7BWZHKJ.js +11 -0
  22. package/dist/KicadViewerCore-U7BWZHKJ.js.map +1 -0
  23. package/dist/PdfViewer-CHPDRK46.js +51 -0
  24. package/dist/PdfViewer-CHPDRK46.js.map +1 -0
  25. package/dist/PdfViewer-LPYGQETK.css +1899 -0
  26. package/dist/PdfViewer-LPYGQETK.css.map +1 -0
  27. package/dist/PdfViewerCore-HJPEHSRA.js +364 -0
  28. package/dist/PdfViewerCore-HJPEHSRA.js.map +1 -0
  29. package/dist/PowerPointCore-FPDR2BL4.js +11 -0
  30. package/dist/PowerPointCore-FPDR2BL4.js.map +1 -0
  31. package/dist/PowerPointViewer-LQTO6UCU.js +61 -0
  32. package/dist/PowerPointViewer-LQTO6UCU.js.map +1 -0
  33. package/dist/StepViewerCore-7W3L3R4E.js +285 -0
  34. package/dist/StepViewerCore-7W3L3R4E.js.map +1 -0
  35. package/dist/ThreeViewerCore-N3QJD5QI.js +161 -0
  36. package/dist/ThreeViewerCore-N3QJD5QI.js.map +1 -0
  37. package/dist/WordCore-JKSXK2XD.js +11 -0
  38. package/dist/WordCore-JKSXK2XD.js.map +1 -0
  39. package/dist/WordViewer-ZHCQMHOH.js +61 -0
  40. package/dist/WordViewer-ZHCQMHOH.js.map +1 -0
  41. package/dist/chunk-2SKA3F5U.js +88 -0
  42. package/dist/chunk-2SKA3F5U.js.map +1 -0
  43. package/dist/chunk-2UC7YLVX.js +318 -0
  44. package/dist/chunk-2UC7YLVX.js.map +1 -0
  45. package/dist/chunk-3R6T3LBR.js +24 -0
  46. package/dist/chunk-3R6T3LBR.js.map +1 -0
  47. package/dist/chunk-56WRZM3R.js +398 -0
  48. package/dist/chunk-56WRZM3R.js.map +1 -0
  49. package/dist/chunk-7A4FY6FK.js +10226 -0
  50. package/dist/chunk-7A4FY6FK.js.map +1 -0
  51. package/dist/chunk-7D4SUZUM.js +38 -0
  52. package/dist/chunk-7D4SUZUM.js.map +1 -0
  53. package/dist/chunk-7S67DOHQ.js +436 -0
  54. package/dist/chunk-7S67DOHQ.js.map +1 -0
  55. package/dist/chunk-CFKGNAJM.js +14013 -0
  56. package/dist/chunk-CFKGNAJM.js.map +1 -0
  57. package/dist/chunk-GAMA3VA7.js +99 -0
  58. package/dist/chunk-GAMA3VA7.js.map +1 -0
  59. package/dist/chunk-GYXTSY22.js +639 -0
  60. package/dist/chunk-GYXTSY22.js.map +1 -0
  61. package/dist/chunk-K4KGNVL5.js +77 -0
  62. package/dist/chunk-K4KGNVL5.js.map +1 -0
  63. package/dist/chunk-KQV7IKET.js +1621 -0
  64. package/dist/chunk-KQV7IKET.js.map +1 -0
  65. package/dist/chunk-O3NXUM6C.js +1871 -0
  66. package/dist/chunk-O3NXUM6C.js.map +1 -0
  67. package/dist/chunk-PZXSASDY.js +83 -0
  68. package/dist/chunk-PZXSASDY.js.map +1 -0
  69. package/dist/chunk-QLVPIM6R.js +595 -0
  70. package/dist/chunk-QLVPIM6R.js.map +1 -0
  71. package/dist/chunk-VXJWGLZ7.js +21 -0
  72. package/dist/chunk-VXJWGLZ7.js.map +1 -0
  73. package/dist/chunk-XQ562W7I.js +116 -0
  74. package/dist/chunk-XQ562W7I.js.map +1 -0
  75. package/dist/components/JsonRender/standalone.cjs +39368 -0
  76. package/dist/components/JsonRender/standalone.cjs.map +1 -0
  77. package/dist/components/JsonRender/standalone.css +2384 -0
  78. package/dist/components/JsonRender/standalone.css.map +1 -0
  79. package/dist/components/JsonRender/standalone.d.cts +16 -0
  80. package/dist/components/JsonRender/standalone.d.ts +16 -0
  81. package/dist/components/JsonRender/standalone.js +38 -0
  82. package/dist/components/JsonRender/standalone.js.map +1 -0
  83. package/dist/gerber-2d-entry-OQ4SQRBY.js +3950 -0
  84. package/dist/gerber-2d-entry-OQ4SQRBY.js.map +1 -0
  85. package/dist/gerber-3d-entry-DEHDBOO2.js +3679 -0
  86. package/dist/gerber-3d-entry-DEHDBOO2.js.map +1 -0
  87. package/dist/gerber-simulation-entry-EBDX72XE.js +1801 -0
  88. package/dist/gerber-simulation-entry-EBDX72XE.js.map +1 -0
  89. package/dist/index.cjs +60113 -2970
  90. package/dist/index.cjs.map +1 -1
  91. package/dist/index.css +11342 -1708
  92. package/dist/index.css.map +1 -1
  93. package/dist/index.d.cts +3275 -77
  94. package/dist/index.d.ts +3275 -77
  95. package/dist/index.js +18078 -2540
  96. package/dist/index.js.map +1 -1
  97. package/dist/provenance/index.cjs +2248 -0
  98. package/dist/provenance/index.cjs.map +1 -0
  99. package/dist/provenance/index.css +52 -0
  100. package/dist/provenance/index.css.map +1 -0
  101. package/dist/provenance/index.d.cts +12 -0
  102. package/dist/provenance/index.d.ts +12 -0
  103. package/dist/provenance/index.js +27 -0
  104. package/dist/provenance/index.js.map +1 -0
  105. package/dist/resolveToArrayBuffer-AQIDZHSQ.js +23 -0
  106. package/dist/resolveToArrayBuffer-AQIDZHSQ.js.map +1 -0
  107. package/package.json +98 -17
@@ -0,0 +1,1621 @@
1
+ import {
2
+ I18nContext,
3
+ resolveMessages,
4
+ useChatWidgetI18n
5
+ } from "./chunk-QLVPIM6R.js";
6
+
7
+ // src/provenance/ProvenanceGraphDrawerContent.tsx
8
+ import React4, { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
9
+ import { Spin, Empty } from "antd";
10
+
11
+ // src/provenance/hooks/useProvenanceGraph.ts
12
+ import { useCallback, useEffect, useRef, useState } from "react";
13
+
14
+ // src/provenance/GraphBuilder.ts
15
+ var GraphBuilder = class _GraphBuilder {
16
+ sessionId = 0;
17
+ agents = /* @__PURE__ */ new Map();
18
+ actions = /* @__PURE__ */ new Map();
19
+ files = /* @__PURE__ */ new Map();
20
+ tasks = /* @__PURE__ */ new Map();
21
+ reasonings = /* @__PURE__ */ new Map();
22
+ edges = [];
23
+ edgeKeys = /* @__PURE__ */ new Set();
24
+ lastCompletedActions = /* @__PURE__ */ new Map();
25
+ handlers = {
26
+ agent_start: (p) => this.handleAgentStart(p),
27
+ agent_end: (p) => this.handleAgentEnd(p),
28
+ mcp_start: (p) => this.handleMcpStart(p),
29
+ mcp_end: (p) => this.handleMcpEnd(p),
30
+ artifact_result: (p) => this.handleArtifactResult(p),
31
+ plan_result: (p) => this.handlePlanResult(p),
32
+ model_start: (p) => this.handleModelStart(p),
33
+ model_result: (p) => this.handleModelResult(p),
34
+ model_end: (p) => this.handleModelEnd(p)
35
+ };
36
+ /** Apply an SSE event. Returns true if the event was handled. */
37
+ applyEvent(payload) {
38
+ const type = payload.notification_type ?? "";
39
+ const handler = this.handlers[type];
40
+ if (!handler) return false;
41
+ handler(payload);
42
+ return true;
43
+ }
44
+ /** Replace all state from a backend snapshot (initial load). */
45
+ replaceWithSnapshot(snapshot) {
46
+ this.sessionId = snapshot.session_id;
47
+ this.agents.clear();
48
+ this.actions.clear();
49
+ this.files.clear();
50
+ this.tasks.clear();
51
+ this.reasonings.clear();
52
+ this.edges = [];
53
+ this.edgeKeys.clear();
54
+ for (const n of snapshot.nodes.agents) this.agents.set(n.agent_instance_id, { ...n });
55
+ for (const n of snapshot.nodes.actions) this.actions.set(n.tool_call_id, { ...n });
56
+ for (const n of snapshot.nodes.files) this.files.set(n.id, { ...n });
57
+ for (const n of snapshot.nodes.tasks) this.tasks.set(n.id, { ...n });
58
+ for (const n of snapshot.nodes.reasonings) this.reasonings.set(n.id, { ...n });
59
+ for (const e of snapshot.edges) {
60
+ const key = this.edgeKey(e.source, e.target, e.type, e.label);
61
+ if (!this.edgeKeys.has(key)) {
62
+ this.edgeKeys.add(key);
63
+ this.edges.push({ ...e });
64
+ }
65
+ }
66
+ }
67
+ /** Merge an incremental snapshot (reconnection). */
68
+ mergeSnapshot(snapshot) {
69
+ for (const n of snapshot.nodes.agents) this.agents.set(n.agent_instance_id, { ...n });
70
+ for (const n of snapshot.nodes.actions) this.actions.set(n.tool_call_id, { ...n });
71
+ for (const n of snapshot.nodes.files) this.files.set(n.id, { ...n });
72
+ for (const n of snapshot.nodes.tasks) this.tasks.set(n.id, { ...n });
73
+ for (const n of snapshot.nodes.reasonings) this.reasonings.set(n.id, { ...n });
74
+ for (const e of snapshot.edges) {
75
+ const key = this.edgeKey(e.source, e.target, e.type, e.label);
76
+ if (!this.edgeKeys.has(key)) {
77
+ this.edgeKeys.add(key);
78
+ this.edges.push({ ...e });
79
+ }
80
+ }
81
+ }
82
+ /** Produce an immutable snapshot for React state. */
83
+ getSnapshot() {
84
+ const agents = Array.from(this.agents.values());
85
+ const actions = Array.from(this.actions.values());
86
+ const files = Array.from(this.files.values());
87
+ const tasks = Array.from(this.tasks.values());
88
+ const reasonings = Array.from(this.reasonings.values());
89
+ const nodeCount = agents.length + actions.length + files.length + tasks.length + reasonings.length;
90
+ const maxDepth = agents.length > 0 ? Math.max(...agents.map((a) => a.level)) : 0;
91
+ return {
92
+ session_id: this.sessionId,
93
+ generated_at: Date.now(),
94
+ node_count: nodeCount,
95
+ edge_count: this.edges.length,
96
+ max_turn_id: null,
97
+ nodes: { agents, actions, files, tasks, reasonings },
98
+ edges: [...this.edges],
99
+ stats: {
100
+ agent_count: agents.length,
101
+ action_count: actions.length,
102
+ file_count: files.length,
103
+ task_count: tasks.length,
104
+ reasoning_count: reasonings.length,
105
+ max_depth: maxDepth
106
+ }
107
+ };
108
+ }
109
+ // -----------------------------------------------------------------------
110
+ // Handlers
111
+ // -----------------------------------------------------------------------
112
+ handleAgentStart(p) {
113
+ const callee = p.callee_instance_id ?? p.agent_instance_id ?? 0;
114
+ const caller = p.caller_instance_id ?? null;
115
+ const ts = this.parseTimestamp(p.timestamp);
116
+ const node = {
117
+ id: `agent-inst-${callee}`,
118
+ type: "agent",
119
+ label: p.agent_name ?? p.agent_id ?? `agent-${callee}`,
120
+ timestamp: ts,
121
+ metadata: {},
122
+ agent_id: p.agent_id ?? `agent-${callee}`,
123
+ agent_instance_id: callee,
124
+ agent_name: p.agent_name ?? `agent-${callee}`,
125
+ level: p.level ?? 0,
126
+ parent_agent_instance_id: caller != null && caller >= 0 ? caller : null,
127
+ status: "running"
128
+ };
129
+ this.agents.set(callee, node);
130
+ if (caller != null && caller >= 0 && this.agents.has(caller)) {
131
+ this.addEdge(`agent-inst-${caller}`, `agent-inst-${callee}`, "spawns", ts);
132
+ }
133
+ }
134
+ handleAgentEnd(p) {
135
+ const callee = p.callee_instance_id ?? p.agent_instance_id;
136
+ if (callee == null) return;
137
+ const existing = this.agents.get(callee);
138
+ if (existing) {
139
+ this.agents.set(callee, { ...existing, status: "completed" });
140
+ }
141
+ }
142
+ handleMcpStart(p) {
143
+ const tcid = this.extractString(p.ext, "tool_call_id") ?? p.tool_call_id;
144
+ if (!tcid) return;
145
+ const callee = p.callee_instance_id ?? p.agent_instance_id ?? 0;
146
+ const ts = this.parseTimestamp(p.timestamp);
147
+ const node = {
148
+ id: `action-${tcid}`,
149
+ type: "action",
150
+ label: this.extractString(p.ext, "tool_name") ?? p.tool_name ?? tcid,
151
+ timestamp: ts,
152
+ metadata: {},
153
+ tool_name: this.extractString(p.ext, "tool_name") ?? p.tool_name ?? "",
154
+ tool_call_id: tcid,
155
+ task_purpose: this.extractString(p.ext, "task_purpose") ?? p.task_purpose ?? "",
156
+ agent_instance_id: callee,
157
+ status: "running",
158
+ duration_ms: null
159
+ };
160
+ this.actions.set(tcid, node);
161
+ if (this.agents.has(callee)) {
162
+ this.addEdge(`agent-inst-${callee}`, `action-${tcid}`, "executes", ts);
163
+ }
164
+ const latestReasoning = this.findLatestReasoning(callee);
165
+ if (latestReasoning) {
166
+ this.addEdge(latestReasoning.id, `action-${tcid}`, "triggers", ts);
167
+ }
168
+ for (const [fid, fileNode] of this.files) {
169
+ if (fileNode.tool_call_id === tcid) {
170
+ const op = fileNode.operation;
171
+ const et = op === "create" ? "produces" : op === "delete" ? "deletes" : "modifies";
172
+ this.addEdge(`action-${tcid}`, fid, et, ts);
173
+ }
174
+ }
175
+ }
176
+ handleMcpEnd(p) {
177
+ const tcid = this.extractString(p.ext, "tool_call_id") ?? p.tool_call_id;
178
+ if (!tcid) return;
179
+ const existing = this.actions.get(tcid);
180
+ if (!existing) return;
181
+ const statusStr = this.extractString(p.ext, "status") ?? p.tool_status ?? "success";
182
+ const status = statusStr === "error" ? "error" : "success";
183
+ const endTs = this.parseTimestamp(p.timestamp);
184
+ const duration = endTs - existing.timestamp;
185
+ this.actions.set(tcid, { ...existing, status, duration_ms: duration > 0 ? duration : null });
186
+ const callee = p.callee_instance_id ?? p.agent_instance_id ?? 0;
187
+ const prev = this.lastCompletedActions.get(callee) ?? [];
188
+ prev.push(tcid);
189
+ this.lastCompletedActions.set(callee, prev);
190
+ }
191
+ handleArtifactResult(p) {
192
+ if (p.ext?.xref_scan_result) {
193
+ this.handleXrefScanResult(p);
194
+ return;
195
+ }
196
+ const data = this.extractArtifactData(p.content);
197
+ if (!data) return;
198
+ const fileId = data.file_id ?? data.identifier ?? "";
199
+ if (!fileId) return;
200
+ const fid = `file-${fileId}`;
201
+ const op = this.normalizeOperation(data.operation_type);
202
+ const tcid = this.extractString(p.ext, "tool_call_id") ?? p.tool_call_id ?? null;
203
+ const callee = p.callee_instance_id ?? p.agent_instance_id ?? 0;
204
+ const ts = this.parseTimestamp(p.timestamp);
205
+ const existing = this.files.get(fid);
206
+ const node = {
207
+ id: fid,
208
+ type: "file",
209
+ label: data.file_name ?? data.git_path ?? fileId,
210
+ timestamp: existing?.timestamp ?? ts,
211
+ metadata: {},
212
+ file_id: fileId,
213
+ file_name: data.file_name ?? "",
214
+ git_path: data.git_path ?? "",
215
+ operation: op,
216
+ agent_instance_id: callee,
217
+ tool_call_id: tcid,
218
+ task_purpose: this.extractString(p.ext, "task_purpose") ?? p.task_purpose ?? null
219
+ };
220
+ this.files.set(fid, node);
221
+ this.absorbPlaceholders(node);
222
+ const edgeType = op === "create" ? "produces" : op === "delete" ? "deletes" : "modifies";
223
+ if (tcid && this.actions.has(tcid)) {
224
+ this.addEdge(`action-${tcid}`, fid, edgeType, ts);
225
+ } else if (this.reasonings.size > 0) {
226
+ let latest = null;
227
+ for (const r of this.reasonings.values()) {
228
+ if (!latest || r.timestamp > latest.timestamp) latest = r;
229
+ }
230
+ if (latest) {
231
+ this.addEdge(latest.id, fid, edgeType, ts);
232
+ }
233
+ }
234
+ }
235
+ /** JETP-026: create references edges from xref scan result. */
236
+ handleXrefScanResult(p) {
237
+ const ext = p.ext;
238
+ if (!ext) return;
239
+ const sourceFileId = ext.source_file_id;
240
+ const xrefs = ext.xrefs;
241
+ if (!sourceFileId || !xrefs) return;
242
+ const ts = this.parseTimestamp(p.timestamp);
243
+ const sourceNodeId = `file-${sourceFileId}`;
244
+ for (const xref of xrefs) {
245
+ const targetFileId = xref.target_file_id;
246
+ const targetResource = xref.target_resource ?? "";
247
+ let targetNodeId;
248
+ if (targetFileId) {
249
+ targetNodeId = `file-${targetFileId}`;
250
+ if (!this.files.has(targetNodeId)) {
251
+ const resolved = this.resolveFileByName(targetFileId);
252
+ if (resolved) {
253
+ targetNodeId = resolved;
254
+ } else {
255
+ this.files.set(targetNodeId, {
256
+ id: targetNodeId,
257
+ type: "file",
258
+ label: targetFileId,
259
+ timestamp: ts,
260
+ metadata: { placeholder: true },
261
+ file_id: targetFileId,
262
+ file_name: "",
263
+ git_path: "",
264
+ operation: "referenced",
265
+ agent_instance_id: 0,
266
+ tool_call_id: null,
267
+ task_purpose: null
268
+ });
269
+ }
270
+ }
271
+ } else {
272
+ const hash = this.simpleHash(targetResource);
273
+ targetNodeId = `file-ext-url-${hash}`;
274
+ if (!this.files.has(targetNodeId)) {
275
+ const displayUrl = targetResource.replace("url://", "");
276
+ this.files.set(targetNodeId, {
277
+ id: targetNodeId,
278
+ type: "file",
279
+ label: displayUrl.length > 50 ? displayUrl.slice(0, 47) + "..." : displayUrl,
280
+ timestamp: ts,
281
+ metadata: { external: true },
282
+ file_id: `ext-url-${hash}`,
283
+ file_name: displayUrl,
284
+ git_path: targetResource,
285
+ operation: "external",
286
+ agent_instance_id: 0,
287
+ tool_call_id: null,
288
+ task_purpose: null
289
+ });
290
+ }
291
+ }
292
+ const description = xref.description ?? "";
293
+ this.addEdge(sourceNodeId, targetNodeId, "references", ts, description);
294
+ }
295
+ }
296
+ handlePlanResult(p) {
297
+ const checklist = this.extractChecklist(p.content);
298
+ if (!checklist) return;
299
+ for (const task of checklist) {
300
+ const tid = task.id ? `task-${task.id}` : `task-${task.description.slice(0, 20)}`;
301
+ const node = {
302
+ id: tid,
303
+ type: "task",
304
+ label: task.description,
305
+ timestamp: Date.now(),
306
+ metadata: {},
307
+ task_id: task.id ?? tid,
308
+ description: task.description,
309
+ status: task.status ?? "pending"
310
+ };
311
+ this.tasks.set(tid, node);
312
+ }
313
+ }
314
+ handleModelStart(p) {
315
+ const callee = p.callee_instance_id ?? p.agent_instance_id ?? 0;
316
+ const ts = this.parseTimestamp(p.timestamp);
317
+ const rid = `reasoning-${callee}-${ts}`;
318
+ const node = {
319
+ id: rid,
320
+ type: "reasoning",
321
+ label: this.extractString(p.ext, "model") ?? "reasoning",
322
+ timestamp: ts,
323
+ metadata: {},
324
+ agent_instance_id: callee,
325
+ model: this.extractString(p.ext, "model") ?? "",
326
+ content_preview: "",
327
+ token_count: null,
328
+ duration_ms: null,
329
+ status: "thinking"
330
+ };
331
+ this.reasonings.set(rid, node);
332
+ if (this.agents.has(callee)) {
333
+ this.addEdge(`agent-inst-${callee}`, rid, "thinks", ts);
334
+ }
335
+ const prevActions = this.lastCompletedActions.get(callee) ?? [];
336
+ for (const tcid of prevActions) {
337
+ this.addEdge(`action-${tcid}`, rid, "informs", ts);
338
+ }
339
+ this.lastCompletedActions.delete(callee);
340
+ }
341
+ handleModelResult(p) {
342
+ const callee = p.callee_instance_id ?? p.agent_instance_id ?? 0;
343
+ const latest = this.findLatestReasoning(callee);
344
+ if (!latest) return;
345
+ const preview = String(this.extractString(p.ext, "content_preview") ?? "").slice(0, 200);
346
+ this.reasonings.set(latest.id, {
347
+ ...latest,
348
+ status: "completed",
349
+ ...preview ? { content_preview: preview } : {}
350
+ });
351
+ }
352
+ handleModelEnd(p) {
353
+ const callee = p.callee_instance_id ?? p.agent_instance_id ?? 0;
354
+ const latest = this.findLatestReasoning(callee);
355
+ if (!latest) return;
356
+ const endTs = this.parseTimestamp(p.timestamp);
357
+ const duration = endTs - latest.timestamp;
358
+ const tokens = this.extractNumber(p.ext, "total_tokens");
359
+ this.reasonings.set(latest.id, {
360
+ ...latest,
361
+ status: "completed",
362
+ duration_ms: duration > 0 ? duration : null,
363
+ token_count: tokens
364
+ });
365
+ }
366
+ // -----------------------------------------------------------------------
367
+ // Helpers
368
+ // -----------------------------------------------------------------------
369
+ addEdge(source, target, type, ts, label) {
370
+ const key = this.edgeKey(source, target, type, label);
371
+ if (this.edgeKeys.has(key)) return;
372
+ this.edgeKeys.add(key);
373
+ this.edges.push({ source, target, type, label, timestamp: ts });
374
+ }
375
+ edgeKey(source, target, type, label) {
376
+ return label ? `${source}:${target}:${type}:${label}` : `${source}:${target}:${type}`;
377
+ }
378
+ findLatestReasoning(agentInstanceId) {
379
+ let latest = null;
380
+ for (const r of this.reasonings.values()) {
381
+ if (r.agent_instance_id === agentInstanceId) {
382
+ if (!latest || r.timestamp > latest.timestamp) latest = r;
383
+ }
384
+ }
385
+ return latest;
386
+ }
387
+ parseTimestamp(ts) {
388
+ if (ts == null) return Date.now();
389
+ if (typeof ts === "number") return ts;
390
+ const n = Date.parse(ts);
391
+ return Number.isNaN(n) ? Date.now() : n;
392
+ }
393
+ extractString(ext, key) {
394
+ if (!ext) return null;
395
+ const v = ext[key];
396
+ return typeof v === "string" ? v : null;
397
+ }
398
+ extractNumber(ext, key) {
399
+ if (!ext) return null;
400
+ const v = ext[key];
401
+ return typeof v === "number" ? v : null;
402
+ }
403
+ extractArtifactData(content) {
404
+ if (!content || !Array.isArray(content)) return null;
405
+ for (const part of content) {
406
+ const c = part?.content;
407
+ if (c?.data) return c.data;
408
+ }
409
+ return null;
410
+ }
411
+ normalizeOperation(op) {
412
+ const o = (op ?? "create").toLowerCase();
413
+ if (o === "create" || o === "update" || o === "delete" || o === "external" || o === "referenced") return o;
414
+ if (o.includes("create") || o.includes("add")) return "create";
415
+ if (o.includes("delete") || o.includes("remove")) return "delete";
416
+ return "update";
417
+ }
418
+ /** Merge placeholder file nodes into a real node, remapping all edges.
419
+ * Uses exact + stem matching (same ambiguity-safe logic as resolveFileByName). */
420
+ absorbPlaceholders(realNode) {
421
+ const names = /* @__PURE__ */ new Set();
422
+ if (realNode.file_name) {
423
+ names.add(realNode.file_name);
424
+ const fnStem = _GraphBuilder.fileStem(realNode.file_name);
425
+ if (fnStem !== realNode.file_name) names.add(fnStem);
426
+ }
427
+ if (realNode.git_path) {
428
+ names.add(realNode.git_path);
429
+ const gpStem = _GraphBuilder.fileStem(realNode.git_path);
430
+ if (gpStem !== realNode.git_path) names.add(gpStem);
431
+ if (realNode.git_path.includes("/")) {
432
+ const basename = realNode.git_path.split("/").pop();
433
+ names.add(basename);
434
+ const bnStem = _GraphBuilder.fileStem(basename);
435
+ if (bnStem !== basename) names.add(bnStem);
436
+ }
437
+ }
438
+ const stemHits = /* @__PURE__ */ new Map();
439
+ for (const [id, node] of this.files) {
440
+ if (id === realNode.id) continue;
441
+ if (!node.metadata?.placeholder) continue;
442
+ const matchKey = node.file_id || node.label;
443
+ if (names.has(matchKey)) {
444
+ stemHits.set(id, [...stemHits.get(id) ?? [], matchKey]);
445
+ }
446
+ }
447
+ const toRemove = [];
448
+ for (const [id] of stemHits) {
449
+ for (const edge of this.edges) {
450
+ if (edge.source === id) edge.source = realNode.id;
451
+ if (edge.target === id) edge.target = realNode.id;
452
+ }
453
+ toRemove.push(id);
454
+ }
455
+ for (const id of toRemove) this.files.delete(id);
456
+ }
457
+ /** Match a file name/path against existing FileNodes by git_path or file_name.
458
+ * Pass 1 — exact match. Pass 2 — stem match (extension stripped) when
459
+ * *name* has no extension and exactly one non-placeholder candidate exists. */
460
+ resolveFileByName(name) {
461
+ for (const node of this.files.values()) {
462
+ if (node.git_path === name || node.file_name === name) return node.id;
463
+ if (node.git_path && node.git_path.endsWith("/" + name)) return node.id;
464
+ }
465
+ if (_GraphBuilder.nameHasExtension(name)) return null;
466
+ const candidates = [];
467
+ for (const node of this.files.values()) {
468
+ if (node.operation === "referenced" || node.operation === "external") continue;
469
+ if (node.file_name && _GraphBuilder.fileStem(node.file_name) === name) {
470
+ candidates.push(node.id);
471
+ continue;
472
+ }
473
+ if (node.git_path) {
474
+ const gpStem = _GraphBuilder.fileStem(node.git_path);
475
+ if (gpStem === name || gpStem.endsWith("/" + name)) {
476
+ candidates.push(node.id);
477
+ continue;
478
+ }
479
+ }
480
+ }
481
+ const unique = [...new Set(candidates)];
482
+ return unique.length === 1 ? unique[0] : null;
483
+ }
484
+ /** Strip trailing extension: 'arch.md' → 'arch', 'a/b.txt' → 'a/b'. */
485
+ static fileStem(name) {
486
+ const slash = name.lastIndexOf("/");
487
+ const basename = name.slice(slash + 1);
488
+ const dot = basename.lastIndexOf(".");
489
+ if (dot > 0) return name.slice(0, slash + 1 + dot);
490
+ return name;
491
+ }
492
+ /** True when the trailing path component carries a dot-extension (ignores dotfiles). */
493
+ static nameHasExtension(name) {
494
+ const slash = name.lastIndexOf("/");
495
+ const basename = name.slice(slash + 1);
496
+ return basename.lastIndexOf(".") > 0;
497
+ }
498
+ simpleHash(s) {
499
+ let h = 0;
500
+ for (let i = 0; i < s.length; i++) {
501
+ h = (h << 5) - h + s.charCodeAt(i) | 0;
502
+ }
503
+ return Math.abs(h).toString(16).padStart(8, "0").slice(0, 8);
504
+ }
505
+ extractChecklist(content) {
506
+ if (!content || !Array.isArray(content)) return null;
507
+ for (const part of content) {
508
+ const typed = part;
509
+ if (typed.type === "checklist" && typed.content?.tasks) {
510
+ return typed.content.tasks;
511
+ }
512
+ }
513
+ return null;
514
+ }
515
+ };
516
+
517
+ // src/provenance/api.ts
518
+ async function fetchProvenanceGraph(fetcher, opts) {
519
+ const params = {};
520
+ if (opts.sinceTurnId != null && opts.sinceTurnId > 0) {
521
+ params.since_turn_id = opts.sinceTurnId;
522
+ }
523
+ if (opts.limit != null && opts.limit > 0) {
524
+ params.limit = opts.limit;
525
+ }
526
+ return fetcher(opts.sessionId, Object.keys(params).length > 0 ? params : void 0);
527
+ }
528
+
529
+ // src/provenance/hooks/useProvenanceGraph.ts
530
+ var EMPTY_SNAPSHOT = {
531
+ session_id: 0,
532
+ generated_at: 0,
533
+ node_count: 0,
534
+ edge_count: 0,
535
+ max_turn_id: null,
536
+ nodes: { agents: [], actions: [], files: [], tasks: [], reasonings: [] },
537
+ edges: [],
538
+ stats: { agent_count: 0, action_count: 0, file_count: 0, task_count: 0, reasoning_count: 0, max_depth: 0 }
539
+ };
540
+ var DEBOUNCE_MS = 80;
541
+ function useProvenanceGraph(opts) {
542
+ const { sessionId, fetcher, enabled = true, pollInterval = 3e3 } = opts;
543
+ const builderRef = useRef(new GraphBuilder());
544
+ const maxTurnIdRef = useRef(null);
545
+ const debounceTimerRef = useRef(null);
546
+ const mountedRef = useRef(true);
547
+ const [graph, setGraph] = useState(null);
548
+ const [loading, setLoading] = useState(false);
549
+ const [error, setError] = useState(null);
550
+ const flushSnapshot = useCallback(() => {
551
+ if (!mountedRef.current) return;
552
+ const snap = builderRef.current.getSnapshot();
553
+ setGraph(snap);
554
+ }, []);
555
+ const scheduleFlush = useCallback(() => {
556
+ if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
557
+ debounceTimerRef.current = setTimeout(flushSnapshot, DEBOUNCE_MS);
558
+ }, [flushSnapshot]);
559
+ const fetchSnapshot = useCallback(
560
+ async (sinceTurnId) => {
561
+ if (!sessionId || !fetcher) return;
562
+ setLoading(true);
563
+ setError(null);
564
+ try {
565
+ const snap = await fetchProvenanceGraph(fetcher, {
566
+ sessionId,
567
+ sinceTurnId
568
+ });
569
+ if (!mountedRef.current) return;
570
+ if (sinceTurnId != null && sinceTurnId > 0) {
571
+ builderRef.current.mergeSnapshot(snap);
572
+ } else {
573
+ builderRef.current.replaceWithSnapshot(snap);
574
+ }
575
+ maxTurnIdRef.current = snap.max_turn_id;
576
+ flushSnapshot();
577
+ } catch (e) {
578
+ if (!mountedRef.current) return;
579
+ const err = e instanceof Error ? e : new Error(String(e));
580
+ setError(err);
581
+ } finally {
582
+ if (mountedRef.current) setLoading(false);
583
+ }
584
+ },
585
+ [sessionId, fetcher, flushSnapshot]
586
+ );
587
+ const processEvent = useCallback(
588
+ (payload) => {
589
+ const handled = builderRef.current.applyEvent(payload);
590
+ if (handled) scheduleFlush();
591
+ },
592
+ [scheduleFlush]
593
+ );
594
+ const refetch = useCallback(() => {
595
+ fetchSnapshot(maxTurnIdRef.current);
596
+ }, [fetchSnapshot]);
597
+ useEffect(() => {
598
+ mountedRef.current = true;
599
+ if (sessionId && enabled) {
600
+ builderRef.current = new GraphBuilder();
601
+ maxTurnIdRef.current = null;
602
+ fetchSnapshot(null);
603
+ } else {
604
+ setGraph(sessionId ? null : EMPTY_SNAPSHOT);
605
+ }
606
+ return () => {
607
+ mountedRef.current = false;
608
+ if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
609
+ };
610
+ }, [sessionId, enabled, fetchSnapshot]);
611
+ useEffect(() => {
612
+ if (!sessionId || !enabled || pollInterval <= 0) return;
613
+ const id = setInterval(() => {
614
+ if (!mountedRef.current) return;
615
+ fetchSnapshot(maxTurnIdRef.current);
616
+ }, pollInterval);
617
+ return () => clearInterval(id);
618
+ }, [sessionId, enabled, pollInterval, fetchSnapshot]);
619
+ return { graph, loading, error, processEvent, refetch };
620
+ }
621
+
622
+ // src/provenance/components/ProvenanceGraph3D.tsx
623
+ import React, { useCallback as useCallback2, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
624
+ import ForceGraph3D from "react-force-graph-3d";
625
+ import * as THREE2 from "three";
626
+ import SpriteText from "three-spritetext";
627
+
628
+ // src/provenance/components/NodeMeshFactory.ts
629
+ import * as THREE from "three";
630
+
631
+ // src/provenance/constants.ts
632
+ var NODE_COLORS = {
633
+ agent: "#667eea",
634
+ action: "#f5a623",
635
+ file: "#50c878",
636
+ task: "#e74c8b",
637
+ reasoning: "#9b59b6"
638
+ };
639
+ var NODE_COLORS_HEX = {
640
+ agent: 6717162,
641
+ action: 16098851,
642
+ file: 5294200,
643
+ task: 15158411,
644
+ reasoning: 10181046
645
+ };
646
+ var NODE_EMISSIVE = {
647
+ agent: 3363008,
648
+ action: 12614160,
649
+ file: 2787912,
650
+ task: 12071024,
651
+ reasoning: 7223946
652
+ };
653
+ var NODE_SIZES = {
654
+ agent: 6,
655
+ action: 4.5,
656
+ file: 3.5,
657
+ task: 4,
658
+ reasoning: 5
659
+ };
660
+ var EDGE_COLORS = {
661
+ spawns: "#667eea",
662
+ executes: "#f5a623",
663
+ produces: "#50c878",
664
+ modifies: "#36a3d9",
665
+ deletes: "#e74c3c",
666
+ thinks: "#9b59b6",
667
+ triggers: "#e67e22",
668
+ informs: "#3498db",
669
+ references: "#8e44ad"
670
+ // JETP-026: purple for cross-references
671
+ };
672
+ var EDGE_WIDTHS = {
673
+ spawns: 3,
674
+ executes: 2,
675
+ produces: 1.8,
676
+ modifies: 1.8,
677
+ deletes: 1.5,
678
+ thinks: 2.5,
679
+ triggers: 2,
680
+ informs: 1.8,
681
+ references: 1
682
+ // JETP-026: thin for cross-references
683
+ };
684
+ var SCENE_BG = 16119802;
685
+ var CAMERA_DISTANCE = 400;
686
+ var CAMERA_FOCUS_DURATION_MS = 600;
687
+ var LINK_PARTICLE_COUNT = 2;
688
+ var LINK_PARTICLE_WIDTH = 3;
689
+ var LINK_PARTICLE_SPEED = 4e-3;
690
+ var DIM_OPACITY = 0.08;
691
+ var SELECTED_SCALE = 1.4;
692
+ var NORMAL_OPACITY = 0.9;
693
+ var LABEL_FONT_SIZE = 3;
694
+ var LABEL_COLOR = "#333";
695
+
696
+ // src/provenance/components/NodeMeshFactory.ts
697
+ var geoCache = /* @__PURE__ */ new Map();
698
+ function getOrCreateGeometry(type) {
699
+ const cached = geoCache.get(type);
700
+ if (cached) return cached;
701
+ let geo;
702
+ const s = NODE_SIZES[type];
703
+ switch (type) {
704
+ case "agent":
705
+ geo = new THREE.SphereGeometry(s, 24, 16);
706
+ break;
707
+ case "action":
708
+ geo = new THREE.IcosahedronGeometry(s, 0);
709
+ break;
710
+ case "file":
711
+ geo = new THREE.BoxGeometry(s * 1.2, s * 1.2, s * 1.2);
712
+ break;
713
+ case "task":
714
+ geo = new THREE.OctahedronGeometry(s, 0);
715
+ break;
716
+ case "reasoning":
717
+ geo = new THREE.TorusGeometry(s * 0.7, s * 0.3, 12, 24);
718
+ break;
719
+ default:
720
+ geo = new THREE.SphereGeometry(s, 16, 12);
721
+ }
722
+ geoCache.set(type, geo);
723
+ return geo;
724
+ }
725
+ function createNodeMesh(type, opts = {}) {
726
+ const geo = getOrCreateGeometry(type);
727
+ const mat = new THREE.MeshStandardMaterial({
728
+ color: NODE_COLORS_HEX[type],
729
+ emissive: NODE_EMISSIVE[type],
730
+ emissiveIntensity: opts.selected ? 0.6 : 0.25,
731
+ metalness: 0.15,
732
+ roughness: 0.55,
733
+ transparent: true,
734
+ opacity: opts.dimmed ? DIM_OPACITY : NORMAL_OPACITY
735
+ });
736
+ const mesh = new THREE.Mesh(geo, mat);
737
+ if (opts.selected) {
738
+ mesh.scale.setScalar(SELECTED_SCALE);
739
+ }
740
+ mesh.castShadow = false;
741
+ mesh.receiveShadow = false;
742
+ return mesh;
743
+ }
744
+ function disposeGeometryCache() {
745
+ for (const geo of geoCache.values()) {
746
+ geo.dispose();
747
+ }
748
+ geoCache.clear();
749
+ }
750
+
751
+ // src/provenance/components/ProvenanceGraph3D.tsx
752
+ import { jsx } from "react/jsx-runtime";
753
+ var HIDDEN_EDGE_TYPES = /* @__PURE__ */ new Set(["spawns", "executes", "thinks"]);
754
+ function flattenNodes(snap) {
755
+ const { actions, files, tasks, reasonings } = snap.nodes;
756
+ return [...actions, ...files, ...tasks, ...reasonings];
757
+ }
758
+ function getConnectedIds(nodeId, edges, hops) {
759
+ const visited = /* @__PURE__ */ new Set([nodeId]);
760
+ let frontier = /* @__PURE__ */ new Set([nodeId]);
761
+ for (let h = 0; h < hops; h++) {
762
+ const next = /* @__PURE__ */ new Set();
763
+ for (const e of edges) {
764
+ if (frontier.has(e.source) && !visited.has(e.target)) {
765
+ visited.add(e.target);
766
+ next.add(e.target);
767
+ }
768
+ if (frontier.has(e.target) && !visited.has(e.source)) {
769
+ visited.add(e.source);
770
+ next.add(e.source);
771
+ }
772
+ }
773
+ if (next.size === 0) break;
774
+ frontier = next;
775
+ }
776
+ return visited;
777
+ }
778
+ function truncate(s, maxLen) {
779
+ return s.length > maxLen ? s.slice(0, maxLen - 1) + "\u2026" : s;
780
+ }
781
+ function wrapText(s, lineWidth, maxLines) {
782
+ const clean = s.replace(/\n/g, " ").trim();
783
+ const lines = [];
784
+ let pos = 0;
785
+ for (let i = 0; i < maxLines && pos < clean.length; i++) {
786
+ const remaining = clean.slice(pos);
787
+ if (remaining.length <= lineWidth) {
788
+ lines.push(remaining);
789
+ break;
790
+ }
791
+ lines.push(remaining.slice(0, lineWidth));
792
+ pos += lineWidth;
793
+ }
794
+ const result = lines.join("\n");
795
+ return pos < clean.length ? result + "\u2026" : result;
796
+ }
797
+ var ProvenanceGraph3DInner = ({
798
+ graph,
799
+ width,
800
+ height,
801
+ selectedNodeId = null,
802
+ onNodeClick,
803
+ onNodeHover,
804
+ onBackgroundClick,
805
+ depthFilter
806
+ }) => {
807
+ const fgRef = useRef2(void 0);
808
+ const [hoveredNodeId, setHoveredNodeId] = useState2(null);
809
+ const graphData = useMemo(() => {
810
+ const allNodes = flattenNodes(graph);
811
+ const nodeMap = new Set(allNodes.map((n) => n.id));
812
+ const purposeByNodeId = /* @__PURE__ */ new Map();
813
+ for (const a of graph.nodes.actions) {
814
+ if (a.task_purpose) purposeByNodeId.set(a.id, a.task_purpose);
815
+ }
816
+ for (const f of graph.nodes.files) {
817
+ if (f.task_purpose) purposeByNodeId.set(f.id, f.task_purpose);
818
+ }
819
+ const previewById = /* @__PURE__ */ new Map();
820
+ for (const r of graph.nodes.reasonings) {
821
+ if (r.content_preview) previewById.set(r.id, r.content_preview);
822
+ }
823
+ const agentNameByInstId = /* @__PURE__ */ new Map();
824
+ for (const a of graph.nodes.agents) {
825
+ agentNameByInstId.set(a.agent_instance_id, a.agent_name || a.agent_id || `agent-${a.agent_instance_id}`);
826
+ }
827
+ const nodes = allNodes.map((n) => {
828
+ const instId = "agent_instance_id" in n ? n.agent_instance_id : void 0;
829
+ return {
830
+ id: n.id,
831
+ type: n.type,
832
+ label: n.label,
833
+ subtitle: purposeByNodeId.get(n.id),
834
+ contentPreview: previewById.get(n.id),
835
+ agentLabel: instId != null ? agentNameByInstId.get(instId) : void 0,
836
+ status: "status" in n ? n.status : void 0,
837
+ level: "level" in n ? n.level : void 0,
838
+ agentInstanceId: instId
839
+ };
840
+ });
841
+ const links = graph.edges.filter((e) => !HIDDEN_EDGE_TYPES.has(e.type) && nodeMap.has(e.source) && nodeMap.has(e.target)).map((e) => {
842
+ const purpose = purposeByNodeId.get(e.target) ?? purposeByNodeId.get(e.source) ?? void 0;
843
+ return {
844
+ source: e.source,
845
+ target: e.target,
846
+ type: e.type,
847
+ timestamp: e.timestamp,
848
+ label: e.label || purpose
849
+ };
850
+ });
851
+ return { nodes, links };
852
+ }, [graph]);
853
+ const connectedSet = useMemo(() => {
854
+ if (!selectedNodeId) return null;
855
+ return getConnectedIds(selectedNodeId, graph.edges, depthFilter ?? 2);
856
+ }, [selectedNodeId, graph.edges, depthFilter]);
857
+ const nodeThreeObject = useCallback2(
858
+ (node) => {
859
+ const isDimmed = connectedSet != null && !connectedSet.has(node.id);
860
+ const isSelected = node.id === selectedNodeId;
861
+ const isRunning = node.status === "running" || node.status === "thinking";
862
+ const group = new THREE2.Group();
863
+ if (node.type === "reasoning") {
864
+ const agentName = node.agentLabel ?? "Agent";
865
+ const bodyText = node.contentPreview ? wrapText(node.contentPreview, 28, 2) : isRunning ? "Thinking\u2026" : "";
866
+ const dimAlpha = isDimmed ? 0.15 : 1;
867
+ const header = new SpriteText(
868
+ (isRunning ? "\u25CF " : "") + agentName,
869
+ 2,
870
+ isDimmed ? "#bbb" : isRunning ? "#9b59b6" : "#667eea"
871
+ );
872
+ header.fontFace = "Inter, system-ui, sans-serif";
873
+ header.fontWeight = "600";
874
+ header.backgroundColor = "transparent";
875
+ header.padding = [0, 0];
876
+ header.position.y = bodyText ? 4.5 : 0;
877
+ header.material.opacity = dimAlpha;
878
+ group.add(header);
879
+ if (bodyText) {
880
+ const body = new SpriteText(
881
+ bodyText,
882
+ 2.2,
883
+ isDimmed ? "#ccc" : "#444"
884
+ );
885
+ body.fontFace = "Inter, system-ui, sans-serif";
886
+ body.backgroundColor = isDimmed ? "rgba(245,245,245,0.2)" : isSelected ? "rgba(102,126,234,0.08)" : "rgba(255,255,255,0.92)";
887
+ body.borderWidth = isSelected ? 1 : 0.4;
888
+ body.borderColor = isDimmed ? "transparent" : isSelected ? "rgba(102,126,234,0.5)" : "rgba(0,0,0,0.08)";
889
+ body.borderRadius = 3;
890
+ body.padding = [2.5, 4];
891
+ body.material.opacity = dimAlpha;
892
+ group.add(body);
893
+ }
894
+ return group;
895
+ }
896
+ const mesh = createNodeMesh(node.type, {
897
+ selected: isSelected,
898
+ dimmed: isDimmed,
899
+ running: isRunning
900
+ });
901
+ group.add(mesh);
902
+ const nodeRadius = NODE_SIZES[node.type] ?? 5;
903
+ if (isRunning && !isDimmed) {
904
+ const ringGeo = new THREE2.RingGeometry(nodeRadius * 1.6, nodeRadius * 1.8, 32);
905
+ const ringMat = new THREE2.MeshBasicMaterial({
906
+ color: NODE_COLORS[node.type] ?? "#667eea",
907
+ transparent: true,
908
+ opacity: 0.4,
909
+ side: THREE2.DoubleSide
910
+ });
911
+ const ring = new THREE2.Mesh(ringGeo, ringMat);
912
+ ring.rotation.x = Math.PI / 2;
913
+ ring.userData.__orbitRing = true;
914
+ group.add(ring);
915
+ }
916
+ if (!isDimmed) {
917
+ const nameLabel = new SpriteText(
918
+ truncate(node.label, 20),
919
+ LABEL_FONT_SIZE,
920
+ LABEL_COLOR
921
+ );
922
+ nameLabel.backgroundColor = "rgba(255,255,255,0.85)";
923
+ nameLabel.borderRadius = 2;
924
+ nameLabel.padding = [1, 3];
925
+ nameLabel.position.y = -(nodeRadius + 4);
926
+ group.add(nameLabel);
927
+ if (node.subtitle) {
928
+ const purposeLabel = new SpriteText(
929
+ truncate(node.subtitle, 16),
930
+ 2.2,
931
+ "#888"
932
+ );
933
+ purposeLabel.backgroundColor = "rgba(245,247,250,0.9)";
934
+ purposeLabel.borderRadius = 1.5;
935
+ purposeLabel.padding = [0.5, 2];
936
+ purposeLabel.position.y = -(nodeRadius + 8);
937
+ group.add(purposeLabel);
938
+ }
939
+ }
940
+ return group;
941
+ },
942
+ [connectedSet, selectedNodeId]
943
+ );
944
+ const CAUSAL_EDGES = /* @__PURE__ */ new Set(["triggers", "informs"]);
945
+ const linkColor = useCallback2(
946
+ (link) => {
947
+ const edgeColor = EDGE_COLORS[link.type] ?? "#c0c0c0";
948
+ if (connectedSet) {
949
+ const srcId = typeof link.source === "object" ? link.source.id : link.source;
950
+ const tgtId = typeof link.target === "object" ? link.target.id : link.target;
951
+ if (!connectedSet.has(srcId) || !connectedSet.has(tgtId)) {
952
+ return `rgba(200,200,200,${DIM_OPACITY})`;
953
+ }
954
+ }
955
+ return edgeColor;
956
+ },
957
+ [connectedSet]
958
+ );
959
+ const linkWidth = useCallback2(
960
+ (link) => {
961
+ const base = EDGE_WIDTHS[link.type] ?? 1.5;
962
+ return base * 1.2;
963
+ },
964
+ []
965
+ );
966
+ const linkCurvature = useCallback2(
967
+ (link) => {
968
+ if (link.type === "references") return 0.35;
969
+ if (link.type === "informs") return 0.3;
970
+ if (link.type === "triggers") return 0.2;
971
+ return 0.06;
972
+ },
973
+ []
974
+ );
975
+ const linkParticleSpeed = useCallback2(
976
+ (link) => {
977
+ if (CAUSAL_EDGES.has(link.type)) return 8e-3;
978
+ return LINK_PARTICLE_SPEED;
979
+ },
980
+ []
981
+ );
982
+ const linkArrowLength = useCallback2(
983
+ (link) => {
984
+ if (CAUSAL_EDGES.has(link.type)) return 7;
985
+ return 4;
986
+ },
987
+ []
988
+ );
989
+ const linkParticles = useCallback2(
990
+ (link) => {
991
+ if (link.type === "references") return 0;
992
+ if (connectedSet) {
993
+ const srcId = typeof link.source === "object" ? link.source.id : link.source;
994
+ const tgtId = typeof link.target === "object" ? link.target.id : link.target;
995
+ if (!connectedSet.has(srcId) || !connectedSet.has(tgtId)) return 0;
996
+ }
997
+ return LINK_PARTICLE_COUNT;
998
+ },
999
+ [connectedSet]
1000
+ );
1001
+ useEffect2(() => {
1002
+ if (!fgRef.current) return;
1003
+ const fg = fgRef.current;
1004
+ try {
1005
+ fg.d3Force("charge")?.strength(-420);
1006
+ fg.d3Force("link")?.distance((link) => {
1007
+ switch (link.type) {
1008
+ case "references":
1009
+ return 160;
1010
+ case "informs":
1011
+ return 120;
1012
+ case "triggers":
1013
+ return 100;
1014
+ case "spawns":
1015
+ return 140;
1016
+ default:
1017
+ return 80;
1018
+ }
1019
+ });
1020
+ fg.d3Force("center")?.strength(0.04);
1021
+ } catch {
1022
+ }
1023
+ }, [graphData]);
1024
+ useEffect2(() => {
1025
+ if (!selectedNodeId || !fgRef.current) return;
1026
+ const node = graphData.nodes.find((n) => n.id === selectedNodeId);
1027
+ if (!node || node.x == null || node.y == null || node.z == null) return;
1028
+ fgRef.current.cameraPosition(
1029
+ { x: node.x + CAMERA_DISTANCE * 0.3, y: node.y + CAMERA_DISTANCE * 0.2, z: node.z + CAMERA_DISTANCE * 0.5 },
1030
+ { x: node.x, y: node.y, z: node.z },
1031
+ CAMERA_FOCUS_DURATION_MS
1032
+ );
1033
+ }, [selectedNodeId, graphData.nodes]);
1034
+ useEffect2(() => {
1035
+ if (!fgRef.current) return;
1036
+ try {
1037
+ const scene = fgRef.current.scene();
1038
+ if (!scene) return;
1039
+ if (scene.userData.__lightsConfigured) return;
1040
+ scene.userData.__lightsConfigured = true;
1041
+ const ambient = new THREE2.AmbientLight(16777215, 0.7);
1042
+ const directional = new THREE2.DirectionalLight(16777215, 0.8);
1043
+ directional.position.set(100, 200, 150);
1044
+ scene.add(ambient, directional);
1045
+ scene.fog = new THREE2.Fog(SCENE_BG, 600, 1600);
1046
+ } catch {
1047
+ }
1048
+ });
1049
+ useEffect2(() => {
1050
+ let frameId;
1051
+ const animate = () => {
1052
+ if (!fgRef.current) return;
1053
+ try {
1054
+ const scene = fgRef.current.scene();
1055
+ if (!scene) return;
1056
+ scene.traverse((obj) => {
1057
+ if (obj.userData.__orbitRing) {
1058
+ obj.rotation.z += 0.02;
1059
+ }
1060
+ });
1061
+ } catch {
1062
+ }
1063
+ frameId = requestAnimationFrame(animate);
1064
+ };
1065
+ frameId = requestAnimationFrame(animate);
1066
+ return () => cancelAnimationFrame(frameId);
1067
+ }, []);
1068
+ useEffect2(() => {
1069
+ return () => {
1070
+ disposeGeometryCache();
1071
+ };
1072
+ }, []);
1073
+ const handleNodeClick = useCallback2(
1074
+ (node) => {
1075
+ onNodeClick?.(node.id, node.type);
1076
+ },
1077
+ [onNodeClick]
1078
+ );
1079
+ const handleNodeHover = useCallback2(
1080
+ (node) => {
1081
+ const id = node?.id ?? null;
1082
+ setHoveredNodeId(id);
1083
+ onNodeHover?.(id);
1084
+ },
1085
+ [onNodeHover]
1086
+ );
1087
+ const handleBgClick = useCallback2(() => {
1088
+ onBackgroundClick?.();
1089
+ }, [onBackgroundClick]);
1090
+ const EDGE_TYPE_LABELS = {
1091
+ spawns: "Spawns",
1092
+ executes: "Executes",
1093
+ produces: "Produces",
1094
+ modifies: "Modifies",
1095
+ deletes: "Deletes",
1096
+ thinks: "Thinks",
1097
+ triggers: "Triggers",
1098
+ informs: "Informs",
1099
+ references: "\u5F15\u7528"
1100
+ // JETP-026
1101
+ };
1102
+ const linkLabel = useCallback2(
1103
+ (link) => {
1104
+ const typeLabel = EDGE_TYPE_LABELS[link.type] ?? link.type;
1105
+ const color = EDGE_COLORS[link.type] ?? "#888";
1106
+ const lines = [
1107
+ `<span style="color:${color};font-weight:600">${typeLabel}</span>`
1108
+ ];
1109
+ if (link.label) lines.push(link.label);
1110
+ return `<div style="background:rgba(255,255,255,0.96);color:#444;padding:8px 12px;border-radius:6px;font-size:13px;line-height:1.6;box-shadow:0 2px 8px rgba(0,0,0,0.12);max-width:320px;word-break:break-all;border:1px solid #e8e8e8">${lines.join("<br/>")}</div>`;
1111
+ },
1112
+ []
1113
+ );
1114
+ const nodeLabel = useCallback2(
1115
+ (node) => {
1116
+ if (hoveredNodeId !== node.id) return "";
1117
+ const lines = [];
1118
+ if (node.type === "reasoning") {
1119
+ lines.push(`<b>${node.agentLabel ?? "Reasoning"}</b>`);
1120
+ if (node.status) lines.push(`Status: ${node.status}`);
1121
+ if (node.contentPreview) {
1122
+ const preview = node.contentPreview.length > 120 ? node.contentPreview.slice(0, 120) + "\u2026" : node.contentPreview;
1123
+ lines.push(`<span style="color:#666">${preview}</span>`);
1124
+ }
1125
+ } else {
1126
+ lines.push(`<b>${node.label}</b>`);
1127
+ if (node.subtitle) lines.push(`Purpose: ${node.subtitle}`);
1128
+ if (node.status) lines.push(`Status: ${node.status}`);
1129
+ }
1130
+ return `<div style="background:rgba(255,255,255,0.96);color:#333;padding:8px 12px;border-radius:6px;font-size:13px;line-height:1.6;box-shadow:0 2px 8px rgba(0,0,0,0.15);border:1px solid #e8e8e8;max-width:360px;word-break:break-all">${lines.join("<br/>")}</div>`;
1131
+ },
1132
+ [hoveredNodeId]
1133
+ );
1134
+ return /* @__PURE__ */ jsx(
1135
+ ForceGraph3D,
1136
+ {
1137
+ ref: fgRef,
1138
+ graphData,
1139
+ width,
1140
+ height,
1141
+ backgroundColor: `#${SCENE_BG.toString(16).padStart(6, "0")}`,
1142
+ showNavInfo: false,
1143
+ nodeThreeObject,
1144
+ nodeLabel,
1145
+ linkLabel,
1146
+ linkColor,
1147
+ linkWidth,
1148
+ linkOpacity: 0.75,
1149
+ linkCurvature,
1150
+ linkDirectionalParticles: linkParticles,
1151
+ linkDirectionalParticleWidth: LINK_PARTICLE_WIDTH,
1152
+ linkDirectionalParticleSpeed: linkParticleSpeed,
1153
+ linkDirectionalParticleColor: linkColor,
1154
+ linkDirectionalArrowLength: linkArrowLength,
1155
+ linkDirectionalArrowRelPos: 0.92,
1156
+ linkDirectionalArrowColor: linkColor,
1157
+ d3AlphaDecay: 0.04,
1158
+ d3VelocityDecay: 0.4,
1159
+ warmupTicks: 200,
1160
+ cooldownTicks: 0,
1161
+ cooldownTime: 3e3,
1162
+ onNodeClick: handleNodeClick,
1163
+ onNodeHover: handleNodeHover,
1164
+ onBackgroundClick: handleBgClick,
1165
+ enableNodeDrag: true,
1166
+ enableNavigationControls: true
1167
+ }
1168
+ );
1169
+ };
1170
+ var ProvenanceGraph3D = React.memo(ProvenanceGraph3DInner);
1171
+
1172
+ // src/provenance/components/GraphStatsLegend.tsx
1173
+ import React2, { useMemo as useMemo2 } from "react";
1174
+ import { Badge, Space } from "antd";
1175
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
1176
+ var LEGEND_LABEL_KEYS = {
1177
+ reasoning: "provenance.legend.reasoning",
1178
+ action: "provenance.legend.action",
1179
+ file: "provenance.legend.file",
1180
+ task: "provenance.legend.task"
1181
+ };
1182
+ var LEGEND_TYPES = ["reasoning", "action", "file", "task"];
1183
+ var statKey = (type) => {
1184
+ const map = {
1185
+ agent: "agent_count",
1186
+ action: "action_count",
1187
+ file: "file_count",
1188
+ task: "task_count",
1189
+ reasoning: "reasoning_count"
1190
+ };
1191
+ return map[type];
1192
+ };
1193
+ var GraphStatsLegendInner = ({
1194
+ stats,
1195
+ edgeCount
1196
+ }) => {
1197
+ const { t } = useChatWidgetI18n();
1198
+ const legendItems = useMemo2(
1199
+ () => LEGEND_TYPES.map((type) => ({ type, label: t(LEGEND_LABEL_KEYS[type]) })),
1200
+ [t]
1201
+ );
1202
+ return /* @__PURE__ */ jsx2(
1203
+ "div",
1204
+ {
1205
+ style: {
1206
+ position: "absolute",
1207
+ left: 16,
1208
+ bottom: 16,
1209
+ zIndex: 10,
1210
+ background: "rgba(255,255,255,0.92)",
1211
+ backdropFilter: "blur(8px)",
1212
+ padding: "10px 14px",
1213
+ borderRadius: 10,
1214
+ boxShadow: "0 1px 6px rgba(0,0,0,0.08)",
1215
+ fontSize: 12,
1216
+ lineHeight: 1.8
1217
+ },
1218
+ children: /* @__PURE__ */ jsxs(Space, { orientation: "vertical", size: 2, children: [
1219
+ legendItems.map(({ type, label }) => {
1220
+ const count = stats[statKey(type)];
1221
+ if (count === 0) return null;
1222
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
1223
+ /* @__PURE__ */ jsx2(Badge, { color: NODE_COLORS[type] }),
1224
+ /* @__PURE__ */ jsx2("span", { style: { color: "#555" }, children: label }),
1225
+ /* @__PURE__ */ jsx2("span", { style: { fontWeight: 600, color: "#333" }, children: count })
1226
+ ] }, type);
1227
+ }),
1228
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
1229
+ /* @__PURE__ */ jsx2(
1230
+ "div",
1231
+ {
1232
+ style: {
1233
+ width: 14,
1234
+ height: 0,
1235
+ borderTop: `2px dashed ${EDGE_COLORS.references}`
1236
+ }
1237
+ }
1238
+ ),
1239
+ /* @__PURE__ */ jsx2("span", { style: { color: "#555" }, children: t("provenance.legend.references") })
1240
+ ] }),
1241
+ /* @__PURE__ */ jsxs("div", { style: { borderTop: "1px solid #eee", paddingTop: 4, marginTop: 2 }, children: [
1242
+ /* @__PURE__ */ jsx2("span", { style: { color: "#999" }, children: t("provenance.legend.edges") }),
1243
+ /* @__PURE__ */ jsx2("span", { style: { fontWeight: 600, color: "#333" }, children: edgeCount }),
1244
+ /* @__PURE__ */ jsxs("span", { style: { color: "#999", marginLeft: 8 }, children: [
1245
+ t("provenance.legend.depth"),
1246
+ stats.max_depth
1247
+ ] })
1248
+ ] })
1249
+ ] })
1250
+ }
1251
+ );
1252
+ };
1253
+ var GraphStatsLegend = React2.memo(GraphStatsLegendInner);
1254
+
1255
+ // src/provenance/components/NodeDetailPanel.tsx
1256
+ import React3, { useMemo as useMemo3 } from "react";
1257
+ import { Drawer, Descriptions, Tag, Typography, Space as Space2, Badge as Badge2 } from "antd";
1258
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1259
+ var { Text, Paragraph } = Typography;
1260
+ var TYPE_LABEL_KEYS = {
1261
+ agent: "provenance.filter.type.agent",
1262
+ action: "provenance.filter.type.action",
1263
+ file: "provenance.filter.type.file",
1264
+ task: "provenance.filter.type.task",
1265
+ reasoning: "provenance.filter.type.reasoning"
1266
+ };
1267
+ function findNode(graph, nodeId) {
1268
+ const all = [
1269
+ ...graph.nodes.agents,
1270
+ ...graph.nodes.actions,
1271
+ ...graph.nodes.files,
1272
+ ...graph.nodes.tasks,
1273
+ ...graph.nodes.reasonings
1274
+ ];
1275
+ return all.find((n) => n.id === nodeId) ?? null;
1276
+ }
1277
+ function formatTimestamp(ts) {
1278
+ return new Date(ts).toLocaleString("zh-CN");
1279
+ }
1280
+ function formatDuration(ms) {
1281
+ if (ms == null) return "-";
1282
+ if (ms < 1e3) return `${ms}ms`;
1283
+ return `${(ms / 1e3).toFixed(1)}s`;
1284
+ }
1285
+ var NodeDetailPanelInner = ({
1286
+ open,
1287
+ nodeId,
1288
+ graph,
1289
+ onClose
1290
+ }) => {
1291
+ const { t } = useChatWidgetI18n();
1292
+ const node = useMemo3(() => nodeId ? findNode(graph, nodeId) : null, [graph, nodeId]);
1293
+ const relatedEdges = useMemo3(() => {
1294
+ if (!nodeId) return [];
1295
+ return graph.edges.filter((e) => e.source === nodeId || e.target === nodeId);
1296
+ }, [graph.edges, nodeId]);
1297
+ if (!node) {
1298
+ return /* @__PURE__ */ jsx3(Drawer, { title: t("provenance.detail.title"), open, onClose, width: 360, children: /* @__PURE__ */ jsx3(Text, { type: "secondary", children: t("provenance.detail.noSelection") }) });
1299
+ }
1300
+ return /* @__PURE__ */ jsxs2(
1301
+ Drawer,
1302
+ {
1303
+ title: /* @__PURE__ */ jsxs2(Space2, { children: [
1304
+ /* @__PURE__ */ jsx3(Badge2, { color: NODE_COLORS[node.type] }),
1305
+ /* @__PURE__ */ jsx3("span", { children: t(TYPE_LABEL_KEYS[node.type]) }),
1306
+ /* @__PURE__ */ jsx3(Tag, { children: node.type })
1307
+ ] }),
1308
+ open,
1309
+ onClose,
1310
+ width: 380,
1311
+ destroyOnHidden: true,
1312
+ children: [
1313
+ /* @__PURE__ */ jsxs2(Descriptions, { column: 1, size: "small", bordered: true, children: [
1314
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.name"), children: /* @__PURE__ */ jsx3(Text, { strong: true, children: node.label }) }),
1315
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.id"), children: /* @__PURE__ */ jsx3(Text, { code: true, copyable: true, children: node.id }) }),
1316
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.timestamp"), children: formatTimestamp(node.timestamp) }),
1317
+ node.type === "agent" && /* @__PURE__ */ jsxs2(Fragment, { children: [
1318
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.agentId"), children: /* @__PURE__ */ jsx3(Text, { code: true, children: node.agent_id }) }),
1319
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.instanceId"), children: node.agent_instance_id }),
1320
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.level"), children: node.level }),
1321
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.status"), children: /* @__PURE__ */ jsx3(Tag, { color: node.status === "running" ? "processing" : node.status === "completed" ? "success" : "error", children: node.status ?? "unknown" }) })
1322
+ ] }),
1323
+ node.type === "action" && /* @__PURE__ */ jsxs2(Fragment, { children: [
1324
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.tool"), children: node.tool_name }),
1325
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.purpose"), children: node.task_purpose || "-" }),
1326
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.status"), children: /* @__PURE__ */ jsx3(Tag, { color: node.status === "success" ? "success" : node.status === "error" ? "error" : "processing", children: node.status }) }),
1327
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.duration"), children: formatDuration(node.duration_ms) })
1328
+ ] }),
1329
+ node.type === "file" && /* @__PURE__ */ jsxs2(Fragment, { children: [
1330
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.path"), children: /* @__PURE__ */ jsx3(Text, { code: true, children: node.git_path }) }),
1331
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.operation"), children: /* @__PURE__ */ jsx3(Tag, { color: node.operation === "create" ? "green" : node.operation === "delete" ? "red" : "blue", children: node.operation }) })
1332
+ ] }),
1333
+ node.type === "task" && /* @__PURE__ */ jsxs2(Fragment, { children: [
1334
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.taskId"), children: node.task_id }),
1335
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.status"), children: /* @__PURE__ */ jsx3(Tag, { children: node.status }) }),
1336
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.description"), children: /* @__PURE__ */ jsx3(Paragraph, { ellipsis: { rows: 3 }, children: node.description }) })
1337
+ ] }),
1338
+ node.type === "reasoning" && /* @__PURE__ */ jsxs2(Fragment, { children: [
1339
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.model"), children: node.model || "-" }),
1340
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.tokenCount"), children: node.token_count ?? "-" }),
1341
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.duration"), children: formatDuration(node.duration_ms) }),
1342
+ /* @__PURE__ */ jsx3(Descriptions.Item, { label: t("provenance.detail.label.reasoningPreview"), children: /* @__PURE__ */ jsx3(Paragraph, { ellipsis: { rows: 5 }, children: node.content_preview || "(empty)" }) })
1343
+ ] })
1344
+ ] }),
1345
+ relatedEdges.length > 0 && /* @__PURE__ */ jsxs2(Fragment, { children: [
1346
+ /* @__PURE__ */ jsx3(Text, { strong: true, style: { display: "block", marginTop: 16, marginBottom: 8 }, children: t("provenance.detail.relatedEdges", { count: relatedEdges.length }) }),
1347
+ /* @__PURE__ */ jsx3(Space2, { orientation: "vertical", size: 4, style: { width: "100%" }, children: relatedEdges.slice(0, 20).map((e, i) => /* @__PURE__ */ jsxs2("div", { style: { fontSize: 12, color: "#666" }, children: [
1348
+ /* @__PURE__ */ jsx3(Tag, { color: "default", style: { fontSize: 11 }, children: e.type }),
1349
+ e.source === nodeId ? `\u2192 ${e.target}` : `\u2190 ${e.source}`
1350
+ ] }, i)) })
1351
+ ] })
1352
+ ]
1353
+ }
1354
+ );
1355
+ };
1356
+ var NodeDetailPanel = React3.memo(NodeDetailPanelInner);
1357
+
1358
+ // src/provenance/ProvenanceGraphDrawerContent.tsx
1359
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1360
+ function useContainerSize(ref) {
1361
+ const [size, setSize] = React4.useState({ width: 600, height: 500 });
1362
+ React4.useEffect(() => {
1363
+ if (!ref.current) return;
1364
+ const observer = new ResizeObserver((entries) => {
1365
+ for (const entry of entries) {
1366
+ const { width, height } = entry.contentRect;
1367
+ setSize({ width: Math.floor(width), height: Math.floor(height) });
1368
+ }
1369
+ });
1370
+ observer.observe(ref.current);
1371
+ return () => observer.disconnect();
1372
+ }, [ref]);
1373
+ return size;
1374
+ }
1375
+ var ProvenanceGraphDrawerContent = ({
1376
+ sessionId,
1377
+ onProcessEventRef
1378
+ }) => {
1379
+ const { t } = useChatWidgetI18n();
1380
+ const containerRef = useRef3(null);
1381
+ const { width, height } = useContainerSize(containerRef);
1382
+ const { graph, loading, error, processEvent } = useProvenanceGraph({
1383
+ sessionId,
1384
+ enabled: true
1385
+ });
1386
+ useEffect3(() => {
1387
+ if (onProcessEventRef) {
1388
+ onProcessEventRef.current = processEvent;
1389
+ }
1390
+ return () => {
1391
+ if (onProcessEventRef) {
1392
+ onProcessEventRef.current = null;
1393
+ }
1394
+ };
1395
+ }, [onProcessEventRef, processEvent]);
1396
+ const [selectedNodeId, setSelectedNodeId] = useState3(null);
1397
+ const [detailOpen, setDetailOpen] = useState3(false);
1398
+ const handleNodeClick = useCallback3((nodeId, _nodeType) => {
1399
+ setSelectedNodeId(nodeId);
1400
+ setDetailOpen(true);
1401
+ }, []);
1402
+ const handleBackgroundClick = useCallback3(() => {
1403
+ setSelectedNodeId(null);
1404
+ setDetailOpen(false);
1405
+ }, []);
1406
+ return /* @__PURE__ */ jsxs3(
1407
+ "div",
1408
+ {
1409
+ ref: containerRef,
1410
+ style: { width: "100%", height: "100%", minHeight: 400, position: "relative" },
1411
+ children: [
1412
+ loading && !graph && /* @__PURE__ */ jsx4("div", { className: "ycw-provenance-loading", children: /* @__PURE__ */ jsx4(Spin, { size: "large" }) }),
1413
+ error && !graph && /* @__PURE__ */ jsx4("div", { className: "ycw-provenance-empty", children: /* @__PURE__ */ jsx4("span", { style: { color: "#ff4d4f" }, children: error.message }) }),
1414
+ graph && graph.node_count === 0 && !loading && /* @__PURE__ */ jsx4(Empty, { description: t("provenance.drawer.emptyGraph"), style: { paddingTop: 80 } }),
1415
+ graph && graph.node_count > 0 && /* @__PURE__ */ jsxs3(Fragment2, { children: [
1416
+ /* @__PURE__ */ jsx4(
1417
+ ProvenanceGraph3D,
1418
+ {
1419
+ graph,
1420
+ width,
1421
+ height,
1422
+ selectedNodeId,
1423
+ onNodeClick: handleNodeClick,
1424
+ onBackgroundClick: handleBackgroundClick
1425
+ }
1426
+ ),
1427
+ /* @__PURE__ */ jsx4(GraphStatsLegend, { stats: graph.stats, edgeCount: graph.edges.length })
1428
+ ] }),
1429
+ graph && /* @__PURE__ */ jsx4(
1430
+ NodeDetailPanel,
1431
+ {
1432
+ open: detailOpen,
1433
+ nodeId: selectedNodeId,
1434
+ graph,
1435
+ onClose: () => {
1436
+ setDetailOpen(false);
1437
+ setSelectedNodeId(null);
1438
+ }
1439
+ }
1440
+ )
1441
+ ]
1442
+ }
1443
+ );
1444
+ };
1445
+ var ProvenanceGraphDrawerContent_default = ProvenanceGraphDrawerContent;
1446
+
1447
+ // src/provenance/ProvenanceI18nProvider.tsx
1448
+ import React5 from "react";
1449
+ import { jsx as jsx5 } from "react/jsx-runtime";
1450
+ var ProvenanceI18nProvider = ({
1451
+ locale = "zh-CN",
1452
+ messages: overrides,
1453
+ children
1454
+ }) => {
1455
+ const resolved = React5.useMemo(
1456
+ () => resolveMessages(locale, overrides),
1457
+ [locale, overrides]
1458
+ );
1459
+ return /* @__PURE__ */ jsx5(I18nContext.Provider, { value: resolved, children });
1460
+ };
1461
+
1462
+ // src/provenance/components/FilterBar.tsx
1463
+ import React6, { useCallback as useCallback4, useMemo as useMemo4 } from "react";
1464
+ import { Checkbox, Slider, Space as Space3, Typography as Typography2, Badge as Badge3 } from "antd";
1465
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1466
+ var { Text: Text2 } = Typography2;
1467
+ var ALL_TYPES = ["agent", "action", "file", "task", "reasoning"];
1468
+ var TYPE_LABEL_KEYS2 = {
1469
+ agent: "provenance.filter.type.agent",
1470
+ action: "provenance.filter.type.action",
1471
+ file: "provenance.filter.type.file",
1472
+ task: "provenance.filter.type.task",
1473
+ reasoning: "provenance.filter.type.reasoning"
1474
+ };
1475
+ var FilterBarInner = ({
1476
+ visibleTypes,
1477
+ onVisibleTypesChange,
1478
+ depth,
1479
+ onDepthChange,
1480
+ hasSelection
1481
+ }) => {
1482
+ const { t } = useChatWidgetI18n();
1483
+ const typeOptions = useMemo4(() => ALL_TYPES.map((tp) => ({
1484
+ label: /* @__PURE__ */ jsxs4(Space3, { size: 4, children: [
1485
+ /* @__PURE__ */ jsx6(Badge3, { color: NODE_COLORS[tp] }),
1486
+ /* @__PURE__ */ jsx6("span", { children: t(TYPE_LABEL_KEYS2[tp]) })
1487
+ ] }),
1488
+ value: tp
1489
+ })), [t]);
1490
+ const handleTypeChange = useCallback4(
1491
+ (values) => {
1492
+ onVisibleTypesChange(new Set(values));
1493
+ },
1494
+ [onVisibleTypesChange]
1495
+ );
1496
+ return /* @__PURE__ */ jsxs4(
1497
+ "div",
1498
+ {
1499
+ style: {
1500
+ display: "flex",
1501
+ alignItems: "center",
1502
+ gap: 16,
1503
+ padding: "8px 16px",
1504
+ background: "rgba(255,255,255,0.92)",
1505
+ backdropFilter: "blur(8px)",
1506
+ borderBottom: "1px solid #eee"
1507
+ },
1508
+ children: [
1509
+ /* @__PURE__ */ jsx6(
1510
+ Checkbox.Group,
1511
+ {
1512
+ options: typeOptions,
1513
+ value: Array.from(visibleTypes),
1514
+ onChange: handleTypeChange
1515
+ }
1516
+ ),
1517
+ hasSelection && /* @__PURE__ */ jsxs4(Space3, { size: 8, style: { marginLeft: "auto" }, children: [
1518
+ /* @__PURE__ */ jsx6(Text2, { type: "secondary", style: { fontSize: 12 }, children: t("provenance.filter.depth") }),
1519
+ /* @__PURE__ */ jsx6(
1520
+ Slider,
1521
+ {
1522
+ min: 1,
1523
+ max: 5,
1524
+ value: depth,
1525
+ onChange: onDepthChange,
1526
+ style: { width: 100 },
1527
+ tooltip: { open: false }
1528
+ }
1529
+ ),
1530
+ /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 12, minWidth: 20 }, children: depth })
1531
+ ] })
1532
+ ]
1533
+ }
1534
+ );
1535
+ };
1536
+ var FilterBar = React6.memo(FilterBarInner);
1537
+
1538
+ // src/provenance/components/GraphToolbar.tsx
1539
+ import React7 from "react";
1540
+ import { Button, Space as Space4, Tooltip } from "antd";
1541
+ import {
1542
+ ExpandOutlined,
1543
+ ReloadOutlined,
1544
+ AimOutlined
1545
+ } from "@ant-design/icons";
1546
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1547
+ var GraphToolbarInner = ({
1548
+ onZoomToFit,
1549
+ onResetCamera,
1550
+ onRefresh,
1551
+ loading
1552
+ }) => {
1553
+ const { t } = useChatWidgetI18n();
1554
+ return /* @__PURE__ */ jsx7(
1555
+ "div",
1556
+ {
1557
+ style: {
1558
+ position: "absolute",
1559
+ right: 16,
1560
+ top: 16,
1561
+ zIndex: 10,
1562
+ display: "flex",
1563
+ flexDirection: "column",
1564
+ gap: 4
1565
+ },
1566
+ children: /* @__PURE__ */ jsxs5(Space4, { orientation: "vertical", size: 4, children: [
1567
+ /* @__PURE__ */ jsx7(Tooltip, { title: t("provenance.toolbar.zoomToFit"), placement: "left", children: /* @__PURE__ */ jsx7(
1568
+ Button,
1569
+ {
1570
+ type: "text",
1571
+ icon: /* @__PURE__ */ jsx7(ExpandOutlined, {}),
1572
+ onClick: onZoomToFit,
1573
+ style: toolBtnStyle
1574
+ }
1575
+ ) }),
1576
+ /* @__PURE__ */ jsx7(Tooltip, { title: t("provenance.toolbar.resetCamera"), placement: "left", children: /* @__PURE__ */ jsx7(
1577
+ Button,
1578
+ {
1579
+ type: "text",
1580
+ icon: /* @__PURE__ */ jsx7(AimOutlined, {}),
1581
+ onClick: onResetCamera,
1582
+ style: toolBtnStyle
1583
+ }
1584
+ ) }),
1585
+ /* @__PURE__ */ jsx7(Tooltip, { title: t("provenance.toolbar.refresh"), placement: "left", children: /* @__PURE__ */ jsx7(
1586
+ Button,
1587
+ {
1588
+ type: "text",
1589
+ icon: /* @__PURE__ */ jsx7(ReloadOutlined, {}),
1590
+ onClick: onRefresh,
1591
+ loading,
1592
+ style: toolBtnStyle
1593
+ }
1594
+ ) })
1595
+ ] })
1596
+ }
1597
+ );
1598
+ };
1599
+ var toolBtnStyle = {
1600
+ background: "rgba(255,255,255,0.92)",
1601
+ backdropFilter: "blur(8px)",
1602
+ boxShadow: "0 1px 4px rgba(0,0,0,0.08)",
1603
+ borderRadius: 8,
1604
+ width: 36,
1605
+ height: 36
1606
+ };
1607
+ var GraphToolbar = React7.memo(GraphToolbarInner);
1608
+
1609
+ export {
1610
+ GraphBuilder,
1611
+ fetchProvenanceGraph,
1612
+ useProvenanceGraph,
1613
+ ProvenanceGraph3D,
1614
+ GraphStatsLegend,
1615
+ NodeDetailPanel,
1616
+ ProvenanceGraphDrawerContent_default,
1617
+ ProvenanceI18nProvider,
1618
+ FilterBar,
1619
+ GraphToolbar
1620
+ };
1621
+ //# sourceMappingURL=chunk-KQV7IKET.js.map