mixdog 0.9.154 → 0.9.155
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/package.json +2 -2
- package/scripts/build-voice-runtime-unix.sh +24 -0
- package/scripts/voice-runtime-config.json +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +47 -4
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +4 -1
- package/src/runtime/channels/data/voice-runtime-manifest.json +17 -17
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +59 -0
- package/src/runtime/office/assurance-benchmark.mjs +67 -1
- package/src/runtime/office/assurance.mjs +24 -17
- package/src/runtime/office/capabilities.mjs +17 -17
- package/src/runtime/office/composition-system.mjs +451 -0
- package/src/runtime/office/design-library.mjs +102 -0
- package/src/runtime/office/design-system.mjs +357 -168
- package/src/runtime/office/index.mjs +149 -20
- package/src/runtime/office/office-com-host.ps1 +132 -46
- package/src/runtime/office/pdf-render.mjs +203 -35
- package/src/runtime/office/portable-ooxml.mjs +1090 -47
- package/src/runtime/office/portable-package.mjs +390 -0
- package/src/runtime/office/portable-sheet-styles.mjs +241 -0
- package/src/runtime/office/portable-slide-shapes.mjs +283 -0
- package/src/runtime/office/quality-live-benchmark.mjs +82 -36
- package/src/runtime/office/quality-pipeline.mjs +3 -0
- package/src/runtime/office/tool-defs.mjs +2 -2
- package/src/session-runtime/goal-reminder.mjs +79 -0
- package/src/session-runtime/goal-runtime.mjs +105 -45
- package/src/session-runtime/goal-text.mjs +58 -0
- package/src/session-runtime/runtime-core.mjs +14 -1
- package/src/session-runtime/session-lifecycle.mjs +1 -1
- package/src/session-runtime/session-turn-api.mjs +5 -0
- package/src/standalone/session-service.mjs +30 -3
- package/src/tui/dist/index.mjs +12 -3
- package/src/tui/lib/voice-setup.mjs +19 -3
- package/src/tui/session/goal-continuation.mjs +7 -0
|
@@ -4,6 +4,12 @@ import {
|
|
|
4
4
|
normalizeOfficeContentModel,
|
|
5
5
|
summarizeOfficeContentModel,
|
|
6
6
|
} from './content-model.mjs';
|
|
7
|
+
import {
|
|
8
|
+
planOfficeComposition,
|
|
9
|
+
resolveOfficeCompositionContext,
|
|
10
|
+
reviewOfficeCompositionSequence,
|
|
11
|
+
summarizeOfficeCompositions,
|
|
12
|
+
} from './composition-system.mjs';
|
|
7
13
|
|
|
8
14
|
const FORMATS = new Set(['docx', 'xlsx', 'pptx', 'pdf', 'csv', 'tsv']);
|
|
9
15
|
const PPTX_BACKGROUND_MODES = new Set(['sandwich', 'light', 'dark', 'custom']);
|
|
@@ -286,6 +292,8 @@ function compactDesign(design) {
|
|
|
286
292
|
audience: design.audience,
|
|
287
293
|
tone: design.tone,
|
|
288
294
|
density: design.density,
|
|
295
|
+
purpose: design.purpose,
|
|
296
|
+
expressionMode: design.expressionMode,
|
|
289
297
|
signature: design.signature,
|
|
290
298
|
source: design.source,
|
|
291
299
|
library: design.library,
|
|
@@ -322,6 +330,7 @@ function compactLibrary(library) {
|
|
|
322
330
|
...(library.coverage ? { coverage: clone(library.coverage) } : {}),
|
|
323
331
|
} : null,
|
|
324
332
|
templateIndexRevision: String(library.templateIndexRevision || ''),
|
|
333
|
+
recentCompositionCount: Array.isArray(library.recentCompositions) ? library.recentCompositions.length : 0,
|
|
325
334
|
pinned: library.pinned === true,
|
|
326
335
|
warning: String(library.warning || ''),
|
|
327
336
|
};
|
|
@@ -378,6 +387,9 @@ export function resolveOfficeDesign(format, request = {}, { library = null } = {
|
|
|
378
387
|
])),
|
|
379
388
|
typography,
|
|
380
389
|
});
|
|
390
|
+
const composition = resolveOfficeCompositionContext(normalizedFormat, input, {
|
|
391
|
+
recentCompositions: library?.recentCompositions,
|
|
392
|
+
});
|
|
381
393
|
const deck = normalizedFormat === 'pptx' ? resolvePptxDeckPlan(input, tokens) : null;
|
|
382
394
|
return {
|
|
383
395
|
profile,
|
|
@@ -389,6 +401,9 @@ export function resolveOfficeDesign(format, request = {}, { library = null } = {
|
|
|
389
401
|
density: ['light', 'balanced', 'dense'].includes(String(input.density || '').toLowerCase())
|
|
390
402
|
? String(input.density).toLowerCase()
|
|
391
403
|
: 'balanced',
|
|
404
|
+
purpose: composition.purpose,
|
|
405
|
+
expressionMode: composition.expressionMode,
|
|
406
|
+
recentCompositions: composition.recentCompositions,
|
|
392
407
|
signature: String(input.signature || ''),
|
|
393
408
|
source: String(input.source || library?.source || 'mixdog-starter'),
|
|
394
409
|
content: normalizeOfficeContentModel(input.content),
|
|
@@ -400,6 +415,7 @@ export function resolveOfficeDesign(format, request = {}, { library = null } = {
|
|
|
400
415
|
...(normalizedFormat === 'pptx' ? {
|
|
401
416
|
slidePlans: Array.isArray(input.slidePlans) ? clone(input.slidePlans) : [],
|
|
402
417
|
} : {}),
|
|
418
|
+
compositions: Array.isArray(input.compositions) ? clone(input.compositions) : [],
|
|
403
419
|
review: {
|
|
404
420
|
required: normalizedFormat === 'pptx'
|
|
405
421
|
? true
|
|
@@ -487,11 +503,17 @@ function pptxLayoutFit(layout, operation, design, usage) {
|
|
|
487
503
|
const densityPenalty = densityDistance(layout.density || design.density, demand.density);
|
|
488
504
|
const textSlots = Math.max(Number(capacity.textSlots) || 0, slots.filter((slot) => slot.type === 'text').length);
|
|
489
505
|
const textSlotShortfall = nativeSample ? Math.max(0, demand.textItems - textSlots) : 0;
|
|
506
|
+
const plannedVariant = String(operation.__composition?.variant || '').toLowerCase();
|
|
507
|
+
const purposeFit = !layout.purposes?.length || layout.purposes.includes(design.purpose);
|
|
508
|
+
const expressionFit = !layout.expressionModes?.length || layout.expressionModes.includes(design.expressionMode);
|
|
490
509
|
const score = (
|
|
491
510
|
(layout.profile === design.profile ? 4 : 0)
|
|
492
511
|
+ (layout.density === demand.density ? 7 : 0)
|
|
493
512
|
- densityPenalty * 2
|
|
494
513
|
+ (String(operation.variant || '').toLowerCase() && layout.variant === String(operation.variant).toLowerCase() ? 2 : 0)
|
|
514
|
+
+ (plannedVariant && layout.variant === plannedVariant ? 3 : 0)
|
|
515
|
+
+ (purposeFit ? 4 : -6)
|
|
516
|
+
+ (expressionFit ? 2 : -3)
|
|
495
517
|
+ demand.capabilities.filter((capability) => layout.capabilities?.includes(capability)).length * 4
|
|
496
518
|
+ Math.min(demand.metrics, available.metrics) * 3
|
|
497
519
|
+ Math.min(demand.columns, available.columns) * 3
|
|
@@ -512,6 +534,8 @@ function pptxLayoutFit(layout, operation, design, usage) {
|
|
|
512
534
|
textSlots,
|
|
513
535
|
textSlotShortfall,
|
|
514
536
|
nativeSample,
|
|
537
|
+
purpose: purposeFit,
|
|
538
|
+
expressionMode: expressionFit,
|
|
515
539
|
},
|
|
516
540
|
};
|
|
517
541
|
}
|
|
@@ -572,7 +596,7 @@ function templateSlotText(operation, role) {
|
|
|
572
596
|
return undefined;
|
|
573
597
|
}
|
|
574
598
|
|
|
575
|
-
function expandTemplatePptxSlide(operation, layout, slide) {
|
|
599
|
+
function expandTemplatePptxSlide(operation, layout, slide, backend = '') {
|
|
576
600
|
if (
|
|
577
601
|
operation.create === false
|
|
578
602
|
|| !layout?.templatePath
|
|
@@ -611,6 +635,9 @@ function expandTemplatePptxSlide(operation, layout, slide) {
|
|
|
611
635
|
path: imagePath,
|
|
612
636
|
});
|
|
613
637
|
} else if (slot.type === 'chart' && plainObject(operation.chart)) {
|
|
638
|
+
if (backend === 'mixdog-ooxml') {
|
|
639
|
+
throw new Error('compose_slide chart requires Microsoft PowerPoint; omit chart or create the deck with PowerPoint installed');
|
|
640
|
+
}
|
|
614
641
|
output.push({
|
|
615
642
|
op: 'set_chart_data',
|
|
616
643
|
slide,
|
|
@@ -743,10 +770,12 @@ function provenanceText(source) {
|
|
|
743
770
|
return target ? `${document}#${target}` : document;
|
|
744
771
|
}
|
|
745
772
|
|
|
746
|
-
function expandPptxSlide(operation, design, slide) {
|
|
773
|
+
function expandPptxSlide(operation, design, slide, backend = '') {
|
|
774
|
+
const portable = backend === 'mixdog-ooxml';
|
|
747
775
|
const colors = design.tokens.colors;
|
|
748
776
|
const type = design.tokens.typography;
|
|
749
777
|
const kind = String(operation.kind || 'content').toLowerCase();
|
|
778
|
+
const compositionVariant = String(operation.__composition?.variant || '').toLowerCase();
|
|
750
779
|
if (!PPTX_COMPONENTS.includes(kind)) {
|
|
751
780
|
throw new Error(`Unknown compose_slide kind "${kind}". Use one of: ${PPTX_COMPONENTS.join(', ')}`);
|
|
752
781
|
}
|
|
@@ -770,16 +799,17 @@ function expandPptxSlide(operation, design, slide) {
|
|
|
770
799
|
const contentTitleSize = pptTitleSize(title, Number(operation.titleSize) || 34);
|
|
771
800
|
|
|
772
801
|
if (kind === 'cover') {
|
|
802
|
+
const minimalFocus = compositionVariant === 'minimal-focus';
|
|
773
803
|
if (eyebrow) output.push(pptText(slide, eyebrow.toUpperCase(), {
|
|
774
804
|
left: 58, top: 58, width: 420, height: 24,
|
|
775
805
|
fontName: type.body, fontSize: 12, bold: true, color: colors.accent,
|
|
776
806
|
}));
|
|
777
807
|
output.push(pptText(slide, title, {
|
|
778
|
-
left: 58, top: 126, width: 720, height:
|
|
779
|
-
fontName: type.display, fontSize: pptTitleSize(title, Number(operation.titleSize) || 44), bold: true, color: colors.onInverse,
|
|
808
|
+
left: 58, top: minimalFocus ? 150 : 126, width: minimalFocus ? 842 : 720, height: 180,
|
|
809
|
+
fontName: type.display, fontSize: pptTitleSize(title, Number(operation.titleSize) || (minimalFocus ? 48 : 44)), bold: true, color: colors.onInverse,
|
|
780
810
|
}));
|
|
781
811
|
if (subtitle) output.push(pptText(slide, subtitle, {
|
|
782
|
-
left: 58, top: 326, width: 650, height: 72,
|
|
812
|
+
left: 58, top: minimalFocus ? 350 : 326, width: minimalFocus ? 760 : 650, height: 72,
|
|
783
813
|
fontName: type.body, fontSize: 18, color: mutedColor,
|
|
784
814
|
}));
|
|
785
815
|
const meta = strings(operation.meta);
|
|
@@ -787,39 +817,30 @@ function expandPptxSlide(operation, design, slide) {
|
|
|
787
817
|
left: 58, top: 474, width: 720, height: 22,
|
|
788
818
|
fontName: type.body, fontSize: 12, color: mutedColor,
|
|
789
819
|
}));
|
|
790
|
-
output.push(pptShape(slide, 'oval', {
|
|
791
|
-
left: 734, top: 92, width: 62, height: 62,
|
|
792
|
-
fillColor: colors.accent, lineColor: colors.accent,
|
|
793
|
-
}));
|
|
794
|
-
output.push(pptShape(slide, 'right_arrow', {
|
|
795
|
-
left: 802, top: 110, width: 46, height: 26,
|
|
796
|
-
fillColor: colors.accent2, lineColor: colors.accent2,
|
|
797
|
-
}));
|
|
798
|
-
output.push(pptShape(slide, 'oval', {
|
|
799
|
-
left: 854, top: 92, width: 62, height: 62,
|
|
800
|
-
fillColor: colors.surface2, lineColor: colors.surface2,
|
|
801
|
-
}));
|
|
802
820
|
} else if (kind === 'statement') {
|
|
803
|
-
output.push(pptShape(slide, 'oval', {
|
|
804
|
-
left: 714, top: 154, width: 164, height: 164,
|
|
805
|
-
fillColor: colors.surface, lineColor: colors.surface,
|
|
806
|
-
}));
|
|
807
821
|
if (eyebrow) output.push(pptText(slide, eyebrow.toUpperCase(), {
|
|
808
822
|
left: 58, top: 52, width: 420, height: 22,
|
|
809
823
|
fontName: type.body, fontSize: 12, bold: true, color: colors.accent,
|
|
810
824
|
}));
|
|
811
825
|
const metric = operation.metric || operation.metrics?.[0] || {};
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
826
|
+
const showMetric = metric.value != null && compositionVariant !== 'typographic-statement';
|
|
827
|
+
if (showMetric) {
|
|
828
|
+
output.push(pptShape(slide, 'oval', {
|
|
829
|
+
left: 714, top: 154, width: 164, height: 164,
|
|
830
|
+
fillColor: colors.surface, lineColor: colors.surface,
|
|
831
|
+
}));
|
|
832
|
+
output.push(pptText(slide, metricDisplayValue(metric), {
|
|
833
|
+
left: 674, top: 188, width: 244, height: 92,
|
|
834
|
+
fontName: type.display, fontSize: 64, bold: true, color: colors.accent, alignment: 'center',
|
|
835
|
+
}));
|
|
836
|
+
if (metric.label) output.push(pptText(slide, String(metric.label), {
|
|
837
|
+
left: 734, top: 286, width: 124, height: 24,
|
|
838
|
+
fontName: type.body, fontSize: 12, bold: true, color: colors.muted, alignment: 'center',
|
|
839
|
+
}));
|
|
840
|
+
}
|
|
820
841
|
output.push(pptText(slide, title, {
|
|
821
|
-
left: 58, top: 144, width:
|
|
822
|
-
fontName: type.display, fontSize: pptTitleSize(title, Number(operation.titleSize) || 38), bold: true, color: colors.ink,
|
|
842
|
+
left: 58, top: showMetric ? 144 : 132, width: showMetric ? 560 : 810, height: showMetric ? 150 : 188,
|
|
843
|
+
fontName: type.display, fontSize: pptTitleSize(title, Number(operation.titleSize) || (showMetric ? 38 : 44)), bold: true, color: colors.ink,
|
|
823
844
|
}));
|
|
824
845
|
if (subtitle) output.push(pptText(slide, subtitle, {
|
|
825
846
|
left: 58, top: 356, width: 650, height: 76,
|
|
@@ -835,51 +856,74 @@ function expandPptxSlide(operation, design, slide) {
|
|
|
835
856
|
fontName: type.body, fontSize: 15, color: colors.muted,
|
|
836
857
|
}));
|
|
837
858
|
const metrics = Array.isArray(operation.metrics) ? operation.metrics.slice(0, 4) : [];
|
|
838
|
-
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
859
|
+
if (compositionVariant === 'metric-band') {
|
|
860
|
+
const metricWidth = 824 / Math.max(1, metrics.length);
|
|
861
|
+
metrics.forEach((metric, index) => {
|
|
862
|
+
const left = 58 + (metricWidth * index);
|
|
863
|
+
output.push(pptText(slide, metricDisplayValue(metric), {
|
|
864
|
+
left, top: 214, width: metricWidth - 18, height: 88,
|
|
865
|
+
fontName: type.display, fontSize: metrics.length >= 4 ? 42 : 50, bold: true,
|
|
866
|
+
color: index === 0 ? colors.accent : colors.ink,
|
|
867
|
+
}));
|
|
868
|
+
output.push(pptText(slide, String(metric.label || ''), {
|
|
869
|
+
left, top: 316, width: metricWidth - 18, height: 34,
|
|
870
|
+
fontName: type.body, fontSize: 13, bold: true, color: colors.muted,
|
|
871
|
+
}));
|
|
872
|
+
if (metric.detail) output.push(pptText(slide, String(metric.detail), {
|
|
873
|
+
left, top: 360, width: metricWidth - 22, height: 54,
|
|
874
|
+
fontName: type.body, fontSize: 12, color: colors.muted,
|
|
875
|
+
}));
|
|
876
|
+
});
|
|
877
|
+
} else {
|
|
878
|
+
metrics.forEach((metric, index) => {
|
|
879
|
+
const primary = index === 0;
|
|
880
|
+
const left = primary ? 58 : 520;
|
|
881
|
+
const top = primary ? 208 : 192 + ((index - 1) * 118);
|
|
882
|
+
output.push(pptText(slide, metricDisplayValue(metric), {
|
|
883
|
+
left, top, width: primary ? 392 : 320, height: primary ? 96 : 62,
|
|
884
|
+
fontName: type.display, fontSize: primary ? 58 : 40, bold: true,
|
|
885
|
+
color: primary ? colors.accent : colors.ink,
|
|
886
|
+
}));
|
|
887
|
+
output.push(pptText(slide, String(metric.label || ''), {
|
|
888
|
+
left, top: top + (primary ? 104 : 58), width: primary ? 360 : 320, height: 32,
|
|
889
|
+
fontName: type.body, fontSize: 13, bold: true, color: colors.muted,
|
|
890
|
+
}));
|
|
891
|
+
if (metric.detail) output.push(pptText(slide, String(metric.detail), {
|
|
892
|
+
left, top: top + (primary ? 148 : 86), width: primary ? 390 : 340, height: 38,
|
|
893
|
+
fontName: type.body, fontSize: 12, color: colors.muted,
|
|
894
|
+
}));
|
|
895
|
+
if (!primary) output.push(pptShape(slide, 'oval', {
|
|
896
|
+
left: 486, top: top + 16, width: 14, height: 14,
|
|
897
|
+
fillColor: index === 1 ? colors.accent2 : colors.surface2,
|
|
898
|
+
lineColor: index === 1 ? colors.accent2 : colors.surface2,
|
|
899
|
+
}));
|
|
900
|
+
});
|
|
901
|
+
}
|
|
861
902
|
} else if (kind === 'comparison') {
|
|
862
903
|
output.push(pptText(slide, title, {
|
|
863
904
|
left: 58, top: 42, width: 842, height: 76,
|
|
864
905
|
fontName: type.display, fontSize: contentTitleSize, bold: true, color: colors.ink,
|
|
865
906
|
}));
|
|
866
907
|
const columns = Array.isArray(operation.columns) ? operation.columns.slice(0, 2) : [];
|
|
867
|
-
const
|
|
908
|
+
const alignedEvidence = compositionVariant === 'aligned-evidence';
|
|
909
|
+
const fills = alignedEvidence ? [colors.canvas, colors.canvas] : [colors.surface, colors.inverse];
|
|
868
910
|
columns.forEach((column, index) => {
|
|
869
911
|
const left = index === 0 ? 58 : 492;
|
|
870
912
|
const fill = fills[index];
|
|
871
|
-
const foreground = index === 0 ? colors.ink : colors.onInverse;
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
913
|
+
const foreground = alignedEvidence ? colors.ink : index === 0 ? colors.ink : colors.onInverse;
|
|
914
|
+
if (!alignedEvidence) {
|
|
915
|
+
output.push(pptShape(slide, 'rectangle', {
|
|
916
|
+
left, top: 142, width: 410, height: 328,
|
|
917
|
+
fillColor: fill, lineColor: fill,
|
|
918
|
+
}));
|
|
919
|
+
}
|
|
876
920
|
output.push(pptText(slide, String(column.title || ''), {
|
|
877
921
|
left: left + 28, top: 174, width: 340, height: 48,
|
|
878
|
-
fontName: type.display, fontSize: 24, bold: true, color: foreground,
|
|
922
|
+
fontName: type.display, fontSize: 24, bold: true, color: alignedEvidence ? colors.accent : foreground,
|
|
879
923
|
}));
|
|
880
924
|
const paragraphs = pptBulletParagraphs(column.items || column.body, design, {
|
|
881
925
|
size: 15,
|
|
882
|
-
color: index === 0 ? colors.muted : colors.surface2,
|
|
926
|
+
color: alignedEvidence || index === 0 ? colors.muted : colors.surface2,
|
|
883
927
|
});
|
|
884
928
|
output.push(pptText(slide, '', {
|
|
885
929
|
left: left + 28, top: 244, width: 340, height: 176,
|
|
@@ -892,33 +936,38 @@ function expandPptxSlide(operation, design, slide) {
|
|
|
892
936
|
}));
|
|
893
937
|
const steps = Array.isArray(operation.steps) ? operation.steps.slice(0, 5) : [];
|
|
894
938
|
const width = 820 / Math.max(1, steps.length);
|
|
939
|
+
const staggered = compositionVariant === 'staggered-flow';
|
|
895
940
|
steps.forEach((step, index) => {
|
|
896
941
|
const left = 58 + (width * index);
|
|
942
|
+
const top = staggered ? (index % 2 === 0 ? 160 : 236) : 172;
|
|
897
943
|
output.push(pptShape(slide, 'oval', {
|
|
898
|
-
left, top
|
|
944
|
+
left, top, width: 58, height: 58,
|
|
899
945
|
fillColor: index === 0 ? colors.accent : colors.surface2,
|
|
900
946
|
lineColor: index === 0 ? colors.accent : colors.surface2,
|
|
901
947
|
}));
|
|
902
948
|
output.push(pptText(slide, String(index + 1).padStart(2, '0'), {
|
|
903
|
-
left, top:
|
|
949
|
+
left, top: top + 15, width: 58, height: 30,
|
|
904
950
|
fontName: type.data, fontSize: 22, bold: true,
|
|
905
951
|
color: index === 0 ? colors.onAccent : colors.muted,
|
|
906
952
|
alignment: 'center',
|
|
907
953
|
}));
|
|
908
954
|
output.push(pptText(slide, String(step.title || step.label || ''), {
|
|
909
|
-
left: left - 24, top:
|
|
955
|
+
left: left - 24, top: top + 88, width: 106, height: 50,
|
|
910
956
|
fontName: type.body, fontSize: 15, bold: true, color: colors.ink, alignment: 'center',
|
|
911
957
|
}));
|
|
912
958
|
if (step.detail || step.body) output.push(pptText(slide, String(step.detail || step.body), {
|
|
913
|
-
left: left -
|
|
959
|
+
left: Math.max(20, left - 40), top: top + 146, width: 150, height: 80,
|
|
914
960
|
fontName: type.body, fontSize: 12, color: colors.muted, alignment: 'center',
|
|
915
961
|
}));
|
|
916
962
|
if (index < steps.length - 1) output.push(pptShape(slide, 'right_arrow', {
|
|
917
|
-
left: left + width - 78, top: 190, width: 52, height: 20,
|
|
963
|
+
left: left + width - 78, top: staggered ? 220 : 190, width: 52, height: 20,
|
|
918
964
|
fillColor: colors.accent2, lineColor: colors.accent2,
|
|
919
965
|
}));
|
|
920
966
|
});
|
|
921
967
|
} else if (kind === 'chart' || (kind === 'content' && plainObject(operation.chart))) {
|
|
968
|
+
if (portable) {
|
|
969
|
+
throw new Error('compose_slide chart requires Microsoft PowerPoint; use kind "metrics" or "table" for a portable deck');
|
|
970
|
+
}
|
|
922
971
|
output.push(pptText(slide, title, {
|
|
923
972
|
left: 58, top: 42, width: 842, height: 76,
|
|
924
973
|
fontName: type.display, fontSize: contentTitleSize, bold: true, color: colors.ink,
|
|
@@ -930,7 +979,10 @@ function expandPptxSlide(operation, design, slide) {
|
|
|
930
979
|
const paragraphs = operation.bullets
|
|
931
980
|
? pptBulletParagraphs(operation.bullets, design, { size: 14 })
|
|
932
981
|
: pptBodyParagraphs(operation.body, design, { size: 14 });
|
|
933
|
-
|
|
982
|
+
const chartLed = compositionVariant === 'chart-led';
|
|
983
|
+
if (paragraphs.length) output.push(pptText(slide, '', chartLed ? {
|
|
984
|
+
left: 58, top: 440, width: 842, height: 54,
|
|
985
|
+
} : {
|
|
934
986
|
left: 58, top: 184, width: 274, height: 238,
|
|
935
987
|
}, paragraphs));
|
|
936
988
|
const chart = operation.chart || {};
|
|
@@ -944,12 +996,13 @@ function expandPptxSlide(operation, design, slide) {
|
|
|
944
996
|
...entry,
|
|
945
997
|
color: entry?.color || (index === 0 ? colors.accent : index === 1 ? colors.accent2 : colors.muted),
|
|
946
998
|
})) : [],
|
|
947
|
-
left: paragraphs.length ?
|
|
999
|
+
left: chartLed || !paragraphs.length ? 58 : 360,
|
|
948
1000
|
top: 176,
|
|
949
|
-
width: paragraphs.length ?
|
|
950
|
-
height: 286,
|
|
1001
|
+
width: chartLed || !paragraphs.length ? 842 : 540,
|
|
1002
|
+
height: chartLed && paragraphs.length ? 244 : 286,
|
|
951
1003
|
});
|
|
952
1004
|
} else if (kind === 'table' || (kind === 'content' && (Array.isArray(operation.table) || plainObject(operation.table)))) {
|
|
1005
|
+
const tableCallout = compositionVariant === 'table-callout';
|
|
953
1006
|
output.push(pptText(slide, title, {
|
|
954
1007
|
left: 58, top: 42, width: 842, height: 76,
|
|
955
1008
|
fontName: type.display, fontSize: contentTitleSize, bold: true, color: colors.ink,
|
|
@@ -968,17 +1021,17 @@ function expandPptxSlide(operation, design, slide) {
|
|
|
968
1021
|
op: 'add_table',
|
|
969
1022
|
slide,
|
|
970
1023
|
values: Array.isArray(values) ? values : [],
|
|
971
|
-
left: 58,
|
|
972
|
-
top: 174,
|
|
973
|
-
width: 842,
|
|
1024
|
+
left: tableCallout ? 116 : 58,
|
|
1025
|
+
top: tableCallout ? 184 : 174,
|
|
1026
|
+
width: tableCallout ? 728 : 842,
|
|
974
1027
|
height: tableHeight,
|
|
975
1028
|
properties: {
|
|
976
1029
|
fontName: type.body,
|
|
977
1030
|
fontSize: 13,
|
|
978
1031
|
color: colors.ink,
|
|
979
|
-
headerFillColor: colors.inverse,
|
|
980
|
-
headerColor: colors.onInverse,
|
|
981
|
-
bodyFillColor: colors.canvas,
|
|
1032
|
+
headerFillColor: tableCallout ? colors.accent : colors.inverse,
|
|
1033
|
+
headerColor: tableCallout ? colors.onAccent : colors.onInverse,
|
|
1034
|
+
bodyFillColor: tableCallout ? colors.canvas : colors.surface,
|
|
982
1035
|
headerRowHeight: 42,
|
|
983
1036
|
bodyRowHeight,
|
|
984
1037
|
},
|
|
@@ -991,8 +1044,9 @@ function expandPptxSlide(operation, design, slide) {
|
|
|
991
1044
|
const paragraphs = operation.bullets
|
|
992
1045
|
? pptBulletParagraphs(operation.bullets, design, { size: 16 })
|
|
993
1046
|
: pptBodyParagraphs(operation.body, design, { size: 16 });
|
|
1047
|
+
const visualLeft = compositionVariant === 'visual-left';
|
|
994
1048
|
output.push(pptText(slide, '', {
|
|
995
|
-
left: 58, top: 156, width: 374, height: 280,
|
|
1049
|
+
left: visualLeft ? 526 : 58, top: 156, width: 374, height: 280,
|
|
996
1050
|
}, paragraphs));
|
|
997
1051
|
const imagePath = operation.image?.path || operation.imagePath;
|
|
998
1052
|
if (imagePath) {
|
|
@@ -1000,45 +1054,49 @@ function expandPptxSlide(operation, design, slide) {
|
|
|
1000
1054
|
op: 'add_image',
|
|
1001
1055
|
slide,
|
|
1002
1056
|
path: imagePath,
|
|
1003
|
-
left: 492,
|
|
1057
|
+
left: visualLeft ? 58 : 492,
|
|
1004
1058
|
top: 142,
|
|
1005
1059
|
width: 408,
|
|
1006
1060
|
height: 306,
|
|
1007
1061
|
});
|
|
1008
1062
|
} else {
|
|
1009
1063
|
output.push(pptShape(slide, 'rectangle', {
|
|
1010
|
-
left: 492, top: 142, width: 408, height: 306,
|
|
1064
|
+
left: visualLeft ? 58 : 492, top: 142, width: 408, height: 306,
|
|
1011
1065
|
fillColor: colors.inverse, lineColor: colors.inverse,
|
|
1012
1066
|
}));
|
|
1013
1067
|
output.push(pptText(slide, String(operation.visualText || subtitle || ''), {
|
|
1014
|
-
left: 530, top: 214, width: 330, height: 156,
|
|
1068
|
+
left: visualLeft ? 96 : 530, top: 214, width: 330, height: 156,
|
|
1015
1069
|
fontName: type.display, fontSize: 30, bold: true, color: colors.onInverse,
|
|
1016
1070
|
}));
|
|
1017
1071
|
}
|
|
1018
1072
|
} else if (kind === 'closing') {
|
|
1073
|
+
const decisionFocus = compositionVariant === 'decision-focus';
|
|
1019
1074
|
if (eyebrow) output.push(pptText(slide, eyebrow.toUpperCase(), {
|
|
1020
|
-
left: 58, top: 68, width: 420, height: 22,
|
|
1075
|
+
left: decisionFocus ? 220 : 58, top: 68, width: decisionFocus ? 520 : 420, height: 22,
|
|
1021
1076
|
fontName: type.body, fontSize: 12, bold: true, color: colors.accent,
|
|
1077
|
+
...(decisionFocus ? { alignment: 'center' } : {}),
|
|
1022
1078
|
}));
|
|
1023
1079
|
output.push(pptText(slide, title, {
|
|
1024
|
-
left: 58, top: 156, width: operation.visualText ?
|
|
1080
|
+
left: decisionFocus ? 120 : 58, top: 156, width: decisionFocus ? 720 : operation.visualText ? 410 : 760, height: 142,
|
|
1025
1081
|
fontName: type.display, fontSize: pptTitleSize(title, 42), bold: true, color: colors.onInverse,
|
|
1082
|
+
...(decisionFocus ? { alignment: 'center' } : {}),
|
|
1026
1083
|
}));
|
|
1027
1084
|
if (subtitle) output.push(pptText(slide, subtitle, {
|
|
1028
|
-
left: 58, top: 330, width: 690, height: 72,
|
|
1085
|
+
left: decisionFocus ? 150 : 58, top: 330, width: decisionFocus ? 660 : 690, height: 72,
|
|
1029
1086
|
fontName: type.body, fontSize: 17, color: mutedColor,
|
|
1087
|
+
...(decisionFocus ? { alignment: 'center' } : {}),
|
|
1030
1088
|
}));
|
|
1031
1089
|
if (operation.visualText) {
|
|
1032
1090
|
output.push(pptShape(slide, 'oval', {
|
|
1033
|
-
left: 770, top: 160, width: 126, height: 126,
|
|
1091
|
+
left: decisionFocus ? 417 : 770, top: decisionFocus ? 398 : 160, width: 126, height: 126,
|
|
1034
1092
|
fillColor: colors.accent, lineColor: colors.accent,
|
|
1035
1093
|
}));
|
|
1036
1094
|
output.push(pptText(slide, String(operation.visualText), {
|
|
1037
|
-
left: 770, top: 184, width: 126, height: 54,
|
|
1095
|
+
left: decisionFocus ? 417 : 770, top: decisionFocus ? 422 : 184, width: 126, height: 54,
|
|
1038
1096
|
fontName: type.display, fontSize: 34, bold: true, color: colors.onAccent, alignment: 'center',
|
|
1039
1097
|
}));
|
|
1040
1098
|
if (operation.visualLabel) output.push(pptText(slide, String(operation.visualLabel), {
|
|
1041
|
-
left: 785, top: 246, width: 96, height: 24,
|
|
1099
|
+
left: decisionFocus ? 432 : 785, top: decisionFocus ? 484 : 246, width: 96, height: 24,
|
|
1042
1100
|
fontName: type.body, fontSize: 12, bold: true, color: colors.onAccent, alignment: 'center',
|
|
1043
1101
|
}));
|
|
1044
1102
|
}
|
|
@@ -1051,14 +1109,30 @@ function expandPptxSlide(operation, design, slide) {
|
|
|
1051
1109
|
left: 58, top: 116, width: 760, height: 38,
|
|
1052
1110
|
fontName: type.body, fontSize: 14, color: mutedColor,
|
|
1053
1111
|
}));
|
|
1054
|
-
const paragraphs = operation.bullets
|
|
1055
|
-
? pptBulletParagraphs(operation.bullets, design)
|
|
1056
|
-
: pptBodyParagraphs(operation.body, design);
|
|
1057
|
-
output.push(pptText(slide, '', {
|
|
1058
|
-
left: 58, top: 178, width: 590, height: 250,
|
|
1059
|
-
}, paragraphs));
|
|
1060
1112
|
const visualText = String(operation.visualText || '');
|
|
1061
|
-
|
|
1113
|
+
const editorialWide = compositionVariant === 'editorial-wide';
|
|
1114
|
+
const body = strings(operation.bullets || operation.body);
|
|
1115
|
+
if (editorialWide) {
|
|
1116
|
+
const lead = visualText || body.shift() || subtitle;
|
|
1117
|
+
if (lead) output.push(pptText(slide, lead, {
|
|
1118
|
+
left: 58, top: 176, width: 804, height: 132,
|
|
1119
|
+
fontName: type.display, fontSize: pptTitleSize(lead, 42), bold: true, color: colors.accent,
|
|
1120
|
+
}));
|
|
1121
|
+
const support = operation.bullets
|
|
1122
|
+
? pptBulletParagraphs(body, design, { size: 16 })
|
|
1123
|
+
: pptBodyParagraphs(body, design, { size: 16 });
|
|
1124
|
+
if (support.length) output.push(pptText(slide, '', {
|
|
1125
|
+
left: 58, top: 332, width: 760, height: 118,
|
|
1126
|
+
}, support));
|
|
1127
|
+
} else {
|
|
1128
|
+
const paragraphs = operation.bullets
|
|
1129
|
+
? pptBulletParagraphs(operation.bullets, design)
|
|
1130
|
+
: pptBodyParagraphs(operation.body, design);
|
|
1131
|
+
output.push(pptText(slide, '', {
|
|
1132
|
+
left: 58, top: 178, width: 590, height: 250,
|
|
1133
|
+
}, paragraphs));
|
|
1134
|
+
}
|
|
1135
|
+
if (visualText && !editorialWide) {
|
|
1062
1136
|
output.push(pptShape(slide, 'rectangle', {
|
|
1063
1137
|
left: 680, top: 178, width: 220, height: 220,
|
|
1064
1138
|
fillColor: colors.surface, lineColor: colors.surface,
|
|
@@ -1075,20 +1149,31 @@ function expandPptxSlide(operation, design, slide) {
|
|
|
1075
1149
|
return output;
|
|
1076
1150
|
}
|
|
1077
1151
|
|
|
1078
|
-
function expandDocxDocument(operation, design, state, backend) {
|
|
1152
|
+
function expandDocxDocument(operation, design, state, backend, composition) {
|
|
1079
1153
|
const output = [];
|
|
1080
1154
|
const colors = design.tokens.colors;
|
|
1081
1155
|
const type = design.tokens.typography;
|
|
1082
1156
|
const format = design.format;
|
|
1157
|
+
const compositionId = String(composition?.id || 'decision-brief');
|
|
1158
|
+
const compactMemo = compositionId === 'compact-memo';
|
|
1159
|
+
const editorialReport = compositionId === 'editorial-report';
|
|
1160
|
+
const evidenceBrief = compositionId === 'evidence-brief';
|
|
1161
|
+
const decisionBrief = compositionId === 'decision-brief';
|
|
1162
|
+
const pageMargin = compactMemo
|
|
1163
|
+
? Math.max(46.8, format.margin * 0.84)
|
|
1164
|
+
: editorialReport
|
|
1165
|
+
? format.margin * 1.08
|
|
1166
|
+
: format.margin;
|
|
1167
|
+
const bodySize = compactMemo ? Math.max(9.5, format.body - 0.5) : format.body;
|
|
1083
1168
|
if (backend === 'microsoft-office-com' && operation.page !== false) {
|
|
1084
1169
|
output.push({
|
|
1085
1170
|
op: 'set_page',
|
|
1086
1171
|
properties: {
|
|
1087
1172
|
orientation: operation.orientation || 'portrait',
|
|
1088
|
-
topMargin:
|
|
1089
|
-
bottomMargin:
|
|
1090
|
-
leftMargin:
|
|
1091
|
-
rightMargin:
|
|
1173
|
+
topMargin: pageMargin,
|
|
1174
|
+
bottomMargin: pageMargin,
|
|
1175
|
+
leftMargin: pageMargin,
|
|
1176
|
+
rightMargin: pageMargin,
|
|
1092
1177
|
},
|
|
1093
1178
|
});
|
|
1094
1179
|
}
|
|
@@ -1105,73 +1190,72 @@ function expandDocxDocument(operation, design, state, backend) {
|
|
|
1105
1190
|
};
|
|
1106
1191
|
append(operation.title, 'Title', {
|
|
1107
1192
|
name: type.display,
|
|
1108
|
-
size: Number(operation.titleSize) || format.title,
|
|
1193
|
+
size: Number(operation.titleSize) || format.title + (editorialReport ? 3 : compactMemo ? -1 : 0),
|
|
1109
1194
|
bold: true,
|
|
1110
1195
|
color: colors.ink,
|
|
1111
1196
|
spacingBefore: 0,
|
|
1112
|
-
spacingAfter: 8,
|
|
1197
|
+
spacingAfter: editorialReport ? 12 : compactMemo ? 5 : 8,
|
|
1113
1198
|
keepWithNext: true,
|
|
1114
1199
|
});
|
|
1115
1200
|
append(operation.subtitle, 'Normal', {
|
|
1116
1201
|
name: type.body,
|
|
1117
|
-
size:
|
|
1202
|
+
size: bodySize,
|
|
1118
1203
|
color: colors.muted,
|
|
1119
1204
|
spacingBefore: 0,
|
|
1120
|
-
spacingAfter: 12,
|
|
1121
|
-
lineSpacing:
|
|
1205
|
+
spacingAfter: compactMemo ? 7 : 12,
|
|
1206
|
+
lineSpacing: bodySize * 1.35,
|
|
1122
1207
|
});
|
|
1123
1208
|
const meta = strings(operation.meta);
|
|
1124
1209
|
if (meta.length) {
|
|
1125
1210
|
append(meta.join(' · '), 'Normal', {
|
|
1126
1211
|
name: type.body,
|
|
1127
|
-
size: Math.max(8.5,
|
|
1212
|
+
size: Math.max(8.5, bodySize - 1),
|
|
1128
1213
|
color: colors.muted,
|
|
1129
1214
|
spacingBefore: 0,
|
|
1130
|
-
spacingAfter: 14,
|
|
1215
|
+
spacingAfter: compactMemo ? 8 : 14,
|
|
1131
1216
|
});
|
|
1132
1217
|
}
|
|
1133
1218
|
if (operation.summary) {
|
|
1134
1219
|
append(strings(operation.summary).join(' '), 'Normal', {
|
|
1135
1220
|
name: type.display,
|
|
1136
|
-
size:
|
|
1221
|
+
size: bodySize + (decisionBrief ? 1.5 : 1),
|
|
1137
1222
|
bold: true,
|
|
1138
|
-
color: colors.accent,
|
|
1139
|
-
spacingBefore: 4,
|
|
1140
|
-
spacingAfter: 14,
|
|
1141
|
-
lineSpacing: (
|
|
1223
|
+
color: decisionBrief ? colors.accent : colors.ink,
|
|
1224
|
+
spacingBefore: compactMemo ? 2 : 4,
|
|
1225
|
+
spacingAfter: editorialReport ? 18 : compactMemo ? 9 : 14,
|
|
1226
|
+
lineSpacing: (bodySize + 1) * 1.35,
|
|
1142
1227
|
keepWithNext: true,
|
|
1143
|
-
border: { side: 'left', color: colors.accent },
|
|
1144
1228
|
});
|
|
1145
1229
|
}
|
|
1146
1230
|
for (const section of Array.isArray(operation.sections) ? operation.sections : []) {
|
|
1147
1231
|
append(section.heading || section.title, Number(section.level) === 2 ? 'Heading 2' : 'Heading 1', {
|
|
1148
1232
|
name: type.display,
|
|
1149
|
-
size: Number(section.level) === 2 ? format.heading2 : format.heading1,
|
|
1233
|
+
size: (Number(section.level) === 2 ? format.heading2 : format.heading1) + (editorialReport ? 1 : 0),
|
|
1150
1234
|
bold: true,
|
|
1151
|
-
color: section.accent === false ? colors.ink : colors.accent,
|
|
1152
|
-
spacingBefore: Number(section.level) === 2 ? 9 : 14,
|
|
1153
|
-
spacingAfter: 5,
|
|
1235
|
+
color: section.accent === false || evidenceBrief ? colors.ink : colors.accent,
|
|
1236
|
+
spacingBefore: compactMemo ? (Number(section.level) === 2 ? 6 : 10) : Number(section.level) === 2 ? 9 : 14,
|
|
1237
|
+
spacingAfter: editorialReport ? 7 : 5,
|
|
1154
1238
|
keepWithNext: true,
|
|
1155
1239
|
pageBreakBefore: section.pageBreak === true,
|
|
1156
1240
|
});
|
|
1157
1241
|
for (const paragraph of strings(section.paragraphs || section.body)) {
|
|
1158
1242
|
append(paragraph, 'Normal', {
|
|
1159
1243
|
name: type.body,
|
|
1160
|
-
size:
|
|
1244
|
+
size: bodySize,
|
|
1161
1245
|
color: colors.ink,
|
|
1162
1246
|
spacingBefore: 0,
|
|
1163
|
-
spacingAfter: 6,
|
|
1164
|
-
lineSpacing:
|
|
1247
|
+
spacingAfter: compactMemo ? 4 : editorialReport ? 8 : 6,
|
|
1248
|
+
lineSpacing: bodySize * (compactMemo ? 1.32 : 1.4),
|
|
1165
1249
|
});
|
|
1166
1250
|
}
|
|
1167
1251
|
for (const bullet of strings(section.bullets)) {
|
|
1168
1252
|
append(bullet, 'Normal', {
|
|
1169
1253
|
name: type.body,
|
|
1170
|
-
size:
|
|
1254
|
+
size: bodySize,
|
|
1171
1255
|
color: colors.ink,
|
|
1172
1256
|
spacingBefore: 0,
|
|
1173
1257
|
spacingAfter: 3,
|
|
1174
|
-
lineSpacing:
|
|
1258
|
+
lineSpacing: bodySize * 1.35,
|
|
1175
1259
|
listKind: 'bullet',
|
|
1176
1260
|
listLevel: 0,
|
|
1177
1261
|
});
|
|
@@ -1179,12 +1263,12 @@ function expandDocxDocument(operation, design, state, backend) {
|
|
|
1179
1263
|
if (section.quote) {
|
|
1180
1264
|
append(section.quote, 'Quote', {
|
|
1181
1265
|
name: type.display,
|
|
1182
|
-
size:
|
|
1266
|
+
size: bodySize + (editorialReport ? 2 : 1),
|
|
1183
1267
|
italic: true,
|
|
1184
1268
|
color: colors.accent,
|
|
1185
1269
|
spacingBefore: 5,
|
|
1186
1270
|
spacingAfter: 9,
|
|
1187
|
-
lineSpacing: (
|
|
1271
|
+
lineSpacing: (bodySize + 1) * 1.35,
|
|
1188
1272
|
});
|
|
1189
1273
|
}
|
|
1190
1274
|
if (Array.isArray(section.table) && section.table.length) {
|
|
@@ -1196,7 +1280,7 @@ function expandDocxDocument(operation, design, state, backend) {
|
|
|
1196
1280
|
style: 'Table Grid',
|
|
1197
1281
|
textStyle: 'Normal',
|
|
1198
1282
|
fontName: type.body,
|
|
1199
|
-
fontSize: Math.max(9,
|
|
1283
|
+
fontSize: Math.max(9, bodySize - 1),
|
|
1200
1284
|
color: colors.ink,
|
|
1201
1285
|
spacingAfter: 0,
|
|
1202
1286
|
borders: true,
|
|
@@ -1212,8 +1296,8 @@ function expandDocxDocument(operation, design, state, backend) {
|
|
|
1212
1296
|
row: 1,
|
|
1213
1297
|
col: column,
|
|
1214
1298
|
properties: {
|
|
1215
|
-
fillColor: colors.inverse,
|
|
1216
|
-
color: colors.onInverse,
|
|
1299
|
+
fillColor: evidenceBrief ? colors.accent : colors.inverse,
|
|
1300
|
+
color: evidenceBrief ? colors.onAccent : colors.onInverse,
|
|
1217
1301
|
bold: true,
|
|
1218
1302
|
verticalAlignment: 'center',
|
|
1219
1303
|
},
|
|
@@ -1222,11 +1306,15 @@ function expandDocxDocument(operation, design, state, backend) {
|
|
|
1222
1306
|
}
|
|
1223
1307
|
}
|
|
1224
1308
|
}
|
|
1225
|
-
if (backend === 'microsoft-office-com' && operation.footer) {
|
|
1226
|
-
output.push({ op: 'set_header_footer', header: false, text: String(operation.footer) });
|
|
1227
|
-
}
|
|
1228
1309
|
if (backend === 'microsoft-office-com' && operation.pageNumbers === true) {
|
|
1229
|
-
output.push({
|
|
1310
|
+
output.push({
|
|
1311
|
+
op: 'add_page_numbers',
|
|
1312
|
+
includeTotal: true,
|
|
1313
|
+
alignment: 'center',
|
|
1314
|
+
...(operation.footer ? { prefix: `${String(operation.footer)} · Page ` } : {}),
|
|
1315
|
+
});
|
|
1316
|
+
} else if (backend === 'microsoft-office-com' && operation.footer) {
|
|
1317
|
+
output.push({ op: 'set_header_footer', header: false, text: String(operation.footer) });
|
|
1230
1318
|
}
|
|
1231
1319
|
return output;
|
|
1232
1320
|
}
|
|
@@ -1252,11 +1340,17 @@ function isExcelTotalRow(row) {
|
|
|
1252
1340
|
return /^(?:(?:grand\s+total|sub\s*total|total)\b|(?:합계|총계|소계)(?:\s|$))/i.test(String(row?.[0] || '').trim());
|
|
1253
1341
|
}
|
|
1254
1342
|
|
|
1255
|
-
function expandXlsxSheet(operation, design) {
|
|
1343
|
+
function expandXlsxSheet(operation, design, composition, backend = '') {
|
|
1344
|
+
const portable = backend === 'mixdog-ooxml';
|
|
1256
1345
|
const output = [];
|
|
1257
1346
|
const colors = design.tokens.colors;
|
|
1258
1347
|
const type = design.tokens.typography;
|
|
1259
1348
|
const format = design.format;
|
|
1349
|
+
const compositionId = String(composition?.id || 'monitor-dashboard');
|
|
1350
|
+
const trendDashboard = compositionId === 'trend-dashboard';
|
|
1351
|
+
const comparisonBoard = compositionId === 'comparison-board';
|
|
1352
|
+
const analysisSheet = compositionId === 'analysis-sheet';
|
|
1353
|
+
const narrativeScorecard = compositionId === 'narrative-scorecard';
|
|
1260
1354
|
const sheet = String(operation.sheet || 'Sheet1');
|
|
1261
1355
|
const headers = strings(operation.headers);
|
|
1262
1356
|
const rows = Array.isArray(operation.rows) ? operation.rows : [];
|
|
@@ -1285,8 +1379,8 @@ function expandXlsxSheet(operation, design) {
|
|
|
1285
1379
|
fontName: type.display,
|
|
1286
1380
|
fontSize: Number(operation.titleSize) || format.title,
|
|
1287
1381
|
bold: true,
|
|
1288
|
-
color: colors.onInverse,
|
|
1289
|
-
fillColor: colors.inverse,
|
|
1382
|
+
color: analysisSheet ? colors.ink : narrativeScorecard ? colors.onAccent : colors.onInverse,
|
|
1383
|
+
fillColor: analysisSheet ? colors.surface2 : narrativeScorecard ? colors.accent : colors.inverse,
|
|
1290
1384
|
verticalAlignment: 'center',
|
|
1291
1385
|
wrapText: true,
|
|
1292
1386
|
},
|
|
@@ -1336,10 +1430,10 @@ function expandXlsxSheet(operation, design) {
|
|
|
1336
1430
|
range: `${start}${valueRow}:${end}${valueRow}`,
|
|
1337
1431
|
properties: {
|
|
1338
1432
|
fontName: type.data,
|
|
1339
|
-
fontSize: 22,
|
|
1433
|
+
fontSize: narrativeScorecard ? 25 : comparisonBoard ? 20 : 22,
|
|
1340
1434
|
bold: true,
|
|
1341
|
-
color: index === 0 ? colors.onAccent : colors.ink,
|
|
1342
|
-
fillColor: index === 0 ? colors.accent : colors.surface,
|
|
1435
|
+
color: index === 0 && !analysisSheet ? colors.onAccent : colors.ink,
|
|
1436
|
+
fillColor: index === 0 && !analysisSheet ? colors.accent : trendDashboard ? colors.canvas : colors.surface,
|
|
1343
1437
|
numberFormat: metric?.numberFormat || 'General',
|
|
1344
1438
|
horizontalAlignment: 'center',
|
|
1345
1439
|
verticalAlignment: 'center',
|
|
@@ -1375,8 +1469,8 @@ function expandXlsxSheet(operation, design) {
|
|
|
1375
1469
|
fontName: type.body,
|
|
1376
1470
|
fontSize: format.body,
|
|
1377
1471
|
bold: true,
|
|
1378
|
-
color: colors.ink,
|
|
1379
|
-
fillColor: colors.surface2,
|
|
1472
|
+
color: narrativeScorecard ? colors.onInverse : colors.ink,
|
|
1473
|
+
fillColor: narrativeScorecard ? colors.inverse : trendDashboard ? colors.surface : colors.surface2,
|
|
1380
1474
|
wrapText: true,
|
|
1381
1475
|
},
|
|
1382
1476
|
});
|
|
@@ -1408,13 +1502,15 @@ function expandXlsxSheet(operation, design) {
|
|
|
1408
1502
|
wrapText: true,
|
|
1409
1503
|
},
|
|
1410
1504
|
});
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1505
|
+
if (!portable) {
|
|
1506
|
+
output.push({
|
|
1507
|
+
op: 'add_table',
|
|
1508
|
+
sheet,
|
|
1509
|
+
range: `A${startRow}:${dataLastColumn}${endRow}`,
|
|
1510
|
+
name: safeTableName(operation.tableName || `${sheet}Data`),
|
|
1511
|
+
style: operation.tableStyle || format.tableStyle,
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1418
1514
|
output.push({ op: 'freeze_panes', sheet, row: startRow + 1, column: 1 });
|
|
1419
1515
|
}
|
|
1420
1516
|
output.push({
|
|
@@ -1441,31 +1537,68 @@ function expandXlsxSheet(operation, design) {
|
|
|
1441
1537
|
});
|
|
1442
1538
|
}
|
|
1443
1539
|
if (plainObject(operation.chart)) {
|
|
1540
|
+
if (portable) {
|
|
1541
|
+
throw new Error('compose_sheet chart requires Microsoft Excel; omit chart or create the workbook with Excel installed');
|
|
1542
|
+
}
|
|
1444
1543
|
let chartRows = rows.length;
|
|
1445
1544
|
while (chartRows > 0 && isExcelTotalRow(rows[chartRows - 1])) chartRows -= 1;
|
|
1446
1545
|
const chartEndRow = startRow + (headers.length ? 1 : 0) + chartRows - 1;
|
|
1447
1546
|
const chartRange = operation.chart.range
|
|
1448
1547
|
|| `A${startRow}:${dataLastColumn}${Math.max(startRow, chartEndRow)}`;
|
|
1548
|
+
const chartType = operation.chart.type || 'column';
|
|
1549
|
+
const showValues = operation.chart.showValues ?? (dashboard && chartRows <= 6);
|
|
1550
|
+
const dataLabelPosition = operation.chart.dataLabelPosition
|
|
1551
|
+
|| (showValues && ['column', 'bar'].includes(String(chartType).toLowerCase()) ? 'inside_end' : '');
|
|
1552
|
+
const chartDefaults = trendDashboard
|
|
1553
|
+
? { left: 360, top: 172, width: 510, height: 286 }
|
|
1554
|
+
: comparisonBoard
|
|
1555
|
+
? { left: 390, top: 184, width: 480, height: 278 }
|
|
1556
|
+
: analysisSheet
|
|
1557
|
+
? { left: 520, top: 40, width: 480, height: 280 }
|
|
1558
|
+
: narrativeScorecard
|
|
1559
|
+
? { left: 430, top: 206, width: 450, height: 258 }
|
|
1560
|
+
: { left: dashboard ? 410 : 520, top: dashboard ? 190 : 40, width: dashboard ? 420 : 480, height: 280 };
|
|
1449
1561
|
output.push({
|
|
1450
1562
|
op: 'add_chart',
|
|
1451
1563
|
sheet,
|
|
1452
1564
|
range: chartRange,
|
|
1453
|
-
chartType
|
|
1565
|
+
chartType,
|
|
1454
1566
|
title: operation.chart.title || '',
|
|
1455
|
-
left: Number(operation.chart.left) ||
|
|
1456
|
-
top: Number(operation.chart.top) ||
|
|
1457
|
-
width: Number(operation.chart.width) ||
|
|
1458
|
-
height: Number(operation.chart.height) ||
|
|
1567
|
+
left: Number(operation.chart.left) || chartDefaults.left,
|
|
1568
|
+
top: Number(operation.chart.top) || chartDefaults.top,
|
|
1569
|
+
width: Number(operation.chart.width) || chartDefaults.width,
|
|
1570
|
+
height: Number(operation.chart.height) || chartDefaults.height,
|
|
1571
|
+
seriesColors: operation.chart.seriesColors || [colors.accent, colors.accent2, colors.muted],
|
|
1572
|
+
showValues,
|
|
1573
|
+
...(dataLabelPosition ? { dataLabelPosition } : {}),
|
|
1574
|
+
...(dataLabelPosition === 'inside_end' ? {
|
|
1575
|
+
dataLabelColor: operation.chart.dataLabelColor || colors.onAccent,
|
|
1576
|
+
} : {}),
|
|
1577
|
+
zeroBaseline: operation.chart.zeroBaseline ?? ['column', 'bar'].includes(String(chartType).toLowerCase()),
|
|
1578
|
+
...(operation.chart.showLegend == null ? {} : { showLegend: operation.chart.showLegend }),
|
|
1579
|
+
...(operation.chart.valueNumberFormat ? { valueNumberFormat: operation.chart.valueNumberFormat } : {}),
|
|
1459
1580
|
});
|
|
1460
1581
|
}
|
|
1461
1582
|
}
|
|
1462
1583
|
if (operation.source) {
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1584
|
+
const attribution = `Source: ${provenanceText(operation.source) || String(operation.source)}`;
|
|
1585
|
+
if (portable) {
|
|
1586
|
+
const captionRow = Math.max(row, startRow + values.length) + 1;
|
|
1587
|
+
output.push({ op: 'set_cell', sheet, cell: `A${captionRow}`, value: attribution });
|
|
1588
|
+
output.push({
|
|
1589
|
+
op: 'set_style',
|
|
1590
|
+
sheet,
|
|
1591
|
+
range: `A${captionRow}:${dataLastColumn}${captionRow}`,
|
|
1592
|
+
properties: {
|
|
1593
|
+
fontName: type.body,
|
|
1594
|
+
fontSize: 9,
|
|
1595
|
+
italic: true,
|
|
1596
|
+
color: colors.muted,
|
|
1597
|
+
},
|
|
1598
|
+
});
|
|
1599
|
+
} else {
|
|
1600
|
+
output.push({ op: 'add_note', sheet, cell: 'A1', text: attribution });
|
|
1601
|
+
}
|
|
1469
1602
|
}
|
|
1470
1603
|
output.push({ op: 'autofit_range', sheet, range: `A:${dataLastColumn}` });
|
|
1471
1604
|
const printColumns = plainObject(operation.chart) ? Math.max(columns, 14) : columns;
|
|
@@ -1477,8 +1610,14 @@ function expandXlsxSheet(operation, design) {
|
|
|
1477
1610
|
fitToPagesWide: 1,
|
|
1478
1611
|
fitToPagesTall: dashboard ? 1 : 0,
|
|
1479
1612
|
centerHorizontally: true,
|
|
1613
|
+
centerVertically: dashboard && !analysisSheet,
|
|
1614
|
+
});
|
|
1615
|
+
output.push({
|
|
1616
|
+
op: 'set_sheet_view',
|
|
1617
|
+
sheet,
|
|
1618
|
+
showGridlines: false,
|
|
1619
|
+
zoom: analysisSheet ? 100 : trendDashboard ? 85 : dashboard ? 90 : 100,
|
|
1480
1620
|
});
|
|
1481
|
-
output.push({ op: 'set_sheet_view', sheet, showGridlines: false, zoom: dashboard ? 90 : 100 });
|
|
1482
1621
|
return output;
|
|
1483
1622
|
}
|
|
1484
1623
|
|
|
@@ -1498,19 +1637,28 @@ export function expandOfficeDesignOperations({
|
|
|
1498
1637
|
let nextSlide = created && Number(snapshotVersion || 0) === 0 ? 1 : null;
|
|
1499
1638
|
const docxState = { paragraph: 0, table: 0 };
|
|
1500
1639
|
const layoutUsage = new Map();
|
|
1640
|
+
const compositionUsage = new Map();
|
|
1501
1641
|
for (const operation of operations || []) {
|
|
1502
1642
|
const bound = bindOfficeContent(operation, design.content);
|
|
1503
1643
|
const contentOperation = bound.operation;
|
|
1504
1644
|
const name = String(contentOperation?.op || '');
|
|
1505
1645
|
if (normalizedFormat === 'pptx' && name === 'compose_slide') {
|
|
1506
|
-
const
|
|
1646
|
+
const composition = planOfficeComposition(normalizedFormat, contentOperation, design, {
|
|
1647
|
+
usage: compositionUsage,
|
|
1648
|
+
});
|
|
1649
|
+
const plannedOperation = {
|
|
1650
|
+
...contentOperation,
|
|
1651
|
+
kind: composition.kind,
|
|
1652
|
+
__composition: composition,
|
|
1653
|
+
};
|
|
1654
|
+
const slide = Number(plannedOperation.slide) || nextSlide;
|
|
1507
1655
|
if (!slide) throw new Error('compose_slide requires slide for an existing presentation');
|
|
1508
|
-
const selectedLayout = selectPptxLayout(
|
|
1656
|
+
const selectedLayout = selectPptxLayout(plannedOperation, design, layoutUsage);
|
|
1509
1657
|
const layout = selectedLayout?.layout || null;
|
|
1510
|
-
const composed = layout ? merge(layout.defaults || {},
|
|
1511
|
-
const backgroundSpec = pptxBackgroundSpec(composed, design,
|
|
1512
|
-
const plan = pptxSlidePlan(composed,
|
|
1513
|
-
const templateOperations = expandTemplatePptxSlide(composed, layout, slide);
|
|
1658
|
+
const composed = layout ? merge(layout.defaults || {}, plannedOperation) : plannedOperation;
|
|
1659
|
+
const backgroundSpec = pptxBackgroundSpec(composed, design, composition.kind, slide);
|
|
1660
|
+
const plan = pptxSlidePlan(composed, composition.kind, slide);
|
|
1661
|
+
const templateOperations = expandTemplatePptxSlide(composed, layout, slide, backend);
|
|
1514
1662
|
if (templateOperations) {
|
|
1515
1663
|
output.push(...templateOperations);
|
|
1516
1664
|
layoutUsage.set(layout.id, (layoutUsage.get(layout.id) || 0) + 1);
|
|
@@ -1518,15 +1666,23 @@ export function expandOfficeDesignOperations({
|
|
|
1518
1666
|
if (design.deck.templateMode === 'strict') {
|
|
1519
1667
|
throw new Error(`compose_slide requires an approved native template layout for kind "${operation.kind}" in strict mode`);
|
|
1520
1668
|
}
|
|
1521
|
-
output.push(...expandPptxSlide(composed, design, slide));
|
|
1669
|
+
output.push(...expandPptxSlide(composed, design, slide, backend));
|
|
1522
1670
|
}
|
|
1523
1671
|
semantic.push({
|
|
1524
1672
|
op: name,
|
|
1525
|
-
kind:
|
|
1673
|
+
kind: composition.kind,
|
|
1674
|
+
requestedKind: String(contentOperation.kind || 'content'),
|
|
1526
1675
|
slide,
|
|
1527
1676
|
slideRole: backgroundSpec.slideRole,
|
|
1528
1677
|
backgroundRole: backgroundSpec.backgroundRole,
|
|
1529
1678
|
plan,
|
|
1679
|
+
composition: layout ? {
|
|
1680
|
+
...composition,
|
|
1681
|
+
id: `${composition.kind}:template:${layout.id}`,
|
|
1682
|
+
family: 'native-template',
|
|
1683
|
+
variant: layout.variant || composition.variant,
|
|
1684
|
+
source: 'native-template',
|
|
1685
|
+
} : composition,
|
|
1530
1686
|
renderMode: templateOperations ? 'native-template' : 'scratch',
|
|
1531
1687
|
...(bound.binding ? { contentBinding: bound.binding } : {}),
|
|
1532
1688
|
...(layout ? {
|
|
@@ -1545,20 +1701,27 @@ export function expandOfficeDesignOperations({
|
|
|
1545
1701
|
continue;
|
|
1546
1702
|
}
|
|
1547
1703
|
if (normalizedFormat === 'docx' && name === 'compose_document') {
|
|
1548
|
-
|
|
1704
|
+
const composition = planOfficeComposition(normalizedFormat, contentOperation, design, {
|
|
1705
|
+
usage: compositionUsage,
|
|
1706
|
+
});
|
|
1707
|
+
output.push(...expandDocxDocument(contentOperation, design, docxState, backend, composition));
|
|
1549
1708
|
semantic.push({
|
|
1550
1709
|
op: name,
|
|
1551
1710
|
sections: Array.isArray(contentOperation.sections) ? contentOperation.sections.length : 0,
|
|
1711
|
+
composition,
|
|
1552
1712
|
...(bound.binding ? { contentBinding: bound.binding } : {}),
|
|
1553
1713
|
});
|
|
1554
1714
|
continue;
|
|
1555
1715
|
}
|
|
1556
1716
|
if (normalizedFormat === 'xlsx' && name === 'compose_sheet') {
|
|
1557
|
-
|
|
1558
|
-
|
|
1717
|
+
const composition = planOfficeComposition(normalizedFormat, contentOperation, design, {
|
|
1718
|
+
usage: compositionUsage,
|
|
1719
|
+
});
|
|
1720
|
+
output.push(...expandXlsxSheet(contentOperation, design, composition, backend));
|
|
1559
1721
|
semantic.push({
|
|
1560
1722
|
op: name,
|
|
1561
1723
|
sheet: String(contentOperation.sheet || 'Sheet1'),
|
|
1724
|
+
composition,
|
|
1562
1725
|
...(bound.binding ? { contentBinding: bound.binding } : {}),
|
|
1563
1726
|
});
|
|
1564
1727
|
continue;
|
|
@@ -1573,6 +1736,7 @@ export function expandOfficeDesignOperations({
|
|
|
1573
1736
|
semantic,
|
|
1574
1737
|
design: compactDesign(design),
|
|
1575
1738
|
content: summarizeOfficeContentModel(design.content),
|
|
1739
|
+
composition: summarizeOfficeCompositions(normalizedFormat, semantic),
|
|
1576
1740
|
};
|
|
1577
1741
|
}
|
|
1578
1742
|
|
|
@@ -1931,12 +2095,19 @@ export function reviewOfficeDesign({
|
|
|
1931
2095
|
} = {}) {
|
|
1932
2096
|
const normalizedFormat = String(format || '').toLowerCase();
|
|
1933
2097
|
const design = resolveOfficeDesign(normalizedFormat, request, { library });
|
|
2098
|
+
const compositionReview = reviewOfficeCompositionSequence({
|
|
2099
|
+
format: normalizedFormat,
|
|
2100
|
+
compositions: design.compositions,
|
|
2101
|
+
recentCompositions: design.recentCompositions,
|
|
2102
|
+
allowRepetition: design.review.allowRepetition,
|
|
2103
|
+
});
|
|
1934
2104
|
if (!design.review.required) {
|
|
1935
2105
|
return {
|
|
1936
2106
|
ok: true,
|
|
1937
2107
|
status: 'skipped',
|
|
1938
2108
|
profile: design.profile,
|
|
1939
2109
|
issues: [],
|
|
2110
|
+
composition: compositionReview.summary,
|
|
1940
2111
|
requiresVisualInspection: false,
|
|
1941
2112
|
};
|
|
1942
2113
|
}
|
|
@@ -1948,11 +2119,29 @@ export function reviewOfficeDesign({
|
|
|
1948
2119
|
const issues = normalizedFormat === 'pptx'
|
|
1949
2120
|
? [...reviewPptx(document, design), ...structureIssues]
|
|
1950
2121
|
: structureIssues;
|
|
2122
|
+
if (
|
|
2123
|
+
compositionReview.repeated
|
|
2124
|
+
&& !issues.some((entry) => entry.code === 'repetitive_composition')
|
|
2125
|
+
) {
|
|
2126
|
+
issues.push(designIssue(
|
|
2127
|
+
'repetitive_composition',
|
|
2128
|
+
'/',
|
|
2129
|
+
`${compositionReview.repeated.count} of ${compositionReview.repeated.total} semantic compositions reuse ${compositionReview.repeated.id}.`,
|
|
2130
|
+
));
|
|
2131
|
+
}
|
|
2132
|
+
if (compositionReview.recentMatch) {
|
|
2133
|
+
issues.push(designIssue(
|
|
2134
|
+
'recent_composition_repeat',
|
|
2135
|
+
'/',
|
|
2136
|
+
'The complete composition sequence matches a recent deliverable; recompose the structure while preserving brand constraints.',
|
|
2137
|
+
));
|
|
2138
|
+
}
|
|
1951
2139
|
return {
|
|
1952
2140
|
ok: issues.length === 0,
|
|
1953
2141
|
status: issues.length ? 'needs-polish' : 'pass',
|
|
1954
2142
|
profile: design.profile,
|
|
1955
2143
|
issues,
|
|
2144
|
+
composition: compositionReview.summary,
|
|
1956
2145
|
requiresVisualInspection: !['csv', 'tsv'].includes(normalizedFormat),
|
|
1957
2146
|
modelReview: [
|
|
1958
2147
|
'Inspect every rendered page, not only lint counts.',
|