@braincrew-lab/langchain-canvas 0.7.6 → 0.7.7

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.
@@ -142,6 +142,114 @@ function docxStats(root) {
142
142
  };
143
143
  }
144
144
 
145
+ // src/io/docxShapes.ts
146
+ var VML_NS = "urn:schemas-microsoft-com:vml";
147
+ var VML_SHAPES = /* @__PURE__ */ new Set([
148
+ "oval",
149
+ "rect",
150
+ "roundrect",
151
+ "line",
152
+ "polyline",
153
+ "arc",
154
+ "curve",
155
+ "shape",
156
+ "group"
157
+ ]);
158
+ var PAINTABLE = "ellipse,rect,circle,path,line,polygon,polyline,image,foreignObject";
159
+ function isVml(node) {
160
+ return node.namespaceURI === VML_NS || node.tagName.startsWith("v:");
161
+ }
162
+ function alphaOf(paint) {
163
+ const inside = /^rgba?\(([^)]*)\)$/.exec(paint);
164
+ if (!inside) return null;
165
+ const [channels, slashAlpha] = inside[1].split("/");
166
+ const raw = slashAlpha !== void 0 ? slashAlpha : (() => {
167
+ const parts = channels.split(",").map((part) => part.trim());
168
+ return parts.length === 4 ? parts[3] : void 0;
169
+ })();
170
+ if (raw === void 0) return null;
171
+ const value = raw.trim();
172
+ const number = value.endsWith("%") ? Number(value.slice(0, -1)) / 100 : Number(value);
173
+ return Number.isFinite(number) ? number : null;
174
+ }
175
+ function invisible(paint) {
176
+ const value = paint.trim();
177
+ if (!value || value === "none" || value === "transparent") return true;
178
+ return alphaOf(value) === 0;
179
+ }
180
+ function paints(element) {
181
+ const tag = element.tagName.toLowerCase();
182
+ if (tag === "image" || tag === "foreignobject") return true;
183
+ const style = element.ownerDocument.defaultView?.getComputedStyle(element);
184
+ const resolved = (name) => style?.getPropertyValue(name) || element.getAttribute(name) || "";
185
+ return !invisible(resolved("fill")) || !invisible(resolved("stroke"));
186
+ }
187
+ function drawsNothing(shape) {
188
+ return shape.getAttribute("filled") === "f" && shape.getAttribute("stroked") === "f";
189
+ }
190
+ function vmlShapeIn(node) {
191
+ if (isVml(node) && VML_SHAPES.has(node.localName)) return node;
192
+ for (const child of Array.from(node.children)) {
193
+ const found = vmlShapeIn(child);
194
+ if (found) return found;
195
+ }
196
+ return null;
197
+ }
198
+ function drawingIsShape(drawing) {
199
+ const data = drawing.getElementsByTagName("*");
200
+ for (const node of Array.from(data)) {
201
+ if (node.localName === "graphicData") {
202
+ return !Array.from(node.children).some((child) => child.localName === "pic");
203
+ }
204
+ }
205
+ return false;
206
+ }
207
+ function insideAlternateContent(node) {
208
+ let parent = node.parentElement;
209
+ while (parent) {
210
+ if (parent.localName === "AlternateContent") return true;
211
+ parent = parent.parentElement;
212
+ }
213
+ return false;
214
+ }
215
+ function shapesAskedFor(xml) {
216
+ let count = 0;
217
+ const seenTwins = /* @__PURE__ */ new Set();
218
+ for (const node of Array.from(xml.getElementsByTagName("*"))) {
219
+ if (node.localName !== "AlternateContent") continue;
220
+ const twin = vmlShapeIn(node);
221
+ const drawing = Array.from(node.getElementsByTagName("*")).find(
222
+ (child) => child.localName === "drawing"
223
+ );
224
+ const isShape = twin !== null || (drawing ? drawingIsShape(drawing) : false);
225
+ if (isShape && !(twin && drawsNothing(twin))) count += 1;
226
+ seenTwins.add(node);
227
+ }
228
+ for (const node of Array.from(xml.getElementsByTagName("*"))) {
229
+ if (insideAlternateContent(node)) continue;
230
+ if (isVml(node) && VML_SHAPES.has(node.localName)) {
231
+ if (node.parentElement && vmlShapeIn(node.parentElement) === node.parentElement) continue;
232
+ if (!drawsNothing(node)) count += 1;
233
+ continue;
234
+ }
235
+ if (node.localName === "drawing" && drawingIsShape(node)) count += 1;
236
+ }
237
+ return count;
238
+ }
239
+ function shapesShown(root) {
240
+ let count = 0;
241
+ for (const svg of Array.from(root.querySelectorAll("svg"))) {
242
+ if (Array.from(svg.querySelectorAll(PAINTABLE)).some(paints)) count += 1;
243
+ }
244
+ return count;
245
+ }
246
+ function tallyShapes(root, xmls) {
247
+ if (xmls.length === 0) return { askedFor: 0, shown: 0, missing: 0 };
248
+ const askedFor = xmls.reduce((total, xml) => total + shapesAskedFor(xml), 0);
249
+ const shown = shapesShown(root);
250
+ return { askedFor, shown, missing: Math.max(0, askedFor - shown) };
251
+ }
252
+
145
253
  // src/io/symbolBullets.ts
146
254
  var SYMBOL_FONT_BULLETS = {
147
255
  symbol: {
@@ -243,6 +351,12 @@ function redrawnFonts(swaps) {
243
351
  return [...new Set(swaps.map((swap) => swap.font))];
244
352
  }
245
353
  var BANNER = "Preview only \u2014 to change it, ask in chat or select some text.";
354
+ var CONTENT_PART = /^word\/(document|header\d*|footer\d*)\.xml$/;
355
+ function storedParts(parsed) {
356
+ const parts = parsed?.parts;
357
+ if (!Array.isArray(parts)) return [];
358
+ return parts.filter((part) => typeof part?.path === "string" && CONTENT_PART.test(part.path)).map((part) => part._xmlDocument).filter((xml) => Boolean(xml));
359
+ }
246
360
  function DocxPreview({
247
361
  artifactId,
248
362
  href,
@@ -253,6 +367,7 @@ function DocxPreview({
253
367
  const [status, setStatus] = useState("loading");
254
368
  const [stats, setStats] = useState(null);
255
369
  const [redrawn, setRedrawn] = useState([]);
370
+ const [missingShapes, setMissingShapes] = useState(0);
256
371
  const [picked, setPicked] = useState(null);
257
372
  const setSelections = useCanvasStore((s) => s.setSelections);
258
373
  useEffect(() => {
@@ -263,6 +378,7 @@ function DocxPreview({
263
378
  setStatus("loading");
264
379
  setStats(null);
265
380
  setRedrawn([]);
381
+ setMissingShapes(0);
266
382
  setPicked(null);
267
383
  (async () => {
268
384
  const { renderAsync } = await loadOptional(
@@ -275,16 +391,18 @@ function DocxPreview({
275
391
  const data = await response.arrayBuffer();
276
392
  if (!live) return;
277
393
  host.replaceChildren();
278
- await renderAsync(data, host, void 0, {
394
+ const parsed = await renderAsync(data, host, void 0, {
279
395
  inWrapper: true,
280
396
  breakPages: true,
281
397
  renderHeaders: true,
282
398
  renderFooters: true,
283
- useBase64URL: true
399
+ useBase64URL: true,
400
+ keepOrigin: true
284
401
  });
285
402
  if (!live) return;
286
403
  stampDocxAddresses(host);
287
404
  setRedrawn(redrawnFonts(restoreSymbolBullets(host)));
405
+ setMissingShapes(tallyShapes(host, storedParts(parsed)).missing);
288
406
  setStats(docxStats(host));
289
407
  setStatus("ready");
290
408
  let fittedFor = host.clientWidth;
@@ -367,6 +485,20 @@ function DocxPreview({
367
485
  "substituted: ",
368
486
  stats.substitutedFonts.join(", ")
369
487
  ] }),
488
+ missingShapes > 0 && /* @__PURE__ */ jsxs(
489
+ "span",
490
+ {
491
+ className: "cv-docx__missing",
492
+ title: "This document draws shapes the preview cannot show. They are in the file \u2014 a download opens with them in place.",
493
+ children: [
494
+ missingShapes,
495
+ " shape",
496
+ missingShapes === 1 ? "" : "s",
497
+ " not shown \u2014 download the file to see ",
498
+ missingShapes === 1 ? "it" : "them"
499
+ ]
500
+ }
501
+ ),
370
502
  redrawn.length > 0 && /* @__PURE__ */ jsxs(
371
503
  "span",
372
504
  {
@@ -10,7 +10,7 @@ function formatSize(size) {
10
10
  return `${(size / (1024 * 1024)).toFixed(1)} MB`;
11
11
  }
12
12
  var DocxPreview = lazy(
13
- () => import('./DocxPreview-NGT3Z5KW.js').then((m) => ({ default: m.DocxPreview }))
13
+ () => import('./DocxPreview-TXEOVBVR.js').then((m) => ({ default: m.DocxPreview }))
14
14
  );
15
15
  function iconFor(mediaType, name) {
16
16
  if (mediaType?.startsWith("image/")) return "\u{1F5BC}\uFE0F";
package/dist/index.js CHANGED
@@ -2264,7 +2264,7 @@ var ChartRenderer = lazy(() => import('./ChartRenderer-TBTWNPV7.js').then((m) =>
2264
2264
  var DocumentRenderer = lazy(() => import('./DocumentRenderer-ZGJXDVKG.js').then((m) => ({ default: m.DocumentRenderer })));
2265
2265
  var TableRenderer = lazy(() => import('./TableRenderer-YTOF2ZIC.js').then((m) => ({ default: m.TableRenderer })));
2266
2266
  var SlidesRenderer = lazy(() => import('./SlidesRenderer-4QWZVWWD.js').then((m) => ({ default: m.SlidesRenderer })));
2267
- var FileRenderer = lazy(() => import('./FileRenderer-TZSZVVLY.js').then((m) => ({ default: m.FileRenderer })));
2267
+ var FileRenderer = lazy(() => import('./FileRenderer-C4IHXUV4.js').then((m) => ({ default: m.FileRenderer })));
2268
2268
  var builtinRenderers = {
2269
2269
  html: HtmlRenderer,
2270
2270
  document: DocumentRenderer,
package/dist/styles.css CHANGED
@@ -1310,6 +1310,7 @@
1310
1310
  .cv-docx__picked { color: var(--cv-accent); }
1311
1311
  .cv-docx__fonts { color: var(--cv-warn, #b45309); }
1312
1312
  .cv-docx__redrawn { color: var(--cv-warn, #b45309); }
1313
+ .cv-docx__missing { color: var(--cv-warn, #b45309); }
1313
1314
 
1314
1315
  /* Tablet / narrow desktop: tighten paddings, let toolbars wrap. */
1315
1316
  @media (max-width: 900px) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@braincrew-lab/langchain-canvas",
3
- "version": "0.7.6",
3
+ "version": "0.7.7",
4
4
  "description": "A live canvas for LangChain agents — stream documents, charts, and rich artifacts into a React panel.",
5
5
  "license": "MIT",
6
6
  "author": "Brain Crew (https://github.com/braincrew-lab)",