@bpmnkit/plugins 0.0.8

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 (140) hide show
  1. package/README.md +153 -0
  2. package/dist/ai-bridge/css.d.ts +2 -0
  3. package/dist/ai-bridge/css.js +366 -0
  4. package/dist/ai-bridge/index.d.ts +44 -0
  5. package/dist/ai-bridge/index.js +132 -0
  6. package/dist/ai-bridge/panel.d.ts +27 -0
  7. package/dist/ai-bridge/panel.js +715 -0
  8. package/dist/ascii-view/css.d.ts +4 -0
  9. package/dist/ascii-view/css.js +83 -0
  10. package/dist/ascii-view/index.d.ts +25 -0
  11. package/dist/ascii-view/index.js +92 -0
  12. package/dist/command-palette/css.d.ts +4 -0
  13. package/dist/command-palette/css.js +157 -0
  14. package/dist/command-palette/index.d.ts +53 -0
  15. package/dist/command-palette/index.js +378 -0
  16. package/dist/command-palette-editor/index.d.ts +35 -0
  17. package/dist/command-palette-editor/index.js +60 -0
  18. package/dist/config-panel/css.d.ts +4 -0
  19. package/dist/config-panel/css.js +719 -0
  20. package/dist/config-panel/index.d.ts +36 -0
  21. package/dist/config-panel/index.js +73 -0
  22. package/dist/config-panel/renderer.d.ts +127 -0
  23. package/dist/config-panel/renderer.js +1162 -0
  24. package/dist/config-panel/types.d.ts +118 -0
  25. package/dist/config-panel/types.js +2 -0
  26. package/dist/config-panel-bpmn/index.d.ts +58 -0
  27. package/dist/config-panel-bpmn/index.js +1320 -0
  28. package/dist/config-panel-bpmn/template-engine.d.ts +17 -0
  29. package/dist/config-panel-bpmn/template-engine.js +307 -0
  30. package/dist/config-panel-bpmn/template-to-service-task.d.ts +30 -0
  31. package/dist/config-panel-bpmn/template-to-service-task.js +92 -0
  32. package/dist/config-panel-bpmn/template-types.d.ts +126 -0
  33. package/dist/config-panel-bpmn/template-types.js +6 -0
  34. package/dist/config-panel-bpmn/templates/generated.d.ts +3 -0
  35. package/dist/config-panel-bpmn/templates/generated.js +57865 -0
  36. package/dist/config-panel-bpmn/util.d.ts +46 -0
  37. package/dist/config-panel-bpmn/util.js +195 -0
  38. package/dist/dmn-editor/css.d.ts +3 -0
  39. package/dist/dmn-editor/css.js +667 -0
  40. package/dist/dmn-editor/dmn-editor.d.ts +34 -0
  41. package/dist/dmn-editor/dmn-editor.js +646 -0
  42. package/dist/dmn-editor/drd-canvas.d.ts +85 -0
  43. package/dist/dmn-editor/drd-canvas.js +1087 -0
  44. package/dist/dmn-editor/index.d.ts +3 -0
  45. package/dist/dmn-editor/index.js +2 -0
  46. package/dist/dmn-viewer/css.d.ts +3 -0
  47. package/dist/dmn-viewer/css.js +151 -0
  48. package/dist/dmn-viewer/dmn-viewer.d.ts +30 -0
  49. package/dist/dmn-viewer/dmn-viewer.js +160 -0
  50. package/dist/dmn-viewer/feel.d.ts +3 -0
  51. package/dist/dmn-viewer/feel.js +2 -0
  52. package/dist/dmn-viewer/index.d.ts +14 -0
  53. package/dist/dmn-viewer/index.js +12 -0
  54. package/dist/element-docs/content.d.ts +20 -0
  55. package/dist/element-docs/content.js +1032 -0
  56. package/dist/element-docs/index.d.ts +7 -0
  57. package/dist/element-docs/index.js +643 -0
  58. package/dist/feel-playground/css.d.ts +4 -0
  59. package/dist/feel-playground/css.js +292 -0
  60. package/dist/feel-playground/index.d.ts +4 -0
  61. package/dist/feel-playground/index.js +3 -0
  62. package/dist/feel-playground/playground-plugin.d.ts +14 -0
  63. package/dist/feel-playground/playground-plugin.js +292 -0
  64. package/dist/form-editor/css.d.ts +3 -0
  65. package/dist/form-editor/css.js +703 -0
  66. package/dist/form-editor/form-editor.d.ts +48 -0
  67. package/dist/form-editor/form-editor.js +1039 -0
  68. package/dist/form-editor/index.d.ts +3 -0
  69. package/dist/form-editor/index.js +2 -0
  70. package/dist/form-viewer/css.d.ts +3 -0
  71. package/dist/form-viewer/css.js +303 -0
  72. package/dist/form-viewer/form-viewer.d.ts +29 -0
  73. package/dist/form-viewer/form-viewer.js +376 -0
  74. package/dist/form-viewer/index.d.ts +12 -0
  75. package/dist/form-viewer/index.js +11 -0
  76. package/dist/history/checkpoint.d.ts +10 -0
  77. package/dist/history/checkpoint.js +92 -0
  78. package/dist/history/css.d.ts +2 -0
  79. package/dist/history/css.js +130 -0
  80. package/dist/history/history-panel.d.ts +12 -0
  81. package/dist/history/history-panel.js +142 -0
  82. package/dist/history/index.d.ts +5 -0
  83. package/dist/history/index.js +3 -0
  84. package/dist/main-menu/css.d.ts +4 -0
  85. package/dist/main-menu/css.js +272 -0
  86. package/dist/main-menu/index.d.ts +59 -0
  87. package/dist/main-menu/index.js +285 -0
  88. package/dist/minimap/css.d.ts +10 -0
  89. package/dist/minimap/css.js +54 -0
  90. package/dist/minimap/index.d.ts +38 -0
  91. package/dist/minimap/index.js +69 -0
  92. package/dist/minimap/minimap.d.ts +35 -0
  93. package/dist/minimap/minimap.js +166 -0
  94. package/dist/optimize/index.d.ts +21 -0
  95. package/dist/optimize/index.js +304 -0
  96. package/dist/process-runner/css.d.ts +2 -0
  97. package/dist/process-runner/css.js +467 -0
  98. package/dist/process-runner/index.d.ts +56 -0
  99. package/dist/process-runner/index.js +549 -0
  100. package/dist/storage/auto-save.d.ts +11 -0
  101. package/dist/storage/auto-save.js +44 -0
  102. package/dist/storage/css.d.ts +2 -0
  103. package/dist/storage/css.js +2 -0
  104. package/dist/storage/db.d.ts +99 -0
  105. package/dist/storage/db.js +116 -0
  106. package/dist/storage/dialog.d.ts +15 -0
  107. package/dist/storage/dialog.js +201 -0
  108. package/dist/storage/export.d.ts +3 -0
  109. package/dist/storage/export.js +137 -0
  110. package/dist/storage/index.d.ts +44 -0
  111. package/dist/storage/index.js +201 -0
  112. package/dist/storage/sidebar.d.ts +2 -0
  113. package/dist/storage/sidebar.js +2 -0
  114. package/dist/storage/storage-api.d.ts +76 -0
  115. package/dist/storage/storage-api.js +342 -0
  116. package/dist/storage/types.d.ts +44 -0
  117. package/dist/storage/types.js +2 -0
  118. package/dist/storage-tabs-bridge/index.d.ts +96 -0
  119. package/dist/storage-tabs-bridge/index.js +502 -0
  120. package/dist/tabs/css.d.ts +3 -0
  121. package/dist/tabs/css.js +642 -0
  122. package/dist/tabs/file-resolver.d.ts +71 -0
  123. package/dist/tabs/file-resolver.js +46 -0
  124. package/dist/tabs/index.d.ts +15 -0
  125. package/dist/tabs/index.js +13 -0
  126. package/dist/tabs/tabs-plugin.d.ts +250 -0
  127. package/dist/tabs/tabs-plugin.js +1119 -0
  128. package/dist/token-highlight/css.d.ts +2 -0
  129. package/dist/token-highlight/css.js +81 -0
  130. package/dist/token-highlight/index.d.ts +44 -0
  131. package/dist/token-highlight/index.js +164 -0
  132. package/dist/watermark/css.d.ts +10 -0
  133. package/dist/watermark/css.js +54 -0
  134. package/dist/watermark/index.d.ts +48 -0
  135. package/dist/watermark/index.js +64 -0
  136. package/dist/zoom-controls/css.d.ts +4 -0
  137. package/dist/zoom-controls/css.js +48 -0
  138. package/dist/zoom-controls/index.d.ts +36 -0
  139. package/dist/zoom-controls/index.js +111 -0
  140. package/package.json +121 -0
@@ -0,0 +1,715 @@
1
+ import { BpmnCanvas } from "@bpmnkit/canvas";
2
+ import { Bpmn, Dmn, Form, compactify } from "@bpmnkit/core";
3
+ import { saveCheckpoint } from "../history/index.js";
4
+ import { injectAiBridgeStyles } from "./css.js";
5
+ const DEFAULT_SERVER = "http://localhost:3033";
6
+ // ── SSE streaming ─────────────────────────────────────────────────────────────
7
+ async function* streamChat(serverUrl, messages, context, backend, signal, action, onXml) {
8
+ let res;
9
+ try {
10
+ res = await fetch(`${serverUrl}/chat`, {
11
+ method: "POST",
12
+ headers: { "Content-Type": "application/json" },
13
+ body: JSON.stringify({
14
+ messages,
15
+ context,
16
+ backend: backend === "auto" ? null : backend,
17
+ action: action ?? null,
18
+ }),
19
+ signal,
20
+ });
21
+ }
22
+ catch (err) {
23
+ if (err instanceof Error && err.name === "AbortError")
24
+ return;
25
+ throw err;
26
+ }
27
+ if (!res.ok || !res.body)
28
+ throw new Error(`Server returned ${res.status}`);
29
+ const reader = res.body.getReader();
30
+ const decoder = new TextDecoder();
31
+ let buf = "";
32
+ try {
33
+ while (!signal.aborted) {
34
+ const { done, value } = await reader.read();
35
+ if (done || signal.aborted)
36
+ break;
37
+ buf += decoder.decode(value, { stream: true });
38
+ const parts = buf.split("\n\n");
39
+ buf = parts.pop() ?? "";
40
+ for (const part of parts) {
41
+ const line = part.startsWith("data: ") ? part.slice(6) : part;
42
+ const trimmed = line.trim();
43
+ if (!trimmed)
44
+ continue;
45
+ try {
46
+ const event = JSON.parse(trimmed);
47
+ if (event.type === "token" && event.text)
48
+ yield event.text;
49
+ if (event.type === "xml" && event.xml)
50
+ onXml?.(event.xml);
51
+ if (event.type === "done")
52
+ return;
53
+ if (event.type === "error")
54
+ throw new Error(event.message ?? event.text ?? "AI error");
55
+ }
56
+ catch (e) {
57
+ if (e instanceof SyntaxError)
58
+ continue;
59
+ throw e;
60
+ }
61
+ }
62
+ }
63
+ }
64
+ finally {
65
+ reader.releaseLock();
66
+ }
67
+ }
68
+ // ── Approval-request detection ────────────────────────────────────────────────
69
+ const APPROVAL_PATTERNS = [
70
+ /\bapprove\b/i,
71
+ /\bapproval\b/i,
72
+ /\bready to apply\b/i,
73
+ /\bonce you confirm\b/i,
74
+ /\bshall I\b/i,
75
+ /\bwould you like me to\b/i,
76
+ /\bwant me to\b/i,
77
+ /\bproceed\?/i,
78
+ /\bgo ahead\?/i,
79
+ /\bconfirm\?/i,
80
+ /ready\?/i,
81
+ /\bgrant permission\b/i,
82
+ /\bneed permission\b/i,
83
+ /\bplease (?:grant|give|allow)\b/i,
84
+ /\bonce you (?:grant|give|allow)\b/i,
85
+ ];
86
+ function looksLikeApprovalRequest(text) {
87
+ return APPROVAL_PATTERNS.some((re) => re.test(text));
88
+ }
89
+ // ── Minimal markdown renderer ─────────────────────────────────────────────────
90
+ // Safe: all text goes through textContent / createTextNode — no innerHTML.
91
+ function renderInline(text, container) {
92
+ const regex = /\*\*(.+?)\*\*|\*(.+?)\*|`([^`]+)`/g;
93
+ let last = 0;
94
+ let match;
95
+ // biome-ignore lint/suspicious/noAssignInExpressions: standard regex loop pattern
96
+ while ((match = regex.exec(text)) !== null) {
97
+ if (match.index > last) {
98
+ container.append(document.createTextNode(text.slice(last, match.index)));
99
+ }
100
+ if (match[1] !== undefined) {
101
+ const el = document.createElement("strong");
102
+ el.textContent = match[1];
103
+ container.append(el);
104
+ }
105
+ else if (match[2] !== undefined) {
106
+ const el = document.createElement("em");
107
+ el.textContent = match[2];
108
+ container.append(el);
109
+ }
110
+ else if (match[3] !== undefined) {
111
+ const el = document.createElement("code");
112
+ el.className = "ai-md-code";
113
+ el.textContent = match[3];
114
+ container.append(el);
115
+ }
116
+ last = match.index + match[0].length;
117
+ }
118
+ if (last < text.length) {
119
+ container.append(document.createTextNode(text.slice(last)));
120
+ }
121
+ }
122
+ function renderMarkdown(text) {
123
+ const frag = document.createDocumentFragment();
124
+ // Split on fenced code blocks first so their content is never processed
125
+ const parts = text.split(/(```[\s\S]*?```)/);
126
+ for (const part of parts) {
127
+ if (part.startsWith("```")) {
128
+ const firstNl = part.indexOf("\n");
129
+ const pre = document.createElement("pre");
130
+ pre.className = "ai-msg-code";
131
+ pre.textContent = firstNl !== -1 ? part.slice(firstNl + 1, -3) : part.slice(3, -3);
132
+ frag.append(pre);
133
+ continue;
134
+ }
135
+ // Split by paragraph breaks (one or more blank lines)
136
+ const blocks = part.split(/\n{2,}/);
137
+ for (const block of blocks) {
138
+ const trimmed = block.trim();
139
+ if (!trimmed)
140
+ continue;
141
+ const lines = trimmed.split("\n");
142
+ // Heading (single line starting with #)
143
+ const headingMatch = /^(#{1,3}) +(.+)/.exec(lines[0] ?? "");
144
+ if (lines.length === 1 && headingMatch) {
145
+ const level = (headingMatch[1]?.length ?? 1);
146
+ const h = document.createElement(`h${level}`);
147
+ h.className = "ai-md-h";
148
+ renderInline(headingMatch[2]?.trim() ?? "", h);
149
+ frag.append(h);
150
+ continue;
151
+ }
152
+ // List block — all lines match bullet or ordered marker
153
+ if (lines.every((l) => /^[-*] /.test(l) || /^\d+\. /.test(l))) {
154
+ const isOrdered = /^\d+\. /.test(lines[0] ?? "");
155
+ const list = document.createElement(isOrdered ? "ol" : "ul");
156
+ list.className = "ai-md-list";
157
+ for (const line of lines) {
158
+ if (!line.trim())
159
+ continue;
160
+ const li = document.createElement("li");
161
+ renderInline(line.replace(/^[-*] /, "").replace(/^\d+\. /, ""), li);
162
+ list.append(li);
163
+ }
164
+ frag.append(list);
165
+ continue;
166
+ }
167
+ // Regular paragraph
168
+ const p = document.createElement("p");
169
+ p.className = "ai-md-p";
170
+ renderInline(lines.join("\n"), p);
171
+ frag.append(p);
172
+ }
173
+ }
174
+ return frag;
175
+ }
176
+ // ── Panel ─────────────────────────────────────────────────────────────────────
177
+ const NEW_ICON = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M8 3H4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V8"/><path d="M12 2l2 2-6 6H6v-2l6-6z"/></svg>`;
178
+ const EXAMPLE_PROMPTS = [
179
+ "Create a loan approval process with review and decision steps",
180
+ "Add error handling and boundary events to all service tasks",
181
+ "Explain what this process does in plain language",
182
+ "Optimize and simplify this diagram — remove redundant elements",
183
+ ];
184
+ export function createAiPanel(options) {
185
+ injectAiBridgeStyles();
186
+ const panel = document.createElement("div");
187
+ panel.className = "ai-panel";
188
+ // ── Header ──
189
+ const header = document.createElement("div");
190
+ header.className = "ai-panel-header";
191
+ const titleEl = document.createElement("span");
192
+ titleEl.className = "ai-panel-title";
193
+ titleEl.textContent = "AI Assistant";
194
+ const actions = document.createElement("div");
195
+ actions.className = "ai-panel-header-actions";
196
+ const backendSelect = document.createElement("select");
197
+ backendSelect.className = "ai-backend-select";
198
+ for (const [value, label] of [
199
+ ["auto", "Auto"],
200
+ ["claude", "Claude"],
201
+ ["copilot", "Copilot"],
202
+ ["gemini", "Gemini"],
203
+ ]) {
204
+ const opt = document.createElement("option");
205
+ opt.value = value;
206
+ opt.textContent = label;
207
+ backendSelect.append(opt);
208
+ }
209
+ backendSelect.value = localStorage.getItem("bpmnkit-ai-backend") ?? "auto";
210
+ backendSelect.addEventListener("change", () => {
211
+ localStorage.setItem("bpmnkit-ai-backend", backendSelect.value);
212
+ });
213
+ const clearBtn = document.createElement("button");
214
+ clearBtn.className = "ai-hdr-btn";
215
+ clearBtn.title = "New conversation";
216
+ clearBtn.innerHTML = NEW_ICON;
217
+ clearBtn.addEventListener("click", clearConversation);
218
+ const closeBtn = document.createElement("button");
219
+ closeBtn.className = "ai-hdr-btn";
220
+ closeBtn.title = "Close";
221
+ closeBtn.textContent = "×";
222
+ closeBtn.addEventListener("click", () => close());
223
+ actions.append(backendSelect, clearBtn, closeBtn);
224
+ header.append(titleEl, actions);
225
+ // ── Status bar ──
226
+ const statusBar = document.createElement("div");
227
+ statusBar.className = "ai-panel-status";
228
+ const statusEl = document.createElement("span");
229
+ statusBar.append(statusEl);
230
+ // ── Messages ──
231
+ const messagesEl = document.createElement("div");
232
+ messagesEl.className = "ai-messages";
233
+ // Welcome state (shown when no messages yet)
234
+ const welcomeEl = document.createElement("div");
235
+ welcomeEl.className = "ai-welcome";
236
+ const welcomeTitle = document.createElement("div");
237
+ welcomeTitle.className = "ai-welcome-title";
238
+ welcomeTitle.textContent = "BPMN AI Assistant";
239
+ const welcomeSub = document.createElement("div");
240
+ welcomeSub.className = "ai-welcome-sub";
241
+ welcomeSub.textContent = "Ask me to create, modify, or explain your diagram";
242
+ const examplesEl = document.createElement("div");
243
+ examplesEl.className = "ai-welcome-examples";
244
+ for (const prompt of EXAMPLE_PROMPTS) {
245
+ const btn = document.createElement("button");
246
+ btn.className = "ai-welcome-example";
247
+ btn.textContent = prompt;
248
+ btn.addEventListener("click", () => {
249
+ textarea.value = prompt;
250
+ autoGrow();
251
+ textarea.focus();
252
+ });
253
+ examplesEl.append(btn);
254
+ }
255
+ welcomeEl.append(welcomeTitle, welcomeSub, examplesEl);
256
+ messagesEl.append(welcomeEl);
257
+ // ── Quick actions ──
258
+ const quickActions = document.createElement("div");
259
+ quickActions.className = "ai-quick-actions";
260
+ const improveBtn = document.createElement("button");
261
+ improveBtn.className = "ai-quick-btn";
262
+ improveBtn.textContent = "✦ Improve";
263
+ improveBtn.title = "Analyze and improve the current diagram";
264
+ const explainBtn = document.createElement("button");
265
+ explainBtn.className = "ai-quick-btn";
266
+ explainBtn.textContent = "Explain diagram";
267
+ explainBtn.title = "Explain what this diagram does";
268
+ const explainElementBtn = document.createElement("button");
269
+ explainElementBtn.className = "ai-quick-btn";
270
+ explainElementBtn.textContent = "Explain element";
271
+ explainElementBtn.title = "Select a node in the diagram to explain it";
272
+ explainElementBtn.disabled = true;
273
+ quickActions.append(improveBtn, explainBtn, explainElementBtn);
274
+ // ── Context references (badge strip shown above input when a node is referenced) ──
275
+ const contextRefsEl = document.createElement("div");
276
+ contextRefsEl.className = "ai-context-refs";
277
+ contextRefsEl.style.display = "none";
278
+ // ── Input area ──
279
+ const inputArea = document.createElement("div");
280
+ inputArea.className = "ai-input-area";
281
+ const textarea = document.createElement("textarea");
282
+ textarea.className = "ai-textarea";
283
+ textarea.placeholder = "Ask AI to create or modify the diagram…";
284
+ textarea.rows = 2;
285
+ const stopBtn = document.createElement("button");
286
+ stopBtn.className = "ai-stop-btn";
287
+ stopBtn.textContent = "Stop";
288
+ stopBtn.style.display = "none";
289
+ stopBtn.addEventListener("click", () => _abortCtrl?.abort());
290
+ const sendBtn = document.createElement("button");
291
+ sendBtn.className = "ai-send-btn";
292
+ sendBtn.textContent = "Send";
293
+ inputArea.append(textarea, stopBtn, sendBtn);
294
+ // ── Input hint ──
295
+ const inputHint = document.createElement("div");
296
+ inputHint.className = "ai-input-hint";
297
+ inputHint.textContent = "Enter to send · Shift+Enter for new line";
298
+ panel.append(header, statusBar, messagesEl, quickActions, contextRefsEl, inputArea, inputHint);
299
+ // ── State ──
300
+ const history = [];
301
+ const _previewCanvases = [];
302
+ let sending = false;
303
+ let _refs = [];
304
+ let _abortCtrl = null;
305
+ let _hasMessages = false;
306
+ // ── Server status check ──
307
+ async function checkStatus() {
308
+ statusBar.className = "ai-panel-status";
309
+ statusEl.textContent = "Checking server…";
310
+ try {
311
+ const res = await fetch(`${options.serverUrl}/status`, { signal: AbortSignal.timeout(3000) });
312
+ const data = (await res.json());
313
+ if (data.ready) {
314
+ statusBar.className = "ai-panel-status ai-panel-status-ok";
315
+ statusEl.textContent = `Connected · ${data.available.join(", ")} available`;
316
+ }
317
+ else {
318
+ showNotRunning();
319
+ }
320
+ }
321
+ catch {
322
+ showNotRunning();
323
+ }
324
+ }
325
+ function showNotRunning() {
326
+ statusBar.className = "ai-panel-status ai-panel-status-err";
327
+ statusEl.textContent = "AI server not running. Start with:";
328
+ for (const el of Array.from(statusBar.querySelectorAll("code")))
329
+ el.remove();
330
+ const code = document.createElement("code");
331
+ code.textContent = "pnpx @bpmnkit/ai-server";
332
+ code.title = "or: npx @bpmnkit/ai-server";
333
+ statusBar.append(code);
334
+ }
335
+ // ── UI busy state ──
336
+ function setUiBusy(busy) {
337
+ sending = busy;
338
+ sendBtn.style.display = busy ? "none" : "";
339
+ stopBtn.style.display = busy ? "" : "none";
340
+ improveBtn.disabled = busy;
341
+ explainBtn.disabled = busy;
342
+ explainElementBtn.disabled = busy || _refs.length === 0;
343
+ }
344
+ // ── Auto-grow textarea ──
345
+ function autoGrow() {
346
+ textarea.style.height = "auto";
347
+ textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`;
348
+ }
349
+ textarea.addEventListener("input", autoGrow);
350
+ // ── Context references ──
351
+ function setContext(node) {
352
+ // Replace unpinned refs; pinned ones survive selection changes
353
+ _refs = _refs.filter((r) => r.pinned);
354
+ if (node && !_refs.some((r) => r.node.id === node.id)) {
355
+ _refs.push({ node, pinned: false });
356
+ }
357
+ renderContextRefs();
358
+ if (!sending) {
359
+ explainElementBtn.disabled = _refs.length === 0;
360
+ }
361
+ }
362
+ function renderContextRefs() {
363
+ contextRefsEl.innerHTML = "";
364
+ const singleRef = _refs.length === 1 ? _refs[0] : null;
365
+ explainElementBtn.textContent = singleRef?.node.name
366
+ ? `Explain "${singleRef.node.name}"`
367
+ : singleRef
368
+ ? `Explain ${singleRef.node.type}`
369
+ : "Explain element";
370
+ explainElementBtn.title = singleRef
371
+ ? `Explain the selected ${singleRef.node.type}`
372
+ : "Select a node in the diagram to explain it";
373
+ if (_refs.length === 0) {
374
+ contextRefsEl.style.display = "none";
375
+ return;
376
+ }
377
+ contextRefsEl.style.display = "";
378
+ for (const ref of _refs) {
379
+ const badge = document.createElement("div");
380
+ badge.className = ref.pinned
381
+ ? "ai-context-badge ai-context-badge--pinned"
382
+ : "ai-context-badge";
383
+ badge.title = ref.pinned ? `id: ${ref.node.id}` : `Double-click to pin · id: ${ref.node.id}`;
384
+ const label = document.createElement("span");
385
+ label.className = "ai-context-badge__label";
386
+ label.textContent = ref.node.name ? `${ref.node.name} (${ref.node.type})` : ref.node.type;
387
+ badge.addEventListener("dblclick", () => {
388
+ ref.pinned = true;
389
+ renderContextRefs();
390
+ });
391
+ const removeBtn = document.createElement("button");
392
+ removeBtn.className = "ai-context-badge__remove";
393
+ removeBtn.textContent = "×";
394
+ removeBtn.title = "Remove reference";
395
+ removeBtn.addEventListener("click", () => {
396
+ _refs = _refs.filter((r) => r !== ref);
397
+ renderContextRefs();
398
+ if (!sending)
399
+ explainElementBtn.disabled = _refs.length === 0;
400
+ });
401
+ badge.append(label, removeBtn);
402
+ contextRefsEl.appendChild(badge);
403
+ }
404
+ }
405
+ // ── Message rendering ──
406
+ function ensureMessagesVisible() {
407
+ if (!_hasMessages) {
408
+ _hasMessages = true;
409
+ welcomeEl.style.display = "none";
410
+ }
411
+ }
412
+ function addMessage(role, content, context) {
413
+ ensureMessagesVisible();
414
+ const msg = document.createElement("div");
415
+ msg.className = role === "user" ? "ai-msg ai-msg-user" : "ai-msg ai-msg-ai";
416
+ if (role === "user" && context && context.length > 0) {
417
+ const chipsEl = document.createElement("div");
418
+ chipsEl.className = "ai-msg-chips";
419
+ for (const node of context) {
420
+ const chip = document.createElement("div");
421
+ chip.className = "ai-msg-context-chip";
422
+ chip.textContent = node.name ? `${node.name} (${node.type})` : node.type;
423
+ chip.title = `id: ${node.id}`;
424
+ chipsEl.append(chip);
425
+ }
426
+ msg.append(chipsEl);
427
+ const textEl = document.createElement("div");
428
+ textEl.textContent = content;
429
+ msg.append(textEl);
430
+ }
431
+ else {
432
+ msg.textContent = content;
433
+ }
434
+ messagesEl.append(msg);
435
+ messagesEl.scrollTop = messagesEl.scrollHeight;
436
+ return msg;
437
+ }
438
+ // ── Companion file detection ──────────────────────────────────────────────
439
+ function showCompanionOffer(msgEl, xml, createFn) {
440
+ let defs;
441
+ try {
442
+ defs = Bpmn.parse(xml);
443
+ }
444
+ catch {
445
+ return;
446
+ }
447
+ const compact = compactify(defs);
448
+ const dmnRefs = [];
449
+ const formRefs = [];
450
+ const seenDecisions = new Set();
451
+ const seenForms = new Set();
452
+ for (const proc of compact.processes) {
453
+ for (const el of proc.elements) {
454
+ if (el.decisionId && !seenDecisions.has(el.decisionId)) {
455
+ seenDecisions.add(el.decisionId);
456
+ dmnRefs.push({ decisionId: el.decisionId, taskName: el.name });
457
+ }
458
+ if (el.formId && !seenForms.has(el.formId)) {
459
+ seenForms.add(el.formId);
460
+ formRefs.push({ formId: el.formId, taskName: el.name });
461
+ }
462
+ }
463
+ }
464
+ if (dmnRefs.length === 0 && formRefs.length === 0)
465
+ return;
466
+ const offer = document.createElement("div");
467
+ offer.className = "ai-companion-offer";
468
+ const title = document.createElement("div");
469
+ title.className = "ai-companion-title";
470
+ title.textContent = "This diagram references external files:";
471
+ offer.append(title);
472
+ function makeRow(label, onCreate) {
473
+ const row = document.createElement("div");
474
+ row.className = "ai-companion-row";
475
+ const labelEl = document.createElement("span");
476
+ labelEl.textContent = label;
477
+ const createBtn = document.createElement("button");
478
+ createBtn.className = "ai-companion-create";
479
+ createBtn.textContent = "Create";
480
+ createBtn.addEventListener("click", async () => {
481
+ createBtn.disabled = true;
482
+ createBtn.textContent = "Creating…";
483
+ try {
484
+ await onCreate();
485
+ createBtn.textContent = "Created ✓";
486
+ }
487
+ catch (err) {
488
+ createBtn.disabled = false;
489
+ createBtn.textContent = `Failed: ${String(err)}`;
490
+ }
491
+ });
492
+ row.append(labelEl, createBtn);
493
+ offer.append(row);
494
+ }
495
+ for (const ref of dmnRefs) {
496
+ const fileName = `${ref.decisionId}.dmn`;
497
+ const label = ref.taskName ? `${ref.taskName} → ${fileName}` : fileName;
498
+ makeRow(`📊 ${label}`, async () => {
499
+ const dmnDefs = Dmn.createDecisionTable(ref.decisionId)
500
+ .name(ref.taskName ?? ref.decisionId)
501
+ .build();
502
+ const content = Dmn.export(dmnDefs);
503
+ await createFn(fileName, "dmn", content);
504
+ });
505
+ }
506
+ for (const ref of formRefs) {
507
+ const fileName = `${ref.formId}.form`;
508
+ const label = ref.taskName ? `${ref.taskName} → ${fileName}` : fileName;
509
+ makeRow(`📝 ${label}`, async () => {
510
+ const formDef = Form.makeEmpty(ref.formId);
511
+ const content = Form.export(formDef);
512
+ await createFn(fileName, "form", content);
513
+ });
514
+ }
515
+ msgEl.append(offer);
516
+ messagesEl.scrollTop = messagesEl.scrollHeight;
517
+ }
518
+ function finalizeAiMessage(msgEl, fullText, directXml) {
519
+ msgEl.classList.remove("ai-msg-cursor");
520
+ while (msgEl.firstChild)
521
+ msgEl.removeChild(msgEl.firstChild);
522
+ // Render as markdown
523
+ msgEl.append(renderMarkdown(fullText));
524
+ // Diagram preview + apply button when XML is available
525
+ if (directXml !== undefined) {
526
+ const previewEl = document.createElement("div");
527
+ previewEl.className = "ai-msg-preview";
528
+ const canvas = new BpmnCanvas({
529
+ container: previewEl,
530
+ xml: directXml,
531
+ grid: false,
532
+ fit: "contain",
533
+ theme: options.getTheme?.() ?? "dark",
534
+ });
535
+ _previewCanvases.push(canvas);
536
+ msgEl.append(previewEl);
537
+ }
538
+ // Action row: copy + optional apply
539
+ const actionRow = document.createElement("div");
540
+ actionRow.className = "ai-msg-actions";
541
+ const copyBtn = document.createElement("button");
542
+ copyBtn.className = "ai-msg-copy";
543
+ copyBtn.textContent = "Copy";
544
+ copyBtn.addEventListener("click", () => {
545
+ void navigator.clipboard
546
+ .writeText(fullText)
547
+ .then(() => {
548
+ copyBtn.textContent = "Copied!";
549
+ setTimeout(() => {
550
+ copyBtn.textContent = "Copy";
551
+ }, 2000);
552
+ })
553
+ .catch(() => {
554
+ copyBtn.textContent = "Failed";
555
+ setTimeout(() => {
556
+ copyBtn.textContent = "Copy";
557
+ }, 2000);
558
+ });
559
+ });
560
+ actionRow.append(copyBtn);
561
+ if (directXml !== undefined) {
562
+ const applyBtn = document.createElement("button");
563
+ applyBtn.className = "ai-msg-apply";
564
+ applyBtn.textContent = "Apply to diagram";
565
+ applyBtn.addEventListener("click", async () => {
566
+ applyBtn.disabled = true;
567
+ applyBtn.textContent = "Applying…";
568
+ try {
569
+ const currentDefs = options.getDefinitions();
570
+ if (currentDefs) {
571
+ const ctx = options.getCurrentContext?.();
572
+ if (ctx) {
573
+ await saveCheckpoint(ctx.projectId, ctx.fileId, Bpmn.export(currentDefs));
574
+ }
575
+ }
576
+ options.loadXml(directXml);
577
+ applyBtn.textContent = "Applied ✓";
578
+ // Detect companion file references and offer to create them
579
+ if (options.createCompanionFile) {
580
+ showCompanionOffer(msgEl, directXml, options.createCompanionFile);
581
+ }
582
+ }
583
+ catch (err) {
584
+ applyBtn.disabled = false;
585
+ applyBtn.textContent = `Apply failed: ${String(err)}`;
586
+ }
587
+ });
588
+ actionRow.append(applyBtn);
589
+ }
590
+ else if (looksLikeApprovalRequest(fullText)) {
591
+ const approveBtn = document.createElement("button");
592
+ approveBtn.className = "ai-msg-approve";
593
+ approveBtn.textContent = "✓ Approve";
594
+ approveBtn.addEventListener("click", () => {
595
+ approveBtn.disabled = true;
596
+ textarea.value = "Approved, please proceed.";
597
+ void send();
598
+ });
599
+ actionRow.append(approveBtn);
600
+ }
601
+ msgEl.append(actionRow);
602
+ messagesEl.scrollTop = messagesEl.scrollHeight;
603
+ }
604
+ // ── Clear conversation ──
605
+ function clearConversation() {
606
+ history.length = 0;
607
+ _hasMessages = false;
608
+ for (const child of Array.from(messagesEl.children)) {
609
+ if (child !== welcomeEl)
610
+ child.remove();
611
+ }
612
+ welcomeEl.style.display = "";
613
+ for (const c of _previewCanvases)
614
+ c.destroy();
615
+ _previewCanvases.length = 0;
616
+ }
617
+ // ── Core streaming helper ──
618
+ async function runStream(messages, context, action, signal, aiMsgEl) {
619
+ let fullText = "";
620
+ let resultXml;
621
+ try {
622
+ for await (const token of streamChat(options.serverUrl, messages, context, backendSelect.value, signal, action, (xml) => {
623
+ resultXml = xml;
624
+ })) {
625
+ fullText += token;
626
+ aiMsgEl.textContent = fullText;
627
+ messagesEl.scrollTop = messagesEl.scrollHeight;
628
+ }
629
+ }
630
+ catch (err) {
631
+ if (!signal.aborted) {
632
+ fullText = `${fullText ? `${fullText}\n\n` : ""}Error: ${err instanceof Error ? err.message : String(err)}`;
633
+ }
634
+ }
635
+ return { fullText, resultXml };
636
+ }
637
+ // ── Send user message ──
638
+ async function send() {
639
+ const text = textarea.value.trim();
640
+ if (!text || sending)
641
+ return;
642
+ _abortCtrl = new AbortController();
643
+ const signal = _abortCtrl.signal;
644
+ setUiBusy(true);
645
+ textarea.value = "";
646
+ textarea.style.height = "";
647
+ const contextNodes = _refs.map((r) => r.node);
648
+ const promptContent = contextNodes.length > 0
649
+ ? `[Context: ${contextNodes.map((n) => `"${n.name ?? n.type}" (${n.type}, id: ${n.id})`).join(", ")}]\n\n${text}`
650
+ : text;
651
+ const userMsg = { role: "user", content: promptContent };
652
+ history.push(userMsg);
653
+ addMessage("user", text, contextNodes.length > 0 ? contextNodes : undefined);
654
+ const aiMsgEl = addMessage("ai", "");
655
+ aiMsgEl.classList.add("ai-msg-cursor");
656
+ const defs = options.getDefinitions();
657
+ const diagramContext = defs ? compactify(defs) : null;
658
+ const { fullText, resultXml } = await runStream(history, diagramContext, undefined, signal, aiMsgEl);
659
+ finalizeAiMessage(aiMsgEl, fullText, resultXml);
660
+ history.push({ role: "ai", content: fullText });
661
+ _abortCtrl = null;
662
+ setUiBusy(false);
663
+ textarea.focus();
664
+ }
665
+ // ── Send a quick action ──
666
+ async function sendAction(label, action, includeContext = false) {
667
+ if (sending)
668
+ return;
669
+ const defs = options.getDefinitions();
670
+ if (!defs)
671
+ return;
672
+ _abortCtrl = new AbortController();
673
+ const signal = _abortCtrl.signal;
674
+ setUiBusy(true);
675
+ const contextNodes = includeContext ? _refs.map((r) => r.node) : [];
676
+ const promptContent = contextNodes.length > 0
677
+ ? `[Context: ${contextNodes.map((n) => `"${n.name ?? n.type}" (${n.type}, id: ${n.id})`).join(", ")}]\n\n${label}`
678
+ : label;
679
+ const userMsg = { role: "user", content: promptContent };
680
+ history.push(userMsg);
681
+ addMessage("user", label, contextNodes.length > 0 ? contextNodes : undefined);
682
+ const aiMsgEl = addMessage("ai", "");
683
+ aiMsgEl.classList.add("ai-msg-cursor");
684
+ const context = compactify(defs);
685
+ const { fullText, resultXml } = await runStream(history, context, action, signal, aiMsgEl);
686
+ finalizeAiMessage(aiMsgEl, fullText, resultXml);
687
+ history.push({ role: "ai", content: fullText });
688
+ _abortCtrl = null;
689
+ setUiBusy(false);
690
+ textarea.focus();
691
+ }
692
+ // ── Event wiring ──
693
+ improveBtn.addEventListener("click", () => void sendAction("Improve this diagram", "improve"));
694
+ explainBtn.addEventListener("click", () => void sendAction("Explain this diagram", "explain"));
695
+ explainElementBtn.addEventListener("click", () => void sendAction("Explain this element", "explain", true));
696
+ sendBtn.addEventListener("click", () => void send());
697
+ textarea.addEventListener("keydown", (e) => {
698
+ if (e.key === "Enter" && !e.shiftKey) {
699
+ e.preventDefault();
700
+ void send();
701
+ }
702
+ });
703
+ // ── Open/close ──
704
+ function open() {
705
+ panel.classList.add("ai-panel-open");
706
+ void checkStatus();
707
+ textarea.focus();
708
+ }
709
+ function close() {
710
+ panel.classList.remove("ai-panel-open");
711
+ }
712
+ return { panel, open, close, setContext };
713
+ }
714
+ export { DEFAULT_SERVER };
715
+ //# sourceMappingURL=panel.js.map