@willwade/aac-processors 0.0.12 → 0.0.14

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.
Files changed (60) hide show
  1. package/README.md +46 -44
  2. package/dist/core/baseProcessor.d.ts +41 -0
  3. package/dist/core/baseProcessor.js +41 -0
  4. package/dist/core/treeStructure.d.ts +35 -2
  5. package/dist/core/treeStructure.js +18 -3
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.js +2 -2
  8. package/dist/processors/astericsGridProcessor.d.ts +15 -0
  9. package/dist/processors/astericsGridProcessor.js +17 -0
  10. package/dist/processors/gridset/helpers.d.ts +4 -1
  11. package/dist/processors/gridset/helpers.js +4 -0
  12. package/dist/processors/gridset/pluginTypes.js +51 -50
  13. package/dist/processors/gridset/symbolAlignment.d.ts +125 -0
  14. package/dist/processors/gridset/symbolAlignment.js +283 -0
  15. package/dist/processors/gridset/symbolExtractor.js +3 -2
  16. package/dist/processors/gridset/symbolSearch.js +9 -7
  17. package/dist/processors/gridsetProcessor.d.ts +26 -0
  18. package/dist/processors/gridsetProcessor.js +178 -25
  19. package/dist/processors/obfProcessor.d.ts +26 -0
  20. package/dist/processors/obfProcessor.js +94 -1
  21. package/dist/processors/snap/helpers.d.ts +5 -1
  22. package/dist/processors/snap/helpers.js +5 -0
  23. package/dist/processors/snapProcessor.d.ts +2 -0
  24. package/dist/processors/snapProcessor.js +156 -5
  25. package/dist/processors/touchchatProcessor.d.ts +26 -0
  26. package/dist/processors/touchchatProcessor.js +106 -6
  27. package/dist/types/aac.d.ts +63 -0
  28. package/dist/types/aac.js +33 -0
  29. package/dist/{optional → utilities}/analytics/history.d.ts +12 -1
  30. package/dist/{optional → utilities}/analytics/index.d.ts +2 -0
  31. package/dist/{optional → utilities}/analytics/index.js +6 -1
  32. package/dist/{optional → utilities}/analytics/metrics/comparison.js +8 -4
  33. package/dist/{optional → utilities}/analytics/metrics/core.d.ts +9 -0
  34. package/dist/{optional → utilities}/analytics/metrics/core.js +190 -37
  35. package/dist/{optional → utilities}/analytics/metrics/effort.d.ts +10 -0
  36. package/dist/{optional → utilities}/analytics/metrics/effort.js +13 -0
  37. package/dist/utilities/analytics/metrics/obl-types.d.ts +93 -0
  38. package/dist/utilities/analytics/metrics/obl-types.js +7 -0
  39. package/dist/utilities/analytics/metrics/obl.d.ts +40 -0
  40. package/dist/utilities/analytics/metrics/obl.js +287 -0
  41. package/dist/{optional → utilities}/analytics/metrics/vocabulary.js +6 -4
  42. package/dist/{optional → utilities}/symbolTools.js +13 -16
  43. package/dist/utilities/translation/translationProcessor.d.ts +119 -0
  44. package/dist/utilities/translation/translationProcessor.js +204 -0
  45. package/dist/validation/gridsetValidator.js +10 -0
  46. package/package.json +1 -1
  47. /package/dist/{optional → utilities}/analytics/history.js +0 -0
  48. /package/dist/{optional → utilities}/analytics/metrics/comparison.d.ts +0 -0
  49. /package/dist/{optional → utilities}/analytics/metrics/index.d.ts +0 -0
  50. /package/dist/{optional → utilities}/analytics/metrics/index.js +0 -0
  51. /package/dist/{optional → utilities}/analytics/metrics/sentence.d.ts +0 -0
  52. /package/dist/{optional → utilities}/analytics/metrics/sentence.js +0 -0
  53. /package/dist/{optional → utilities}/analytics/metrics/types.d.ts +0 -0
  54. /package/dist/{optional → utilities}/analytics/metrics/types.js +0 -0
  55. /package/dist/{optional → utilities}/analytics/metrics/vocabulary.d.ts +0 -0
  56. /package/dist/{optional → utilities}/analytics/reference/index.d.ts +0 -0
  57. /package/dist/{optional → utilities}/analytics/reference/index.js +0 -0
  58. /package/dist/{optional → utilities}/analytics/utils/idGenerator.d.ts +0 -0
  59. /package/dist/{optional → utilities}/analytics/utils/idGenerator.js +0 -0
  60. /package/dist/{optional → utilities}/symbolTools.d.ts +0 -0
@@ -10,6 +10,7 @@ const adm_zip_1 = __importDefault(require("adm-zip"));
10
10
  const fs_1 = __importDefault(require("fs"));
11
11
  const fast_xml_parser_1 = require("fast-xml-parser");
12
12
  const resolver_1 = require("./gridset/resolver");
13
+ const translationProcessor_1 = require("../utilities/translation/translationProcessor");
13
14
  const password_1 = require("./gridset/password");
14
15
  const crypto_1 = __importDefault(require("crypto"));
15
16
  const zlib_1 = __importDefault(require("zlib"));
@@ -19,7 +20,8 @@ const pluginTypes_1 = require("./gridset/pluginTypes");
19
20
  const commands_1 = require("./gridset/commands");
20
21
  const symbols_1 = require("./gridset/symbols");
21
22
  const resolver_2 = require("./gridset/resolver");
22
- const idGenerator_1 = require("../optional/analytics/utils/idGenerator");
23
+ const idGenerator_1 = require("../utilities/analytics/utils/idGenerator");
24
+ const symbolAlignment_1 = require("./gridset/symbolAlignment");
23
25
  class GridsetProcessor extends baseProcessor_1.BaseProcessor {
24
26
  constructor(options) {
25
27
  super(options);
@@ -490,18 +492,62 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
490
492
  const cellY = Math.max(0, parseInt(String(cell['@_Y'] || '1'), 10) - 1);
491
493
  const colSpan = parseInt(String(cell['@_ColumnSpan'] || '1'), 10);
492
494
  const rowSpan = parseInt(String(cell['@_RowSpan'] || '1'), 10);
495
+ // Extract scan block number (1-8) for block scanning support
496
+ const scanBlock = parseInt(String(cell['@_ScanBlock'] || '1'), 10);
497
+ // Extract visibility from Grid 3's <Visibility> child element
498
+ // Grid 3 stores visibility as a child element, not an attribute
499
+ // Valid values: Visible, Hidden, Disabled, PointerAndTouchOnly, TouchOnly, PointerOnly
500
+ const grid3Visibility = cell.Visibility || cell.visibility;
501
+ // Map Grid 3 visibility values to AAC standard values
502
+ // Grid 3 can have additional values like TouchOnly, PointerOnly that map to PointerAndTouchOnly
503
+ let cellVisibility;
504
+ if (grid3Visibility) {
505
+ const vis = String(grid3Visibility);
506
+ // Direct mapping for standard values
507
+ if (vis === 'Visible' ||
508
+ vis === 'Hidden' ||
509
+ vis === 'Disabled' ||
510
+ vis === 'PointerAndTouchOnly') {
511
+ cellVisibility = vis;
512
+ }
513
+ // Map Grid 3 specific values to AAC standard
514
+ else if (vis === 'TouchOnly' || vis === 'PointerOnly') {
515
+ cellVisibility = 'PointerAndTouchOnly';
516
+ }
517
+ // Grid 3 may use 'Empty' for cells that exist but have no content
518
+ else if (vis === 'Empty') {
519
+ cellVisibility = 'Empty';
520
+ }
521
+ // Unknown visibility - default to Visible
522
+ else {
523
+ cellVisibility = undefined; // Let it default
524
+ }
525
+ }
493
526
  // Extract label from CaptionAndImage/Caption
494
527
  const content = cell.Content;
495
528
  const captionAndImage = content.CaptionAndImage || content.captionAndImage;
496
529
  let label = captionAndImage?.Caption || captionAndImage?.caption || '';
530
+ // Check if cell has an image/symbol (needed to decide if we should keep it)
531
+ const hasImageCandidate = !!(captionAndImage?.Image ||
532
+ captionAndImage?.image ||
533
+ captionAndImage?.ImageName ||
534
+ captionAndImage?.imageName ||
535
+ captionAndImage?.Symbol ||
536
+ captionAndImage?.symbol);
497
537
  // If no caption, try other sources or create a placeholder
498
538
  if (!label) {
499
- // For cells without captions (like AutoContent cells), create a meaningful label
539
+ // For cells without captions, check if they have images/symbols before skipping
500
540
  if (content.ContentType === 'AutoContent') {
501
541
  label = `AutoContent_${idx}`;
502
542
  }
543
+ else if (hasImageCandidate ||
544
+ content.ContentType === 'Workspace' ||
545
+ content.ContentType === 'LiveCell') {
546
+ // Keep cells with images/symbols even if no caption
547
+ label = `Cell_${idx}`;
548
+ }
503
549
  else {
504
- return; // Skip cells without labels
550
+ return; // Skip cells without labels AND without images/symbols
505
551
  }
506
552
  }
507
553
  const message = label; // Use caption as message
@@ -930,6 +976,7 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
930
976
  y: cellY,
931
977
  columnSpan: colSpan,
932
978
  rowSpan: rowSpan,
979
+ scanBlock: scanBlock, // Add scan block number for block scanning metrics
933
980
  contentType: pluginMetadata.cellType === pluginTypes_1.Grid3CellType.Regular
934
981
  ? 'Normal'
935
982
  : pluginMetadata.cellType === pluginTypes_1.Grid3CellType.Workspace
@@ -942,6 +989,7 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
942
989
  pluginMetadata.autoContentType,
943
990
  symbolLibrary: symbolLibraryRef?.library || undefined,
944
991
  symbolPath: symbolLibraryRef?.path || undefined,
992
+ visibility: cellVisibility,
945
993
  style: {
946
994
  ...cellStyle,
947
995
  ...inlineStyle, // Inline styles override referenced styles
@@ -1038,17 +1086,51 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
1038
1086
  if (tPage)
1039
1087
  page.name = tPage;
1040
1088
  }
1041
- // Translate button labels and messages
1089
+ // Translate button labels and messages, preserving symbol positions
1042
1090
  page.buttons.forEach((button) => {
1091
+ // Translate label
1043
1092
  if (button.label && translations.has(button.label)) {
1044
1093
  const tLabel = translations.get(button.label);
1045
1094
  if (tLabel)
1046
1095
  button.label = tLabel;
1047
1096
  }
1097
+ // Translate message with symbol preservation
1048
1098
  if (button.message && translations.has(button.message)) {
1049
- const tMsg = translations.get(button.message);
1050
- if (tMsg)
1051
- button.message = tMsg;
1099
+ const originalMessage = button.message;
1100
+ const translatedText = translations.get(originalMessage);
1101
+ if (translatedText) {
1102
+ // Extract symbols from the button (from richText or image fields)
1103
+ const symbols = (0, symbolAlignment_1.extractSymbolsFromButton)(button);
1104
+ if (symbols && symbols.length > 0) {
1105
+ // Use symbol-aware translation to preserve symbol positions
1106
+ const result = (0, symbolAlignment_1.translateWithSymbols)(originalMessage, translatedText, symbols);
1107
+ // Update the message
1108
+ button.message = result.text;
1109
+ // Update the rich text structure if it exists
1110
+ if (button.semanticAction?.richText) {
1111
+ button.semanticAction.richText.text = result.text;
1112
+ button.semanticAction.richText.symbols = result.richTextSymbols;
1113
+ }
1114
+ else if (result.richTextSymbols.length > 0) {
1115
+ // Create rich text structure if it doesn't exist but we have symbols
1116
+ if (!button.semanticAction) {
1117
+ button.semanticAction = {
1118
+ category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
1119
+ intent: treeStructure_1.AACSemanticIntent.SPEAK_TEXT,
1120
+ text: result.text,
1121
+ };
1122
+ }
1123
+ button.semanticAction.richText = {
1124
+ text: result.text,
1125
+ symbols: result.richTextSymbols,
1126
+ };
1127
+ }
1128
+ }
1129
+ else {
1130
+ // No symbols to preserve, simple translation
1131
+ button.message = translatedText;
1132
+ }
1133
+ }
1052
1134
  }
1053
1135
  });
1054
1136
  });
@@ -1056,6 +1138,86 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
1056
1138
  this.saveFromTree(tree, outputPath);
1057
1139
  return fs_1.default.readFileSync(outputPath);
1058
1140
  }
1141
+ /**
1142
+ * Extract symbol information from a gridset for LLM-based translation.
1143
+ * Returns a structured format showing which buttons have symbols and their context.
1144
+ *
1145
+ * This method uses shared translation utilities that work across all AAC formats.
1146
+ *
1147
+ * @param filePathOrBuffer - Path to gridset file or buffer
1148
+ * @returns Array of symbol information for LLM processing
1149
+ */
1150
+ extractSymbolsForLLM(filePathOrBuffer) {
1151
+ const tree = this.loadIntoTree(filePathOrBuffer);
1152
+ // Collect all buttons from all pages
1153
+ const allButtons = [];
1154
+ Object.values(tree.pages).forEach((page) => {
1155
+ page.buttons.forEach((button) => {
1156
+ // Add page context to each button
1157
+ button.pageId = page.id;
1158
+ button.pageName = page.name || page.id;
1159
+ allButtons.push(button);
1160
+ });
1161
+ });
1162
+ // Use shared utility to extract buttons with translation context
1163
+ return (0, translationProcessor_1.extractAllButtonsForTranslation)(allButtons, (button) => ({
1164
+ pageId: button.pageId,
1165
+ pageName: button.pageName,
1166
+ }));
1167
+ }
1168
+ /**
1169
+ * Apply LLM translations with symbol information.
1170
+ * The LLM should provide translations with symbol attachments in the correct positions.
1171
+ *
1172
+ * This method uses shared translation utilities that work across all AAC formats.
1173
+ *
1174
+ * @param filePathOrBuffer - Path to gridset file or buffer
1175
+ * @param llmTranslations - Array of LLM translations with symbol info
1176
+ * @param outputPath - Where to save the translated gridset
1177
+ * @param options - Translation options (e.g., allowPartial for testing)
1178
+ * @returns Buffer of the translated gridset
1179
+ */
1180
+ processLLMTranslations(filePathOrBuffer, llmTranslations, outputPath, options) {
1181
+ const tree = this.loadIntoTree(filePathOrBuffer);
1182
+ // Validate translations using shared utility
1183
+ const buttonIds = Object.values(tree.pages).flatMap((page) => page.buttons.map((b) => b.id));
1184
+ (0, translationProcessor_1.validateTranslationResults)(llmTranslations, buttonIds, options);
1185
+ // Create a map for quick lookup
1186
+ const translationMap = new Map(llmTranslations.map((t) => [t.buttonId, t]));
1187
+ // Apply translations
1188
+ Object.values(tree.pages).forEach((page) => {
1189
+ page.buttons.forEach((button) => {
1190
+ const translation = translationMap.get(button.id);
1191
+ if (!translation)
1192
+ return;
1193
+ // Apply label translation
1194
+ if (translation.translatedLabel) {
1195
+ button.label = translation.translatedLabel;
1196
+ }
1197
+ // Apply message translation
1198
+ if (translation.translatedMessage) {
1199
+ button.message = translation.translatedMessage;
1200
+ // Update rich text if symbols provided
1201
+ if (translation.symbols && translation.symbols.length > 0) {
1202
+ if (!button.semanticAction) {
1203
+ button.semanticAction = {
1204
+ category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
1205
+ intent: treeStructure_1.AACSemanticIntent.SPEAK_TEXT,
1206
+ text: translation.translatedMessage,
1207
+ };
1208
+ }
1209
+ button.semanticAction.richText = {
1210
+ text: translation.translatedMessage,
1211
+ symbols: translation.symbols,
1212
+ };
1213
+ }
1214
+ }
1215
+ });
1216
+ });
1217
+ // Save and return
1218
+ this.saveFromTree(tree, outputPath);
1219
+ return fs_1.default.readFileSync(outputPath);
1220
+ }
1059
1221
  saveFromTree(tree, outputPath) {
1060
1222
  const zip = new adm_zip_1.default();
1061
1223
  if (Object.keys(tree.pages).length === 0) {
@@ -1168,30 +1330,18 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
1168
1330
  GridGuid: page.id,
1169
1331
  // Calculate grid dimensions based on actual layout
1170
1332
  ColumnDefinitions: this.calculateColumnDefinitions(page),
1171
- RowDefinitions: this.calculateRowDefinitions(page),
1333
+ RowDefinitions: this.calculateRowDefinitions(page, false), // No automatic workspace row injection
1172
1334
  AutoContentCommands: '',
1173
1335
  Cells: page.buttons.length > 0
1174
1336
  ? {
1175
1337
  Cell: [
1176
- // Add workspace/message bar cell at the top of ALL pages
1177
- // Grid3 uses 0-based coordinates; omit X and Y to use defaults (0, 0)
1178
- {
1179
- '@_ColumnSpan': 4,
1180
- Content: {
1181
- ContentType: 'Workspace',
1182
- ContentSubType: 'Chat',
1183
- Style: {
1184
- BasedOnStyle: 'Workspace',
1185
- },
1186
- },
1187
- },
1188
1338
  // Regular button cells
1189
1339
  ...this.filterPageButtons(page.buttons).map((button, btnIndex) => {
1190
1340
  const buttonStyleId = button.style ? addStyle(button.style) : '';
1191
1341
  // Find button position in grid layout
1192
1342
  const position = this.findButtonPosition(page, button, btnIndex);
1193
- // Shift all buttons down by 1 row to make room for workspace
1194
- const yOffset = 1;
1343
+ // Use position directly from tree
1344
+ const yOffset = 0;
1195
1345
  // Build CaptionAndImage object
1196
1346
  const captionAndImage = {
1197
1347
  Caption: button.label || '',
@@ -1232,6 +1382,8 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
1232
1382
  '@_ColumnSpan': position.columnSpan,
1233
1383
  '@_RowSpan': position.rowSpan,
1234
1384
  Content: {
1385
+ ContentType: button.contentType === 'Normal' ? undefined : button.contentType,
1386
+ ContentSubType: button.contentSubType,
1235
1387
  Commands: this.generateCommandsFromSemanticAction(button, tree),
1236
1388
  CaptionAndImage: captionAndImage,
1237
1389
  },
@@ -1347,15 +1499,16 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
1347
1499
  };
1348
1500
  }
1349
1501
  // Helper method to calculate row definitions based on page layout
1350
- calculateRowDefinitions(page) {
1502
+ calculateRowDefinitions(page, addWorkspaceOffset = false) {
1351
1503
  let maxRows = 4; // Default minimum
1504
+ const offset = addWorkspaceOffset ? 1 : 0;
1352
1505
  if (page.grid && page.grid.length > 0) {
1353
- maxRows = Math.max(maxRows, page.grid.length);
1506
+ maxRows = Math.max(maxRows, page.grid.length + offset);
1354
1507
  }
1355
1508
  else {
1356
1509
  // Fallback: estimate from button count
1357
1510
  const estimatedCols = Math.ceil(Math.sqrt(page.buttons.length));
1358
- maxRows = Math.max(4, Math.ceil(page.buttons.length / estimatedCols));
1511
+ maxRows = Math.max(4, Math.ceil(page.buttons.length / estimatedCols)) + offset;
1359
1512
  }
1360
1513
  return {
1361
1514
  RowDefinition: Array(maxRows).fill({}),
@@ -1,6 +1,7 @@
1
1
  import { BaseProcessor, ProcessorOptions, ExtractStringsResult, TranslatedString, SourceString } from '../core/baseProcessor';
2
2
  import { AACTree } from '../core/treeStructure';
3
3
  import { ValidationResult } from '../validation/validationTypes';
4
+ import { type ButtonForTranslation, type LLMLTranslationResult } from '../utilities/translation/translationProcessor';
4
5
  declare class ObfProcessor extends BaseProcessor {
5
6
  constructor(options?: ProcessorOptions);
6
7
  private processBoard;
@@ -26,5 +27,30 @@ declare class ObfProcessor extends BaseProcessor {
26
27
  * @returns Promise with validation result
27
28
  */
28
29
  validate(filePath: string): Promise<ValidationResult>;
30
+ /**
31
+ * Extract symbol information from an OBF/OBZ file for LLM-based translation.
32
+ * Returns a structured format showing which buttons have symbols and their context.
33
+ *
34
+ * This method uses shared translation utilities that work across all AAC formats.
35
+ *
36
+ * @param filePathOrBuffer - Path to OBF/OBZ file or buffer
37
+ * @returns Array of symbol information for LLM processing
38
+ */
39
+ extractSymbolsForLLM(filePathOrBuffer: string | Buffer): ButtonForTranslation[];
40
+ /**
41
+ * Apply LLM translations with symbol information.
42
+ * The LLM should provide translations with symbol attachments in the correct positions.
43
+ *
44
+ * This method uses shared translation utilities that work across all AAC formats.
45
+ *
46
+ * @param filePathOrBuffer - Path to OBF/OBZ file or buffer
47
+ * @param llmTranslations - Array of LLM translations with symbol info
48
+ * @param outputPath - Where to save the translated OBF/OBZ file
49
+ * @param options - Translation options (e.g., allowPartial for testing)
50
+ * @returns Buffer of the translated OBF/OBZ file
51
+ */
52
+ processLLMTranslations(filePathOrBuffer: string | Buffer, llmTranslations: LLMLTranslationResult[], outputPath: string, options?: {
53
+ allowPartial?: boolean;
54
+ }): Buffer;
29
55
  }
30
56
  export { ObfProcessor };
@@ -6,11 +6,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ObfProcessor = void 0;
7
7
  const baseProcessor_1 = require("../core/baseProcessor");
8
8
  const treeStructure_1 = require("../core/treeStructure");
9
- const idGenerator_1 = require("../optional/analytics/utils/idGenerator");
9
+ const idGenerator_1 = require("../utilities/analytics/utils/idGenerator");
10
10
  const adm_zip_1 = __importDefault(require("adm-zip"));
11
11
  const fs_1 = __importDefault(require("fs"));
12
12
  const obfValidator_1 = require("../validation/obfValidator");
13
+ const translationProcessor_1 = require("../utilities/translation/translationProcessor");
13
14
  const OBF_FORMAT_VERSION = 'open-board-0.1';
15
+ /**
16
+ * Map OBF hidden value to AAC standard visibility
17
+ * OBF: true = hidden, false/undefined = visible
18
+ * Maps to: 'Hidden' | 'Visible' | undefined
19
+ */
20
+ function mapObfVisibility(hidden) {
21
+ if (hidden === undefined) {
22
+ return undefined; // Default to visible
23
+ }
24
+ return hidden ? 'Hidden' : 'Visible';
25
+ }
14
26
  class ObfProcessor extends baseProcessor_1.BaseProcessor {
15
27
  constructor(options) {
16
28
  super(options);
@@ -41,6 +53,7 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
41
53
  id: String(btn?.id || ''),
42
54
  label: String(btn?.label || ''),
43
55
  message: String(btn?.vocalization || btn?.label || ''),
56
+ visibility: mapObfVisibility(btn.hidden),
44
57
  style: {
45
58
  backgroundColor: btn.background_color,
46
59
  borderColor: btn.border_color,
@@ -391,5 +404,85 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
391
404
  async validate(filePath) {
392
405
  return obfValidator_1.ObfValidator.validateFile(filePath);
393
406
  }
407
+ /**
408
+ * Extract symbol information from an OBF/OBZ file for LLM-based translation.
409
+ * Returns a structured format showing which buttons have symbols and their context.
410
+ *
411
+ * This method uses shared translation utilities that work across all AAC formats.
412
+ *
413
+ * @param filePathOrBuffer - Path to OBF/OBZ file or buffer
414
+ * @returns Array of symbol information for LLM processing
415
+ */
416
+ extractSymbolsForLLM(filePathOrBuffer) {
417
+ const tree = this.loadIntoTree(filePathOrBuffer);
418
+ // Collect all buttons from all pages
419
+ const allButtons = [];
420
+ Object.values(tree.pages).forEach((page) => {
421
+ page.buttons.forEach((button) => {
422
+ // Add page context to each button
423
+ button.pageId = page.id;
424
+ button.pageName = page.name || page.id;
425
+ allButtons.push(button);
426
+ });
427
+ });
428
+ // Use shared utility to extract buttons with translation context
429
+ return (0, translationProcessor_1.extractAllButtonsForTranslation)(allButtons, (button) => ({
430
+ pageId: button.pageId,
431
+ pageName: button.pageName,
432
+ }));
433
+ }
434
+ /**
435
+ * Apply LLM translations with symbol information.
436
+ * The LLM should provide translations with symbol attachments in the correct positions.
437
+ *
438
+ * This method uses shared translation utilities that work across all AAC formats.
439
+ *
440
+ * @param filePathOrBuffer - Path to OBF/OBZ file or buffer
441
+ * @param llmTranslations - Array of LLM translations with symbol info
442
+ * @param outputPath - Where to save the translated OBF/OBZ file
443
+ * @param options - Translation options (e.g., allowPartial for testing)
444
+ * @returns Buffer of the translated OBF/OBZ file
445
+ */
446
+ processLLMTranslations(filePathOrBuffer, llmTranslations, outputPath, options) {
447
+ const tree = this.loadIntoTree(filePathOrBuffer);
448
+ // Validate translations using shared utility
449
+ const buttonIds = Object.values(tree.pages).flatMap((page) => page.buttons.map((b) => b.id));
450
+ (0, translationProcessor_1.validateTranslationResults)(llmTranslations, buttonIds, options);
451
+ // Create a map for quick lookup
452
+ const translationMap = new Map(llmTranslations.map((t) => [t.buttonId, t]));
453
+ // Apply translations
454
+ Object.values(tree.pages).forEach((page) => {
455
+ page.buttons.forEach((button) => {
456
+ const translation = translationMap.get(button.id);
457
+ if (!translation)
458
+ return;
459
+ // Apply label translation
460
+ if (translation.translatedLabel) {
461
+ button.label = translation.translatedLabel;
462
+ }
463
+ // Apply message translation (vocalization in OBF)
464
+ if (translation.translatedMessage) {
465
+ button.message = translation.translatedMessage;
466
+ // Update semantic action if symbols provided
467
+ if (translation.symbols && translation.symbols.length > 0) {
468
+ if (!button.semanticAction) {
469
+ button.semanticAction = {
470
+ category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
471
+ intent: treeStructure_1.AACSemanticIntent.SPEAK_TEXT,
472
+ text: translation.translatedMessage,
473
+ };
474
+ }
475
+ button.semanticAction.richText = {
476
+ text: translation.translatedMessage,
477
+ symbols: translation.symbols,
478
+ };
479
+ }
480
+ }
481
+ });
482
+ });
483
+ // Save and return
484
+ this.saveFromTree(tree, outputPath);
485
+ return fs_1.default.readFileSync(outputPath);
486
+ }
394
487
  }
395
488
  exports.ObfProcessor = ObfProcessor;
@@ -1,4 +1,4 @@
1
- import { AACTree } from '../../core/treeStructure';
1
+ import { AACTree, AACSemanticCategory, AACSemanticIntent } from '../../core/treeStructure';
2
2
  /**
3
3
  * Build a map of button IDs to resolved image entries for a specific page.
4
4
  * Mirrors the Grid helper for consumers that expect image reference data.
@@ -33,6 +33,10 @@ export interface SnapUsageEntry {
33
33
  timestamp: Date;
34
34
  modeling?: boolean;
35
35
  accessMethod?: number | null;
36
+ type?: 'button' | 'action' | 'utterance' | 'note' | 'other';
37
+ buttonId?: string | null;
38
+ intent?: AACSemanticIntent | string;
39
+ category?: AACSemanticCategory;
36
40
  }>;
37
41
  platform?: {
38
42
  label?: string;
@@ -37,6 +37,7 @@ exports.findSnapUserHistory = findSnapUserHistory;
37
37
  exports.isSnapInstalled = isSnapInstalled;
38
38
  exports.readSnapUsage = readSnapUsage;
39
39
  exports.readSnapUsageForUser = readSnapUsageForUser;
40
+ const treeStructure_1 = require("../../core/treeStructure");
40
41
  const fs = __importStar(require("fs"));
41
42
  const path = __importStar(require("path"));
42
43
  const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
@@ -271,6 +272,10 @@ function readSnapUsage(pagesetPath) {
271
272
  timestamp: (0, dotnetTicks_1.dotNetTicksToDate)(BigInt(row.TickValue ?? 0)),
272
273
  modeling: row.Modeling === 1,
273
274
  accessMethod: row.AccessMethod ?? null,
275
+ type: 'button',
276
+ buttonId: row.ButtonId,
277
+ intent: treeStructure_1.AACSemanticIntent.SPEAK_TEXT,
278
+ category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
274
279
  });
275
280
  events.set(buttonId, entry);
276
281
  }
@@ -4,8 +4,10 @@ import { ValidationResult } from '../validation/validationTypes';
4
4
  declare class SnapProcessor extends BaseProcessor {
5
5
  private symbolResolver;
6
6
  private loadAudio;
7
+ private pageLayoutPreference;
7
8
  constructor(symbolResolver?: unknown | null, options?: ProcessorOptions & {
8
9
  loadAudio?: boolean;
10
+ pageLayoutPreference?: 'largest' | 'smallest' | 'scanning' | number;
9
11
  });
10
12
  extractTexts(filePathOrBuffer: string | Buffer): string[];
11
13
  loadIntoTree(filePathOrBuffer: string | Buffer): AACTree;