@agent-native/core 0.157.7 → 0.157.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/actions/purge-first-party-analytics-postgres.ts +26 -8
- package/corpus/templates/analytics/scripts/first-party-analytics-postgres-retention-dry-run.sql +258 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-purge.ts +59 -27
- package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +4 -1
- package/corpus/templates/slides/actions/export-pptx.ts +261 -71
- package/corpus/templates/slides/actions/import-file.ts +12 -0
- package/corpus/templates/slides/actions/import-pptx.ts +9 -0
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +45 -5
- package/corpus/templates/slides/server/handlers/import/html-converter.ts +69 -1
- package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +316 -64
- package/corpus/templates/slides/server/lib/source-import.ts +10 -1
- package/dist/agent/harness/migrations.d.ts +4 -0
- package/dist/agent/harness/migrations.js +39 -0
- package/dist/agent/production-agent.d.ts +2 -0
- package/dist/agent/production-agent.js +4 -0
- package/dist/agent/run-loop-with-resume.d.ts +1 -0
- package/dist/agent/run-loop-with-resume.js +45 -30
- package/dist/agent/run-migrations.d.ts +8 -0
- package/dist/agent/run-migrations.js +61 -0
- package/dist/chat-threads/migrations.d.ts +1 -0
- package/dist/chat-threads/migrations.js +1 -0
- package/dist/chat-threads/schema-migrations.d.ts +8 -0
- package/dist/chat-threads/schema-migrations.js +60 -0
- package/dist/client/AssistantChat.js +4 -0
- package/dist/client/chat/message-components.d.ts +2 -0
- package/dist/client/chat/message-components.js +11 -1
- package/dist/client/observability/ThumbsFeedback.js +35 -22
- package/dist/client/observability/useObservability.d.ts +1 -1
- package/dist/client/observability/useObservability.js +6 -3
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/feature-flags/a2a-action-route.js +4 -2
- package/dist/feature-flags/permissions.d.ts +2 -0
- package/dist/feature-flags/permissions.js +6 -1
- package/dist/ingestion/pptx.d.ts +14 -1
- package/dist/ingestion/pptx.js +222 -41
- package/dist/localization/core-messages/ar-SA.js +4 -0
- package/dist/localization/core-messages/de-DE.js +4 -0
- package/dist/localization/core-messages/en-US.d.ts +4 -0
- package/dist/localization/core-messages/en-US.js +4 -0
- package/dist/localization/core-messages/es-ES.js +4 -0
- package/dist/localization/core-messages/fr-FR.js +4 -0
- package/dist/localization/core-messages/hi-IN.js +4 -0
- package/dist/localization/core-messages/ja-JP.js +4 -0
- package/dist/localization/core-messages/ko-KR.js +4 -0
- package/dist/localization/core-messages/pt-BR.js +4 -0
- package/dist/localization/core-messages/zh-CN.js +4 -0
- package/dist/localization/core-messages/zh-TW.js +4 -0
- package/dist/localization/core-messages.d.ts +4 -0
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/feedback.js +40 -16
- package/dist/observability/routes.d.ts +1 -1
- package/dist/observability/routes.js +15 -3
- package/dist/observability/store.js +2 -0
- package/dist/observability/traces.js +5 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
- package/dist/provider-api/actions/provider-api.d.ts +10 -10
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/action-routes.d.ts +7 -5
- package/dist/server/action-routes.js +9 -4
- package/dist/server/better-auth-instance.js +4 -0
- package/dist/server/poll.d.ts +4 -4
- package/dist/server/poll.js +45 -13
- package/dist/server/realtime-voice.js +2 -1
- package/dist/server/release-migrations.js +16 -0
- package/dist/usage/migrations.d.ts +4 -0
- package/dist/usage/migrations.js +45 -0
- package/package.json +3 -3
|
@@ -13,6 +13,11 @@ function productionAdminAllowlist() {
|
|
|
13
13
|
.map((email) => email.trim().toLowerCase())
|
|
14
14
|
.filter(Boolean));
|
|
15
15
|
}
|
|
16
|
+
/** Return whether an email is explicitly allowed to manage no-org flags. */
|
|
17
|
+
export function isFeatureFlagAdminEmail(email) {
|
|
18
|
+
const normalized = email?.trim().toLowerCase();
|
|
19
|
+
return Boolean(normalized && productionAdminAllowlist().has(normalized));
|
|
20
|
+
}
|
|
16
21
|
/** Require an org admin/owner, or an explicit production no-org allowlist entry. */
|
|
17
22
|
export async function requireFeatureFlagManager(scope) {
|
|
18
23
|
const email = scope.userEmail?.trim().toLowerCase();
|
|
@@ -29,7 +34,7 @@ export async function requireFeatureFlagManager(scope) {
|
|
|
29
34
|
}
|
|
30
35
|
if (process.env.NODE_ENV !== "production")
|
|
31
36
|
return { email, orgId: null };
|
|
32
|
-
if (!
|
|
37
|
+
if (!isFeatureFlagAdminEmail(email)) {
|
|
33
38
|
throw new FeatureFlagPermissionError("Feature flag management without an organization requires an explicit admin allowlist entry.", 403);
|
|
34
39
|
}
|
|
35
40
|
return { email, orgId: null };
|
package/dist/ingestion/pptx.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export interface ParsedPptxElement {
|
|
|
26
26
|
id: string;
|
|
27
27
|
name?: string;
|
|
28
28
|
placeholderType?: string;
|
|
29
|
-
kind: "text" | "image" | "shape";
|
|
29
|
+
kind: "text" | "image" | "shape" | "table";
|
|
30
30
|
x: number;
|
|
31
31
|
y: number;
|
|
32
32
|
width: number;
|
|
@@ -45,6 +45,17 @@ export interface ParsedPptxElement {
|
|
|
45
45
|
verticalAlign?: "top" | "middle" | "bottom";
|
|
46
46
|
paragraphs?: ParsedPptxParagraph[];
|
|
47
47
|
image?: ParsedPptxImage;
|
|
48
|
+
table?: ParsedPptxTable;
|
|
49
|
+
}
|
|
50
|
+
/** A `p:graphicFrame`'s `a:tbl` flattened into a simple row/cell grid. `hMerge`/`vMerge` continuation cells are omitted — their content is already represented once, by the spanning cell's `colSpan`/`rowSpan`. */
|
|
51
|
+
export interface ParsedPptxTable {
|
|
52
|
+
rows: ParsedPptxTableCell[][];
|
|
53
|
+
}
|
|
54
|
+
export interface ParsedPptxTableCell {
|
|
55
|
+
paragraphs: ParsedPptxParagraph[];
|
|
56
|
+
colSpan?: number;
|
|
57
|
+
rowSpan?: number;
|
|
58
|
+
fill?: string;
|
|
48
59
|
}
|
|
49
60
|
export type ParsedPptxTransition = "instant" | "none" | "fade" | "slide" | "zoom";
|
|
50
61
|
export interface ParsedPptxImage {
|
|
@@ -75,6 +86,8 @@ export interface ParsedPptxSlide {
|
|
|
75
86
|
layoutHint?: string;
|
|
76
87
|
transition?: ParsedPptxTransition;
|
|
77
88
|
splitByParagraph?: boolean;
|
|
89
|
+
/** Count of this slide's `graphicFrame` shapes that could not be converted into a `"table"` element (charts, SmartArt, embedded OLE objects, or a malformed/empty `a:tbl`) — a fidelity signal for `buildSourceImportMetadata`, the same way `imagesSkipped` already works. */
|
|
90
|
+
tablesDegraded?: number;
|
|
78
91
|
}
|
|
79
92
|
export interface ParsedPptxGrid {
|
|
80
93
|
color: string;
|
package/dist/ingestion/pptx.js
CHANGED
|
@@ -81,6 +81,7 @@ export async function parsePptxPresentation(fileBuffer) {
|
|
|
81
81
|
const metadata = parsePptxSlideMetadata(slide);
|
|
82
82
|
let elements = [];
|
|
83
83
|
const images = [];
|
|
84
|
+
const tablesDegraded = { count: 0 };
|
|
84
85
|
const relationshipPath = slidePath.replace(/slides\/(slide\d+\.xml)/, "slides/_rels/$1.rels");
|
|
85
86
|
const slideRelationshipsXml = await zip
|
|
86
87
|
.file(relationshipPath)
|
|
@@ -109,6 +110,7 @@ export async function parsePptxPresentation(fileBuffer) {
|
|
|
109
110
|
slideWidthEmu,
|
|
110
111
|
slideHeightEmu,
|
|
111
112
|
images,
|
|
113
|
+
tablesDegraded,
|
|
112
114
|
colorContext: slideMasterContext.colorContext,
|
|
113
115
|
placeholderDefaults: slideMasterContext.placeholderDefaults,
|
|
114
116
|
});
|
|
@@ -138,6 +140,9 @@ export async function parsePptxPresentation(fileBuffer) {
|
|
|
138
140
|
...(backgroundGrid ? { backgroundGrid } : {}),
|
|
139
141
|
notes,
|
|
140
142
|
layoutHint: guessLayoutHint(texts, images.length > 0),
|
|
143
|
+
...(tablesDegraded.count > 0
|
|
144
|
+
? { tablesDegraded: tablesDegraded.count }
|
|
145
|
+
: {}),
|
|
141
146
|
...metadata,
|
|
142
147
|
});
|
|
143
148
|
}
|
|
@@ -340,6 +345,34 @@ function levelFillsFromTextStyle(style) {
|
|
|
340
345
|
}
|
|
341
346
|
return fills;
|
|
342
347
|
}
|
|
348
|
+
const IDENTITY_MAT = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
|
|
349
|
+
/** Composes two transforms so `inner` is applied to a point first, then `outer`. */
|
|
350
|
+
function composeMat(outer, inner) {
|
|
351
|
+
return {
|
|
352
|
+
a: outer.a * inner.a + outer.c * inner.b,
|
|
353
|
+
b: outer.b * inner.a + outer.d * inner.b,
|
|
354
|
+
c: outer.a * inner.c + outer.c * inner.d,
|
|
355
|
+
d: outer.b * inner.c + outer.d * inner.d,
|
|
356
|
+
e: outer.a * inner.e + outer.c * inner.f + outer.e,
|
|
357
|
+
f: outer.b * inner.e + outer.d * inner.f + outer.f,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function scaleMat(sx, sy) {
|
|
361
|
+
return { a: sx, b: 0, c: 0, d: sy, e: 0, f: 0 };
|
|
362
|
+
}
|
|
363
|
+
function translateMat(tx, ty) {
|
|
364
|
+
return { a: 1, b: 0, c: 0, d: 1, e: tx, f: ty };
|
|
365
|
+
}
|
|
366
|
+
function rotateAroundMat(degrees, cx, cy) {
|
|
367
|
+
const radians = (degrees * Math.PI) / 180;
|
|
368
|
+
const cos = Math.cos(radians);
|
|
369
|
+
const sin = Math.sin(radians);
|
|
370
|
+
const rotation = { a: cos, b: sin, c: -sin, d: cos, e: 0, f: 0 };
|
|
371
|
+
return composeMat(translateMat(cx, cy), composeMat(rotation, translateMat(-cx, -cy)));
|
|
372
|
+
}
|
|
373
|
+
function applyMatPoint(m, x, y) {
|
|
374
|
+
return { x: m.a * x + m.c * y + m.e, y: m.b * x + m.d * y + m.f };
|
|
375
|
+
}
|
|
343
376
|
const SHAPE_ELEMENT_NAMES = new Set([
|
|
344
377
|
"sp",
|
|
345
378
|
"pic",
|
|
@@ -347,14 +380,14 @@ const SHAPE_ELEMENT_NAMES = new Set([
|
|
|
347
380
|
"cxnSp",
|
|
348
381
|
"graphicFrame",
|
|
349
382
|
]);
|
|
383
|
+
/** Standard 16:9 widescreen slide size — a last-resort content box for a placeholder shape whose own `<a:xfrm>` is missing and the deck's own slide size wasn't determinable either. */
|
|
384
|
+
const FALLBACK_SLIDE_WIDTH_EMU = 12192000;
|
|
385
|
+
const FALLBACK_SLIDE_HEIGHT_EMU = 6858000;
|
|
350
386
|
async function parseSlideElements(args) {
|
|
351
387
|
const fragments = extractDirectShapeFragments(args.xml, "spTree");
|
|
352
388
|
const elements = [];
|
|
353
389
|
const context = {
|
|
354
|
-
|
|
355
|
-
originY: 0,
|
|
356
|
-
scaleX: 1,
|
|
357
|
-
scaleY: 1,
|
|
390
|
+
matrix: IDENTITY_MAT,
|
|
358
391
|
rotation: 0,
|
|
359
392
|
};
|
|
360
393
|
for (const fragment of fragments) {
|
|
@@ -406,13 +439,20 @@ async function parseShapeFragment(fragment, args) {
|
|
|
406
439
|
const childExtent = readPoint(groupXfrm?.["a:chExt"]);
|
|
407
440
|
const groupScaleX = childExtent.x > 0 ? groupTransform.width / childExtent.x : 1;
|
|
408
441
|
const groupScaleY = childExtent.y > 0 ? groupTransform.height / childExtent.y : 1;
|
|
442
|
+
// Places this group's children into the parent's coordinate space,
|
|
443
|
+
// ignoring the group's own rotation for now.
|
|
444
|
+
let localToParent = composeMat(translateMat(groupTransform.x - childOffset.x * groupScaleX, groupTransform.y - childOffset.y * groupScaleY), scaleMat(groupScaleX, groupScaleY));
|
|
445
|
+
// PowerPoint rotates the entire *placed* group box — every child already
|
|
446
|
+
// positioned by `localToParent` above — as a rigid body around that
|
|
447
|
+
// box's own center, not each child around its own center. Composing this
|
|
448
|
+
// rotation on top of the placement (instead of only summing `rotation`
|
|
449
|
+
// degrees, as before) is what makes children actually orbit the group's
|
|
450
|
+
// pivot instead of just spinning in place.
|
|
451
|
+
if (groupTransform.rotation) {
|
|
452
|
+
localToParent = composeMat(rotateAroundMat(groupTransform.rotation, groupTransform.x + groupTransform.width / 2, groupTransform.y + groupTransform.height / 2), localToParent);
|
|
453
|
+
}
|
|
409
454
|
const nextContext = {
|
|
410
|
-
|
|
411
|
-
args.context.scaleX * (groupTransform.x - childOffset.x * groupScaleX),
|
|
412
|
-
originY: args.context.originY +
|
|
413
|
-
args.context.scaleY * (groupTransform.y - childOffset.y * groupScaleY),
|
|
414
|
-
scaleX: args.context.scaleX * groupScaleX,
|
|
415
|
-
scaleY: args.context.scaleY * groupScaleY,
|
|
455
|
+
matrix: composeMat(args.context.matrix, localToParent),
|
|
416
456
|
rotation: args.context.rotation + (groupTransform.rotation ?? 0),
|
|
417
457
|
};
|
|
418
458
|
const output = [];
|
|
@@ -424,7 +464,29 @@ async function parseShapeFragment(fragment, args) {
|
|
|
424
464
|
}
|
|
425
465
|
return output;
|
|
426
466
|
}
|
|
427
|
-
|
|
467
|
+
if (entry === "graphicFrame") {
|
|
468
|
+
return parseGraphicFrameFragment(node, args);
|
|
469
|
+
}
|
|
470
|
+
const localTransform = readTransform(node, "p:spPr");
|
|
471
|
+
// A placeholder shape (`<p:ph>`) commonly omits its own `<a:xfrm>` on the
|
|
472
|
+
// slide, inheriting position/size from the matching placeholder on the
|
|
473
|
+
// slide layout/master instead — that layout/master inheritance chain isn't
|
|
474
|
+
// implemented, so without this fallback the shape silently lands at a
|
|
475
|
+
// literal 0×0 box, making real title/body text invisible instead of just
|
|
476
|
+
// mispositioned. This substitutes the slide's own content box so the text
|
|
477
|
+
// is at least visible; it won't match the placeholder's real authored
|
|
478
|
+
// position/size.
|
|
479
|
+
const hasOwnSize = localTransform.width > 0 && localTransform.height > 0;
|
|
480
|
+
const effectiveLocalTransform = entry === "sp" && !hasOwnSize
|
|
481
|
+
? {
|
|
482
|
+
...localTransform,
|
|
483
|
+
x: 0,
|
|
484
|
+
y: 0,
|
|
485
|
+
width: args.slideWidthEmu ?? FALLBACK_SLIDE_WIDTH_EMU,
|
|
486
|
+
height: args.slideHeightEmu ?? FALLBACK_SLIDE_HEIGHT_EMU,
|
|
487
|
+
}
|
|
488
|
+
: localTransform;
|
|
489
|
+
const transform = applyTransform(effectiveLocalTransform, args.context);
|
|
428
490
|
const id = readShapeId(node);
|
|
429
491
|
const name = readShapeName(node);
|
|
430
492
|
const placeholderType = stringValue(record(record(record(node["p:nvSpPr"])?.["p:nvPr"])?.["p:ph"])?.["@_type"]);
|
|
@@ -502,6 +564,64 @@ async function parseShapeFragment(fragment, args) {
|
|
|
502
564
|
}
|
|
503
565
|
return [];
|
|
504
566
|
}
|
|
567
|
+
/**
|
|
568
|
+
* `p:graphicFrame` has no `p:spPr` (its transform lives at `p:xfrm` directly)
|
|
569
|
+
* and its content is `a:graphic/a:graphicData` rather than `p:txBody` — most
|
|
570
|
+
* commonly a table, but also charts, SmartArt, and embedded OLE objects that
|
|
571
|
+
* have no shape structure we can reconstruct. Only tables convert; anything
|
|
572
|
+
* else is counted as a dropped-content fidelity signal instead of silently
|
|
573
|
+
* vanishing with no signal at all.
|
|
574
|
+
*/
|
|
575
|
+
function parseGraphicFrameFragment(node, args) {
|
|
576
|
+
const transform = applyTransform(transformFromXfrmNode(record(node["p:xfrm"])), args.context);
|
|
577
|
+
const table = parseGraphicFrameTable(node, args.colorContext);
|
|
578
|
+
if (!table) {
|
|
579
|
+
args.tablesDegraded.count += 1;
|
|
580
|
+
return [];
|
|
581
|
+
}
|
|
582
|
+
return [
|
|
583
|
+
{
|
|
584
|
+
id: readShapeId(node),
|
|
585
|
+
name: readShapeName(node),
|
|
586
|
+
kind: "table",
|
|
587
|
+
...transform,
|
|
588
|
+
table,
|
|
589
|
+
},
|
|
590
|
+
];
|
|
591
|
+
}
|
|
592
|
+
/** Reads a graphicFrame's `a:graphic/a:graphicData/a:tbl` into a row/cell grid. Returns `undefined` for a non-table graphicFrame (chart/SmartArt/OLE) or a table with no rows. */
|
|
593
|
+
function parseGraphicFrameTable(node, context) {
|
|
594
|
+
const graphicData = record(record(node["a:graphic"])?.["a:graphicData"]);
|
|
595
|
+
const tbl = record(graphicData?.["a:tbl"]);
|
|
596
|
+
if (!tbl)
|
|
597
|
+
return undefined;
|
|
598
|
+
const rows = asArray(tbl["a:tr"]).map((rawRow) => {
|
|
599
|
+
const row = record(rawRow);
|
|
600
|
+
const cells = [];
|
|
601
|
+
for (const rawCell of asArray(row?.["a:tc"])) {
|
|
602
|
+
const cell = record(rawCell);
|
|
603
|
+
if (!cell)
|
|
604
|
+
continue;
|
|
605
|
+
// A merge-continuation cell's content is already represented once, by
|
|
606
|
+
// the spanning cell's gridSpan/rowSpan below.
|
|
607
|
+
if (cell["@_hMerge"] === "1" || cell["@_vMerge"] === "1")
|
|
608
|
+
continue;
|
|
609
|
+
const gridSpan = Number(cell["@_gridSpan"]);
|
|
610
|
+
const rowSpan = Number(cell["@_rowSpan"]);
|
|
611
|
+
const fill = parseShapeFill(record(cell["a:tcPr"]), context);
|
|
612
|
+
cells.push({
|
|
613
|
+
paragraphs: parseTextBodyParagraphs(record(cell["a:txBody"]), context),
|
|
614
|
+
...(Number.isFinite(gridSpan) && gridSpan > 1
|
|
615
|
+
? { colSpan: gridSpan }
|
|
616
|
+
: {}),
|
|
617
|
+
...(Number.isFinite(rowSpan) && rowSpan > 1 ? { rowSpan } : {}),
|
|
618
|
+
...(fill ? { fill } : {}),
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
return cells;
|
|
622
|
+
});
|
|
623
|
+
return rows.length > 0 ? { rows } : undefined;
|
|
624
|
+
}
|
|
505
625
|
function extractDirectShapeFragments(xml, container) {
|
|
506
626
|
const containerMatch = new RegExp(`<p:${container}\\b[^>]*>`, "i").exec(xml);
|
|
507
627
|
if (!containerMatch)
|
|
@@ -561,12 +681,15 @@ function findMatchingXmlTag(xml, start, localName) {
|
|
|
561
681
|
}
|
|
562
682
|
function readShapeId(node) {
|
|
563
683
|
const cNvPr = record(record(node["p:nvSpPr"])?.["p:cNvPr"]) ??
|
|
564
|
-
record(record(node["p:nvPicPr"])?.["p:cNvPr"])
|
|
684
|
+
record(record(node["p:nvPicPr"])?.["p:cNvPr"]) ??
|
|
685
|
+
record(record(node["p:nvGraphicFramePr"])?.["p:cNvPr"]);
|
|
565
686
|
return (stringValue(cNvPr?.["@_id"]) ??
|
|
566
687
|
`shape-${Math.random().toString(36).slice(2)}`);
|
|
567
688
|
}
|
|
568
689
|
function readShapeName(node) {
|
|
569
|
-
|
|
690
|
+
const cNvPr = record(record(node["p:nvSpPr"])?.["p:cNvPr"]) ??
|
|
691
|
+
record(record(node["p:nvGraphicFramePr"])?.["p:cNvPr"]);
|
|
692
|
+
return stringValue(cNvPr?.["@_name"]);
|
|
570
693
|
}
|
|
571
694
|
function readPoint(value) {
|
|
572
695
|
const point = record(value);
|
|
@@ -583,7 +706,10 @@ function readExtent(value) {
|
|
|
583
706
|
};
|
|
584
707
|
}
|
|
585
708
|
function readTransform(node, key) {
|
|
586
|
-
|
|
709
|
+
return transformFromXfrmNode(record(record(node[key])?.["a:xfrm"]));
|
|
710
|
+
}
|
|
711
|
+
/** Shared by `readTransform` (a wrapper-nested `a:xfrm`, e.g. `p:spPr/a:xfrm`) and `graphicFrame`, whose `p:xfrm` sits directly on the node instead of inside a wrapper element. */
|
|
712
|
+
function transformFromXfrmNode(xfrm) {
|
|
587
713
|
const off = readPoint(xfrm?.["a:off"]);
|
|
588
714
|
const ext = readExtent(xfrm?.["a:ext"]);
|
|
589
715
|
const rawRotation = Number(xfrm?.["@_rot"]);
|
|
@@ -598,20 +724,40 @@ function readTransform(node, key) {
|
|
|
598
724
|
};
|
|
599
725
|
}
|
|
600
726
|
function applyTransform(transform, context) {
|
|
727
|
+
const scaleX = Math.hypot(context.matrix.a, context.matrix.b);
|
|
728
|
+
const scaleY = Math.hypot(context.matrix.c, context.matrix.d);
|
|
729
|
+
const width = transform.width * scaleX;
|
|
730
|
+
const height = transform.height * scaleY;
|
|
731
|
+
// Map the shape's own (un-rotated) box center through the accumulated
|
|
732
|
+
// group matrix so a child inside a rotated group orbits the group's pivot
|
|
733
|
+
// — the box's own visual spin is applied separately below via `rotation`,
|
|
734
|
+
// so only the center (not the whole box) needs to go through the matrix.
|
|
735
|
+
const center = applyMatPoint(context.matrix, transform.x + transform.width / 2, transform.y + transform.height / 2);
|
|
736
|
+
const rotation = (transform.rotation ?? 0) + context.rotation;
|
|
601
737
|
return {
|
|
602
|
-
x:
|
|
603
|
-
y:
|
|
604
|
-
width
|
|
605
|
-
height
|
|
606
|
-
...(
|
|
607
|
-
? { rotation: (transform.rotation ?? 0) + context.rotation }
|
|
608
|
-
: {}),
|
|
738
|
+
x: center.x - width / 2,
|
|
739
|
+
y: center.y - height / 2,
|
|
740
|
+
width,
|
|
741
|
+
height,
|
|
742
|
+
...(rotation ? { rotation } : {}),
|
|
609
743
|
};
|
|
610
744
|
}
|
|
611
745
|
function parseTextBody(node, context) {
|
|
612
|
-
|
|
746
|
+
return parseTextBodyParagraphs(record(node["p:txBody"]), context);
|
|
747
|
+
}
|
|
748
|
+
/** PowerPoint's `buAutoNum` variants (arabicPeriod, alphaLcPeriod, romanUcPeriod, ...) all number the same underlying sequence — approximating every variant with arabic digits keeps list order/grouping correct even though the glyph style doesn't match `type` exactly. */
|
|
749
|
+
function formatAutoNumBullet(n) {
|
|
750
|
+
return `${n}.`;
|
|
751
|
+
}
|
|
752
|
+
/** Core `a:p` paragraph parsing, shared by a shape's `p:txBody` and a table cell's `a:txBody` (which sit at different paths in their parent node, so the caller resolves the `txBody` record itself). */
|
|
753
|
+
function parseTextBodyParagraphs(txBody, context) {
|
|
613
754
|
if (!txBody)
|
|
614
755
|
return [];
|
|
756
|
+
// `a:buAutoNum` carries no explicit number — PowerPoint derives it from
|
|
757
|
+
// paragraph order — so the sequence has to be tracked per nesting level as
|
|
758
|
+
// paragraphs are walked in document order, restarting whenever a deeper
|
|
759
|
+
// level's own sequence begins or a non-auto-numbered paragraph interrupts it.
|
|
760
|
+
const autoNumCounters = new Map();
|
|
615
761
|
return asArray(txBody["a:p"]).map((rawParagraph) => {
|
|
616
762
|
const paragraph = record(rawParagraph);
|
|
617
763
|
const pPr = record(paragraph?.["a:pPr"]);
|
|
@@ -644,12 +790,30 @@ function parseTextBody(node, context) {
|
|
|
644
790
|
const spaceBeforePt = parsePoints(pPr?.["a:spcBef"]);
|
|
645
791
|
const spaceAfterPt = parsePoints(pPr?.["a:spcAft"]);
|
|
646
792
|
const alignment = mapAlignment(stringValue(pPr?.["@_algn"]));
|
|
793
|
+
const level = Number.isFinite(Number(pPr?.["@_lvl"]))
|
|
794
|
+
? Number(pPr?.["@_lvl"])
|
|
795
|
+
: 0;
|
|
796
|
+
let autoNumBullet;
|
|
797
|
+
if (pPr?.["a:buAutoNum"] !== undefined && pPr?.["a:buNone"] === undefined) {
|
|
798
|
+
for (const key of [...autoNumCounters.keys()]) {
|
|
799
|
+
if (key > level)
|
|
800
|
+
autoNumCounters.delete(key);
|
|
801
|
+
}
|
|
802
|
+
const next = (autoNumCounters.get(level) ?? 0) + 1;
|
|
803
|
+
autoNumCounters.set(level, next);
|
|
804
|
+
autoNumBullet = formatAutoNumBullet(next);
|
|
805
|
+
}
|
|
806
|
+
else if (bullet?.["@_char"] === undefined) {
|
|
807
|
+
autoNumCounters.clear();
|
|
808
|
+
}
|
|
647
809
|
return {
|
|
648
810
|
runs,
|
|
649
811
|
...(alignment ? { alignment } : {}),
|
|
650
812
|
...(bullet?.["@_char"] && !pPr?.["a:buNone"]
|
|
651
813
|
? { bulletChar: String(bullet["@_char"]) }
|
|
652
|
-
:
|
|
814
|
+
: autoNumBullet
|
|
815
|
+
? { bulletChar: autoNumBullet }
|
|
816
|
+
: {}),
|
|
653
817
|
...(bulletColor ? { bulletColor } : {}),
|
|
654
818
|
...(bulletFont ? { bulletFontFamily: bulletFont } : {}),
|
|
655
819
|
...(Number.isFinite(bulletSize) && bulletSize > 0
|
|
@@ -694,7 +858,14 @@ function parseShapeFill(shapeProperties, context) {
|
|
|
694
858
|
return undefined;
|
|
695
859
|
if (shapeProperties["a:noFill"] !== undefined)
|
|
696
860
|
return undefined;
|
|
697
|
-
|
|
861
|
+
const solid = parseColor(record(shapeProperties["a:solidFill"]), context);
|
|
862
|
+
if (solid)
|
|
863
|
+
return solid;
|
|
864
|
+
// A gradient fill (`<a:gradFill>`) has no `a:solidFill` at all — approximate
|
|
865
|
+
// it with its first stop's resolved color rather than leaving the shape
|
|
866
|
+
// unfilled, which silently turned every gradient-filled shape transparent.
|
|
867
|
+
const stops = asArray(record(record(shapeProperties["a:gradFill"])?.["a:gsLst"])?.["a:gs"]);
|
|
868
|
+
return parseColor(record(stops[0]), context);
|
|
698
869
|
}
|
|
699
870
|
function parseShapeLine(shapeProperties, context) {
|
|
700
871
|
const line = record(shapeProperties?.["a:ln"]);
|
|
@@ -819,32 +990,42 @@ function readColorTransforms(node) {
|
|
|
819
990
|
const raw = stringValue(record(node[key])?.["@_val"]);
|
|
820
991
|
return raw !== undefined ? Number(raw) / 100000 : undefined;
|
|
821
992
|
};
|
|
993
|
+
const alphaRaw = stringValue(record(node["a:alpha"])?.["@_val"]);
|
|
822
994
|
return {
|
|
823
995
|
lumMod: percent("a:lumMod"),
|
|
824
996
|
lumOff: percent("a:lumOff"),
|
|
825
997
|
tint: percent("a:tint"),
|
|
826
998
|
shade: percent("a:shade"),
|
|
999
|
+
...(alphaRaw !== undefined
|
|
1000
|
+
? { alphaPercent: Number(alphaRaw) / 1000 }
|
|
1001
|
+
: {}),
|
|
827
1002
|
};
|
|
828
1003
|
}
|
|
1004
|
+
/** Colors flow through the parser as plain `#rrggbb` hex strings, and every consumer just drops that string straight into CSS — an 8-digit `#rrggbbaa` hex is valid CSS and needs no consumer changes, so alpha rides along as extra hex digits instead of a new color shape. */
|
|
829
1005
|
function applyColorTransforms(hex, transforms) {
|
|
830
|
-
const { lumMod, lumOff, tint, shade } = transforms;
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1006
|
+
const { lumMod, lumOff, tint, shade, alphaPercent } = transforms;
|
|
1007
|
+
let result = hex;
|
|
1008
|
+
if (lumMod !== undefined ||
|
|
1009
|
+
lumOff !== undefined ||
|
|
1010
|
+
tint !== undefined ||
|
|
1011
|
+
shade !== undefined) {
|
|
1012
|
+
const [h, s, initialL] = hexToHsl(hex);
|
|
1013
|
+
let l = initialL;
|
|
1014
|
+
if (lumMod !== undefined)
|
|
1015
|
+
l *= lumMod;
|
|
1016
|
+
if (lumOff !== undefined)
|
|
1017
|
+
l += lumOff;
|
|
1018
|
+
if (tint !== undefined)
|
|
1019
|
+
l = l * tint + (1 - tint);
|
|
1020
|
+
if (shade !== undefined)
|
|
1021
|
+
l *= shade;
|
|
1022
|
+
result = hslToHex(h, s, Math.min(1, Math.max(0, l)));
|
|
1023
|
+
}
|
|
1024
|
+
if (alphaPercent !== undefined) {
|
|
1025
|
+
const alphaByte = Math.max(0, Math.min(255, Math.round((alphaPercent / 100) * 255)));
|
|
1026
|
+
result = `${result}${alphaByte.toString(16).padStart(2, "0")}`;
|
|
836
1027
|
}
|
|
837
|
-
|
|
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)));
|
|
1028
|
+
return result;
|
|
848
1029
|
}
|
|
849
1030
|
function parseColor(value, context) {
|
|
850
1031
|
if (!value)
|
|
@@ -273,10 +273,14 @@ const messages = {
|
|
|
273
273
|
"errorMessages.startNewChat": "بدء محادثة جديدة",
|
|
274
274
|
"errorMessages.upgradeAtBuilder": "الترقية عبر Builder.io",
|
|
275
275
|
"feedback.inaccurate": "غير دقيق",
|
|
276
|
+
"feedback.keyboardHint": "{{shortcut}} Enter للإرسال",
|
|
276
277
|
"feedback.notHelpful": "غير مفيد",
|
|
278
|
+
"feedback.placeholder": "أخبرنا بما حدث من خطأ...",
|
|
279
|
+
"feedback.submit": "إرسال",
|
|
277
280
|
"feedback.thumbsDown": "عدم إعجاب",
|
|
278
281
|
"feedback.thumbsUp": "إعجاب",
|
|
279
282
|
"feedback.tooSlow": "بطيء جدًا",
|
|
283
|
+
"feedback.whatWentWrong": "ما الذي حدث بشكل خاطئ؟",
|
|
280
284
|
"feedback.wrongTool": "أداة غير صحيحة",
|
|
281
285
|
"header.switchToCli": "التبديل إلى CLI",
|
|
282
286
|
"history.active": "نشطة",
|
|
@@ -401,10 +401,14 @@ const messages = {
|
|
|
401
401
|
"errorMessages.startNewChat": "Neuen Chat starten",
|
|
402
402
|
"errorMessages.upgradeAtBuilder": "Bei Builder.io upgraden",
|
|
403
403
|
"feedback.inaccurate": "Ungenau",
|
|
404
|
+
"feedback.keyboardHint": "{{shortcut}} Enter zum Senden",
|
|
404
405
|
"feedback.notHelpful": "Nicht hilfreich",
|
|
406
|
+
"feedback.placeholder": "Sag uns, was schiefgelaufen ist...",
|
|
407
|
+
"feedback.submit": "Senden",
|
|
405
408
|
"feedback.thumbsDown": "Daumen runter",
|
|
406
409
|
"feedback.thumbsUp": "Daumen hoch",
|
|
407
410
|
"feedback.tooSlow": "Zu langsam",
|
|
411
|
+
"feedback.whatWentWrong": "Was ist schiefgelaufen?",
|
|
408
412
|
"feedback.wrongTool": "Falsches Tool",
|
|
409
413
|
"contextMeter.ariaLabel": "Kontext {{percent}} %, {{totalTokens}}{{breakdown}}. Kontextanalyse öffnen.",
|
|
410
414
|
"contextMeter.breakdown": " gesamt: {{systemTokens}} System + {{conversationTokens}} Unterhaltung",
|
|
@@ -265,10 +265,14 @@ declare const messages: {
|
|
|
265
265
|
readonly "errorMessages.startNewChat": "Start new chat";
|
|
266
266
|
readonly "errorMessages.upgradeAtBuilder": "Upgrade at Builder.io";
|
|
267
267
|
readonly "feedback.inaccurate": "Inaccurate";
|
|
268
|
+
readonly "feedback.keyboardHint": "{{shortcut}} Enter to send";
|
|
268
269
|
readonly "feedback.notHelpful": "Not helpful";
|
|
270
|
+
readonly "feedback.placeholder": "Tell us what went wrong...";
|
|
271
|
+
readonly "feedback.submit": "Submit";
|
|
269
272
|
readonly "feedback.thumbsDown": "Thumbs down";
|
|
270
273
|
readonly "feedback.thumbsUp": "Thumbs up";
|
|
271
274
|
readonly "feedback.tooSlow": "Too slow";
|
|
275
|
+
readonly "feedback.whatWentWrong": "What went wrong?";
|
|
272
276
|
readonly "feedback.wrongTool": "Wrong tool";
|
|
273
277
|
readonly "header.switchToCli": "Switch to CLI";
|
|
274
278
|
readonly "history.active": "Active";
|
|
@@ -265,10 +265,14 @@ const messages = {
|
|
|
265
265
|
"errorMessages.startNewChat": "Start new chat",
|
|
266
266
|
"errorMessages.upgradeAtBuilder": "Upgrade at Builder.io",
|
|
267
267
|
"feedback.inaccurate": "Inaccurate",
|
|
268
|
+
"feedback.keyboardHint": "{{shortcut}} Enter to send",
|
|
268
269
|
"feedback.notHelpful": "Not helpful",
|
|
270
|
+
"feedback.placeholder": "Tell us what went wrong...",
|
|
271
|
+
"feedback.submit": "Submit",
|
|
269
272
|
"feedback.thumbsDown": "Thumbs down",
|
|
270
273
|
"feedback.thumbsUp": "Thumbs up",
|
|
271
274
|
"feedback.tooSlow": "Too slow",
|
|
275
|
+
"feedback.whatWentWrong": "What went wrong?",
|
|
272
276
|
"feedback.wrongTool": "Wrong tool",
|
|
273
277
|
"header.switchToCli": "Switch to CLI",
|
|
274
278
|
"history.active": "Active",
|
|
@@ -408,10 +408,14 @@ const messages = {
|
|
|
408
408
|
"errorMessages.startNewChat": "Iniciar un chat nuevo",
|
|
409
409
|
"errorMessages.upgradeAtBuilder": "Mejorar el plan en Builder.io",
|
|
410
410
|
"feedback.inaccurate": "Impreciso",
|
|
411
|
+
"feedback.keyboardHint": "{{shortcut}} Enter para enviar",
|
|
411
412
|
"feedback.notHelpful": "Poco útil",
|
|
413
|
+
"feedback.placeholder": "Cuéntanos qué salió mal...",
|
|
414
|
+
"feedback.submit": "Enviar",
|
|
412
415
|
"feedback.thumbsDown": "No me gusta",
|
|
413
416
|
"feedback.thumbsUp": "Me gusta",
|
|
414
417
|
"feedback.tooSlow": "Demasiado lento",
|
|
418
|
+
"feedback.whatWentWrong": "¿Qué salió mal?",
|
|
415
419
|
"feedback.wrongTool": "Herramienta incorrecta",
|
|
416
420
|
"contextMeter.ariaLabel": "Contexto {{percent}} %, {{totalTokens}}{{breakdown}}. Abrir el análisis de contexto.",
|
|
417
421
|
"contextMeter.breakdown": " en total: {{systemTokens}} del sistema + {{conversationTokens}} de la conversación",
|
|
@@ -408,10 +408,14 @@ const messages = {
|
|
|
408
408
|
"errorMessages.startNewChat": "Démarrer une nouvelle discussion",
|
|
409
409
|
"errorMessages.upgradeAtBuilder": "Mettre à niveau sur Builder.io",
|
|
410
410
|
"feedback.inaccurate": "Inexact",
|
|
411
|
+
"feedback.keyboardHint": "{{shortcut}} Entrée pour envoyer",
|
|
411
412
|
"feedback.notHelpful": "Peu utile",
|
|
413
|
+
"feedback.placeholder": "Dites-nous ce qui n'a pas fonctionné...",
|
|
414
|
+
"feedback.submit": "Envoyer",
|
|
412
415
|
"feedback.thumbsDown": "Pouce vers le bas",
|
|
413
416
|
"feedback.thumbsUp": "Pouce vers le haut",
|
|
414
417
|
"feedback.tooSlow": "Trop lent",
|
|
418
|
+
"feedback.whatWentWrong": "Qu'est-ce qui n'a pas fonctionné ?",
|
|
415
419
|
"feedback.wrongTool": "Mauvais outil",
|
|
416
420
|
"contextMeter.ariaLabel": "Contexte {{percent}} %, {{totalTokens}}{{breakdown}}. Ouvrir l’analyse du contexte.",
|
|
417
421
|
"contextMeter.breakdown": " au total : {{systemTokens}} système + {{conversationTokens}} conversation",
|
|
@@ -265,10 +265,14 @@ const messages = {
|
|
|
265
265
|
"errorMessages.startNewChat": "नई चैट शुरू करें",
|
|
266
266
|
"errorMessages.upgradeAtBuilder": "Builder.io पर अपग्रेड करें",
|
|
267
267
|
"feedback.inaccurate": "गलत जानकारी",
|
|
268
|
+
"feedback.keyboardHint": "भेजने के लिए {{shortcut}} Enter",
|
|
268
269
|
"feedback.notHelpful": "उपयोगी नहीं",
|
|
270
|
+
"feedback.placeholder": "बताएं कि क्या गलत हुआ...",
|
|
271
|
+
"feedback.submit": "जमा करें",
|
|
269
272
|
"feedback.thumbsDown": "नापसंद",
|
|
270
273
|
"feedback.thumbsUp": "पसंद",
|
|
271
274
|
"feedback.tooSlow": "बहुत धीमा",
|
|
275
|
+
"feedback.whatWentWrong": "क्या गलत हुआ?",
|
|
272
276
|
"feedback.wrongTool": "गलत टूल",
|
|
273
277
|
"header.switchToCli": "CLI पर जाएँ",
|
|
274
278
|
"history.active": "सक्रिय",
|
|
@@ -263,10 +263,14 @@ const messages = {
|
|
|
263
263
|
"errorMessages.startNewChat": "新しいチャットを開始",
|
|
264
264
|
"errorMessages.upgradeAtBuilder": "Builder.io でアップグレード",
|
|
265
265
|
"feedback.inaccurate": "不正確",
|
|
266
|
+
"feedback.keyboardHint": "{{shortcut}} Enter で送信",
|
|
266
267
|
"feedback.notHelpful": "役に立たない",
|
|
268
|
+
"feedback.placeholder": "問題点を教えてください…",
|
|
269
|
+
"feedback.submit": "送信",
|
|
267
270
|
"feedback.thumbsDown": "低評価",
|
|
268
271
|
"feedback.thumbsUp": "高評価",
|
|
269
272
|
"feedback.tooSlow": "遅すぎる",
|
|
273
|
+
"feedback.whatWentWrong": "何が問題でしたか?",
|
|
270
274
|
"feedback.wrongTool": "ツールが不適切",
|
|
271
275
|
"header.switchToCli": "CLI に切り替え",
|
|
272
276
|
"history.active": "アクティブ",
|
|
@@ -263,10 +263,14 @@ const messages = {
|
|
|
263
263
|
"errorMessages.startNewChat": "새 채팅 시작",
|
|
264
264
|
"errorMessages.upgradeAtBuilder": "Builder.io에서 업그레이드",
|
|
265
265
|
"feedback.inaccurate": "부정확함",
|
|
266
|
+
"feedback.keyboardHint": "보내려면 {{shortcut}} Enter",
|
|
266
267
|
"feedback.notHelpful": "도움이 되지 않음",
|
|
268
|
+
"feedback.placeholder": "무엇이 문제였는지 알려주세요…",
|
|
269
|
+
"feedback.submit": "제출",
|
|
267
270
|
"feedback.thumbsDown": "싫어요",
|
|
268
271
|
"feedback.thumbsUp": "좋아요",
|
|
269
272
|
"feedback.tooSlow": "너무 느림",
|
|
273
|
+
"feedback.whatWentWrong": "무엇이 잘못되었나요?",
|
|
270
274
|
"feedback.wrongTool": "잘못된 도구",
|
|
271
275
|
"header.switchToCli": "CLI로 전환",
|
|
272
276
|
"history.active": "활성",
|
|
@@ -408,10 +408,14 @@ const messages = {
|
|
|
408
408
|
"errorMessages.startNewChat": "Iniciar novo chat",
|
|
409
409
|
"errorMessages.upgradeAtBuilder": "Fazer upgrade no Builder.io",
|
|
410
410
|
"feedback.inaccurate": "Impreciso",
|
|
411
|
+
"feedback.keyboardHint": "{{shortcut}} Enter para enviar",
|
|
411
412
|
"feedback.notHelpful": "Pouco útil",
|
|
413
|
+
"feedback.placeholder": "Conte-nos o que deu errado...",
|
|
414
|
+
"feedback.submit": "Enviar",
|
|
412
415
|
"feedback.thumbsDown": "Não gostei",
|
|
413
416
|
"feedback.thumbsUp": "Gostei",
|
|
414
417
|
"feedback.tooSlow": "Muito lento",
|
|
418
|
+
"feedback.whatWentWrong": "O que deu errado?",
|
|
415
419
|
"feedback.wrongTool": "Ferramenta errada",
|
|
416
420
|
"contextMeter.ariaLabel": "Contexto {{percent}}%, {{totalTokens}}{{breakdown}}. Abrir a análise de contexto.",
|
|
417
421
|
"contextMeter.breakdown": " no total: {{systemTokens}} do sistema + {{conversationTokens}} da conversa",
|
|
@@ -263,10 +263,14 @@ const messages = {
|
|
|
263
263
|
"errorMessages.startNewChat": "开始新聊天",
|
|
264
264
|
"errorMessages.upgradeAtBuilder": "在 Builder.io 升级",
|
|
265
265
|
"feedback.inaccurate": "不准确",
|
|
266
|
+
"feedback.keyboardHint": "{{shortcut}} Enter 发送",
|
|
266
267
|
"feedback.notHelpful": "没有帮助",
|
|
268
|
+
"feedback.placeholder": "告诉我们哪里出了问题……",
|
|
269
|
+
"feedback.submit": "提交",
|
|
267
270
|
"feedback.thumbsDown": "不喜欢",
|
|
268
271
|
"feedback.thumbsUp": "喜欢",
|
|
269
272
|
"feedback.tooSlow": "太慢",
|
|
273
|
+
"feedback.whatWentWrong": "哪里出了问题?",
|
|
270
274
|
"feedback.wrongTool": "工具错误",
|
|
271
275
|
"header.switchToCli": "切换到 CLI",
|
|
272
276
|
"history.active": "活跃",
|
|
@@ -263,10 +263,14 @@ const messages = {
|
|
|
263
263
|
"errorMessages.startNewChat": "開始新聊天",
|
|
264
264
|
"errorMessages.upgradeAtBuilder": "在 Builder.io 升級",
|
|
265
265
|
"feedback.inaccurate": "不準確",
|
|
266
|
+
"feedback.keyboardHint": "{{shortcut}} Enter 送出",
|
|
266
267
|
"feedback.notHelpful": "沒有幫助",
|
|
268
|
+
"feedback.placeholder": "告訴我們哪裡出了問題……",
|
|
269
|
+
"feedback.submit": "送出",
|
|
267
270
|
"feedback.thumbsDown": "不喜歡",
|
|
268
271
|
"feedback.thumbsUp": "喜歡",
|
|
269
272
|
"feedback.tooSlow": "太慢",
|
|
273
|
+
"feedback.whatWentWrong": "哪裡出了問題?",
|
|
270
274
|
"feedback.wrongTool": "工具錯誤",
|
|
271
275
|
"header.switchToCli": "切換到 CLI",
|
|
272
276
|
"history.active": "使用中",
|
|
@@ -267,10 +267,14 @@ export declare const englishAgentChatMessages: {
|
|
|
267
267
|
readonly "errorMessages.startNewChat": "Start new chat";
|
|
268
268
|
readonly "errorMessages.upgradeAtBuilder": "Upgrade at Builder.io";
|
|
269
269
|
readonly "feedback.inaccurate": "Inaccurate";
|
|
270
|
+
readonly "feedback.keyboardHint": "{{shortcut}} Enter to send";
|
|
270
271
|
readonly "feedback.notHelpful": "Not helpful";
|
|
272
|
+
readonly "feedback.placeholder": "Tell us what went wrong...";
|
|
273
|
+
readonly "feedback.submit": "Submit";
|
|
271
274
|
readonly "feedback.thumbsDown": "Thumbs down";
|
|
272
275
|
readonly "feedback.thumbsUp": "Thumbs up";
|
|
273
276
|
readonly "feedback.tooSlow": "Too slow";
|
|
277
|
+
readonly "feedback.whatWentWrong": "What went wrong?";
|
|
274
278
|
readonly "feedback.wrongTool": "Wrong tool";
|
|
275
279
|
readonly "header.switchToCli": "Switch to CLI";
|
|
276
280
|
readonly "history.active": "Active";
|