@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/index.js CHANGED
@@ -1,10 +1,14 @@
1
1
  "use client";
2
+ import { projectSheetIntoRows, mergeRowsIntoSheet } from './chunk-IFNRLN4Y.js';
2
3
  import { loadOptional } from './chunk-YZZSJJMQ.js';
3
4
  import { resolveElements } from './chunk-6L3AL6W4.js';
4
- export { useArtifactPatch } from './chunk-UL5F66PN.js';
5
- import { useCanvasStoreApi, useCanvasStore } from './chunk-S54GJDSJ.js';
6
- export { CanvasProvider, createCanvasStore, emptyCanvasState, isCanvasEvent, isChatEvent, mergePatch, reduceCanvas, useCanvasStore, useCanvasStoreApi } from './chunk-S54GJDSJ.js';
7
- import { createContext, lazy, useRef, useCallback, useEffect, useContext, useState, useMemo, Suspense, Component } from 'react';
5
+ export { useAssetUrl } from './chunk-FTNRRJ3K.js';
6
+ import { inlineArtifactAssets, inlineHtmlAssets } from './chunk-7T5DRR3F.js';
7
+ export { ASSET_REFERENCE_PREFIXES, fetchAssetDataUri, inlineArtifactAssets, inlineHtmlAssets, isAssetReference, normalizeAssetReference, resolveAssetUrl } from './chunk-7T5DRR3F.js';
8
+ export { useArtifactPatch } from './chunk-K2UZAYW2.js';
9
+ import { useCanvasStoreApi, useCanvasStore } from './chunk-EHW446VF.js';
10
+ export { CanvasProvider, createCanvasStore, emptyCanvasState, isCanvasEvent, isChatEvent, mergePatch, reduceCanvas, useCanvasStore, useCanvasStoreApi } from './chunk-EHW446VF.js';
11
+ import { createContext, lazy, useMemo, useRef, useCallback, useEffect, useContext, useState, Suspense, Component } from 'react';
8
12
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
13
 
10
14
  // src/client/sse-client.ts
@@ -61,9 +65,10 @@ function parseFrame(frame) {
61
65
 
62
66
  // src/client/inspector.ts
63
67
  var INSPECTOR_MARK = "langchain-canvas";
64
- function withInspector(html) {
68
+ function withInspector(html, assetBaseUrl) {
65
69
  let out = withViewport(html);
66
- const injection = `<style data-lcx>${INSPECTOR_CSS}</style><script data-lcx>${INSPECTOR_SCRIPT}</script>`;
70
+ const config = assetBaseUrl ? `<script data-lcx>window.__LCX_ASSET_BASE=${JSON.stringify(assetBaseUrl)}</script>` : "";
71
+ const injection = `<style data-lcx>${INSPECTOR_CSS}</style>${config}<script data-lcx>${INSPECTOR_SCRIPT}</script>`;
67
72
  const marker = "</body>";
68
73
  const at = out.lastIndexOf(marker);
69
74
  out = at === -1 ? out + injection : out.slice(0, at) + injection + out.slice(at);
@@ -131,6 +136,33 @@ var INSPECTOR_SCRIPT = `
131
136
  el.removeAttribute("contenteditable");
132
137
  if (el.classList) { el.classList.remove("lcx-hover"); el.classList.remove("lcx-selected"); }
133
138
  if (el.getAttribute && el.getAttribute("class") === "") el.removeAttribute("class");
139
+ // A display-resolved asset src goes back to its stored relative form.
140
+ var orig = el.getAttribute && el.getAttribute("data-lcx-src");
141
+ if (orig) { el.setAttribute("src", orig); el.removeAttribute("data-lcx-src"); }
142
+ }
143
+ // --- canvas-asset references: resolve for display, keep the source relative ---
144
+ var ASSET_BASE = window.__LCX_ASSET_BASE || "";
145
+ var ASSET_REF = /^(?:\\.\\.?\\/)*(?:assets|sources)\\//;
146
+ // Leading ./ and ../ fold onto the canvas root (assets/ and sources/ exist
147
+ // only there) \u2014 same lenient reading as canvasAssets.normalizeAssetReference.
148
+ function foldAssetRef(s) {
149
+ while (s.lastIndexOf("./", 0) === 0 || s.lastIndexOf("../", 0) === 0) {
150
+ s = s.lastIndexOf("./", 0) === 0 ? s.slice(2) : s.slice(3);
151
+ }
152
+ return s;
153
+ }
154
+ function rewriteAssetSrcs() {
155
+ if (!ASSET_BASE) return;
156
+ var imgs = document.querySelectorAll("img[src]");
157
+ for (var i = 0; i < imgs.length; i++) {
158
+ var el = imgs[i];
159
+ if (el.hasAttribute("data-lcx-src")) continue;
160
+ var src = el.getAttribute("src") || "";
161
+ if (ASSET_REF.test(src)) {
162
+ el.setAttribute("data-lcx-src", src);
163
+ el.setAttribute("src", ASSET_BASE + encodeURIComponent(foldAssetRef(src)));
164
+ }
165
+ }
134
166
  }
135
167
  function emitEdit(el) {
136
168
  // Serialize the *canonical* HTML \u2014 strip the inspector's own injected
@@ -138,7 +170,7 @@ var INSPECTOR_SCRIPT = `
138
170
  var cid = el.getAttribute("data-cid");
139
171
  var clone = el.cloneNode(true);
140
172
  scrub(clone);
141
- var inner = clone.querySelectorAll ? clone.querySelectorAll("[data-cid],[contenteditable],.lcx-hover,.lcx-selected") : [];
173
+ var inner = clone.querySelectorAll ? clone.querySelectorAll("[data-cid],[contenteditable],[data-lcx-src],.lcx-hover,.lcx-selected") : [];
142
174
  for (var i = 0; i < inner.length; i++) scrub(inner[i]);
143
175
  parent.postMessage({ source: MARK, type: "node_edit", cid: cid, html: clone.outerHTML }, "*");
144
176
  }
@@ -153,51 +185,20 @@ var INSPECTOR_SCRIPT = `
153
185
  var clone = document.documentElement.cloneNode(true);
154
186
  var injected = clone.querySelectorAll("[data-lcx]");
155
187
  for (var i = 0; i < injected.length; i++) injected[i].parentNode && injected[i].parentNode.removeChild(injected[i]);
156
- var marked = clone.querySelectorAll("[data-cid],[contenteditable],.lcx-hover,.lcx-selected");
188
+ var marked = clone.querySelectorAll("[data-cid],[contenteditable],[data-lcx-src],.lcx-hover,.lcx-selected");
157
189
  for (var j = 0; j < marked.length; j++) scrub(marked[j]);
158
190
  parent.postMessage({ source: MARK, type: "doc_edit", self: !!selfApplied, html: "<!doctype html>\\n" + clone.outerHTML }, "*");
159
191
  }
160
- // Copy the visual style of an existing element onto a new block, so inserts
161
- // match the page's own design system instead of landing as bare UA-styled
162
- // tags (a default grey <button> on a dark page reads as broken).
163
- function adoptStyleFrom(el, sample) {
164
- if (!sample) return false;
165
- var cs = window.getComputedStyle(sample);
166
- var props = ["background-color", "color", "border", "border-radius", "padding",
167
- "font-family", "font-size", "font-weight", "letter-spacing", "box-shadow", "cursor"];
168
- for (var i = 0; i < props.length; i++) el.style.setProperty(props[i], cs.getPropertyValue(props[i]));
169
- return true;
170
- }
171
192
  function newBlock(tag) {
172
193
  var el = document.createElement(tag);
173
194
  if (tag === "img") {
174
195
  el.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='480' height='270'%3E%3Crect width='100%25' height='100%25' fill='%23e5e7eb'/%3E%3Cpath d='M190 155l40-45 35 40 25-25 40 45z' fill='%23c3c8d0'/%3E%3Ccircle cx='300' cy='105' r='16' fill='%23c3c8d0'/%3E%3C/svg%3E";
175
196
  el.alt = "image"; el.style.maxWidth = "100%";
176
197
  }
177
- else if (tag === "button") {
178
- el.textContent = "Button";
179
- // Match an existing button (skipping editor chrome); else a clean accent default.
180
- var sampleBtn = null;
181
- var btns = document.querySelectorAll("button");
182
- for (var sb = 0; sb < btns.length; sb++) {
183
- if (!btns[sb].closest("[data-lcx]")) { sampleBtn = btns[sb]; break; }
184
- }
185
- if (!adoptStyleFrom(el, sampleBtn)) {
186
- el.style.cssText = "padding:10px 18px;border:0;border-radius:9px;background:#6366f1;color:#fff;font:600 15px/1.2 inherit;cursor:pointer";
187
- }
188
- }
198
+ else if (tag === "button") el.textContent = "Button";
189
199
  else if (tag === "hr") { /* no content */ }
190
200
  else if (tag === "section") { var p = document.createElement("p"); p.textContent = "New section"; el.appendChild(p); }
191
- else {
192
- el.textContent = tag === "h1" || tag === "h2" ? "New heading" : "New text";
193
- // Headings/text adopt an existing peer's look too (font, colour) so they
194
- // don't appear in default serif-black on a styled page.
195
- adoptStyleFrom(el, document.querySelector(tag === "p" ? "main p, section p, div p" : tag));
196
- el.style.removeProperty("border");
197
- el.style.removeProperty("box-shadow");
198
- el.style.removeProperty("cursor");
199
- el.style.removeProperty("background-color");
200
- }
201
+ else el.textContent = tag === "h1" || tag === "h2" ? "New heading" : "New text";
201
202
  return el;
202
203
  }
203
204
  // Floating rich-text toolbar shown while a text element is being edited.
@@ -287,6 +288,15 @@ var INSPECTOR_SCRIPT = `
287
288
  }
288
289
  function start() {
289
290
  assign(document.body, "e");
291
+ // Resolve asset references now and after any change (insert_html, set_src,
292
+ // duplicate). Idempotent: rewritten images carry data-lcx-src and are
293
+ // skipped, so the observer settles after one pass.
294
+ rewriteAssetSrcs();
295
+ if (ASSET_BASE) {
296
+ new MutationObserver(rewriteAssetSrcs).observe(document.documentElement, {
297
+ subtree: true, childList: true, attributes: true, attributeFilter: ["src"]
298
+ });
299
+ }
290
300
  var hovered = null;
291
301
  var selected = []; // currently highlighted elements
292
302
  var marquee = null, sx = 0, sy = 0, dragging = false, moved = false, suppressClick = false;
@@ -294,31 +304,16 @@ var INSPECTOR_SCRIPT = `
294
304
  // into absolute positioning inside its own parent, and its final spot + size are
295
305
  // stored as percentages of that parent \u2014 so it stays put proportionally across
296
306
  // responsive breakpoints, instead of a fixed pixel offset that drifts off.
297
- var dragEls = null, dragStart = null, dragBases = null, parentMutated = false;
307
+ var dragEls = null, dragStart = null, dragBases = null, groupSeq = 0;
298
308
 
299
309
  function ensurePositioned(parent) {
300
- if (!parent || parent === document.documentElement) return;
301
- // body included: a static body makes absolute children resolve against the
302
- // initial containing block (html), so a body margin or html padding shifts
303
- // every free-dragged element \u2014 relative pins them to the body box itself.
304
- if (window.getComputedStyle(parent).position === "static") {
305
- parent.style.position = "relative";
306
- parentMutated = true; // the parent must be persisted too, not just the child
307
- }
308
- }
309
- // Persist a completed free-drag. A single element normally commits as a cheap
310
- // node_edit \u2014 but when its parent was pulled to position:relative that parent
311
- // lives only in the live DOM, so a node-only patch would store an absolute
312
- // child inside a still-static parent (it jumps on the next reload). In that
313
- // case persist the whole document instead.
314
- function commitDrag(els) {
315
- if (els.length === 1 && !parentMutated) emitEdit(els[0]); else emitDoc(true);
310
+ if (!parent || parent === document.body || parent === document.documentElement) return;
311
+ if (window.getComputedStyle(parent).position === "static") parent.style.position = "relative";
316
312
  }
317
313
  // Pull each element out into absolute positioning at its current spot (no visual
318
314
  // jump), so it can then be moved freely.
319
315
  function beginFreeDrag(els) {
320
316
  dragBases = [];
321
- parentMutated = false;
322
317
  for (var i = 0; i < els.length; i++) {
323
318
  var el = els[i], parent = el.parentElement || document.body;
324
319
  ensurePositioned(parent);
@@ -341,16 +336,13 @@ var INSPECTOR_SCRIPT = `
341
336
  }
342
337
  // Commit the current position (in px, as set live by moveFree) as % of the
343
338
  // parent \u2014 position and width \u2014 so it scales with the layout. Falls back to px
344
- // only if the parent has collapsed to zero on that axis. The vertical axis is
345
- // an exception: a container's *height* is content-driven and reflows once the
346
- // dragged element leaves the flow, so a top stored as % of the old height
347
- // lands somewhere else after reload \u2014 top always commits in px.
339
+ // only if the parent has collapsed to zero on that axis.
348
340
  function commitFree() {
349
341
  for (var i = 0; i < dragBases.length; i++) {
350
342
  var b = dragBases[i], pr = b.parent.getBoundingClientRect();
351
343
  var curLeft = parseFloat(b.el.style.left) || 0, curTop = parseFloat(b.el.style.top) || 0;
352
344
  b.el.style.left = pr.width ? ((curLeft / pr.width) * 100).toFixed(3) + "%" : curLeft + "px";
353
- b.el.style.top = Math.round(curTop) + "px";
345
+ b.el.style.top = pr.height ? ((curTop / pr.height) * 100).toFixed(3) + "%" : curTop + "px";
354
346
  if (pr.width) b.el.style.width = ((b.w / pr.width) * 100).toFixed(3) + "%";
355
347
  }
356
348
  }
@@ -507,8 +499,8 @@ var INSPECTOR_SCRIPT = `
507
499
  positionResize();
508
500
  // The new position is already shown in the iframe with every cid intact,
509
501
  // so persist without a reload (no flicker): one element \u2192 node_edit,
510
- // several (or a repositioned parent) \u2192 a self-applied doc_edit.
511
- commitDrag(els);
502
+ // several \u2192 a self-applied doc_edit.
503
+ if (els.length === 1) emitEdit(els[0]); else emitDoc(true);
512
504
  }
513
505
  dragBases = null;
514
506
  return;
@@ -559,7 +551,7 @@ var INSPECTOR_SCRIPT = `
559
551
  // Pointer left the frame mid-drag: commit the move at its last position so
560
552
  // it isn't lost (the element is already placed absolutely in the iframe).
561
553
  var els = dragEls; dragEls = null;
562
- if (moved && dragBases) { commitFree(); commitDrag(els); }
554
+ if (moved && dragBases) { commitFree(); if (els.length === 1) emitEdit(els[0]); else emitDoc(true); }
563
555
  dragBases = null;
564
556
  }
565
557
  if (dragging) {
@@ -630,15 +622,20 @@ var INSPECTOR_SCRIPT = `
630
622
  if (root && d.style) { for (var sk in d.style) { try { root.style[sk] = d.style[sk]; } catch (_e) {} } emitDoc(); }
631
623
  return;
632
624
  }
633
- if (d.type === "set_src") { var ei = byCid(d.cid); if (ei) { ei.setAttribute("src", d.value); emitEdit(ei); } return; }
625
+ if (d.type === "set_src") {
626
+ var ei = byCid(d.cid);
627
+ if (ei) {
628
+ // Drop stale asset bookkeeping first, or scrub would restore the old
629
+ // src over the new one. The observer re-resolves if the new value is
630
+ // itself an asset reference.
631
+ ei.removeAttribute("data-lcx-src");
632
+ ei.setAttribute("src", d.value);
633
+ emitEdit(ei);
634
+ }
635
+ return;
636
+ }
634
637
  if (d.type === "commit") { var el2 = byCid(d.cid); if (el2) emitEdit(el2); return; }
635
638
 
636
- // No-selection inserts land in the slide root when there is one \u2014 a slide
637
- // document is body > .slide-container (fixed 720px), so appending to body
638
- // puts content below the visible slide where it silently never shows.
639
- function insertRoot() {
640
- return document.querySelector(".slide-container") || document.body;
641
- }
642
639
  // Structural edits \u2014 mutate the tree, then persist the whole document.
643
640
  if (d.type === "insert") {
644
641
  var block = newBlock(d.block || "p");
@@ -646,7 +643,7 @@ var INSPECTOR_SCRIPT = `
646
643
  if (anchor && anchor.parentNode && anchor.parentNode !== document.documentElement) {
647
644
  anchor.parentNode.insertBefore(block, anchor.nextSibling);
648
645
  } else {
649
- insertRoot().appendChild(block);
646
+ document.body.appendChild(block);
650
647
  }
651
648
  emitDoc();
652
649
  return;
@@ -654,7 +651,7 @@ var INSPECTOR_SCRIPT = `
654
651
  if (d.type === "insert_html") {
655
652
  // A built-in section template (trusted markup from the toolbar).
656
653
  var anc = d.cid ? byCid(d.cid) : null;
657
- var container = (anc && anc.parentNode && anc.parentNode !== document.documentElement) ? anc.parentNode : insertRoot();
654
+ var container = (anc && anc.parentNode && anc.parentNode !== document.documentElement) ? anc.parentNode : document.body;
658
655
  var ref = (anc && anc.parentNode === container) ? anc.nextSibling : null;
659
656
  var frag = document.createElement("div");
660
657
  frag.innerHTML = d.html || "";
@@ -669,16 +666,7 @@ var INSPECTOR_SCRIPT = `
669
666
  var cids = d.cids || [];
670
667
  for (var g = 0; g < cids.length; g++) { var m = byCid(cids[g]); if (m) members.push(m); }
671
668
  if (members.length < 2) return;
672
- // Next free id is derived from the document, not a counter \u2014 the counter
673
- // reset on every reload, so a second group session reused "g0" and merged
674
- // with the previously-persisted group.
675
- var maxGid = -1;
676
- var existing = document.querySelectorAll("[data-group-id]");
677
- for (var q = 0; q < existing.length; q++) {
678
- var mm = /^g(d+)$/.exec(existing[q].getAttribute("data-group-id") || "");
679
- if (mm && Number(mm[1]) > maxGid) maxGid = Number(mm[1]);
680
- }
681
- var gid = "g" + (maxGid + 1);
669
+ var gid = "g" + (groupSeq++);
682
670
  for (var w = 0; w < members.length; w++) members[w].setAttribute("data-group-id", gid);
683
671
  clearSelected();
684
672
  emitDoc();
@@ -735,8 +723,41 @@ async function* mockStream(events, options = {}) {
735
723
  function sleep(ms) {
736
724
  return new Promise((resolve) => setTimeout(resolve, ms));
737
725
  }
738
- function useCanvasStream(options = {}) {
726
+
727
+ // src/transports/sse.ts
728
+ function sseTransport(options = {}) {
739
729
  const endpoint = options.endpoint ?? "/api/chat";
730
+ return {
731
+ stream(request) {
732
+ const streamOptions = { signal: request.signal, headers: options.headers };
733
+ return streamChat(
734
+ endpoint,
735
+ { threadId: request.threadId, message: request.message, selections: request.selections },
736
+ streamOptions
737
+ );
738
+ }
739
+ };
740
+ }
741
+
742
+ // src/transports/mock.ts
743
+ function mockTransport(script, fallback, options = {}) {
744
+ return {
745
+ stream(request) {
746
+ const events = script(request.message);
747
+ if (events) {
748
+ return mockStream(events, { delayMs: options.delayMs ?? 60, signal: request.signal });
749
+ }
750
+ if (fallback) return fallback.stream(request);
751
+ return mockStream([], { delayMs: 0 });
752
+ }
753
+ };
754
+ }
755
+ function useCanvasStream(options = {}) {
756
+ const { transport: customTransport, endpoint, mock } = options;
757
+ const transport = useMemo(() => {
758
+ const base = customTransport ?? sseTransport({ endpoint });
759
+ return mock ? mockTransport(mock, base) : base;
760
+ }, [customTransport, endpoint, mock]);
740
761
  const threadIdRef = useRef(options.threadId ?? crypto.randomUUID());
741
762
  const abortRef = useRef(null);
742
763
  const api = useCanvasStoreApi();
@@ -775,12 +796,12 @@ function useCanvasStream(options = {}) {
775
796
  const controller = new AbortController();
776
797
  abortRef.current = controller;
777
798
  try {
778
- const mockEvents = options.mock?.(text) ?? null;
779
- const stream = mockEvents ? mockStream(mockEvents, { delayMs: 60, signal: controller.signal }) : streamChat(
780
- endpoint,
781
- { threadId: threadIdRef.current, message: text, selections: withSelections },
782
- { signal: controller.signal }
783
- );
799
+ const stream = transport.stream({
800
+ threadId: threadIdRef.current,
801
+ message: text,
802
+ selections: withSelections,
803
+ signal: controller.signal
804
+ });
784
805
  for await (const event of stream) {
785
806
  enqueue(event);
786
807
  }
@@ -793,7 +814,7 @@ function useCanvasStream(options = {}) {
793
814
  api.getState().setStreaming(false);
794
815
  }
795
816
  },
796
- [api, endpoint, enqueue, flush, options.mock]
817
+ [api, transport, enqueue, flush]
797
818
  );
798
819
  const stop = useCallback(() => abortRef.current?.abort(), []);
799
820
  const reset = useCallback(() => api.getState().reset(), [api]);
@@ -841,7 +862,7 @@ function useCanvasReplay() {
841
862
  api.getState().applyEvent(event);
842
863
  }
843
864
  } finally {
844
- if (abortRef.current === controller) api.getState().setStreaming(false);
865
+ api.getState().setStreaming(false);
845
866
  }
846
867
  },
847
868
  [api]
@@ -850,6 +871,39 @@ function useCanvasReplay() {
850
871
  const reset = useCallback(() => api.getState().reset(), [api]);
851
872
  return { play, stop, reset, canvas, isPlaying };
852
873
  }
874
+ var DEFAULT_DEBOUNCE_MS = 800;
875
+ function useCanvasSave(onSave, debounceMs = DEFAULT_DEBOUNCE_MS) {
876
+ const timers = useRef(/* @__PURE__ */ new Map());
877
+ const latest = useRef(/* @__PURE__ */ new Map());
878
+ const handler = useRef(onSave);
879
+ handler.current = onSave;
880
+ const enabled = Boolean(onSave);
881
+ useEffect(() => {
882
+ const pending = timers.current;
883
+ return () => pending.forEach((t) => clearTimeout(t));
884
+ }, []);
885
+ return useMemo(() => {
886
+ if (!enabled) return null;
887
+ return (artifact2) => {
888
+ latest.current.set(artifact2.id, artifact2);
889
+ const existing = timers.current.get(artifact2.id);
890
+ if (existing) clearTimeout(existing);
891
+ timers.current.set(
892
+ artifact2.id,
893
+ setTimeout(() => {
894
+ timers.current.delete(artifact2.id);
895
+ const current = latest.current.get(artifact2.id);
896
+ if (!current || !handler.current) return;
897
+ void handler.current({
898
+ artifactId: current.id,
899
+ artifact: current,
900
+ baseRevision: typeof current.meta?.revision === "string" ? current.meta.revision : null
901
+ });
902
+ }, debounceMs)
903
+ );
904
+ };
905
+ }, [enabled, debounceMs]);
906
+ }
853
907
 
854
908
  // src/fixtures/scenarios.ts
855
909
  var PRICING_HTML = `<!doctype html>
@@ -1021,29 +1075,32 @@ var slides = {
1021
1075
  { type: "done" }
1022
1076
  ]
1023
1077
  };
1024
- var PDF_DATA_URL = "data:application/pdf;base64,JVBERi0xLjQKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqCjIgMCBvYmo8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iagozIDAgb2JqPDwvVHlwZS9QYWdlL1BhcmVudCAyIDAgUi9NZWRpYUJveFswIDAgNjEyIDc5Ml0vQ29udGVudHMgNCAwIFIvUmVzb3VyY2VzPDwvRm9udDw8L0YxIDUgMCBSPj4+Pj4+ZW5kb2JqCjQgMCBvYmo8PC9MZW5ndGggODA+PnN0cmVhbQpCVCAvRjEgMjggVGYgNzIgNzAwIFRkIChsYW5nY2hhaW4tY2FudmFzIFBERiB2aWV3ZXIpIFRqIEVUCmVuZHN0cmVhbQplbmRvYmoKNSAwIG9iajw8L1R5cGUvRm9udC9TdWJ0eXBlL1R5cGUxL0Jhc2VGb250L0hlbHZldGljYT4+ZW5kb2JqCnhyZWYKMCA2CnRyYWlsZXI8PC9TaXplIDYvUm9vdCAxIDAgUj4+CiUlRU9GCg==";
1025
- var pdf = {
1026
- id: "pdf",
1027
- title: "PDF viewer",
1028
- description: "A PDF artifact shown in the browser's built-in viewer \u2014 zero dependencies.",
1078
+ var VERSIONED_HTML = `<!doctype html><html><body style="font-family:sans-serif;padding:40px">
1079
+ <h1>Coffee history</h1><p>From the Ethiopian highlands to the espresso bar.</p>
1080
+ </body></html>`;
1081
+ var versions = {
1082
+ id: "versions",
1083
+ title: "Version history",
1084
+ description: "An agent builds a page, a user edit and an agent edit each land as described commits \u2014 open the version rail to browse and restore-view snapshots.",
1029
1085
  events: [
1030
- { type: "message.delta", messageId: "m-pdf", text: "Here's the signed report as a PDF." },
1086
+ { type: "message.delta", messageId: "m1", text: "Built the page \u2014 every change now lands in the version history." },
1087
+ { type: "message.end", messageId: "m1" },
1031
1088
  {
1032
1089
  type: "canvas.create",
1033
- artifact: {
1034
- id: "report-pdf",
1035
- type: "pdf",
1036
- title: "Signed report",
1037
- version: 1,
1038
- status: "complete",
1039
- data: { src: PDF_DATA_URL, filename: "signed-report.pdf" }
1040
- }
1090
+ artifact: { id: "page", type: "html", title: "Coffee history", version: 1, status: "streaming", data: { html: VERSIONED_HTML } }
1041
1091
  },
1042
- { type: "canvas.status", id: "report-pdf", status: "complete" },
1092
+ { type: "canvas.status", id: "page", status: "complete" },
1093
+ { type: "canvas.commit", id: "page", description: "Create page", revision: "v1" },
1094
+ // A human tweaks the headline by hand, then saves — one described commit.
1095
+ { type: "canvas.patch", id: "page", patch: { html: VERSIONED_HTML.replace("Coffee history", "A short history of coffee") } },
1096
+ { type: "canvas.commit", id: "page", description: "Manual edit: 1 change", revision: "v2" },
1097
+ // The agent applies a targeted follow-up edit on the current state.
1098
+ { type: "canvas.patch", id: "page", patch: { html: VERSIONED_HTML.replace("Coffee history", "A short history of coffee").replace("espresso bar", "third-wave caf\xE9") } },
1099
+ { type: "canvas.commit", id: "page", description: "Update closing phrase", revision: "v3" },
1043
1100
  { type: "done" }
1044
1101
  ]
1045
1102
  };
1046
- var scenarios = [htmlPage, document2, chart, table, slides, pdf];
1103
+ var scenarios = [htmlPage, document2, chart, table, slides, versions];
1047
1104
  var RegistryContext = createContext({});
1048
1105
  function CanvasRegistryProvider({ registry, children }) {
1049
1106
  return /* @__PURE__ */ jsx(RegistryContext.Provider, { value: registry, children });
@@ -1189,202 +1246,40 @@ function display(value) {
1189
1246
  if (value == null) return "";
1190
1247
  if (typeof value === "object") {
1191
1248
  if (value.result != null) return display(value.result);
1192
- if (Array.isArray(value.richText)) return value.richText.map((r) => r?.text ?? "").join("");
1193
1249
  if (typeof value.text === "string") return value.text;
1194
1250
  if (value instanceof Date) return formatDate(value, "yyyy-mm-dd");
1195
1251
  return "";
1196
1252
  }
1197
1253
  return String(value);
1198
1254
  }
1199
- function splitSections(fmt) {
1200
- const sections = [];
1201
- let cur = "";
1202
- for (let i = 0; i < fmt.length; i++) {
1203
- const ch = fmt[i];
1204
- if (ch === '"') {
1205
- cur += ch;
1206
- i++;
1207
- while (i < fmt.length && fmt[i] !== '"') cur += fmt[i++];
1208
- if (i < fmt.length) cur += fmt[i];
1209
- continue;
1210
- }
1211
- if (ch === "[") {
1212
- cur += ch;
1213
- i++;
1214
- while (i < fmt.length && fmt[i] !== "]") cur += fmt[i++];
1215
- if (i < fmt.length) cur += fmt[i];
1216
- continue;
1217
- }
1218
- if (ch === "\\") {
1219
- cur += ch + (fmt[i + 1] ?? "");
1220
- i++;
1221
- continue;
1222
- }
1223
- if (ch === ";") {
1224
- sections.push(cur);
1225
- cur = "";
1226
- continue;
1227
- }
1228
- cur += ch;
1229
- }
1230
- sections.push(cur);
1231
- return sections;
1232
- }
1233
- function numberSkeleton(fmt) {
1234
- let currency = "";
1235
- let currencyTrails = false;
1236
- let skeleton = "";
1237
- let seenDigit = false;
1238
- const found = (sym) => {
1239
- if (currency) return;
1240
- currency = sym;
1241
- currencyTrails = seenDigit;
1242
- };
1243
- for (let i = 0; i < fmt.length; i++) {
1244
- const ch = fmt[i];
1245
- if (ch === '"') {
1246
- let lit = "";
1247
- i++;
1248
- while (i < fmt.length && fmt[i] !== '"') lit += fmt[i++];
1249
- if (/[$₩€£¥]/.test(lit)) found(lit);
1250
- continue;
1251
- }
1252
- if (ch === "\\") {
1253
- const c = fmt[i + 1] ?? "";
1254
- if (/[$₩€£¥]/.test(c)) found(c);
1255
- i++;
1256
- continue;
1257
- }
1258
- if (ch === "[") {
1259
- const end = fmt.indexOf("]", i);
1260
- const group = fmt.slice(i + 1, end === -1 ? void 0 : end);
1261
- const cur = /^\$(.*?)-/.exec(group)?.[1] ?? (group.startsWith("$") ? group.slice(1) : "");
1262
- if (cur) found(cur);
1263
- i = end === -1 ? fmt.length : end;
1264
- continue;
1265
- }
1266
- if (ch === "_" || ch === "*") {
1267
- i++;
1268
- continue;
1269
- }
1270
- if (/[$₩€£¥]/.test(ch)) {
1271
- found(ch);
1272
- continue;
1273
- }
1274
- if (ch === "0" || ch === "#") seenDigit = true;
1275
- skeleton += ch;
1276
- }
1277
- return { skeleton, currency, currencyTrails };
1278
- }
1279
1255
  function formatNumber(value, numFmt) {
1280
- const raw = numFmt && numFmt !== "General" ? numFmt : "";
1281
- if (!raw) return String(value);
1282
- const sections = splitSections(raw);
1283
- const negSection = value < 0 && sections.length > 1 ? sections[1] : void 0;
1284
- const section = negSection ?? (value === 0 && sections[2] ? sections[2] : sections[0]);
1285
- const { skeleton, currency, currencyTrails } = numberSkeleton(section);
1286
- if (!/[0#]/.test(skeleton)) return String(value);
1287
- const decimals = (skeleton.match(/\.([0#]+)/)?.[1] ?? "").length;
1288
- const thousands = /[#0],[#0]/.test(skeleton);
1289
- const n = negSection ? Math.abs(value) : value;
1290
- const percents = (skeleton.match(/%/g) ?? []).length;
1291
- const scaled = percents ? n * 100 ** percents : n;
1292
- let out = thousands ? scaled.toLocaleString("en-US", { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) : scaled.toFixed(decimals);
1293
- if (percents) out += "%";
1294
- if (currency) {
1295
- if (currencyTrails) out = `${out}${currency}`;
1296
- else if (out.startsWith("-")) out = `-${currency}${out.slice(1)}`;
1297
- else out = `${currency}${out}`;
1298
- }
1299
- if (negSection && /\(/.test(skeleton)) out = `(${out})`;
1256
+ const fmt = numFmt && numFmt !== "General" ? numFmt : "";
1257
+ if (!fmt) return String(value);
1258
+ const decimals = (fmt.match(/\.([0#]+)/)?.[1] ?? "").length;
1259
+ if (fmt.includes("%")) return `${(value * 100).toFixed(decimals)}%`;
1260
+ const thousands = /[#0],[#0]/.test(fmt);
1261
+ let out = thousands ? value.toLocaleString("en-US", { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) : value.toFixed(decimals);
1262
+ const currency = fmt.match(/[$₩€£¥]/);
1263
+ if (currency) out = value < 0 ? `-${currency[0]}${out.slice(1)}` : `${currency[0]}${out}`;
1300
1264
  return out;
1301
1265
  }
1302
- var EXCEL_EPOCH_MS = Date.UTC(1899, 11, 30);
1303
1266
  function formatDate(d, numFmt) {
1304
- const fmt = numFmt && numFmt !== "General" && /[ymdhs]/i.test(numFmt) ? splitSections(numFmt)[0] : "yyyy-mm-dd";
1267
+ const fmt = numFmt && numFmt !== "General" && /[ymdhs]/i.test(numFmt) ? numFmt : "yyyy-mm-dd";
1305
1268
  const p = (n, w = 2) => String(n).padStart(w, "0");
1306
- const meridiem = /AM\/PM|A\/P/i.test(fmt);
1307
- const H = d.getUTCHours();
1308
- const hour12 = H % 12 === 0 ? 12 : H % 12;
1309
- const hourText = (width) => meridiem ? p(hour12, width) : p(H, width);
1310
- const elapsedMs = d.getTime() - EXCEL_EPOCH_MS;
1311
1269
  const map = {
1312
- yyyy: () => String(d.getUTCFullYear()),
1313
- yy: () => p(d.getUTCFullYear() % 100),
1314
- mmmm: () => d.toLocaleString("en-US", { month: "long", timeZone: "UTC" }),
1315
- mmm: () => d.toLocaleString("en-US", { month: "short", timeZone: "UTC" }),
1316
- dd: () => p(d.getUTCDate()),
1317
- d: () => String(d.getUTCDate()),
1318
- hh: () => hourText(2),
1319
- h: () => hourText(1),
1320
- ss: () => p(d.getUTCSeconds()),
1321
- s: () => String(d.getUTCSeconds())
1270
+ yyyy: String(d.getFullYear()),
1271
+ yy: p(d.getFullYear() % 100),
1272
+ mmmm: d.toLocaleString("en-US", { month: "long" }),
1273
+ mmm: d.toLocaleString("en-US", { month: "short" }),
1274
+ mm: p(d.getMonth() + 1),
1275
+ m: String(d.getMonth() + 1),
1276
+ dd: p(d.getDate()),
1277
+ d: String(d.getDate()),
1278
+ hh: p(d.getHours()),
1279
+ h: String(d.getHours()),
1280
+ ss: p(d.getSeconds())
1322
1281
  };
1323
- const month = (t) => t === "mm" ? p(d.getUTCMonth() + 1) : String(d.getUTCMonth() + 1);
1324
- const minute = (t) => t === "mm" ? p(d.getUTCMinutes()) : String(d.getUTCMinutes());
1325
- const TOKEN = /AM\/PM|A\/P|yyyy|yy|mmmm|mmm|mm|m|dd|d|hh|h|ss|s/iy;
1326
- let out = "";
1327
- let lastWasHours = false;
1328
- for (let i = 0; i < fmt.length; ) {
1329
- const ch = fmt[i];
1330
- if (ch === '"') {
1331
- i++;
1332
- while (i < fmt.length && fmt[i] !== '"') out += fmt[i++];
1333
- i++;
1334
- continue;
1335
- }
1336
- if (ch === "\\") {
1337
- out += fmt[i + 1] ?? "";
1338
- i += 2;
1339
- continue;
1340
- }
1341
- if (ch === "[") {
1342
- const end = fmt.indexOf("]", i);
1343
- const group = fmt.slice(i + 1, end === -1 ? fmt.length : end);
1344
- i = end === -1 ? fmt.length : end + 1;
1345
- if (/^h+$/i.test(group)) {
1346
- out += p(Math.floor(elapsedMs / 36e5), group.length);
1347
- lastWasHours = true;
1348
- } else if (/^m+$/i.test(group)) out += p(Math.floor(elapsedMs / 6e4), group.length);
1349
- else if (/^s+$/i.test(group)) out += p(Math.floor(elapsedMs / 1e3), group.length);
1350
- continue;
1351
- }
1352
- if (ch === "_") {
1353
- out += " ";
1354
- i += 2;
1355
- continue;
1356
- }
1357
- if (ch === "*") {
1358
- i += 2;
1359
- continue;
1360
- }
1361
- TOKEN.lastIndex = i;
1362
- const m = TOKEN.exec(fmt);
1363
- if (m) {
1364
- const tok = m[0].toLowerCase();
1365
- i += m[0].length;
1366
- if (tok === "am/pm") {
1367
- out += H < 12 ? "AM" : "PM";
1368
- continue;
1369
- }
1370
- if (tok === "a/p") {
1371
- out += H < 12 ? "A" : "P";
1372
- continue;
1373
- }
1374
- if (tok === "m" || tok === "mm") {
1375
- const minutes = lastWasHours || /^[\s:.,\-]*s/i.test(fmt.slice(i));
1376
- out += minutes ? minute(tok) : month(tok);
1377
- lastWasHours = false;
1378
- continue;
1379
- }
1380
- lastWasHours = tok === "h" || tok === "hh";
1381
- out += map[tok]();
1382
- continue;
1383
- }
1384
- out += ch;
1385
- i++;
1386
- }
1387
- return out;
1282
+ return fmt.replace(/yyyy|yy|mmmm|mmm|mm|m|dd|d|hh|h|ss/g, (t) => map[t] ?? t);
1388
1283
  }
1389
1284
  function cellValue(cell) {
1390
1285
  const raw = cell.value;
@@ -1583,7 +1478,6 @@ function cellVal(v) {
1583
1478
  if (typeof v === "number" || typeof v === "string") return v;
1584
1479
  if (typeof v === "object") {
1585
1480
  if (v.result != null) return cellVal(v.result);
1586
- if (Array.isArray(v.richText)) return v.richText.map((r) => r?.text ?? "").join("");
1587
1481
  if (typeof v.text === "string") return v.text;
1588
1482
  if (v instanceof Date) return v.toISOString().slice(0, 10);
1589
1483
  }
@@ -1626,416 +1520,8 @@ function flatten(ws) {
1626
1520
  return { columns, rows };
1627
1521
  }
1628
1522
 
1629
- // src/io/hwpx.ts
1630
- var EOCD_SIG = 101010256;
1631
- var CDIR_SIG = 33639248;
1632
- var LOCAL_SIG = 67324752;
1633
- async function inflateRaw(bytes) {
1634
- if (typeof DecompressionStream === "undefined") {
1635
- throw new Error("\uC774 \uBE0C\uB77C\uC6B0\uC800\uB294 \uC555\uCD95 \uD574\uC81C\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (DecompressionStream unavailable).");
1636
- }
1637
- const ds = new DecompressionStream("deflate-raw");
1638
- const writer = ds.writable.getWriter();
1639
- const wrote = writer.write(bytes).then(() => writer.close());
1640
- const reader = ds.readable.getReader();
1641
- const chunks = [];
1642
- let total = 0;
1643
- for (; ; ) {
1644
- const { done, value } = await reader.read();
1645
- if (done) break;
1646
- chunks.push(value);
1647
- total += value.length;
1648
- }
1649
- await wrote;
1650
- const out = new Uint8Array(total);
1651
- let pos = 0;
1652
- for (const c of chunks) {
1653
- out.set(c, pos);
1654
- pos += c.length;
1655
- }
1656
- return out;
1657
- }
1658
- async function readZip(buffer) {
1659
- const view = new DataView(buffer);
1660
- const bytes = new Uint8Array(buffer);
1661
- let eocd = -1;
1662
- const floor = Math.max(0, buffer.byteLength - 22 - 65535);
1663
- for (let i = buffer.byteLength - 22; i >= floor; i--) {
1664
- if (view.getUint32(i, true) === EOCD_SIG) {
1665
- eocd = i;
1666
- break;
1667
- }
1668
- }
1669
- if (eocd === -1) throw new Error("ZIP \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4 (no end-of-central-directory).");
1670
- const count = view.getUint16(eocd + 10, true);
1671
- let offset = view.getUint32(eocd + 16, true);
1672
- const entries = /* @__PURE__ */ new Map();
1673
- const decoder = new TextDecoder();
1674
- for (let i = 0; i < count; i++) {
1675
- if (offset + 46 > buffer.byteLength || view.getUint32(offset, true) !== CDIR_SIG) {
1676
- throw new Error("ZIP \uC911\uC559 \uB514\uB809\uD130\uB9AC\uAC00 \uC190\uC0C1\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (corrupt central directory).");
1677
- }
1678
- const method = view.getUint16(offset + 10, true);
1679
- const compressedSize = view.getUint32(offset + 20, true);
1680
- const uncompressedSize = view.getUint32(offset + 24, true);
1681
- const nameLen = view.getUint16(offset + 28, true);
1682
- const extraLen = view.getUint16(offset + 30, true);
1683
- const commentLen = view.getUint16(offset + 32, true);
1684
- const localOffset = view.getUint32(offset + 42, true);
1685
- const name = decoder.decode(bytes.subarray(offset + 46, offset + 46 + nameLen));
1686
- offset += 46 + nameLen + extraLen + commentLen;
1687
- if (compressedSize === 4294967295 || uncompressedSize === 4294967295) {
1688
- throw new Error("Zip64 \uD615\uC2DD\uC740 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (Zip64 not supported).");
1689
- }
1690
- if (name.endsWith("/")) continue;
1691
- if (localOffset + 30 > buffer.byteLength || view.getUint32(localOffset, true) !== LOCAL_SIG) {
1692
- throw new Error("ZIP \uB85C\uCEEC \uD5E4\uB354\uAC00 \uC190\uC0C1\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (corrupt local header).");
1693
- }
1694
- const localNameLen = view.getUint16(localOffset + 26, true);
1695
- const localExtraLen = view.getUint16(localOffset + 28, true);
1696
- const start = localOffset + 30 + localNameLen + localExtraLen;
1697
- const data = bytes.subarray(start, start + compressedSize);
1698
- if (method === 0) entries.set(name, data);
1699
- else if (method === 8) entries.set(name, await inflateRaw(data));
1700
- else throw new Error(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC555\uCD95 \uBC29\uC2DD\uC785\uB2C8\uB2E4 (compression method ${method}).`);
1701
- }
1702
- return entries;
1703
- }
1704
- function paragraphText(p) {
1705
- let out = "";
1706
- const walk = (el) => {
1707
- for (const child of Array.from(el.children)) {
1708
- const tag = child.localName;
1709
- if (tag === "tbl") continue;
1710
- if (tag === "t") out += child.textContent ?? "";
1711
- else if (tag === "lineBreak") out += "\n";
1712
- else walk(child);
1713
- }
1714
- };
1715
- walk(p);
1716
- return out;
1717
- }
1718
- function tableToMarkdown(tbl) {
1719
- const rows = [];
1720
- for (const tr of Array.from(tbl.getElementsByTagNameNS("*", "tr"))) {
1721
- if (tr.closest("tbl") !== tbl) continue;
1722
- const cells = [];
1723
- for (const tc of Array.from(tr.children)) {
1724
- if (tc.localName !== "tc") continue;
1725
- const text = Array.from(tc.getElementsByTagNameNS("*", "p")).map((p) => paragraphText(p).trim()).filter(Boolean).join(" ").replace(/\|/g, "\\|").replace(/\n/g, " ");
1726
- cells.push(text);
1727
- }
1728
- if (cells.length) rows.push(cells);
1729
- }
1730
- if (!rows.length) return "";
1731
- const width = Math.max(...rows.map((r) => r.length));
1732
- const pad = (r) => Array.from({ length: width }, (_, i) => r[i] ?? "");
1733
- const line = (r) => `| ${pad(r).join(" | ")} |`;
1734
- const [head, ...body] = rows;
1735
- return [line(head), `| ${Array(width).fill("---").join(" | ")} |`, ...body.map(line)].join("\n");
1736
- }
1737
- function sectionToMarkdown(xml) {
1738
- const doc = new DOMParser().parseFromString(xml, "application/xml");
1739
- if (doc.querySelector("parsererror")) {
1740
- throw new Error("HWPX \uBCF8\uBB38 XML\uC744 \uD574\uC11D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (invalid section XML).");
1741
- }
1742
- const blocks = [];
1743
- const walk = (el) => {
1744
- for (const child of Array.from(el.children)) {
1745
- const tag = child.localName;
1746
- if (tag === "p") {
1747
- const text = paragraphText(child).trim();
1748
- if (text) blocks.push(text);
1749
- for (const tbl of Array.from(child.getElementsByTagNameNS("*", "tbl"))) {
1750
- if ((tbl.parentElement?.closest("tbl") ?? null) === null) {
1751
- const md = tableToMarkdown(tbl);
1752
- if (md) blocks.push(md);
1753
- }
1754
- }
1755
- } else if (tag !== "tbl") {
1756
- walk(child);
1757
- }
1758
- }
1759
- };
1760
- walk(doc.documentElement);
1761
- return blocks;
1762
- }
1763
- async function hwpxToMarkdown(buffer) {
1764
- const entries = await readZip(buffer);
1765
- const sections = [...entries.keys()].filter((n) => /^Contents\/section\d+\.xml$/i.test(n)).sort((a, b) => Number(/(\d+)/.exec(a)?.[1] ?? 0) - Number(/(\d+)/.exec(b)?.[1] ?? 0));
1766
- if (!sections.length) {
1767
- throw new Error("HWPX \uBB38\uC11C\uAC00 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uBCF8\uBB38(section XML)\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 (no Contents/section*.xml).");
1768
- }
1769
- const decoder = new TextDecoder();
1770
- const blocks = [];
1771
- for (const name of sections) {
1772
- blocks.push(...sectionToMarkdown(decoder.decode(entries.get(name))));
1773
- }
1774
- return blocks.join("\n\n").trim();
1775
- }
1776
-
1777
- // src/io/hwp.ts
1778
- var ERR = {
1779
- notCfb: "CFB(OLE) \uCEE8\uD14C\uC774\uB108 \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uC62C\uBC14\uB978 HWP \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4. / Not a CFB (OLE) compound file container \u2014 not a valid HWP file.",
1780
- notHwp: '"HWP Document File" \uC2DC\uADF8\uB2C8\uCC98\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 HWP \uBB38\uC11C\uAC00 \uC544\uB2D9\uB2C8\uB2E4. / Missing "HWP Document File" signature \u2014 not an HWP document.',
1781
- encrypted: "\uC554\uD638\uB85C \uBCF4\uD638\uB41C HWP \uBB38\uC11C\uC785\uB2C8\uB2E4 \u2014 \uD55C\uAE00\uC5D0\uC11C \uC554\uD638\uB97C \uD574\uC81C\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694. / This HWP document is password-encrypted \u2014 remove the password in Hangul and try again.",
1782
- drm: "\uBC30\uD3EC\uC6A9(DRM) HWP \uBB38\uC11C\uC785\uB2C8\uB2E4 \u2014 \uBCF8\uBB38\uC774 \uB09C\uB3C5\uD654\uB418\uC5B4 \uC788\uC5B4 \uD14D\uC2A4\uD2B8\uB97C \uCD94\uCD9C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. / This is a distribution (DRM) HWP document \u2014 its body text is obfuscated and cannot be extracted.",
1783
- legacy: (version) => `\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 HWP \uBC84\uC804\uC785\uB2C8\uB2E4 (${version} < 5.0) \u2014 \uD55C\uAE00\uC5D0\uC11C HWP 5.0 \uC774\uC0C1\uC73C\uB85C \uC800\uC7A5\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694. / Unsupported HWP version (${version} < 5.0) \u2014 re-save as HWP 5.0+ in Hangul and try again.`,
1784
- corrupt: (detail) => `\uC190\uC0C1\uB41C HWP \uD30C\uC77C\uC785\uB2C8\uB2E4 (${detail}). / Corrupt HWP file (${detail}).`,
1785
- noInflate: "\uC774 \uD658\uACBD\uC5D0\uC11C\uB294 DecompressionStream\uC744 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 \uC555\uCD95\uB41C HWP\uB97C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. / DecompressionStream is unavailable in this environment, so compressed HWP files cannot be opened."
1786
- };
1787
- var ENDOFCHAIN = 4294967294;
1788
- var CFB_SIGNATURE = [208, 207, 17, 224, 161, 177, 26, 225];
1789
- function parseCfb(bytes) {
1790
- if (bytes.length < 512 || CFB_SIGNATURE.some((b, i) => bytes[i] !== b)) {
1791
- throw new Error(ERR.notCfb);
1792
- }
1793
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1794
- const sectorSize = 1 << view.getUint16(30, true);
1795
- const miniSectorSize = 1 << view.getUint16(32, true);
1796
- const numFatSectors = view.getUint32(44, true);
1797
- const firstDirSector = view.getUint32(48, true);
1798
- const miniCutoff = view.getUint32(56, true);
1799
- const firstMiniFatSector = view.getUint32(60, true);
1800
- const firstDifatSector = view.getUint32(68, true);
1801
- const totalSectors = Math.max(0, Math.ceil((bytes.length - 512) / sectorSize));
1802
- const sectorBytes = (sector) => {
1803
- const start = 512 + sector * sectorSize;
1804
- if (sector >= totalSectors || start >= bytes.length) {
1805
- throw new Error(ERR.corrupt("sector out of range"));
1806
- }
1807
- return bytes.subarray(start, start + sectorSize);
1808
- };
1809
- const fatSectorIds = [];
1810
- for (let i = 0; i < 109 && fatSectorIds.length < numFatSectors; i++) {
1811
- fatSectorIds.push(view.getUint32(76 + i * 4, true));
1812
- }
1813
- let difatSector = firstDifatSector;
1814
- let difatHops = 0;
1815
- while (difatSector !== ENDOFCHAIN && difatSector < 4294967290) {
1816
- if (difatHops++ > totalSectors) throw new Error(ERR.corrupt("DIFAT chain loops"));
1817
- const s = sectorBytes(difatSector);
1818
- const sv = new DataView(s.buffer, s.byteOffset, s.byteLength);
1819
- const perSector = sectorSize / 4 - 1;
1820
- for (let i = 0; i < perSector && fatSectorIds.length < numFatSectors; i++) {
1821
- fatSectorIds.push(sv.getUint32(i * 4, true));
1822
- }
1823
- difatSector = sv.getUint32(sectorSize - 4, true);
1824
- }
1825
- const fat = new Uint32Array(fatSectorIds.length * (sectorSize / 4));
1826
- fatSectorIds.forEach((id, idx) => {
1827
- const s = sectorBytes(id);
1828
- const sv = new DataView(s.buffer, s.byteOffset, s.byteLength);
1829
- for (let i = 0; i < sectorSize / 4; i++) fat[idx * (sectorSize / 4) + i] = sv.getUint32(i * 4, true);
1830
- });
1831
- const readChain = (start, size) => {
1832
- const out = new Uint8Array(size);
1833
- let sector = start;
1834
- let written = 0;
1835
- let hops = 0;
1836
- while (sector !== ENDOFCHAIN && written < size) {
1837
- if (hops++ > totalSectors || sector >= fat.length) throw new Error(ERR.corrupt("FAT chain loops"));
1838
- const chunk = sectorBytes(sector);
1839
- out.set(chunk.subarray(0, Math.min(sectorSize, size - written)), written);
1840
- written += sectorSize;
1841
- sector = fat[sector];
1842
- }
1843
- return out;
1844
- };
1845
- const dirSectors = [];
1846
- let dirSector = firstDirSector;
1847
- let dirHops = 0;
1848
- while (dirSector !== ENDOFCHAIN) {
1849
- if (dirHops++ > totalSectors || dirSector >= fat.length) throw new Error(ERR.corrupt("directory chain loops"));
1850
- dirSectors.push(sectorBytes(dirSector));
1851
- dirSector = fat[dirSector];
1852
- }
1853
- const entries = [];
1854
- for (const sector of dirSectors) {
1855
- const sv = new DataView(sector.buffer, sector.byteOffset, sector.byteLength);
1856
- for (let off = 0; off + 128 <= sector.length; off += 128) {
1857
- const nameLen = sv.getUint16(off + 64, true);
1858
- const type = sv.getUint8(off + 66);
1859
- if (type === 0 || nameLen < 2 || nameLen > 64) continue;
1860
- let name = "";
1861
- for (let i = 0; i < nameLen - 2; i += 2) name += String.fromCharCode(sv.getUint16(off + i, true));
1862
- entries.push({
1863
- name,
1864
- type,
1865
- startSector: sv.getUint32(off + 116, true),
1866
- // Size is a uint64, but HWP streams are far below 4 GB — the low half suffices.
1867
- size: sv.getUint32(off + 120, true)
1868
- });
1869
- }
1870
- }
1871
- const root = entries.find((e) => e.type === 5);
1872
- if (!root) throw new Error(ERR.corrupt("missing root directory entry"));
1873
- let miniStream = null;
1874
- let miniFat = null;
1875
- const lazyMini = () => {
1876
- if (miniStream && miniFat) return;
1877
- miniStream = readChain(root.startSector, root.size);
1878
- const miniFatSectors = Math.ceil(root.size / miniSectorSize) + 1;
1879
- const raw = readChain(firstMiniFatSector, miniFatSectors * 4 + sectorSize);
1880
- const rv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
1881
- miniFat = new Uint32Array(Math.floor(raw.length / 4));
1882
- for (let i = 0; i < miniFat.length; i++) miniFat[i] = rv.getUint32(i * 4, true);
1883
- };
1884
- const readMiniChain = (start, size) => {
1885
- lazyMini();
1886
- const out = new Uint8Array(size);
1887
- let sector = start;
1888
- let written = 0;
1889
- let hops = 0;
1890
- const maxMini = Math.ceil(miniStream.length / miniSectorSize);
1891
- while (sector !== ENDOFCHAIN && written < size) {
1892
- if (hops++ > maxMini || sector >= miniFat.length || sector >= maxMini) {
1893
- throw new Error(ERR.corrupt("mini FAT chain loops"));
1894
- }
1895
- const at = sector * miniSectorSize;
1896
- out.set(miniStream.subarray(at, at + Math.min(miniSectorSize, size - written)), written);
1897
- written += miniSectorSize;
1898
- sector = miniFat[sector];
1899
- }
1900
- return out;
1901
- };
1902
- return {
1903
- entries,
1904
- readStream: (entry) => entry.size < miniCutoff && entry.type !== 5 ? readMiniChain(entry.startSector, entry.size) : readChain(entry.startSector, entry.size)
1905
- };
1906
- }
1907
- var HWP_SIGNATURE = "HWP Document File";
1908
- function parseFileHeader(bytes) {
1909
- if (bytes.length < 40) throw new Error(ERR.notHwp);
1910
- let sig = "";
1911
- for (let i = 0; i < HWP_SIGNATURE.length; i++) sig += String.fromCharCode(bytes[i]);
1912
- if (sig !== HWP_SIGNATURE) throw new Error(ERR.notHwp);
1913
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1914
- const version = view.getUint32(32, true);
1915
- const major = version >>> 24 & 255;
1916
- if (major < 5) {
1917
- throw new Error(ERR.legacy(`${major}.${version >>> 16 & 255}`));
1918
- }
1919
- const flags = view.getUint32(36, true);
1920
- if (flags & 2) throw new Error(ERR.encrypted);
1921
- if (flags & 4) throw new Error(ERR.drm);
1922
- return { compressed: (flags & 1) !== 0 };
1923
- }
1924
- var HWPTAG_PARA_TEXT = 16 + 51;
1925
- function decodeParaText(view, offset, byteLength) {
1926
- const paragraphs = [];
1927
- let text = "";
1928
- const count = byteLength >> 1;
1929
- for (let i = 0; i < count; i++) {
1930
- const code = view.getUint16(offset + i * 2, true);
1931
- if (code >= 32) {
1932
- text += String.fromCharCode(code);
1933
- continue;
1934
- }
1935
- switch (code) {
1936
- case 13:
1937
- paragraphs.push(text);
1938
- text = "";
1939
- break;
1940
- case 10:
1941
- text += "\n";
1942
- break;
1943
- case 0:
1944
- case 24:
1945
- case 25:
1946
- case 26:
1947
- case 27:
1948
- case 28:
1949
- case 29:
1950
- case 30:
1951
- case 31:
1952
- break;
1953
- // 1-WCHAR char controls with no text representation
1954
- case 9:
1955
- i += 7;
1956
- text += " ";
1957
- break;
1958
- default:
1959
- i += 7;
1960
- break;
1961
- }
1962
- }
1963
- if (text) paragraphs.push(text);
1964
- return paragraphs;
1965
- }
1966
- function extractSectionParagraphs(section) {
1967
- const view = new DataView(section.buffer, section.byteOffset, section.byteLength);
1968
- const paragraphs = [];
1969
- let off = 0;
1970
- while (off + 4 <= section.length) {
1971
- const header = view.getUint32(off, true);
1972
- off += 4;
1973
- const tagId = header & 1023;
1974
- let size = header >>> 20 & 4095;
1975
- if (size === 4095) {
1976
- if (off + 4 > section.length) throw new Error(ERR.corrupt("truncated record header"));
1977
- size = view.getUint32(off, true);
1978
- off += 4;
1979
- }
1980
- if (off + size > section.length) throw new Error(ERR.corrupt("record overruns section"));
1981
- if (tagId === HWPTAG_PARA_TEXT) paragraphs.push(...decodeParaText(view, off, size));
1982
- off += size;
1983
- }
1984
- return paragraphs;
1985
- }
1986
- async function inflateRaw2(data) {
1987
- if (typeof DecompressionStream === "undefined") throw new Error(ERR.noInflate);
1988
- const stream = new DecompressionStream("deflate-raw");
1989
- const writer = stream.writable.getWriter();
1990
- const writing = writer.write(data).then(() => writer.close());
1991
- writing.catch(() => {
1992
- });
1993
- const reader = stream.readable.getReader();
1994
- const chunks = [];
1995
- let total = 0;
1996
- for (; ; ) {
1997
- const { done, value } = await reader.read();
1998
- if (done) break;
1999
- chunks.push(value);
2000
- total += value.length;
2001
- }
2002
- const out = new Uint8Array(total);
2003
- let at = 0;
2004
- for (const chunk of chunks) {
2005
- out.set(chunk, at);
2006
- at += chunk.length;
2007
- }
2008
- return out;
2009
- }
2010
- async function hwpToText(buffer) {
2011
- const cfb = parseCfb(new Uint8Array(buffer));
2012
- const headerEntry = cfb.entries.find((e) => e.type === 2 && e.name === "FileHeader");
2013
- if (!headerEntry) throw new Error(ERR.notHwp);
2014
- const header = parseFileHeader(cfb.readStream(headerEntry));
2015
- const sections = cfb.entries.map((e) => ({ entry: e, match: e.type === 2 ? /^Section(\d+)$/.exec(e.name) : null })).filter((s) => s.match !== null).sort((a, b) => Number(a.match[1]) - Number(b.match[1]));
2016
- const paragraphs = [];
2017
- for (const { entry } of sections) {
2018
- let bytes = cfb.readStream(entry);
2019
- if (header.compressed) bytes = await inflateRaw2(bytes);
2020
- paragraphs.push(...extractSectionParagraphs(bytes).filter((p) => p.length > 0));
2021
- }
2022
- return paragraphs.join("\n\n").replace(/\s+$/, "");
2023
- }
2024
-
2025
1523
  // src/io/importers.ts
2026
- var IMPORTABLE_EXTENSIONS = [
2027
- ".csv",
2028
- ".md",
2029
- ".markdown",
2030
- ".txt",
2031
- ".html",
2032
- ".htm",
2033
- ".json",
2034
- ".xlsx",
2035
- ".pdf",
2036
- ".hwpx",
2037
- ".hwp"
2038
- ];
1524
+ var IMPORTABLE_EXTENSIONS = [".csv", ".md", ".markdown", ".txt", ".html", ".htm", ".json", ".xlsx"];
2039
1525
  var extensionOf = (name) => {
2040
1526
  const dot = name.lastIndexOf(".");
2041
1527
  return dot === -1 ? "" : name.slice(dot).toLowerCase();
@@ -2076,18 +1562,6 @@ async function importFile(file) {
2076
1562
  const { sheets, columns, rows } = await xlsxToSheets(await file.arrayBuffer(), () => loadOptional("exceljs", () => import('exceljs')));
2077
1563
  return toEvents(artifact(id, "table", title, { columns, rows, sheet: sheets }));
2078
1564
  }
2079
- case ".pdf": {
2080
- const src = await fileToDataUrl(file);
2081
- return toEvents(artifact(id, "pdf", title, { src, filename: file.name }));
2082
- }
2083
- case ".hwpx": {
2084
- const content = await hwpxToMarkdown(await file.arrayBuffer());
2085
- return toEvents(artifact(id, "document", title, { format: "markdown", content }));
2086
- }
2087
- case ".hwp": {
2088
- const content = await hwpToText(await file.arrayBuffer());
2089
- return toEvents(artifact(id, "document", title, { format: "markdown", content }));
2090
- }
2091
1565
  default:
2092
1566
  throw new Error(`Unsupported file type "${ext || file.name}". Supported: ${IMPORTABLE_EXTENSIONS.join(", ")}`);
2093
1567
  }
@@ -2095,14 +1569,6 @@ async function importFile(file) {
2095
1569
  function artifact(id, type, title, data) {
2096
1570
  return { id, type, title, version: 1, status: "complete", data };
2097
1571
  }
2098
- function fileToDataUrl(file) {
2099
- return new Promise((resolve, reject) => {
2100
- const reader = new FileReader();
2101
- reader.onload = () => resolve(reader.result);
2102
- reader.onerror = () => reject(reader.error ?? new Error("Failed to read file."));
2103
- reader.readAsDataURL(file);
2104
- });
2105
- }
2106
1572
  function importJson(text, id, title) {
2107
1573
  let parsed;
2108
1574
  try {
@@ -2420,8 +1886,8 @@ function checkA11y(html) {
2420
1886
  if (!doc.querySelector("h1")) issues.push("No <h1> \u2014 every page needs one top-level heading");
2421
1887
  return issues;
2422
1888
  }
2423
- var SLIDE_H = 720;
2424
- var SCROLL_FIX = "<style data-lcx>html,body{overflow:auto!important;height:auto!important;min-height:100%!important}</style>";
1889
+ var SLIDE_W = 1280;
1890
+ var SCROLL_FIX = "<style>html,body{overflow:auto!important;height:auto!important;min-height:100%!important}</style>";
2425
1891
  function withScrollableBody(html) {
2426
1892
  const i = html.lastIndexOf("</body>");
2427
1893
  return i === -1 ? html + SCROLL_FIX : html.slice(0, i) + SCROLL_FIX + html.slice(i);
@@ -2429,7 +1895,7 @@ function withScrollableBody(html) {
2429
1895
  function useSlideFit(ratio, boxRef) {
2430
1896
  const [scale, setScale] = useState(1);
2431
1897
  const [rw, rh] = (ratio ?? "16:9").split(/[:x/]/).map(Number);
2432
- const width = rw && rh ? Math.round(SLIDE_H * rw / rh) : 1280;
1898
+ const height = rw && rh ? Math.round(SLIDE_W * rh / rw) : 720;
2433
1899
  useEffect(() => {
2434
1900
  if (!ratio) return;
2435
1901
  const el = boxRef.current;
@@ -2437,14 +1903,14 @@ function useSlideFit(ratio, boxRef) {
2437
1903
  const fit = () => {
2438
1904
  const w = el.clientWidth;
2439
1905
  if (w <= 40) return;
2440
- setScale(Math.min(1, (w - 40) / width));
1906
+ setScale(Math.min(1, (w - 40) / SLIDE_W));
2441
1907
  };
2442
1908
  fit();
2443
1909
  const ro = new ResizeObserver(fit);
2444
1910
  ro.observe(el);
2445
1911
  return () => ro.disconnect();
2446
- }, [ratio, width]);
2447
- return { scale, width, height: SLIDE_H };
1912
+ }, [ratio, height]);
1913
+ return { scale, width: SLIDE_W, height };
2448
1914
  }
2449
1915
  function HtmlRenderer({ artifact: artifact2 }) {
2450
1916
  const iframeRef = useRef(null);
@@ -2457,6 +1923,7 @@ function HtmlRenderer({ artifact: artifact2 }) {
2457
1923
  const sendIframeCommand = useCanvasStore((s) => s.sendIframeCommand);
2458
1924
  const selections = useCanvasStore((s) => s.selections);
2459
1925
  const iframeCommand = useCanvasStore((s) => s.iframeCommand);
1926
+ const assetBaseUrl = useCanvasStore((s) => s.assetBaseUrl);
2460
1927
  const [device, setDevice] = useState("desktop");
2461
1928
  const [mode, setMode] = useState("design");
2462
1929
  const [a11y, setA11y] = useState(null);
@@ -2465,11 +1932,11 @@ function HtmlRenderer({ artifact: artifact2 }) {
2465
1932
  const isFixedSlide = Boolean(artifact2.meta?.ratio);
2466
1933
  const srcDoc = useMemo(() => {
2467
1934
  if (mode === "design" && artifact2.data.html === lastSelfHtml.current) return srcDocRef.current;
2468
- const base = withInspector(artifact2.data.html);
1935
+ const base = withInspector(artifact2.data.html, assetBaseUrl ?? void 0);
2469
1936
  srcDocRef.current = isFixedSlide ? base : withScrollableBody(base);
2470
1937
  lastSelfHtml.current = null;
2471
1938
  return srcDocRef.current;
2472
- }, [artifact2.data.html, mode, isFixedSlide]);
1939
+ }, [artifact2.data.html, mode, isFixedSlide, assetBaseUrl]);
2473
1940
  const selected = selections.filter((s) => s.artifactId === artifact2.id);
2474
1941
  const single = selected.length === 1 ? selected[0] : null;
2475
1942
  const outline = useMemo(() => {
@@ -2773,48 +2240,33 @@ function HtmlRenderer({ artifact: artifact2 }) {
2773
2240
  sandbox: "allow-scripts allow-popups allow-modals",
2774
2241
  style: { width: DEVICES.find((d) => d.id === device).width }
2775
2242
  }
2776
- ) }) : /* @__PURE__ */ jsx(CodePane, { html: artifact2.data.html, onCommit: commitCode })
2777
- ] });
2778
- }
2779
- function CodePane({ html, onCommit }) {
2780
- const [draft, setDraft] = useState(html);
2781
- const dirty = useRef(false);
2782
- useEffect(() => {
2783
- if (!dirty.current) setDraft(html);
2784
- }, [html]);
2785
- return /* @__PURE__ */ jsx(
2786
- "textarea",
2787
- {
2788
- className: "cv-html-code",
2789
- value: draft,
2790
- spellCheck: false,
2791
- onChange: (e) => {
2792
- dirty.current = true;
2793
- setDraft(e.target.value);
2794
- },
2795
- onBlur: () => {
2796
- if (!dirty.current) return;
2797
- dirty.current = false;
2798
- onCommit(draft);
2243
+ ) }) : /* @__PURE__ */ jsx(
2244
+ "textarea",
2245
+ {
2246
+ className: "cv-html-code",
2247
+ defaultValue: artifact2.data.html,
2248
+ spellCheck: false,
2249
+ onBlur: (e) => commitCode(e.target.value),
2250
+ "aria-label": "HTML source"
2799
2251
  },
2800
- "aria-label": "HTML source"
2801
- }
2802
- );
2252
+ artifact2.data.html
2253
+ )
2254
+ ] });
2803
2255
  }
2804
2256
 
2805
2257
  // src/components/renderers/index.ts
2806
- var ChartRenderer = lazy(() => import('./ChartRenderer-AAWVGKV5.js').then((m) => ({ default: m.ChartRenderer })));
2807
- var DocumentRenderer = lazy(() => import('./DocumentRenderer-NBRBPYU6.js').then((m) => ({ default: m.DocumentRenderer })));
2808
- var TableRenderer = lazy(() => import('./TableRenderer-VKDD3CB6.js').then((m) => ({ default: m.TableRenderer })));
2809
- var SlidesRenderer = lazy(() => import('./SlidesRenderer-6ZGHXTQX.js').then((m) => ({ default: m.SlidesRenderer })));
2810
- var PdfRenderer = lazy(() => import('./PdfRenderer-DPQT4E7O.js').then((m) => ({ default: m.PdfRenderer })));
2258
+ var ChartRenderer = lazy(() => import('./ChartRenderer-JGRJ23OB.js').then((m) => ({ default: m.ChartRenderer })));
2259
+ var DocumentRenderer = lazy(() => import('./DocumentRenderer-ZQDQMX7X.js').then((m) => ({ default: m.DocumentRenderer })));
2260
+ var TableRenderer = lazy(() => import('./TableRenderer-3ESF577F.js').then((m) => ({ default: m.TableRenderer })));
2261
+ var SlidesRenderer = lazy(() => import('./SlidesRenderer-56R3TNWN.js').then((m) => ({ default: m.SlidesRenderer })));
2262
+ var FileRenderer = lazy(() => import('./FileRenderer-3ZHNORZJ.js').then((m) => ({ default: m.FileRenderer })));
2811
2263
  var builtinRenderers = {
2812
2264
  html: HtmlRenderer,
2813
2265
  document: DocumentRenderer,
2814
2266
  chart: ChartRenderer,
2815
2267
  table: TableRenderer,
2816
2268
  slides: SlidesRenderer,
2817
- pdf: PdfRenderer
2269
+ file: FileRenderer
2818
2270
  };
2819
2271
 
2820
2272
  // src/export/download.ts
@@ -2875,43 +2327,38 @@ ${renderedHtml}
2875
2327
  </html>`;
2876
2328
  }
2877
2329
  function tableToCsv(data) {
2878
- if (data.sheet?.length) return fortuneToCsv(data.sheet[0]);
2330
+ const rows = data.sheet?.length ? projectSheetIntoRows(data.columns, data.rows, data.sheet) : data.rows;
2879
2331
  const header = data.columns.map((c) => csvCell(c.label ?? c.key)).join(",");
2880
- const body = data.rows.map((row) => data.columns.map((c) => csvCell(String(row[c.key] ?? ""))).join(",")).join("\n");
2332
+ const body = rows.map((row) => data.columns.map((c) => csvCell(String(row[c.key] ?? ""))).join(",")).join("\n");
2881
2333
  return `${header}
2882
2334
  ${body}`;
2883
2335
  }
2884
- function fortuneToCsv(sheet) {
2885
- let maxR = -1;
2886
- let maxC = -1;
2887
- const grid = /* @__PURE__ */ new Map();
2888
- for (const cell of sheet.celldata ?? []) {
2889
- const v = cell?.v;
2890
- if (v == null) continue;
2891
- if (typeof v === "object" && v.mc && v.v == null && v.m == null) continue;
2892
- const value = typeof v === "object" ? v.v ?? v.m ?? "" : v;
2893
- if (value === "" || value == null) continue;
2894
- grid.set(`${cell.r},${cell.c}`, String(value));
2895
- if (cell.r > maxR) maxR = cell.r;
2896
- if (cell.c > maxC) maxC = cell.c;
2897
- }
2898
- const lines = [];
2899
- for (let r = 0; r <= maxR; r++) {
2900
- const row = [];
2901
- for (let c = 0; c <= maxC; c++) row.push(csvCell(grid.get(`${r},${c}`) ?? ""));
2902
- lines.push(row.join(","));
2903
- }
2904
- return lines.join("\n");
2905
- }
2906
2336
  async function tableToXlsx(data) {
2907
2337
  const { Workbook } = await loadOptional("exceljs", () => import('exceljs'));
2908
2338
  const workbook = new Workbook();
2909
2339
  if (data.sheet?.length) {
2910
- fortuneToWorkbook(workbook, data.sheet);
2340
+ const { computeFormulas } = await import('./formula-27TCEZI5.js');
2341
+ const merged = mergeRowsIntoSheet(
2342
+ data.columns,
2343
+ data.rows,
2344
+ data.sheet,
2345
+ await computeFormulas(data.columns, data.rows)
2346
+ );
2347
+ fortuneToWorkbook(workbook, merged);
2911
2348
  } else {
2912
2349
  const sheet = workbook.addWorksheet("Sheet1");
2913
2350
  sheet.addRow(data.columns.map((c) => c.label ?? c.key));
2914
- for (const row of data.rows) sheet.addRow(data.columns.map((c) => row[c.key] ?? ""));
2351
+ const { computeFormulas } = await import('./formula-27TCEZI5.js');
2352
+ const results = await computeFormulas(data.columns, data.rows);
2353
+ data.rows.forEach((row, dataIdx) => {
2354
+ sheet.addRow(
2355
+ data.columns.map((c, colIdx) => {
2356
+ const v = row[c.key] ?? "";
2357
+ if (typeof v !== "string" || !v.startsWith("=")) return v;
2358
+ return { formula: v.slice(1), result: results.get(`${dataIdx + 1},${colIdx}`) };
2359
+ })
2360
+ );
2361
+ });
2915
2362
  sheet.getRow(1).font = { bold: true };
2916
2363
  }
2917
2364
  return workbook.xlsx.writeBuffer();
@@ -2924,7 +2371,8 @@ function fortuneToWorkbook(workbook, sheets) {
2924
2371
  const v = cell.v;
2925
2372
  const value = v && typeof v === "object" ? v.v ?? v.m ?? null : v;
2926
2373
  const xc = ws.getCell(cell.r + 1, cell.c + 1);
2927
- xc.value = value;
2374
+ const formula = v && typeof v === "object" && typeof v.f === "string" ? v.f : null;
2375
+ xc.value = formula ? { formula: formula.replace(/^=/, ""), result: value ?? void 0 } : value;
2928
2376
  if (v && typeof v === "object") {
2929
2377
  if (v.bl) xc.font = { ...xc.font, bold: true };
2930
2378
  if (v.it) xc.font = { ...xc.font, italic: true };
@@ -3090,62 +2538,62 @@ function printToPdf(html) {
3090
2538
  document.body.appendChild(iframe);
3091
2539
  }
3092
2540
  var PDF_TYPES = /* @__PURE__ */ new Set(["html", "document", "chart", "slides"]);
3093
- var escapeHtml2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3094
- var escapeAttr2 = (s) => escapeHtml2(s).replace(/"/g, "&quot;");
3095
2541
  function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
3096
2542
  const [open, setOpen] = useState(false);
3097
2543
  const stem = slugify(artifact2.title);
3098
2544
  const dataOptions = dataExporters[artifact2.type] ?? [];
3099
- const exportHtml = () => {
2545
+ const assetBaseUrl = useCanvasStore((s) => s.assetBaseUrl);
2546
+ const prepare = () => inlineArtifactAssets(artifact2, assetBaseUrl);
2547
+ const prepareHtml = (html) => assetBaseUrl ? inlineHtmlAssets(html, assetBaseUrl) : Promise.resolve(html);
2548
+ const exportHtml = async () => {
3100
2549
  if (artifact2.type === "html") {
3101
2550
  const ratio = artifact2.meta?.ratio;
3102
- const html = artifact2.data.html;
2551
+ const html = (await prepare()).data.html;
3103
2552
  downloadBlob(`${stem}.html`, "text/html", ratio ? htmlSlideToPrintHtml(html, ratio) : html);
3104
2553
  } else {
3105
2554
  const html = getRenderedHtml();
3106
2555
  if (html == null) return;
3107
- downloadBlob(`${stem}.html`, "text/html", toStandaloneHtml(artifact2.title, html));
2556
+ downloadBlob(`${stem}.html`, "text/html", toStandaloneHtml(artifact2.title, await prepareHtml(html)));
3108
2557
  }
3109
2558
  setOpen(false);
3110
2559
  };
3111
2560
  const exportData = async (option) => {
3112
- const content = await option.build(artifact2);
2561
+ const content = await option.build(await prepare());
3113
2562
  downloadBlob(`${stem}.${option.extension}`, option.mime, content);
3114
2563
  setOpen(false);
3115
2564
  };
3116
- const exportPdf = () => {
2565
+ const exportPdf = async () => {
3117
2566
  if (artifact2.type === "slides") {
3118
- printToPdf(slidesToPrintHtml(artifact2.data, artifact2.title));
2567
+ printToPdf(slidesToPrintHtml((await prepare()).data, artifact2.title));
3119
2568
  } else if (artifact2.type === "html") {
3120
2569
  const ratio = artifact2.meta?.ratio;
3121
- const html = artifact2.data.html;
2570
+ const html = (await prepare()).data.html;
3122
2571
  printToPdf(ratio ? htmlSlideToPrintHtml(html, ratio) : html);
3123
2572
  } else {
3124
2573
  const html = getRenderedHtml();
3125
2574
  if (html == null) return;
3126
- printToPdf(toStandaloneHtml(artifact2.title, html));
2575
+ printToPdf(toStandaloneHtml(artifact2.title, await prepareHtml(html)));
3127
2576
  }
3128
2577
  setOpen(false);
3129
2578
  };
3130
- const openInTab = () => {
3131
- const html = artifact2.type === "html" ? artifact2.data.html : artifact2.type === "slides" ? slidesToPrintHtml(artifact2.data, artifact2.title) : (() => {
2579
+ const openInTab = async () => {
2580
+ const html = artifact2.type === "html" ? (await prepare()).data.html : artifact2.type === "slides" ? slidesToPrintHtml((await prepare()).data, artifact2.title) : await (async () => {
3132
2581
  const h = getRenderedHtml();
3133
- return h == null ? null : toStandaloneHtml(artifact2.title, h);
2582
+ return h == null ? null : toStandaloneHtml(artifact2.title, await prepareHtml(h));
3134
2583
  })();
3135
2584
  if (html == null) return;
3136
- const wrapper = `<!doctype html><html><head><meta charset="utf-8"><title>${escapeHtml2(artifact2.title)}</title><style>html,body{margin:0;height:100%}iframe{display:block;width:100%;height:100%;border:0}</style></head><body><iframe sandbox="allow-scripts allow-popups allow-modals" srcdoc="${escapeAttr2(html)}"></iframe></body></html>`;
3137
- const url = URL.createObjectURL(new Blob([wrapper], { type: "text/html" }));
2585
+ const url = URL.createObjectURL(new Blob([html], { type: "text/html" }));
3138
2586
  window.open(url, "_blank", "noopener");
3139
2587
  setTimeout(() => URL.revokeObjectURL(url), 1e4);
3140
2588
  setOpen(false);
3141
2589
  };
3142
2590
  const [copied, setCopied] = useState(false);
3143
2591
  const copyHtml = async () => {
3144
- const html = artifact2.type === "html" ? artifact2.data.html : getRenderedHtml();
2592
+ const html = artifact2.type === "html" ? (await prepare()).data.html : getRenderedHtml();
3145
2593
  if (html == null) return;
3146
2594
  try {
3147
2595
  await navigator.clipboard.writeText(
3148
- artifact2.type === "html" ? html : toStandaloneHtml(artifact2.title, html)
2596
+ artifact2.type === "html" ? html : toStandaloneHtml(artifact2.title, await prepareHtml(html))
3149
2597
  );
3150
2598
  setCopied(true);
3151
2599
  setTimeout(() => setCopied(false), 1400);
@@ -3482,8 +2930,10 @@ function toHex2(value) {
3482
2930
  if (!parts || parts.length < 3) return "#000000";
3483
2931
  return "#" + parts.slice(0, 3).map((n) => Number(n).toString(16).padStart(2, "0")).join("");
3484
2932
  }
3485
- function useCanvasImport() {
2933
+ function useCanvasImport({ onImported } = {}) {
3486
2934
  const api = useCanvasStoreApi();
2935
+ const imported = useRef(onImported);
2936
+ imported.current = onImported;
3487
2937
  const importFiles = useCallback(
3488
2938
  async (files) => {
3489
2939
  let lastId = null;
@@ -3496,6 +2946,7 @@ function useCanvasImport() {
3496
2946
  if (created && created.type === "canvas.create") {
3497
2947
  lastId = created.artifact.id;
3498
2948
  api.getState().setActiveArtifact(lastId);
2949
+ imported.current?.(created.artifact, file);
3499
2950
  }
3500
2951
  } catch (err) {
3501
2952
  console.error("[langchain-canvas] import failed:", file.name, err);
@@ -3508,22 +2959,66 @@ function useCanvasImport() {
3508
2959
  return { importFiles, canImport };
3509
2960
  }
3510
2961
  var ACCEPT = IMPORTABLE_EXTENSIONS.join(",");
3511
- function Canvas({ registry = builtinRenderers, emptyState, onEditElement, onUserEdit }) {
3512
- return /* @__PURE__ */ jsx(CanvasRegistryProvider, { registry, children: /* @__PURE__ */ jsx(CanvasPanel, { emptyState, onEditElement, onUserEdit }) });
3513
- }
3514
- function CanvasPanel({ emptyState, onEditElement, onUserEdit }) {
2962
+ function Canvas({
2963
+ registry = builtinRenderers,
2964
+ emptyState,
2965
+ onEditElement,
2966
+ onUserEdit,
2967
+ onSave,
2968
+ onFilesOpened,
2969
+ onImported,
2970
+ assetBaseUrl
2971
+ }) {
2972
+ return /* @__PURE__ */ jsx(CanvasRegistryProvider, { registry, children: /* @__PURE__ */ jsx(
2973
+ CanvasPanel,
2974
+ {
2975
+ emptyState,
2976
+ onEditElement,
2977
+ onUserEdit,
2978
+ onSave,
2979
+ onFilesOpened,
2980
+ onImported,
2981
+ assetBaseUrl
2982
+ }
2983
+ ) });
2984
+ }
2985
+ function CanvasPanel({
2986
+ emptyState,
2987
+ onEditElement,
2988
+ onUserEdit,
2989
+ onSave,
2990
+ onFilesOpened,
2991
+ onImported,
2992
+ assetBaseUrl
2993
+ }) {
2994
+ const debouncedSave = useCanvasSave(onSave);
3515
2995
  const { artifacts, order, activeId } = useCanvasStore((s) => s.canvas);
3516
2996
  const history = useCanvasStore((s) => s.canvas.history);
3517
2997
  const setActive = useCanvasStore((s) => s.setActiveArtifact);
3518
2998
  const selections = useCanvasStore((s) => s.selections);
3519
2999
  const setSelections = useCanvasStore((s) => s.setSelections);
3520
3000
  const setOnUserEdit = useCanvasStore((s) => s.setOnUserEdit);
3521
- const { importFiles } = useCanvasImport();
3001
+ const setAssetBaseUrl = useCanvasStore((s) => s.setAssetBaseUrl);
3002
+ const { importFiles } = useCanvasImport({ onImported });
3522
3003
  const [dropping, setDropping] = useState(false);
3523
3004
  useEffect(() => {
3524
- setOnUserEdit(onUserEdit ?? null);
3005
+ setAssetBaseUrl(assetBaseUrl ?? null);
3006
+ }, [assetBaseUrl, setAssetBaseUrl]);
3007
+ const openFiles = (files) => {
3008
+ onFilesOpened?.(Array.from(files));
3009
+ void importFiles(files);
3010
+ };
3011
+ useEffect(() => {
3012
+ if (!onUserEdit && !debouncedSave) {
3013
+ setOnUserEdit(null);
3014
+ return;
3015
+ }
3016
+ setOnUserEdit((artifact2) => {
3017
+ onUserEdit?.(artifact2);
3018
+ debouncedSave?.(artifact2);
3019
+ });
3525
3020
  return () => setOnUserEdit(null);
3526
- }, [onUserEdit, setOnUserEdit]);
3021
+ }, [onUserEdit, debouncedSave, setOnUserEdit]);
3527
3022
  useEffect(() => {
3528
3023
  if (!selections.length) return;
3529
3024
  const onKey = (e) => {
@@ -3546,17 +3041,17 @@ function CanvasPanel({ emptyState, onEditElement, onUserEdit }) {
3546
3041
  onDrop: (e) => {
3547
3042
  e.preventDefault();
3548
3043
  setDropping(false);
3549
- if (e.dataTransfer.files.length) void importFiles(e.dataTransfer.files);
3044
+ if (e.dataTransfer.files.length) openFiles(e.dataTransfer.files);
3550
3045
  }
3551
3046
  };
3552
3047
  const dropOverlay = dropping ? /* @__PURE__ */ jsx("div", { className: "cv-canvas__drop", children: "Drop to open on the canvas" }) : null;
3553
3048
  if (!active) {
3554
3049
  return /* @__PURE__ */ jsxs("aside", { className: "cv-canvas cv-canvas--empty", ...dropProps, children: [
3555
- emptyState ?? /* @__PURE__ */ jsx(EmptyState, { onOpenFiles: importFiles }),
3050
+ emptyState ?? /* @__PURE__ */ jsx(EmptyState, { onOpenFiles: openFiles, acceptAll: Boolean(onFilesOpened) }),
3556
3051
  dropOverlay
3557
3052
  ] });
3558
3053
  }
3559
- const versions = history[active.id] ?? [active];
3054
+ const versions2 = history[active.id] ?? [active];
3560
3055
  const showSelection = Boolean(onEditElement) && selections.length > 0 && selections[0].artifactId === active.id;
3561
3056
  return /* @__PURE__ */ jsxs("aside", { className: "cv-canvas", ...dropProps, children: [
3562
3057
  dropOverlay,
@@ -3571,18 +3066,18 @@ function CanvasPanel({ emptyState, onEditElement, onUserEdit }) {
3571
3066
  },
3572
3067
  id
3573
3068
  )) }),
3574
- /* @__PURE__ */ jsx(ArtifactView, { artifact: active, versions }, active.id),
3069
+ /* @__PURE__ */ jsx(ArtifactView, { artifact: active, versions: versions2 }, active.id),
3575
3070
  showSelection && onEditElement && /* @__PURE__ */ jsxs(Fragment, { children: [
3576
3071
  active.type === "html" && selections.length === 1 && /* @__PURE__ */ jsx(StylePanel, { selection: selections[0] }, selections[0].cid),
3577
3072
  /* @__PURE__ */ jsx(SelectionBar, { selections, onEdit: onEditElement, onClear: () => setSelections([]) })
3578
3073
  ] })
3579
3074
  ] });
3580
3075
  }
3581
- function ArtifactView({ artifact: artifact2, versions }) {
3076
+ function ArtifactView({ artifact: artifact2, versions: versions2 }) {
3582
3077
  const [viewIndex, setViewIndex] = useState(null);
3583
3078
  const bodyRef = useRef(null);
3584
- const shown = viewIndex === null ? artifact2 : versions[Math.min(viewIndex, versions.length - 1)] ?? artifact2;
3585
- const isHistory = viewIndex !== null && shown !== artifact2;
3079
+ const shown = viewIndex === null ? artifact2 : versions2[viewIndex];
3080
+ const viewingHistory = viewIndex !== null && viewIndex !== versions2.length - 1;
3586
3081
  const Renderer = useRenderer(shown.type);
3587
3082
  const getRenderedHtml = () => {
3588
3083
  const node = bodyRef.current;
@@ -3600,35 +3095,36 @@ function ArtifactView({ artifact: artifact2, versions }) {
3600
3095
  ] }),
3601
3096
  /* @__PURE__ */ jsxs("div", { className: "cv-header__actions", children: [
3602
3097
  /* @__PURE__ */ jsx(UndoRedo, {}),
3603
- versions.length > 1 && /* @__PURE__ */ jsx(
3604
- VersionRail,
3098
+ versions2.length > 1 && /* @__PURE__ */ jsx(
3099
+ VersionHistory,
3605
3100
  {
3606
- total: versions.length,
3607
- index: viewIndex ?? versions.length - 1,
3608
- onSelect: (i) => setViewIndex(i === versions.length - 1 ? null : i)
3101
+ versions: versions2,
3102
+ index: viewIndex ?? versions2.length - 1,
3103
+ onSelect: (i) => setViewIndex(i === versions2.length - 1 ? null : i)
3609
3104
  }
3610
3105
  ),
3611
3106
  /* @__PURE__ */ jsx(ExportMenu, { artifact: shown, getRenderedHtml })
3612
3107
  ] })
3613
3108
  ] }),
3614
- /* @__PURE__ */ jsxs(
3109
+ viewingHistory && /* @__PURE__ */ jsxs("div", { className: "cv-history-banner", role: "status", children: [
3110
+ "Viewing v",
3111
+ (viewIndex ?? 0) + 1,
3112
+ " of ",
3113
+ versions2.length,
3114
+ " \u2014 read-only.",
3115
+ " ",
3116
+ /* @__PURE__ */ jsx("button", { onClick: () => setViewIndex(null), children: "Back to latest" })
3117
+ ] }),
3118
+ /* @__PURE__ */ jsx(
3615
3119
  "div",
3616
3120
  {
3617
- className: `cv-body${shown.type === "table" || shown.type === "pdf" ? " cv-body--flush" : ""}${isHistory ? " cv-body--history" : ""}`,
3121
+ className: `cv-body${shown.type === "table" ? " cv-body--flush" : ""}${viewingHistory ? " cv-body--history" : ""}`,
3618
3122
  ref: bodyRef,
3619
- "aria-readonly": isHistory || void 0,
3620
- children: [
3621
- isHistory && /* @__PURE__ */ jsxs("div", { className: "cv-history-note", role: "status", children: [
3622
- "Viewing v",
3623
- Math.min(viewIndex + 1, versions.length),
3624
- " (read-only) \u2014 select the latest version to edit"
3625
- ] }),
3626
- Renderer ? /* @__PURE__ */ jsx(RendererBoundary, { resetKey: `${shown.id}:${shown.version}`, children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "cv-fallback", children: "Loading\u2026" }), children: /* @__PURE__ */ jsx(Renderer, { artifact: shown }) }) }) : /* @__PURE__ */ jsxs("div", { className: "cv-fallback", children: [
3627
- "No renderer registered for type \u201C",
3628
- shown.type,
3629
- "\u201D."
3630
- ] })
3631
- ]
3123
+ children: Renderer ? /* @__PURE__ */ jsx(RendererBoundary, { resetKey: `${shown.id}:${shown.version}`, children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "cv-fallback", children: "Loading\u2026" }), children: /* @__PURE__ */ jsx(Renderer, { artifact: shown }) }) }) : /* @__PURE__ */ jsxs("div", { className: "cv-fallback", children: [
3124
+ "No renderer registered for type \u201C",
3125
+ shown.type,
3126
+ "\u201D."
3127
+ ] })
3632
3128
  }
3633
3129
  )
3634
3130
  ] });
@@ -3657,32 +3153,70 @@ function UndoRedo() {
3657
3153
  /* @__PURE__ */ jsx("button", { onClick: redo, disabled: !canRedo, title: "Redo (\u2318\u21E7Z)", "aria-label": "Redo", children: "\u21B7" })
3658
3154
  ] });
3659
3155
  }
3660
- function VersionRail({ total, index, onSelect }) {
3156
+ function VersionHistory({
3157
+ versions: versions2,
3158
+ index,
3159
+ onSelect
3160
+ }) {
3161
+ const [open, setOpen] = useState(false);
3162
+ const total = versions2.length;
3163
+ const pick = (i) => {
3164
+ setOpen(false);
3165
+ onSelect(i);
3166
+ };
3661
3167
  return /* @__PURE__ */ jsxs("div", { className: "cv-versions", role: "group", "aria-label": "Version history", children: [
3662
- /* @__PURE__ */ jsx("button", { className: "cv-versions__nav", disabled: index === 0, onClick: () => onSelect(index - 1), "aria-label": "Previous version", children: "\u2039" }),
3663
- /* @__PURE__ */ jsxs("span", { className: "cv-versions__label", children: [
3664
- "v",
3665
- index + 1,
3666
- " / ",
3667
- total
3668
- ] }),
3168
+ /* @__PURE__ */ jsx("button", { className: "cv-versions__nav", disabled: index === 0, onClick: () => pick(index - 1), "aria-label": "Previous version", children: "\u2039" }),
3169
+ /* @__PURE__ */ jsxs(
3170
+ "button",
3171
+ {
3172
+ className: "cv-versions__label",
3173
+ "aria-expanded": open,
3174
+ "aria-label": "Open version history",
3175
+ onClick: () => setOpen((v) => !v),
3176
+ children: [
3177
+ "v",
3178
+ index + 1,
3179
+ " / ",
3180
+ total
3181
+ ]
3182
+ }
3183
+ ),
3669
3184
  /* @__PURE__ */ jsx(
3670
3185
  "button",
3671
3186
  {
3672
3187
  className: "cv-versions__nav",
3673
3188
  disabled: index === total - 1,
3674
- onClick: () => onSelect(index + 1),
3189
+ onClick: () => pick(index + 1),
3675
3190
  "aria-label": "Next version",
3676
3191
  children: "\u203A"
3677
3192
  }
3678
- )
3193
+ ),
3194
+ open && /* @__PURE__ */ jsx("ul", { className: "cv-versions__list", role: "listbox", "aria-label": "Versions", children: versions2.map((snapshot, i) => ({ snapshot, i })).reverse().map(({ snapshot, i }) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
3195
+ "button",
3196
+ {
3197
+ role: "option",
3198
+ "aria-selected": i === index,
3199
+ className: i === index ? "is-current" : void 0,
3200
+ onClick: () => pick(i),
3201
+ children: [
3202
+ /* @__PURE__ */ jsxs("span", { className: "cv-versions__v", children: [
3203
+ "v",
3204
+ i + 1
3205
+ ] }),
3206
+ /* @__PURE__ */ jsx("span", { className: "cv-versions__desc", children: typeof snapshot.meta?.commitDescription === "string" ? snapshot.meta.commitDescription : "Snapshot" })
3207
+ ]
3208
+ }
3209
+ ) }, i)) })
3679
3210
  ] });
3680
3211
  }
3681
3212
  function StatusBadge({ status }) {
3682
3213
  const label = status === "streaming" ? "Writing\u2026" : status === "error" ? "Error" : "Ready";
3683
3214
  return /* @__PURE__ */ jsx("span", { className: `cv-badge cv-badge--${status}`, children: label });
3684
3215
  }
3685
- function EmptyState({ onOpenFiles }) {
3216
+ function EmptyState({
3217
+ onOpenFiles,
3218
+ acceptAll = false
3219
+ }) {
3686
3220
  const inputRef = useRef(null);
3687
3221
  return /* @__PURE__ */ jsxs("div", { className: "cv-empty", children: [
3688
3222
  /* @__PURE__ */ jsx("p", { className: "cv-empty__title", children: "Nothing on the canvas yet" }),
@@ -3694,7 +3228,7 @@ function EmptyState({ onOpenFiles }) {
3694
3228
  {
3695
3229
  ref: inputRef,
3696
3230
  type: "file",
3697
- accept: ACCEPT,
3231
+ accept: acceptAll ? void 0 : ACCEPT,
3698
3232
  multiple: true,
3699
3233
  hidden: true,
3700
3234
  onChange: (e) => {
@@ -3703,7 +3237,7 @@ function EmptyState({ onOpenFiles }) {
3703
3237
  }
3704
3238
  }
3705
3239
  ),
3706
- /* @__PURE__ */ jsx("p", { className: "cv-empty__formats", children: "CSV \xB7 Excel \xB7 Markdown \xB7 HTML \xB7 JSON" })
3240
+ /* @__PURE__ */ jsx("p", { className: "cv-empty__formats", children: acceptAll ? "Any file \u2014 tables and pages open here, the rest goes to the agent" : "CSV \xB7 Excel \xB7 Markdown \xB7 HTML \xB7 JSON" })
3707
3241
  ] })
3708
3242
  ] });
3709
3243
  }
@@ -3713,7 +3247,7 @@ var TYPE_META = {
3713
3247
  chart: { icon: "\u{1F4CA}", label: "Chart" },
3714
3248
  table: { icon: "\u{1F522}", label: "Excel sheet" },
3715
3249
  slides: { icon: "\u{1F4FD}\uFE0F", label: "PowerPoint deck" },
3716
- pdf: { icon: "\u{1F4D5}", label: "PDF" }
3250
+ file: { icon: "\u{1F4CE}", label: "File" }
3717
3251
  };
3718
3252
  var KIND_META = {
3719
3253
  web: TYPE_META.html,
@@ -3724,8 +3258,7 @@ var KIND_META = {
3724
3258
  table: TYPE_META.table,
3725
3259
  sheet: TYPE_META.table,
3726
3260
  slide: TYPE_META.slides,
3727
- slides: TYPE_META.slides,
3728
- pdf: TYPE_META.pdf
3261
+ slides: TYPE_META.slides
3729
3262
  };
3730
3263
  function resolveCardMeta(artifact2) {
3731
3264
  const kind = typeof artifact2.meta?.kind === "string" ? artifact2.meta.kind : void 0;
@@ -3750,4 +3283,4 @@ function ArtifactCard({ artifactId }) {
3750
3283
  ] });
3751
3284
  }
3752
3285
 
3753
- export { ArtifactCard, Canvas, CanvasRegistryProvider, ChartRenderer, DocumentRenderer, ExportMenu, HtmlRenderer, IMPORTABLE_EXTENSIONS, INSPECTOR_MARK, PdfRenderer, STYLE_PROPS, SelectionBar, SlidesRenderer, StylePanel, TableRenderer, builtinRenderers, canImport, dataExporters, downloadBlob, importFile, mergeRegistries, mockStream, parseCsv, parseSSE, printToPdf, scenarios, slidesToPrintHtml, slugify, streamChat, toStandaloneHtml, useCanvasImport, useCanvasReplay, useCanvasStream, useRenderer, withInspector };
3286
+ export { ArtifactCard, Canvas, CanvasRegistryProvider, ChartRenderer, DocumentRenderer, ExportMenu, FileRenderer, HtmlRenderer, IMPORTABLE_EXTENSIONS, INSPECTOR_MARK, STYLE_PROPS, SelectionBar, SlidesRenderer, StylePanel, TableRenderer, builtinRenderers, canImport, dataExporters, downloadBlob, importFile, mergeRegistries, mockStream, mockTransport, parseCsv, parseSSE, printToPdf, scenarios, slidesToPrintHtml, slugify, sseTransport, streamChat, toStandaloneHtml, useCanvasImport, useCanvasReplay, useCanvasSave, useCanvasStream, useRenderer, withInspector };