@agent-native/core 0.146.1 → 0.146.2
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/corpus/README.md +1 -1
- package/corpus/templates/slides/actions/import-file.ts +144 -99
- package/corpus/templates/slides/package.json +1 -0
- package/corpus/templates/slides/server/handlers/import/html-converter.ts +9 -0
- package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +960 -0
- package/dist/ingestion/pptx.d.ts +1 -0
- package/dist/ingestion/pptx.js +353 -28
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/package.json +2 -2
package/dist/ingestion/pptx.d.ts
CHANGED
package/dist/ingestion/pptx.js
CHANGED
|
@@ -40,7 +40,32 @@ export async function parsePptxPresentation(fileBuffer) {
|
|
|
40
40
|
.filter((path) => /^ppt\/slides\/slide\d+\.xml$/.test(path))
|
|
41
41
|
.sort((a, b) => slideNumber(a) - slideNumber(b)));
|
|
42
42
|
}
|
|
43
|
+
// Deck-wide fallback, used when a slide's own layout→master chain can't be
|
|
44
|
+
// resolved (missing rels, unusual authoring tools) — see
|
|
45
|
+
// `resolveSlideMasterContext` below for the per-slide resolution that
|
|
46
|
+
// presentations with more than one slide master actually need.
|
|
43
47
|
const theme = await parseTheme(zip, parseXml);
|
|
48
|
+
const masterColorInfo = slideMasterRelationship
|
|
49
|
+
? await parseMasterColorInfo({
|
|
50
|
+
zip,
|
|
51
|
+
target: slideMasterRelationship.target,
|
|
52
|
+
parseXml,
|
|
53
|
+
})
|
|
54
|
+
: { clrMap: {}, titleFillByLevel: {}, bodyFillByLevel: {} };
|
|
55
|
+
const colorContext = {
|
|
56
|
+
themeColorsByName: theme.colorsByName,
|
|
57
|
+
clrMap: masterColorInfo.clrMap,
|
|
58
|
+
};
|
|
59
|
+
const resolveFillsByLevel = (fillsByLevel) => Object.fromEntries(Object.entries(fillsByLevel).map(([level, fill]) => [
|
|
60
|
+
Number(level),
|
|
61
|
+
parseColor(fill, colorContext),
|
|
62
|
+
]));
|
|
63
|
+
const placeholderDefaults = {
|
|
64
|
+
title: resolveFillsByLevel(masterColorInfo.titleFillByLevel),
|
|
65
|
+
body: resolveFillsByLevel(masterColorInfo.bodyFillByLevel),
|
|
66
|
+
};
|
|
67
|
+
const themeCache = new Map();
|
|
68
|
+
const masterInfoCache = new Map();
|
|
44
69
|
const slides = [];
|
|
45
70
|
for (const slidePath of slidePaths) {
|
|
46
71
|
const xml = await zip.file(slidePath)?.async("string");
|
|
@@ -63,6 +88,18 @@ export async function parsePptxPresentation(fileBuffer) {
|
|
|
63
88
|
const slideRelationships = slideRelationshipsXml
|
|
64
89
|
? parseRelationships(parseXml(slideRelationshipsXml))
|
|
65
90
|
: new Map();
|
|
91
|
+
// Presentations can mix multiple masters (e.g. combined templates), each
|
|
92
|
+
// with its own color map / theme / placeholder defaults — resolve this
|
|
93
|
+
// slide's own layout→master chain instead of reusing whichever master
|
|
94
|
+
// happened to be first in the presentation, falling back to the
|
|
95
|
+
// deck-wide default above only when that chain can't be resolved.
|
|
96
|
+
const slideMasterContext = (await resolveSlideMasterContext({
|
|
97
|
+
zip,
|
|
98
|
+
parseXml,
|
|
99
|
+
slideRelationships,
|
|
100
|
+
themeCache,
|
|
101
|
+
masterInfoCache,
|
|
102
|
+
})) ?? { colorContext, placeholderDefaults };
|
|
66
103
|
elements = await parseSlideElements({
|
|
67
104
|
xml,
|
|
68
105
|
parseXml,
|
|
@@ -72,6 +109,8 @@ export async function parsePptxPresentation(fileBuffer) {
|
|
|
72
109
|
slideWidthEmu,
|
|
73
110
|
slideHeightEmu,
|
|
74
111
|
images,
|
|
112
|
+
colorContext: slideMasterContext.colorContext,
|
|
113
|
+
placeholderDefaults: slideMasterContext.placeholderDefaults,
|
|
75
114
|
});
|
|
76
115
|
const texts = flattenElementText(elements);
|
|
77
116
|
const number = slideNumber(slidePath);
|
|
@@ -95,7 +134,7 @@ export async function parsePptxPresentation(fileBuffer) {
|
|
|
95
134
|
elements,
|
|
96
135
|
widthEmu: slideWidthEmu,
|
|
97
136
|
heightEmu: slideHeightEmu,
|
|
98
|
-
backgroundColor: extractSlideBackgroundColor(slide),
|
|
137
|
+
backgroundColor: extractSlideBackgroundColor(slide, slideMasterContext.colorContext),
|
|
99
138
|
...(backgroundGrid ? { backgroundGrid } : {}),
|
|
100
139
|
notes,
|
|
101
140
|
layoutHint: guessLayoutHint(texts, images.length > 0),
|
|
@@ -184,6 +223,123 @@ async function parseMasterGrid(args) {
|
|
|
184
223
|
lineWidthEmu,
|
|
185
224
|
};
|
|
186
225
|
}
|
|
226
|
+
/** Resolves an OOXML relationship `Target` (package-absolute like "/ppt/foo.xml", or relative like "../slideMasters/slideMaster1.xml") against the directory of the part that declared it. */
|
|
227
|
+
function resolvePptxRelationshipPath(baseDir, target) {
|
|
228
|
+
if (target.startsWith("/"))
|
|
229
|
+
return target.slice(1);
|
|
230
|
+
const segments = `${baseDir}/${target}`.split("/");
|
|
231
|
+
const resolved = [];
|
|
232
|
+
for (const segment of segments) {
|
|
233
|
+
if (segment === "." || segment === "")
|
|
234
|
+
continue;
|
|
235
|
+
if (segment === "..")
|
|
236
|
+
resolved.pop();
|
|
237
|
+
else
|
|
238
|
+
resolved.push(segment);
|
|
239
|
+
}
|
|
240
|
+
return resolved.join("/");
|
|
241
|
+
}
|
|
242
|
+
/** The `_rels/<file>.rels` part that carries relationships for a given OOXML part path. */
|
|
243
|
+
function relsPathForPptxPart(path) {
|
|
244
|
+
const slashIndex = path.lastIndexOf("/");
|
|
245
|
+
const dir = slashIndex >= 0 ? path.slice(0, slashIndex) : "";
|
|
246
|
+
const file = slashIndex >= 0 ? path.slice(slashIndex + 1) : path;
|
|
247
|
+
return `${dir ? `${dir}/` : ""}_rels/${file}.rels`;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Walks this slide's own `slideLayout` → `slideMaster` → `theme` relationship
|
|
251
|
+
* chain so slides that belong to a different master than the deck's first
|
|
252
|
+
* one (a presentation with more than one master) resolve `schemeClr`
|
|
253
|
+
* aliases and placeholder defaults against their own palette instead of an
|
|
254
|
+
* unrelated master's. Returns `undefined` when any hop in that chain is
|
|
255
|
+
* missing, letting the caller fall back to the deck-wide default.
|
|
256
|
+
*/
|
|
257
|
+
async function resolveSlideMasterContext(args) {
|
|
258
|
+
const layoutRelationship = [...args.slideRelationships.values()].find((relationship) => relationship.type.endsWith("/slideLayout"));
|
|
259
|
+
if (!layoutRelationship)
|
|
260
|
+
return undefined;
|
|
261
|
+
const layoutPath = resolvePptxRelationshipPath("ppt/slides", layoutRelationship.target);
|
|
262
|
+
const layoutRelsXml = await args.zip
|
|
263
|
+
.file(relsPathForPptxPart(layoutPath))
|
|
264
|
+
?.async("string");
|
|
265
|
+
if (!layoutRelsXml)
|
|
266
|
+
return undefined;
|
|
267
|
+
const layoutRelationships = parseRelationships(args.parseXml(layoutRelsXml));
|
|
268
|
+
const masterRelationship = [...layoutRelationships.values()].find((relationship) => relationship.type.endsWith("/slideMaster"));
|
|
269
|
+
if (!masterRelationship)
|
|
270
|
+
return undefined;
|
|
271
|
+
const masterPath = resolvePptxRelationshipPath("ppt/slideLayouts", masterRelationship.target);
|
|
272
|
+
let masterInfoPromise = args.masterInfoCache.get(masterPath);
|
|
273
|
+
if (!masterInfoPromise) {
|
|
274
|
+
masterInfoPromise = parseMasterColorInfo({
|
|
275
|
+
zip: args.zip,
|
|
276
|
+
target: masterRelationship.target,
|
|
277
|
+
parseXml: args.parseXml,
|
|
278
|
+
});
|
|
279
|
+
args.masterInfoCache.set(masterPath, masterInfoPromise);
|
|
280
|
+
}
|
|
281
|
+
const masterInfo = await masterInfoPromise;
|
|
282
|
+
const masterRelsXml = await args.zip
|
|
283
|
+
.file(relsPathForPptxPart(masterPath))
|
|
284
|
+
?.async("string");
|
|
285
|
+
const masterRelationships = masterRelsXml
|
|
286
|
+
? parseRelationships(args.parseXml(masterRelsXml))
|
|
287
|
+
: new Map();
|
|
288
|
+
const themeRelationship = [...masterRelationships.values()].find((relationship) => relationship.type.endsWith("/theme"));
|
|
289
|
+
if (!themeRelationship)
|
|
290
|
+
return undefined;
|
|
291
|
+
const themePath = resolvePptxRelationshipPath("ppt/slideMasters", themeRelationship.target);
|
|
292
|
+
let themePromise = args.themeCache.get(themePath);
|
|
293
|
+
if (!themePromise) {
|
|
294
|
+
themePromise = parseThemeFromPath(args.zip, args.parseXml, themePath);
|
|
295
|
+
args.themeCache.set(themePath, themePromise);
|
|
296
|
+
}
|
|
297
|
+
const theme = await themePromise;
|
|
298
|
+
const colorContext = {
|
|
299
|
+
themeColorsByName: theme.colorsByName,
|
|
300
|
+
clrMap: masterInfo.clrMap,
|
|
301
|
+
};
|
|
302
|
+
const resolveFillsByLevel = (fillsByLevel) => Object.fromEntries(Object.entries(fillsByLevel).map(([level, fill]) => [
|
|
303
|
+
Number(level),
|
|
304
|
+
parseColor(fill, colorContext),
|
|
305
|
+
]));
|
|
306
|
+
return {
|
|
307
|
+
colorContext,
|
|
308
|
+
placeholderDefaults: {
|
|
309
|
+
title: resolveFillsByLevel(masterInfo.titleFillByLevel),
|
|
310
|
+
body: resolveFillsByLevel(masterInfo.bodyFillByLevel),
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
/** Resolves a slide master's color-alias mapping plus its title/body default text-color fills (from p:txStyles), so placeholder text without its own explicit color can inherit the right one. */
|
|
315
|
+
async function parseMasterColorInfo(args) {
|
|
316
|
+
const empty = { clrMap: {}, titleFillByLevel: {}, bodyFillByLevel: {} };
|
|
317
|
+
const path = args.target.startsWith("/")
|
|
318
|
+
? args.target.slice(1)
|
|
319
|
+
: "ppt/" + args.target.replace(/^\.\.\//, "");
|
|
320
|
+
const xml = await args.zip.file(path)?.async("string");
|
|
321
|
+
if (!xml)
|
|
322
|
+
return empty;
|
|
323
|
+
const root = record(record(args.parseXml(xml))?.["p:sldMaster"]);
|
|
324
|
+
if (!root)
|
|
325
|
+
return empty;
|
|
326
|
+
const clrMap = parseClrMapNode(record(root["p:clrMap"]));
|
|
327
|
+
const txStyles = record(root["p:txStyles"]);
|
|
328
|
+
return {
|
|
329
|
+
clrMap,
|
|
330
|
+
titleFillByLevel: levelFillsFromTextStyle(record(txStyles?.["p:titleStyle"])),
|
|
331
|
+
bodyFillByLevel: levelFillsFromTextStyle(record(txStyles?.["p:bodyStyle"])),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/** Reads a `<p:titleStyle>`/`<p:bodyStyle>` node's per-level (`a:lvl1pPr`..`a:lvl9pPr`) default run fill, keyed 0-indexed to match `ParsedPptxParagraph.level` (`a:lvl1pPr` is level 0, `a:lvl2pPr` is level 1, etc.) — using only the first level's default for every nested bullet level silently drops the distinct colors PowerPoint themes commonly assign to deeper levels. */
|
|
335
|
+
function levelFillsFromTextStyle(style) {
|
|
336
|
+
const fills = {};
|
|
337
|
+
for (let level = 0; level < 9; level++) {
|
|
338
|
+
const defRPr = record(record(style?.[`a:lvl${level + 1}pPr`])?.["a:defRPr"]);
|
|
339
|
+
fills[level] = record(defRPr?.["a:solidFill"]);
|
|
340
|
+
}
|
|
341
|
+
return fills;
|
|
342
|
+
}
|
|
187
343
|
const SHAPE_ELEMENT_NAMES = new Set([
|
|
188
344
|
"sp",
|
|
189
345
|
"pic",
|
|
@@ -273,9 +429,15 @@ async function parseShapeFragment(fragment, args) {
|
|
|
273
429
|
const name = readShapeName(node);
|
|
274
430
|
const placeholderType = stringValue(record(record(record(node["p:nvSpPr"])?.["p:nvPr"])?.["p:ph"])?.["@_type"]);
|
|
275
431
|
const shapeProperties = record(node["p:spPr"]);
|
|
276
|
-
const
|
|
277
|
-
const
|
|
278
|
-
|
|
432
|
+
const rawText = parseTextBody(node, args.colorContext);
|
|
433
|
+
const placeholderDefaultColor = placeholderType === "title" || placeholderType === "ctrTitle"
|
|
434
|
+
? args.placeholderDefaults?.title
|
|
435
|
+
: placeholderType
|
|
436
|
+
? args.placeholderDefaults?.body
|
|
437
|
+
: undefined;
|
|
438
|
+
const text = applyPlaceholderDefaultColor(rawText, placeholderDefaultColor);
|
|
439
|
+
const fill = parseShapeFill(shapeProperties, args.colorContext);
|
|
440
|
+
const line = parseShapeLine(shapeProperties, args.colorContext);
|
|
279
441
|
const shapeType = stringValue(record(shapeProperties?.["a:prstGeom"])?.["@_prst"]);
|
|
280
442
|
if (entry === "pic") {
|
|
281
443
|
const embedId = stringValue(record(record(node["p:blipFill"])?.["a:blip"])?.["@_r:embed"]);
|
|
@@ -446,7 +608,7 @@ function applyTransform(transform, context) {
|
|
|
446
608
|
: {}),
|
|
447
609
|
};
|
|
448
610
|
}
|
|
449
|
-
function parseTextBody(node) {
|
|
611
|
+
function parseTextBody(node, context) {
|
|
450
612
|
const txBody = record(node["p:txBody"]);
|
|
451
613
|
if (!txBody)
|
|
452
614
|
return [];
|
|
@@ -460,7 +622,7 @@ function parseTextBody(node) {
|
|
|
460
622
|
if (content) {
|
|
461
623
|
runs.push({
|
|
462
624
|
content,
|
|
463
|
-
...runProperties(record(run?.["a:rPr"]), {}),
|
|
625
|
+
...runProperties(record(run?.["a:rPr"]), {}, context),
|
|
464
626
|
});
|
|
465
627
|
}
|
|
466
628
|
}
|
|
@@ -470,12 +632,12 @@ function parseTextBody(node) {
|
|
|
470
632
|
if (content) {
|
|
471
633
|
runs.push({
|
|
472
634
|
content,
|
|
473
|
-
...runProperties(record(field?.["a:rPr"]), {}),
|
|
635
|
+
...runProperties(record(field?.["a:rPr"]), {}, context),
|
|
474
636
|
});
|
|
475
637
|
}
|
|
476
638
|
}
|
|
477
639
|
const bullet = record(pPr?.["a:buChar"]);
|
|
478
|
-
const bulletColor = parseColor(record(pPr?.["a:buClr"]));
|
|
640
|
+
const bulletColor = parseColor(record(pPr?.["a:buClr"]), context);
|
|
479
641
|
const bulletFont = stringValue(record(pPr?.["a:buFont"])?.["@_typeface"]);
|
|
480
642
|
const bulletSize = Number(record(pPr?.["a:buSzPts"])?.["@_val"]);
|
|
481
643
|
const lineSpacing = parseParagraphSpacing(pPr?.["a:lnSpc"]);
|
|
@@ -527,18 +689,18 @@ function parseTextBoxProperties(node) {
|
|
|
527
689
|
: { verticalAlign: "top" }),
|
|
528
690
|
};
|
|
529
691
|
}
|
|
530
|
-
function parseShapeFill(shapeProperties) {
|
|
692
|
+
function parseShapeFill(shapeProperties, context) {
|
|
531
693
|
if (!shapeProperties)
|
|
532
694
|
return undefined;
|
|
533
695
|
if (shapeProperties["a:noFill"] !== undefined)
|
|
534
696
|
return undefined;
|
|
535
|
-
return parseColor(record(shapeProperties["a:solidFill"]));
|
|
697
|
+
return parseColor(record(shapeProperties["a:solidFill"]), context);
|
|
536
698
|
}
|
|
537
|
-
function parseShapeLine(shapeProperties) {
|
|
699
|
+
function parseShapeLine(shapeProperties, context) {
|
|
538
700
|
const line = record(shapeProperties?.["a:ln"]);
|
|
539
701
|
if (!line || line["a:noFill"] !== undefined)
|
|
540
702
|
return undefined;
|
|
541
|
-
const color = parseColor(record(line["a:solidFill"]));
|
|
703
|
+
const color = parseColor(record(line["a:solidFill"]), context);
|
|
542
704
|
if (!color)
|
|
543
705
|
return undefined;
|
|
544
706
|
const width = Number(line["@_w"]);
|
|
@@ -547,15 +709,163 @@ function parseShapeLine(shapeProperties) {
|
|
|
547
709
|
...(Number.isFinite(width) && width > 0 ? { width } : {}),
|
|
548
710
|
};
|
|
549
711
|
}
|
|
550
|
-
|
|
712
|
+
/** Placeholder text has no explicit color of its own when the author relied on the slide master's default run properties — apply that inherited color to any run that didn't resolve one, using each paragraph's own nested-bullet level (falling back to level 0's color when a deeper level has no default of its own). */
|
|
713
|
+
function applyPlaceholderDefaultColor(paragraphs, colorsByLevel) {
|
|
714
|
+
if (!colorsByLevel)
|
|
715
|
+
return paragraphs;
|
|
716
|
+
return paragraphs.map((paragraph) => {
|
|
717
|
+
const level = paragraph.level ?? 0;
|
|
718
|
+
const color = colorsByLevel[level] ?? colorsByLevel[0];
|
|
719
|
+
if (!color)
|
|
720
|
+
return paragraph;
|
|
721
|
+
return {
|
|
722
|
+
...paragraph,
|
|
723
|
+
runs: paragraph.runs.map((run) => (run.color ? run : { ...run, color })),
|
|
724
|
+
};
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
/** PowerPoint's default `bg1`/`tx1`-style alias mapping, used whenever a master doesn't declare its own `<p:clrMap>`. */
|
|
728
|
+
const IDENTITY_CLR_MAP = {
|
|
729
|
+
bg1: "lt1",
|
|
730
|
+
tx1: "dk1",
|
|
731
|
+
bg2: "lt2",
|
|
732
|
+
tx2: "dk2",
|
|
733
|
+
accent1: "accent1",
|
|
734
|
+
accent2: "accent2",
|
|
735
|
+
accent3: "accent3",
|
|
736
|
+
accent4: "accent4",
|
|
737
|
+
accent5: "accent5",
|
|
738
|
+
accent6: "accent6",
|
|
739
|
+
hlink: "hlink",
|
|
740
|
+
folHlink: "folHlink",
|
|
741
|
+
};
|
|
742
|
+
const CLR_MAP_ALIASES = Object.keys(IDENTITY_CLR_MAP);
|
|
743
|
+
/** Reads a `<p:clrMap .../>` or `<a:overrideClrMapping .../>` node's alias attributes into an alias→theme-slot map. */
|
|
744
|
+
function parseClrMapNode(node) {
|
|
745
|
+
const map = {};
|
|
746
|
+
if (!node)
|
|
747
|
+
return map;
|
|
748
|
+
for (const alias of CLR_MAP_ALIASES) {
|
|
749
|
+
const target = stringValue(node[`@_${alias}`]);
|
|
750
|
+
if (target)
|
|
751
|
+
map[alias] = target;
|
|
752
|
+
}
|
|
753
|
+
return map;
|
|
754
|
+
}
|
|
755
|
+
function resolveSchemeColorName(name, context) {
|
|
756
|
+
if (!context)
|
|
757
|
+
return undefined;
|
|
758
|
+
const slot = context.clrMap[name] ?? IDENTITY_CLR_MAP[name] ?? name;
|
|
759
|
+
return context.themeColorsByName[slot];
|
|
760
|
+
}
|
|
761
|
+
function hexToHsl(hex) {
|
|
762
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
763
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
764
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
765
|
+
const max = Math.max(r, g, b);
|
|
766
|
+
const min = Math.min(r, g, b);
|
|
767
|
+
const l = (max + min) / 2;
|
|
768
|
+
if (max === min)
|
|
769
|
+
return [0, 0, l];
|
|
770
|
+
const d = max - min;
|
|
771
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
772
|
+
let h;
|
|
773
|
+
switch (max) {
|
|
774
|
+
case r:
|
|
775
|
+
h = (g - b) / d + (g < b ? 6 : 0);
|
|
776
|
+
break;
|
|
777
|
+
case g:
|
|
778
|
+
h = (b - r) / d + 2;
|
|
779
|
+
break;
|
|
780
|
+
default:
|
|
781
|
+
h = (r - g) / d + 4;
|
|
782
|
+
}
|
|
783
|
+
return [h * 60, s, l];
|
|
784
|
+
}
|
|
785
|
+
function hslToHex(h, s, l) {
|
|
786
|
+
const toByte = (v) => Math.max(0, Math.min(255, Math.round(v * 255)))
|
|
787
|
+
.toString(16)
|
|
788
|
+
.padStart(2, "0");
|
|
789
|
+
if (s === 0) {
|
|
790
|
+
const gray = toByte(l);
|
|
791
|
+
return `#${gray}${gray}${gray}`;
|
|
792
|
+
}
|
|
793
|
+
const hue2rgb = (p, q, t) => {
|
|
794
|
+
let tt = t;
|
|
795
|
+
if (tt < 0)
|
|
796
|
+
tt += 1;
|
|
797
|
+
if (tt > 1)
|
|
798
|
+
tt -= 1;
|
|
799
|
+
if (tt < 1 / 6)
|
|
800
|
+
return p + (q - p) * 6 * tt;
|
|
801
|
+
if (tt < 1 / 2)
|
|
802
|
+
return q;
|
|
803
|
+
if (tt < 2 / 3)
|
|
804
|
+
return p + (q - p) * (2 / 3 - tt) * 6;
|
|
805
|
+
return p;
|
|
806
|
+
};
|
|
807
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
808
|
+
const p = 2 * l - q;
|
|
809
|
+
const hNorm = h / 360;
|
|
810
|
+
const r = hue2rgb(p, q, hNorm + 1 / 3);
|
|
811
|
+
const g = hue2rgb(p, q, hNorm);
|
|
812
|
+
const b = hue2rgb(p, q, hNorm - 1 / 3);
|
|
813
|
+
return `#${toByte(r)}${toByte(g)}${toByte(b)}`;
|
|
814
|
+
}
|
|
815
|
+
function readColorTransforms(node) {
|
|
816
|
+
if (!node)
|
|
817
|
+
return {};
|
|
818
|
+
const percent = (key) => {
|
|
819
|
+
const raw = stringValue(record(node[key])?.["@_val"]);
|
|
820
|
+
return raw !== undefined ? Number(raw) / 100000 : undefined;
|
|
821
|
+
};
|
|
822
|
+
return {
|
|
823
|
+
lumMod: percent("a:lumMod"),
|
|
824
|
+
lumOff: percent("a:lumOff"),
|
|
825
|
+
tint: percent("a:tint"),
|
|
826
|
+
shade: percent("a:shade"),
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
function applyColorTransforms(hex, transforms) {
|
|
830
|
+
const { lumMod, lumOff, tint, shade } = transforms;
|
|
831
|
+
if (lumMod === undefined &&
|
|
832
|
+
lumOff === undefined &&
|
|
833
|
+
tint === undefined &&
|
|
834
|
+
shade === undefined) {
|
|
835
|
+
return hex;
|
|
836
|
+
}
|
|
837
|
+
const [h, s, initialL] = hexToHsl(hex);
|
|
838
|
+
let l = initialL;
|
|
839
|
+
if (lumMod !== undefined)
|
|
840
|
+
l *= lumMod;
|
|
841
|
+
if (lumOff !== undefined)
|
|
842
|
+
l += lumOff;
|
|
843
|
+
if (tint !== undefined)
|
|
844
|
+
l = l * tint + (1 - tint);
|
|
845
|
+
if (shade !== undefined)
|
|
846
|
+
l *= shade;
|
|
847
|
+
return hslToHex(h, s, Math.min(1, Math.max(0, l)));
|
|
848
|
+
}
|
|
849
|
+
function parseColor(value, context) {
|
|
551
850
|
if (!value)
|
|
552
851
|
return undefined;
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
852
|
+
const srgbNode = record(value["a:srgbClr"]);
|
|
853
|
+
const rgb = stringValue(srgbNode?.["@_val"]);
|
|
854
|
+
if (rgb) {
|
|
855
|
+
return applyColorTransforms(`#${rgb}`, readColorTransforms(srgbNode));
|
|
856
|
+
}
|
|
857
|
+
const schemeNode = record(value["a:schemeClr"]);
|
|
858
|
+
const scheme = stringValue(schemeNode?.["@_val"]);
|
|
557
859
|
if (!scheme)
|
|
558
860
|
return undefined;
|
|
861
|
+
const transforms = readColorTransforms(schemeNode);
|
|
862
|
+
const resolved = resolveSchemeColorName(scheme, context);
|
|
863
|
+
if (resolved)
|
|
864
|
+
return applyColorTransforms(resolved, transforms);
|
|
865
|
+
// No theme/clrMap available for this slot (or the theme didn't define
|
|
866
|
+
// it) — fall back to a coarse dark/light guess along the standard
|
|
867
|
+
// identity mapping, so tx1/dk1/bg1/lt1 text stays visible rather than
|
|
868
|
+
// silently vanishing.
|
|
559
869
|
const pptxDarkColor = "#000000"; // guard:allow-raw-color - PPTX dark scheme fallback
|
|
560
870
|
const pptxLightColor = "#ffffff"; // guard:allow-raw-color - PPTX light scheme fallback
|
|
561
871
|
const fallback = {
|
|
@@ -563,8 +873,15 @@ function parseColor(value) {
|
|
|
563
873
|
dk2: pptxDarkColor,
|
|
564
874
|
lt1: pptxLightColor,
|
|
565
875
|
lt2: pptxLightColor,
|
|
876
|
+
tx1: pptxDarkColor,
|
|
877
|
+
tx2: pptxDarkColor,
|
|
878
|
+
bg1: pptxLightColor,
|
|
879
|
+
bg2: pptxLightColor,
|
|
566
880
|
};
|
|
567
|
-
|
|
881
|
+
const fallbackColor = fallback[scheme];
|
|
882
|
+
return fallbackColor
|
|
883
|
+
? applyColorTransforms(fallbackColor, transforms)
|
|
884
|
+
: undefined;
|
|
568
885
|
}
|
|
569
886
|
function parseParagraphSpacing(value) {
|
|
570
887
|
const node = record(value);
|
|
@@ -662,11 +979,11 @@ function flattenElementText(elements) {
|
|
|
662
979
|
}
|
|
663
980
|
return output;
|
|
664
981
|
}
|
|
665
|
-
function extractSlideBackgroundColor(value) {
|
|
982
|
+
function extractSlideBackgroundColor(value, context) {
|
|
666
983
|
const root = record(value);
|
|
667
984
|
const cSld = record(record(root?.["p:sld"])?.["p:cSld"] ?? root?.["p:cSld"]);
|
|
668
985
|
const bgPr = record(record(cSld?.["p:bg"])?.["p:bgPr"]);
|
|
669
|
-
return parseColor(record(bgPr?.["a:solidFill"]));
|
|
986
|
+
return parseColor(record(bgPr?.["a:solidFill"]), context);
|
|
670
987
|
}
|
|
671
988
|
export function parsePptxSlideMetadata(value) {
|
|
672
989
|
const slide = record(value)?.["p:sld"] ?? value;
|
|
@@ -677,28 +994,36 @@ export function parsePptxSlideMetadata(value) {
|
|
|
677
994
|
};
|
|
678
995
|
}
|
|
679
996
|
async function parseTheme(zip, parseXml) {
|
|
680
|
-
|
|
997
|
+
return parseThemeFromPath(zip, parseXml, "ppt/theme/theme1.xml");
|
|
998
|
+
}
|
|
999
|
+
async function parseThemeFromPath(zip, parseXml, themePath) {
|
|
1000
|
+
const xml = await zip.file(themePath)?.async("string");
|
|
681
1001
|
if (!xml)
|
|
682
|
-
return
|
|
1002
|
+
return { colors: [], colorsByName: {}, fonts: [] };
|
|
683
1003
|
const root = record(parseXml(xml));
|
|
684
1004
|
const elements = record(record(root?.["a:theme"])?.["a:themeElements"]);
|
|
685
1005
|
const scheme = record(elements?.["a:clrScheme"]);
|
|
686
1006
|
const colors = [];
|
|
1007
|
+
const colorsByName = {};
|
|
687
1008
|
for (const [key, value] of Object.entries(scheme ?? {})) {
|
|
688
1009
|
if (key.startsWith("@_"))
|
|
689
1010
|
continue;
|
|
690
1011
|
const color = record(value);
|
|
691
1012
|
const rgb = stringValue(record(color?.["a:srgbClr"])?.["@_val"]);
|
|
692
1013
|
const system = stringValue(record(color?.["a:sysClr"])?.["@_lastClr"]);
|
|
693
|
-
if (rgb
|
|
694
|
-
|
|
1014
|
+
if (!rgb && !system)
|
|
1015
|
+
continue;
|
|
1016
|
+
const hex = `#${rgb ?? system}`;
|
|
1017
|
+
colors.push(hex);
|
|
1018
|
+
const slotName = key.replace(/^a:/, "");
|
|
1019
|
+
colorsByName[slotName] = hex;
|
|
695
1020
|
}
|
|
696
1021
|
const fontScheme = record(elements?.["a:fontScheme"]);
|
|
697
1022
|
const fonts = ["a:majorFont", "a:minorFont"].flatMap((key) => {
|
|
698
1023
|
const value = stringValue(record(record(fontScheme?.[key])?.["a:latin"])?.["@_typeface"]);
|
|
699
1024
|
return value ? [value] : [];
|
|
700
1025
|
});
|
|
701
|
-
return
|
|
1026
|
+
return { colors, colorsByName, fonts };
|
|
702
1027
|
}
|
|
703
1028
|
function collectTextRuns(value, runs, inherited = {}) {
|
|
704
1029
|
const node = record(value);
|
|
@@ -795,11 +1120,11 @@ function extractBackgroundFillEmbedId(slide) {
|
|
|
795
1120
|
const blip = record(record(bgPr?.["a:blipFill"])?.["a:blip"]);
|
|
796
1121
|
return stringValue(blip?.["@_r:embed"]);
|
|
797
1122
|
}
|
|
798
|
-
function runProperties(value, inherited) {
|
|
1123
|
+
function runProperties(value, inherited, context) {
|
|
799
1124
|
if (!value)
|
|
800
1125
|
return inherited;
|
|
801
1126
|
const size = Number(value["@_sz"]);
|
|
802
|
-
const
|
|
1127
|
+
const color = parseColor(record(value["a:solidFill"]), context);
|
|
803
1128
|
const fontFamily = stringValue(record(value["a:latin"])?.["@_typeface"]) ??
|
|
804
1129
|
stringValue(record(value["a:ea"])?.["@_typeface"]) ??
|
|
805
1130
|
stringValue(record(value["a:cs"])?.["@_typeface"]);
|
|
@@ -812,7 +1137,7 @@ function runProperties(value, inherited) {
|
|
|
812
1137
|
? { italic: true }
|
|
813
1138
|
: {}),
|
|
814
1139
|
...(Number.isFinite(size) && size > 0 ? { fontSize: size / 100 } : {}),
|
|
815
|
-
...(
|
|
1140
|
+
...(color ? { color } : {}),
|
|
816
1141
|
...(fontFamily ? { fontFamily } : {}),
|
|
817
1142
|
...(value["@_u"] && value["@_u"] !== "none" ? { underline: true } : {}),
|
|
818
1143
|
};
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
|
|
14
14
|
count: number;
|
|
15
15
|
updated?: undefined;
|
|
16
|
-
error?: undefined;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error?: undefined;
|
|
18
18
|
} | {
|
|
19
19
|
count?: undefined;
|
|
20
20
|
updated: number;
|
|
21
|
-
error?: undefined;
|
|
22
21
|
ok?: undefined;
|
|
22
|
+
error?: undefined;
|
|
23
23
|
} | {
|
|
24
24
|
count?: undefined;
|
|
25
25
|
updated?: undefined;
|
|
@@ -28,6 +28,6 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
|
|
|
28
28
|
} | {
|
|
29
29
|
count?: undefined;
|
|
30
30
|
updated?: undefined;
|
|
31
|
-
error?: undefined;
|
|
32
31
|
ok: boolean;
|
|
32
|
+
error?: undefined;
|
|
33
33
|
}>>;
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -37,17 +37,17 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
|
|
|
37
37
|
ok?: undefined;
|
|
38
38
|
status?: undefined;
|
|
39
39
|
} | {
|
|
40
|
-
error?: undefined;
|
|
41
40
|
ok: boolean;
|
|
42
41
|
status: string;
|
|
42
|
+
error?: undefined;
|
|
43
43
|
} | {
|
|
44
44
|
ok?: undefined;
|
|
45
45
|
error: string;
|
|
46
46
|
removed?: undefined;
|
|
47
47
|
} | {
|
|
48
|
-
error?: undefined;
|
|
49
48
|
ok: boolean;
|
|
50
49
|
removed: boolean;
|
|
50
|
+
error?: undefined;
|
|
51
51
|
}>>;
|
|
52
52
|
/**
|
|
53
53
|
* POST /_agent-native/secrets/:key/test — validate an optional candidate value
|
|
@@ -58,13 +58,13 @@ export declare function createTestSecretHandler(): import("h3").EventHandlerWith
|
|
|
58
58
|
error: string;
|
|
59
59
|
note?: undefined;
|
|
60
60
|
} | {
|
|
61
|
-
error?: undefined;
|
|
62
61
|
ok: boolean;
|
|
63
62
|
note?: undefined;
|
|
64
|
-
} | {
|
|
65
63
|
error?: undefined;
|
|
64
|
+
} | {
|
|
66
65
|
ok: boolean;
|
|
67
66
|
note: string;
|
|
67
|
+
error?: undefined;
|
|
68
68
|
} | {
|
|
69
69
|
note?: undefined;
|
|
70
70
|
ok: boolean;
|
|
@@ -95,11 +95,11 @@ export interface AdHocSecretPayload {
|
|
|
95
95
|
export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
|
|
96
96
|
error: string;
|
|
97
97
|
} | {
|
|
98
|
-
error?: undefined;
|
|
99
98
|
ok: boolean;
|
|
100
99
|
key: string;
|
|
101
|
-
} | {
|
|
102
100
|
error?: undefined;
|
|
101
|
+
} | {
|
|
103
102
|
ok: boolean;
|
|
104
103
|
removed: boolean;
|
|
104
|
+
error?: undefined;
|
|
105
105
|
}>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.146.
|
|
3
|
+
"version": "0.146.2",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -413,7 +413,7 @@
|
|
|
413
413
|
"yjs": "^13.6.31",
|
|
414
414
|
"zod": "^4.3.6",
|
|
415
415
|
"@agent-native/recap-cli": "0.5.3",
|
|
416
|
-
"@agent-native/toolkit": "^0.13.
|
|
416
|
+
"@agent-native/toolkit": "^0.13.5"
|
|
417
417
|
},
|
|
418
418
|
"devDependencies": {
|
|
419
419
|
"@ai-sdk/anthropic": "^3.0.71",
|