@braincrew-lab/langchain-canvas 0.2.0 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ChartRenderer-AAWVGKV5.js → ChartRenderer-JGRJ23OB.js} +10 -20
- package/dist/{DocumentRenderer-NBRBPYU6.js → DocumentRenderer-ZQDQMX7X.js} +8 -4
- package/dist/FileRenderer-3ZHNORZJ.js +39 -0
- package/dist/SlidesRenderer-56R3TNWN.js +496 -0
- package/dist/{TableRenderer-VKDD3CB6.js → TableRenderer-3ESF577F.js} +49 -193
- package/dist/chunk-7T5DRR3F.js +109 -0
- package/dist/{chunk-S54GJDSJ.js → chunk-EHW446VF.js} +62 -47
- package/dist/chunk-FTNRRJ3K.js +13 -0
- package/dist/chunk-IFNRLN4Y.js +137 -0
- package/dist/{chunk-UL5F66PN.js → chunk-K2UZAYW2.js} +1 -1
- package/dist/chunk-SGOPRUQ4.js +182 -0
- package/dist/formula-27TCEZI5.js +2 -0
- package/dist/formula-cli.d.ts +2 -0
- package/dist/formula-cli.js +37 -0
- package/dist/index.d.ts +201 -289
- package/dist/index.js +393 -860
- package/dist/langgraph/index.d.ts +68 -0
- package/dist/langgraph/index.js +111 -0
- package/dist/styles.css +125 -100
- package/dist/types-BfGP9R2I.d.ts +324 -0
- package/package.json +40 -5
- package/dist/PdfRenderer-DPQT4E7O.js +0 -97
- package/dist/SlidesRenderer-6ZGHXTQX.js +0 -877
|
@@ -1,77 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { mergeRowsIntoSheet } from './chunk-IFNRLN4Y.js';
|
|
2
|
+
import { computeFormulas } from './chunk-SGOPRUQ4.js';
|
|
3
|
+
import './chunk-YZZSJJMQ.js';
|
|
4
|
+
import { useCanvasStore } from './chunk-EHW446VF.js';
|
|
3
5
|
import { lazy, useMemo, useState, useRef, useEffect, useCallback, Suspense } from 'react';
|
|
4
6
|
import '@fortune-sheet/react/dist/index.css';
|
|
5
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
6
8
|
|
|
7
|
-
// src/io/formula.ts
|
|
8
|
-
var EMPTY = /* @__PURE__ */ new Map();
|
|
9
|
-
async function computeFormulas(columns, rows) {
|
|
10
|
-
const formulaCells = [];
|
|
11
|
-
rows.forEach(
|
|
12
|
-
(row, dataIdx) => columns.forEach((col, c) => {
|
|
13
|
-
const v = row[col.key];
|
|
14
|
-
if (typeof v === "string" && v.startsWith("=")) formulaCells.push({ dataIdx, col: c, formula: v });
|
|
15
|
-
})
|
|
16
|
-
);
|
|
17
|
-
if (formulaCells.length === 0) return EMPTY;
|
|
18
|
-
const mod = await loadOptional("fast-formula-parser", () => import('fast-formula-parser'));
|
|
19
|
-
const FormulaParser = mod.default ?? mod;
|
|
20
|
-
const memo = /* @__PURE__ */ new Map();
|
|
21
|
-
const inProgress = /* @__PURE__ */ new Set();
|
|
22
|
-
const rawAt = (row, col) => {
|
|
23
|
-
const colIdx = col - 1;
|
|
24
|
-
if (row === 1) return columns[colIdx]?.label ?? columns[colIdx]?.key ?? null;
|
|
25
|
-
const dataRow = rows[row - 2];
|
|
26
|
-
const column = columns[colIdx];
|
|
27
|
-
if (!dataRow || !column) return null;
|
|
28
|
-
const v = dataRow[column.key];
|
|
29
|
-
return v ?? null;
|
|
30
|
-
};
|
|
31
|
-
const valueAt = (row, col) => {
|
|
32
|
-
const raw = rawAt(row, col);
|
|
33
|
-
if (typeof raw !== "string" || !raw.startsWith("=")) return raw ?? 0;
|
|
34
|
-
const key = `${row},${col}`;
|
|
35
|
-
const cached = memo.get(key);
|
|
36
|
-
if (cached !== void 0) return cached;
|
|
37
|
-
if (inProgress.has(key)) return 0;
|
|
38
|
-
inProgress.add(key);
|
|
39
|
-
const value = evaluate(raw, row, col);
|
|
40
|
-
inProgress.delete(key);
|
|
41
|
-
memo.set(key, value);
|
|
42
|
-
return value;
|
|
43
|
-
};
|
|
44
|
-
const parser = new FormulaParser({
|
|
45
|
-
onCell: ({ row, col }) => valueAt(row, col),
|
|
46
|
-
onRange: (ref) => {
|
|
47
|
-
const maxRow = Math.min(ref.to.row, rows.length + 1);
|
|
48
|
-
const maxCol = Math.min(ref.to.col, columns.length);
|
|
49
|
-
const grid = [];
|
|
50
|
-
for (let r = ref.from.row; r <= maxRow; r++) {
|
|
51
|
-
const line = [];
|
|
52
|
-
for (let c = ref.from.col; c <= maxCol; c++) line.push(valueAt(r, c));
|
|
53
|
-
grid.push(line);
|
|
54
|
-
}
|
|
55
|
-
return grid;
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
const evaluate = (formula, row, col) => {
|
|
59
|
-
try {
|
|
60
|
-
const result = parser.parse(formula.slice(1), { row, col });
|
|
61
|
-
if (result != null && typeof result === "object") return "#ERR";
|
|
62
|
-
return result ?? 0;
|
|
63
|
-
} catch {
|
|
64
|
-
return "#ERR";
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
const out = /* @__PURE__ */ new Map();
|
|
68
|
-
for (const { dataIdx, col, formula } of formulaCells) {
|
|
69
|
-
out.set(`${dataIdx + 1},${col}`, evaluate(formula, dataIdx + 2, col + 1));
|
|
70
|
-
}
|
|
71
|
-
return out;
|
|
72
|
-
}
|
|
73
9
|
var Workbook = lazy(() => import('@fortune-sheet/react').then((m) => ({ default: m.Workbook })));
|
|
74
10
|
var isFormula = (v) => typeof v === "string" && v.startsWith("=");
|
|
11
|
+
function sheetHasContent(sheet) {
|
|
12
|
+
if (!sheet?.length) return false;
|
|
13
|
+
return sheet.some((s) => {
|
|
14
|
+
const celldata = s.celldata;
|
|
15
|
+
if (celldata?.some((cell) => cell?.v != null)) return true;
|
|
16
|
+
const data = s.data;
|
|
17
|
+
return !!data?.some((row) => row?.some((cell) => cell != null));
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function normalizeSheets(sheet) {
|
|
21
|
+
if (!sheet?.length) return sheet;
|
|
22
|
+
return sheet.map((s) => {
|
|
23
|
+
const { luckysheet_select_save: _selection, data, ...rest } = s;
|
|
24
|
+
if (!data || rest.celldata) return rest;
|
|
25
|
+
const celldata = [];
|
|
26
|
+
data.forEach(
|
|
27
|
+
(row, r) => row?.forEach((cell, c) => {
|
|
28
|
+
if (cell != null) celldata.push({ r, c, v: cell });
|
|
29
|
+
})
|
|
30
|
+
);
|
|
31
|
+
return { ...rest, celldata };
|
|
32
|
+
});
|
|
33
|
+
}
|
|
75
34
|
function toWorkbook(columns, rows, formulas) {
|
|
76
35
|
const celldata = [];
|
|
77
36
|
columns.forEach((col, c) => {
|
|
@@ -177,7 +136,13 @@ function TableRenderer({ artifact }) {
|
|
|
177
136
|
root.addEventListener("wheel", onWheel, { passive: false, capture: true });
|
|
178
137
|
return () => root.removeEventListener("wheel", onWheel, { capture: true });
|
|
179
138
|
}, [mounted]);
|
|
180
|
-
const
|
|
139
|
+
const rowsSig = useMemo(() => {
|
|
140
|
+
const json = JSON.stringify(rows);
|
|
141
|
+
let hash = 5381;
|
|
142
|
+
for (let i = 0; i < json.length; i++) hash = (hash << 5) + hash + json.charCodeAt(i) | 0;
|
|
143
|
+
return (hash >>> 0).toString(36);
|
|
144
|
+
}, [rows]);
|
|
145
|
+
const dataKey = `${artifact.id}:v${artifact.version}:${columns.length}x${rows.length}:${rowsSig}`;
|
|
181
146
|
const hasFormulas = useMemo(
|
|
182
147
|
() => rows.slice(0, 400).some((row) => columns.some((col) => isFormula(row[col.key]))),
|
|
183
148
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -202,7 +167,7 @@ function TableRenderer({ artifact }) {
|
|
|
202
167
|
alive = false;
|
|
203
168
|
};
|
|
204
169
|
}, [dataKey, hasFormulas]);
|
|
205
|
-
const hasSheet =
|
|
170
|
+
const hasSheet = sheetHasContent(artifact.data.sheet);
|
|
206
171
|
const [sortCol, setSortCol] = useState("");
|
|
207
172
|
const [sortDir, setSortDir] = useState(1);
|
|
208
173
|
const [filter, setFilter] = useState("");
|
|
@@ -228,7 +193,11 @@ function TableRenderer({ artifact }) {
|
|
|
228
193
|
const viewActive = !!appliedFilter.trim() || !!sortCol;
|
|
229
194
|
const wbKey = `${dataKey}:${viewActive ? `view-s${sortCol}${sortDir}-f${appliedFilter}` : hasSheet ? "sheet" : "rows"}`;
|
|
230
195
|
const initialData = useMemo(
|
|
231
|
-
() => viewActive ? toWorkbook(columns, viewRows, formulas) : hasSheet ?
|
|
196
|
+
() => viewActive ? toWorkbook(columns, viewRows, formulas) : hasSheet ? (
|
|
197
|
+
// Rows the agent wrote after the person's last edit win their cells;
|
|
198
|
+
// the person's formatting and out-of-table cells survive.
|
|
199
|
+
mergeRowsIntoSheet(columns, rows, normalizeSheets(artifact.data.sheet), formulas)
|
|
200
|
+
) : toWorkbook(columns, rows, formulas),
|
|
232
201
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
233
202
|
[wbKey, formulasReady]
|
|
234
203
|
);
|
|
@@ -238,7 +207,12 @@ function TableRenderer({ artifact }) {
|
|
|
238
207
|
(sheets) => {
|
|
239
208
|
if (persistTimer.current) clearTimeout(persistTimer.current);
|
|
240
209
|
persistTimer.current = setTimeout(() => {
|
|
241
|
-
|
|
210
|
+
if (!sheetHasContent(sheets)) return;
|
|
211
|
+
applyEvent({
|
|
212
|
+
type: "canvas.patch",
|
|
213
|
+
id: artifact.id,
|
|
214
|
+
patch: { sheet: normalizeSheets(sheets) }
|
|
215
|
+
});
|
|
242
216
|
}, 400);
|
|
243
217
|
},
|
|
244
218
|
[applyEvent, artifact.id]
|
|
@@ -252,137 +226,19 @@ function TableRenderer({ artifact }) {
|
|
|
252
226
|
const range = sel?.[0]?.[type] ?? [0, 0];
|
|
253
227
|
wbRef.current?.insertRowOrColumn(type, Math.max(0, range[1]), 1, "rightbottom");
|
|
254
228
|
};
|
|
255
|
-
const [selection, setSelection] = useState(null);
|
|
256
|
-
const [boldOn, setBoldOn] = useState(false);
|
|
257
|
-
const [fillColor, setFillColor] = useState("#fef3c7");
|
|
258
|
-
const [textColor, setTextColor] = useState("#111827");
|
|
259
|
-
const handleSelectionChange = useCallback((_sheetId, sel) => {
|
|
260
|
-
setSelection({ row: [...sel.row], column: [...sel.column] });
|
|
261
|
-
const bl = wbRef.current?.getCellValue?.(sel.row[0], sel.column[0], { type: "bl" });
|
|
262
|
-
setBoldOn(bl === 1 || bl === "1");
|
|
263
|
-
}, []);
|
|
264
|
-
const workbookHooks = useMemo(() => ({ afterSelectionChange: handleSelectionChange }), [handleSelectionChange]);
|
|
265
|
-
const applyFormat = (attr, value) => {
|
|
266
|
-
const sel = wbRef.current?.getSelection?.();
|
|
267
|
-
if (!sel?.length) return;
|
|
268
|
-
const ranges = sel.map((s) => ({ row: [s.row[0], s.row[1]], column: [s.column[0], s.column[1]] }));
|
|
269
|
-
wbRef.current?.setCellFormatByRange(attr, value, ranges);
|
|
270
|
-
};
|
|
271
|
-
const toggleBold = () => {
|
|
272
|
-
applyFormat("bl", boldOn ? 0 : 1);
|
|
273
|
-
setBoldOn((b) => !b);
|
|
274
|
-
};
|
|
275
|
-
const cleanStyling = () => {
|
|
276
|
-
const wb = wbRef.current;
|
|
277
|
-
const sheet = wb?.getSheet?.();
|
|
278
|
-
const sheetId = sheet?.id;
|
|
279
|
-
if (!wb || !sheet || !sheetId) return;
|
|
280
|
-
const ops = [];
|
|
281
|
-
sheet.celldata.forEach(({ r, c, v }) => {
|
|
282
|
-
if (!v || typeof v !== "object") return;
|
|
283
|
-
const cell = v;
|
|
284
|
-
if (cell.bg != null) ops.push({ op: "remove", id: sheetId, path: ["data", r, c, "bg"] });
|
|
285
|
-
if (cell.fc != null) ops.push({ op: "remove", id: sheetId, path: ["data", r, c, "fc"] });
|
|
286
|
-
const spans = cell.ct?.s;
|
|
287
|
-
if (!Array.isArray(spans)) return;
|
|
288
|
-
spans.forEach((span, i) => {
|
|
289
|
-
if (!span || typeof span !== "object") return;
|
|
290
|
-
const run = span;
|
|
291
|
-
if (run.bg != null) ops.push({ op: "remove", id: sheetId, path: ["data", r, c, "ct", "s", i, "bg"] });
|
|
292
|
-
if (run.fc != null) ops.push({ op: "remove", id: sheetId, path: ["data", r, c, "ct", "s", i, "fc"] });
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
if (ops.length) wb.applyOp(ops);
|
|
296
|
-
};
|
|
297
|
-
const [frozen, setFrozen] = useState(false);
|
|
298
|
-
const toggleFreeze = () => {
|
|
299
|
-
const wb = wbRef.current;
|
|
300
|
-
if (!wb) return;
|
|
301
|
-
if (frozen) {
|
|
302
|
-
const sheetId = wb.getSheet?.()?.id;
|
|
303
|
-
if (sheetId) wb.applyOp([{ op: "remove", id: sheetId, path: ["frozen"] }]);
|
|
304
|
-
} else {
|
|
305
|
-
wb.freeze("row", { row: 0, column: 0 });
|
|
306
|
-
}
|
|
307
|
-
setFrozen((f) => !f);
|
|
308
|
-
};
|
|
309
|
-
useEffect(() => {
|
|
310
|
-
setFrozen(!viewActive && hasSheet && !!artifact.data.sheet?.[0]?.frozen);
|
|
311
|
-
setSelection(null);
|
|
312
|
-
setBoldOn(false);
|
|
313
|
-
}, [wbKey]);
|
|
314
229
|
if (!mounted) {
|
|
315
230
|
return /* @__PURE__ */ jsx("div", { className: "cv-sheet cv-sheet--empty", children: "Loading spreadsheet\u2026" });
|
|
316
231
|
}
|
|
317
232
|
if (!hasSheet && columns.length === 0) {
|
|
318
233
|
return /* @__PURE__ */ jsx("div", { className: "cv-sheet cv-sheet--empty", children: "Waiting for data\u2026" });
|
|
319
234
|
}
|
|
320
|
-
if (!
|
|
235
|
+
if (!formulasReady) {
|
|
321
236
|
return /* @__PURE__ */ jsx("div", { className: "cv-sheet cv-sheet--empty", children: "Calculating\u2026" });
|
|
322
237
|
}
|
|
323
238
|
return /* @__PURE__ */ jsxs("div", { className: "cv-sheet-panel", children: [
|
|
324
239
|
/* @__PURE__ */ jsxs("div", { className: "cv-sheet-tools", children: [
|
|
325
240
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => insert("column"), children: "\uFF0B Column" }),
|
|
326
241
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => insert("row"), children: "\uFF0B Row" }),
|
|
327
|
-
/* @__PURE__ */ jsx("span", { className: "cv-sheet-tools__sep" }),
|
|
328
|
-
/* @__PURE__ */ jsxs("span", { className: "cv-sheet-tools__fmt", children: [
|
|
329
|
-
/* @__PURE__ */ jsx(
|
|
330
|
-
"button",
|
|
331
|
-
{
|
|
332
|
-
type: "button",
|
|
333
|
-
className: `cv-sheet-tools__bold${boldOn ? " cv-sheet-tools__bold--on" : ""}`,
|
|
334
|
-
title: "Bold",
|
|
335
|
-
"aria-pressed": boldOn,
|
|
336
|
-
disabled: !selection,
|
|
337
|
-
onClick: toggleBold,
|
|
338
|
-
children: "B"
|
|
339
|
-
}
|
|
340
|
-
),
|
|
341
|
-
/* @__PURE__ */ jsx(
|
|
342
|
-
"input",
|
|
343
|
-
{
|
|
344
|
-
type: "color",
|
|
345
|
-
className: "cv-sheet-tools__color cv-sheet-tools__color--fill",
|
|
346
|
-
title: "Fill color",
|
|
347
|
-
disabled: !selection,
|
|
348
|
-
value: fillColor,
|
|
349
|
-
onChange: (e) => {
|
|
350
|
-
setFillColor(e.target.value);
|
|
351
|
-
applyFormat("bg", e.target.value);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
),
|
|
355
|
-
/* @__PURE__ */ jsx(
|
|
356
|
-
"input",
|
|
357
|
-
{
|
|
358
|
-
type: "color",
|
|
359
|
-
className: "cv-sheet-tools__color cv-sheet-tools__color--text",
|
|
360
|
-
title: "Text color",
|
|
361
|
-
disabled: !selection,
|
|
362
|
-
value: textColor,
|
|
363
|
-
onChange: (e) => {
|
|
364
|
-
setTextColor(e.target.value);
|
|
365
|
-
applyFormat("fc", e.target.value);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
),
|
|
369
|
-
/* @__PURE__ */ jsx("button", { type: "button", className: "cv-sheet-tools__align", title: "Align left", disabled: !selection, onClick: () => applyFormat("ht", 1), children: "\u21E4" }),
|
|
370
|
-
/* @__PURE__ */ jsx("button", { type: "button", className: "cv-sheet-tools__align", title: "Align center", disabled: !selection, onClick: () => applyFormat("ht", 0), children: "\u2194" }),
|
|
371
|
-
/* @__PURE__ */ jsx("button", { type: "button", className: "cv-sheet-tools__align", title: "Align right", disabled: !selection, onClick: () => applyFormat("ht", 2), children: "\u21E5" })
|
|
372
|
-
] }),
|
|
373
|
-
/* @__PURE__ */ jsx("span", { className: "cv-sheet-tools__sep" }),
|
|
374
|
-
/* @__PURE__ */ jsx("button", { type: "button", className: "cv-sheet-tools__clean", title: "Remove all cell fills and font colors", onClick: cleanStyling, children: "Clean styling" }),
|
|
375
|
-
/* @__PURE__ */ jsx(
|
|
376
|
-
"button",
|
|
377
|
-
{
|
|
378
|
-
type: "button",
|
|
379
|
-
className: `cv-sheet-tools__freeze${frozen ? " cv-sheet-tools__freeze--on" : ""}`,
|
|
380
|
-
title: frozen ? "Unfreeze the header row" : "Keep the header row visible while scrolling",
|
|
381
|
-
"aria-pressed": frozen,
|
|
382
|
-
onClick: toggleFreeze,
|
|
383
|
-
children: "Freeze header"
|
|
384
|
-
}
|
|
385
|
-
),
|
|
386
242
|
columns.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
387
243
|
/* @__PURE__ */ jsx("span", { className: "cv-sheet-tools__sep" }),
|
|
388
244
|
/* @__PURE__ */ jsxs(
|
|
@@ -412,8 +268,8 @@ function TableRenderer({ artifact }) {
|
|
|
412
268
|
] }),
|
|
413
269
|
/* @__PURE__ */ jsx("span", { className: "cv-sheet-tools__hint", children: "Right-click a header for more, or drag to edit" })
|
|
414
270
|
] }),
|
|
415
|
-
/* @__PURE__ */ jsx("div", { className: "cv-sheet", ref: rootRef, children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "cv-sheet--empty", children: "Loading\u2026" }), children: /* @__PURE__ */ jsx(Workbook, { ref: wbRef, data: initialData, onChange: handleChange
|
|
271
|
+
/* @__PURE__ */ jsx("div", { className: "cv-sheet", ref: rootRef, children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "cv-sheet--empty", children: "Loading\u2026" }), children: /* @__PURE__ */ jsx(Workbook, { ref: wbRef, data: initialData, onChange: handleChange }, wbKey) }) })
|
|
416
272
|
] });
|
|
417
273
|
}
|
|
418
274
|
|
|
419
|
-
export { TableRenderer };
|
|
275
|
+
export { TableRenderer, normalizeSheets, sheetHasContent };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// src/io/canvasAssets.ts
|
|
2
|
+
var ASSET_REFERENCE_PREFIXES = ["assets/", "sources/"];
|
|
3
|
+
function normalizeAssetReference(src) {
|
|
4
|
+
if (typeof src !== "string") return null;
|
|
5
|
+
let folded = src;
|
|
6
|
+
while (folded.startsWith("./") || folded.startsWith("../")) {
|
|
7
|
+
folded = folded.startsWith("./") ? folded.slice(2) : folded.slice(3);
|
|
8
|
+
}
|
|
9
|
+
return ASSET_REFERENCE_PREFIXES.some((p) => folded.startsWith(p)) ? folded : null;
|
|
10
|
+
}
|
|
11
|
+
function isAssetReference(src) {
|
|
12
|
+
return normalizeAssetReference(src) !== null;
|
|
13
|
+
}
|
|
14
|
+
function resolveAssetUrl(src, assetBaseUrl) {
|
|
15
|
+
return assetBaseUrl + encodeURIComponent(normalizeAssetReference(src) ?? src);
|
|
16
|
+
}
|
|
17
|
+
var REF_ALTERNATION = ASSET_REFERENCE_PREFIXES.map((p) => p.slice(0, -1)).join("|");
|
|
18
|
+
var srcAttrPattern = () => new RegExp(`(src=(["']))((?:\\.\\.?/)*(?:${REF_ALTERNATION})/[^"']+)(\\2)`, "g");
|
|
19
|
+
var escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20
|
+
async function fetchAssetDataUri(path, assetBaseUrl) {
|
|
21
|
+
try {
|
|
22
|
+
const res = await fetch(resolveAssetUrl(path, assetBaseUrl));
|
|
23
|
+
if (!res.ok) return null;
|
|
24
|
+
const type = res.headers.get("content-type")?.split(";")[0] || "application/octet-stream";
|
|
25
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
26
|
+
let binary = "";
|
|
27
|
+
for (let i = 0; i < bytes.length; i += 32768) {
|
|
28
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
29
|
+
}
|
|
30
|
+
return `data:${type};base64,${btoa(binary)}`;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function inlineHtmlAssets(html, assetBaseUrl) {
|
|
36
|
+
const relative = srcAttrPattern();
|
|
37
|
+
const absolute = new RegExp(
|
|
38
|
+
`(src=(["']))${escapeRegExp(assetBaseUrl)}([^"']+)(\\2)`,
|
|
39
|
+
"g"
|
|
40
|
+
);
|
|
41
|
+
const paths = /* @__PURE__ */ new Set();
|
|
42
|
+
for (const m of html.matchAll(relative)) paths.add(m[3]);
|
|
43
|
+
for (const m of html.matchAll(absolute)) {
|
|
44
|
+
const decoded = safeDecode(m[3]);
|
|
45
|
+
if (isAssetReference(decoded)) paths.add(decoded);
|
|
46
|
+
}
|
|
47
|
+
if (!paths.size) return html;
|
|
48
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
49
|
+
await Promise.all(
|
|
50
|
+
[...paths].map(async (p) => {
|
|
51
|
+
const uri = await fetchAssetDataUri(p, assetBaseUrl);
|
|
52
|
+
if (uri) resolved.set(p, uri);
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
return html.replace(relative, (whole, pre, _q, path, post) => {
|
|
56
|
+
const uri = resolved.get(path);
|
|
57
|
+
return uri ? `${pre}${uri}${post}` : whole;
|
|
58
|
+
}).replace(absolute, (whole, pre, _q, encoded, post) => {
|
|
59
|
+
const uri = resolved.get(safeDecode(encoded));
|
|
60
|
+
return uri ? `${pre}${uri}${post}` : whole;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function safeDecode(value) {
|
|
64
|
+
try {
|
|
65
|
+
return decodeURIComponent(value);
|
|
66
|
+
} catch {
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function inlineArtifactAssets(artifact, assetBaseUrl) {
|
|
71
|
+
if (!assetBaseUrl) return artifact;
|
|
72
|
+
if (artifact.type === "html") {
|
|
73
|
+
const data = artifact.data;
|
|
74
|
+
const html = await inlineHtmlAssets(data.html, assetBaseUrl);
|
|
75
|
+
return html === data.html ? artifact : { ...artifact, data: { ...data, html } };
|
|
76
|
+
}
|
|
77
|
+
if (artifact.type === "slides") {
|
|
78
|
+
const data = artifact.data;
|
|
79
|
+
let changed = false;
|
|
80
|
+
const slides = await Promise.all(
|
|
81
|
+
(data.slides ?? []).map(async (slide) => {
|
|
82
|
+
let next = slide;
|
|
83
|
+
if (isAssetReference(slide.image)) {
|
|
84
|
+
const uri = await fetchAssetDataUri(slide.image, assetBaseUrl);
|
|
85
|
+
if (uri) {
|
|
86
|
+
next = { ...next, image: uri };
|
|
87
|
+
changed = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (next.elements?.some((el) => isAssetReference(el.src))) {
|
|
91
|
+
const elements = await Promise.all(
|
|
92
|
+
next.elements.map(async (el) => {
|
|
93
|
+
if (!isAssetReference(el.src)) return el;
|
|
94
|
+
const uri = await fetchAssetDataUri(el.src, assetBaseUrl);
|
|
95
|
+
return uri ? { ...el, src: uri } : el;
|
|
96
|
+
})
|
|
97
|
+
);
|
|
98
|
+
next = { ...next, elements };
|
|
99
|
+
changed = true;
|
|
100
|
+
}
|
|
101
|
+
return next;
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
return changed ? { ...artifact, data: { ...data, slides } } : artifact;
|
|
105
|
+
}
|
|
106
|
+
return artifact;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export { ASSET_REFERENCE_PREFIXES, fetchAssetDataUri, inlineArtifactAssets, inlineHtmlAssets, isAssetReference, normalizeAssetReference, resolveAssetUrl };
|
|
@@ -23,13 +23,13 @@ function reduceCanvas(state, event) {
|
|
|
23
23
|
const current = state.artifacts[event.id];
|
|
24
24
|
if (!current) return state;
|
|
25
25
|
const data = appendAtPath(current.data, event.path, event.text);
|
|
26
|
-
return
|
|
26
|
+
return updateLive(state, { ...current, data });
|
|
27
27
|
}
|
|
28
28
|
case "canvas.patch": {
|
|
29
29
|
const current = state.artifacts[event.id];
|
|
30
30
|
if (!current) return state;
|
|
31
31
|
const data = mergePatch(current.data, event.patch);
|
|
32
|
-
return
|
|
32
|
+
return updateLive(state, { ...current, data });
|
|
33
33
|
}
|
|
34
34
|
case "canvas.node_patch": {
|
|
35
35
|
const current = state.artifacts[event.id];
|
|
@@ -37,7 +37,7 @@ function reduceCanvas(state, event) {
|
|
|
37
37
|
if (!current || typeof html !== "string") return state;
|
|
38
38
|
const next = applyNodePatch(html, event.cid, event.html);
|
|
39
39
|
const data = { ...current.data, html: next };
|
|
40
|
-
return
|
|
40
|
+
return updateLive(state, { ...current, data });
|
|
41
41
|
}
|
|
42
42
|
case "canvas.replace":
|
|
43
43
|
return pushVersion(state, event.artifact);
|
|
@@ -46,8 +46,26 @@ function reduceCanvas(state, event) {
|
|
|
46
46
|
if (!current) return state;
|
|
47
47
|
return replaceInPlace(state, { ...current, status: event.status });
|
|
48
48
|
}
|
|
49
|
-
case "canvas.
|
|
50
|
-
|
|
49
|
+
case "canvas.commit": {
|
|
50
|
+
const current = state.artifacts[event.id];
|
|
51
|
+
if (!current) return state;
|
|
52
|
+
const versions = state.history[event.id] ?? [];
|
|
53
|
+
const committed = {
|
|
54
|
+
...current,
|
|
55
|
+
meta: {
|
|
56
|
+
...current.meta ?? {},
|
|
57
|
+
commitDescription: event.description,
|
|
58
|
+
...event.revision ? { revision: event.revision } : {}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
...state,
|
|
63
|
+
artifacts: { ...state.artifacts, [event.id]: committed },
|
|
64
|
+
history: { ...state.history, [event.id]: [...versions.slice(0, -1), committed] }
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
default:
|
|
68
|
+
return state;
|
|
51
69
|
}
|
|
52
70
|
}
|
|
53
71
|
function create(state, artifact) {
|
|
@@ -59,6 +77,20 @@ function create(state, artifact) {
|
|
|
59
77
|
activeId: artifact.id
|
|
60
78
|
};
|
|
61
79
|
}
|
|
80
|
+
function updateLive(state, artifact) {
|
|
81
|
+
const versions = state.history[artifact.id] ?? [];
|
|
82
|
+
const last = versions[versions.length - 1];
|
|
83
|
+
if (!last || typeof last.meta?.commitDescription !== "string") {
|
|
84
|
+
return replaceInPlace(state, artifact);
|
|
85
|
+
}
|
|
86
|
+
const { commitDescription: _stale, ...meta } = artifact.meta ?? {};
|
|
87
|
+
const working = { ...artifact, version: last.version + 1, meta };
|
|
88
|
+
return {
|
|
89
|
+
...state,
|
|
90
|
+
artifacts: { ...state.artifacts, [artifact.id]: working },
|
|
91
|
+
history: { ...state.history, [artifact.id]: [...versions, working] }
|
|
92
|
+
};
|
|
93
|
+
}
|
|
62
94
|
function replaceInPlace(state, artifact) {
|
|
63
95
|
const versions = state.history[artifact.id] ?? [];
|
|
64
96
|
const history = versions.length ? { ...state.history, [artifact.id]: [...versions.slice(0, -1), artifact] } : { ...state.history, [artifact.id]: [artifact] };
|
|
@@ -125,18 +157,6 @@ function mergePatch(target, patch) {
|
|
|
125
157
|
function isPlainObject(value) {
|
|
126
158
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
127
159
|
}
|
|
128
|
-
function lastOf(order, excludeId) {
|
|
129
|
-
const remaining = order.filter((id) => id !== excludeId);
|
|
130
|
-
return remaining.length ? remaining[remaining.length - 1] : null;
|
|
131
|
-
}
|
|
132
|
-
function notifyTimeTravel(store, before) {
|
|
133
|
-
const handler = store.onUserEdit;
|
|
134
|
-
if (!handler || store.canvas === before) return;
|
|
135
|
-
for (const id of Object.keys(store.canvas.artifacts)) {
|
|
136
|
-
const now = store.canvas.artifacts[id];
|
|
137
|
-
if (now && now !== before.artifacts[id]) handler(now);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
160
|
function editedArtifactId(event) {
|
|
141
161
|
switch (event.type) {
|
|
142
162
|
case "canvas.create":
|
|
@@ -159,7 +179,8 @@ var initialState = () => ({
|
|
|
159
179
|
iframeCommand: null,
|
|
160
180
|
undoStack: [],
|
|
161
181
|
redoStack: [],
|
|
162
|
-
onUserEdit: null
|
|
182
|
+
onUserEdit: null,
|
|
183
|
+
assetBaseUrl: null
|
|
163
184
|
});
|
|
164
185
|
function createCanvasStore() {
|
|
165
186
|
return createStore((set, get) => ({
|
|
@@ -176,34 +197,26 @@ function createCanvasStore() {
|
|
|
176
197
|
const artifact = id ? state.canvas.artifacts[id] : void 0;
|
|
177
198
|
if (artifact) state.onUserEdit?.(artifact);
|
|
178
199
|
},
|
|
179
|
-
undo: () => {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
canvas: next,
|
|
200
|
-
redoStack: state.redoStack.slice(0, -1),
|
|
201
|
-
undoStack: [...state.undoStack, state.canvas].slice(-50),
|
|
202
|
-
selections: []
|
|
203
|
-
};
|
|
204
|
-
});
|
|
205
|
-
notifyTimeTravel(get(), before);
|
|
206
|
-
},
|
|
200
|
+
undo: () => set((state) => {
|
|
201
|
+
if (!state.undoStack.length) return state;
|
|
202
|
+
const previous = state.undoStack[state.undoStack.length - 1];
|
|
203
|
+
return {
|
|
204
|
+
canvas: previous,
|
|
205
|
+
undoStack: state.undoStack.slice(0, -1),
|
|
206
|
+
redoStack: [...state.redoStack, state.canvas].slice(-50),
|
|
207
|
+
selections: []
|
|
208
|
+
};
|
|
209
|
+
}),
|
|
210
|
+
redo: () => set((state) => {
|
|
211
|
+
if (!state.redoStack.length) return state;
|
|
212
|
+
const next = state.redoStack[state.redoStack.length - 1];
|
|
213
|
+
return {
|
|
214
|
+
canvas: next,
|
|
215
|
+
redoStack: state.redoStack.slice(0, -1),
|
|
216
|
+
undoStack: [...state.undoStack, state.canvas].slice(-50),
|
|
217
|
+
selections: []
|
|
218
|
+
};
|
|
219
|
+
}),
|
|
207
220
|
addUserMessage: (text) => set((state) => ({
|
|
208
221
|
messages: [...state.messages, { id: `user_${state.messages.length}`, role: "user", text }],
|
|
209
222
|
error: null
|
|
@@ -213,7 +226,9 @@ function createCanvasStore() {
|
|
|
213
226
|
setSelections: (selections) => set({ selections }),
|
|
214
227
|
sendIframeCommand: (command) => set((state) => ({ iframeCommand: { ...command, seq: (state.iframeCommand?.seq ?? 0) + 1 } })),
|
|
215
228
|
setOnUserEdit: (handler) => set({ onUserEdit: handler }),
|
|
216
|
-
|
|
229
|
+
setAssetBaseUrl: (url) => set({ assetBaseUrl: url }),
|
|
230
|
+
// Host configuration (the asset endpoint) survives a session reset.
|
|
231
|
+
reset: () => set({ ...initialState(), assetBaseUrl: get().assetBaseUrl })
|
|
217
232
|
}));
|
|
218
233
|
}
|
|
219
234
|
function foldEvent(state, event) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { isAssetReference, resolveAssetUrl } from './chunk-7T5DRR3F.js';
|
|
2
|
+
import { useCanvasStore } from './chunk-EHW446VF.js';
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
|
|
5
|
+
function useAssetUrl() {
|
|
6
|
+
const assetBaseUrl = useCanvasStore((s) => s.assetBaseUrl);
|
|
7
|
+
return useCallback(
|
|
8
|
+
(src) => src && assetBaseUrl && isAssetReference(src) ? resolveAssetUrl(src, assetBaseUrl) : src,
|
|
9
|
+
[assetBaseUrl]
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { useAssetUrl };
|