@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.
- package/README.md +46 -44
- package/dist/core/baseProcessor.d.ts +41 -0
- package/dist/core/baseProcessor.js +41 -0
- package/dist/core/treeStructure.d.ts +35 -2
- package/dist/core/treeStructure.js +18 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/processors/astericsGridProcessor.d.ts +15 -0
- package/dist/processors/astericsGridProcessor.js +17 -0
- package/dist/processors/gridset/helpers.d.ts +4 -1
- package/dist/processors/gridset/helpers.js +4 -0
- package/dist/processors/gridset/pluginTypes.js +51 -50
- package/dist/processors/gridset/symbolAlignment.d.ts +125 -0
- package/dist/processors/gridset/symbolAlignment.js +283 -0
- package/dist/processors/gridset/symbolExtractor.js +3 -2
- package/dist/processors/gridset/symbolSearch.js +9 -7
- package/dist/processors/gridsetProcessor.d.ts +26 -0
- package/dist/processors/gridsetProcessor.js +178 -25
- package/dist/processors/obfProcessor.d.ts +26 -0
- package/dist/processors/obfProcessor.js +94 -1
- package/dist/processors/snap/helpers.d.ts +5 -1
- package/dist/processors/snap/helpers.js +5 -0
- package/dist/processors/snapProcessor.d.ts +2 -0
- package/dist/processors/snapProcessor.js +156 -5
- package/dist/processors/touchchatProcessor.d.ts +26 -0
- package/dist/processors/touchchatProcessor.js +106 -6
- package/dist/types/aac.d.ts +63 -0
- package/dist/types/aac.js +33 -0
- package/dist/{optional → utilities}/analytics/history.d.ts +12 -1
- package/dist/{optional → utilities}/analytics/index.d.ts +2 -0
- package/dist/{optional → utilities}/analytics/index.js +6 -1
- package/dist/{optional → utilities}/analytics/metrics/comparison.js +8 -4
- package/dist/{optional → utilities}/analytics/metrics/core.d.ts +9 -0
- package/dist/{optional → utilities}/analytics/metrics/core.js +190 -37
- package/dist/{optional → utilities}/analytics/metrics/effort.d.ts +10 -0
- package/dist/{optional → utilities}/analytics/metrics/effort.js +13 -0
- package/dist/utilities/analytics/metrics/obl-types.d.ts +93 -0
- package/dist/utilities/analytics/metrics/obl-types.js +7 -0
- package/dist/utilities/analytics/metrics/obl.d.ts +40 -0
- package/dist/utilities/analytics/metrics/obl.js +287 -0
- package/dist/{optional → utilities}/analytics/metrics/vocabulary.js +6 -4
- package/dist/{optional → utilities}/symbolTools.js +13 -16
- package/dist/utilities/translation/translationProcessor.d.ts +119 -0
- package/dist/utilities/translation/translationProcessor.js +204 -0
- package/dist/validation/gridsetValidator.js +10 -0
- package/package.json +1 -1
- /package/dist/{optional → utilities}/analytics/history.js +0 -0
- /package/dist/{optional → utilities}/analytics/metrics/comparison.d.ts +0 -0
- /package/dist/{optional → utilities}/analytics/metrics/index.d.ts +0 -0
- /package/dist/{optional → utilities}/analytics/metrics/index.js +0 -0
- /package/dist/{optional → utilities}/analytics/metrics/sentence.d.ts +0 -0
- /package/dist/{optional → utilities}/analytics/metrics/sentence.js +0 -0
- /package/dist/{optional → utilities}/analytics/metrics/types.d.ts +0 -0
- /package/dist/{optional → utilities}/analytics/metrics/types.js +0 -0
- /package/dist/{optional → utilities}/analytics/metrics/vocabulary.d.ts +0 -0
- /package/dist/{optional → utilities}/analytics/reference/index.d.ts +0 -0
- /package/dist/{optional → utilities}/analytics/reference/index.js +0 -0
- /package/dist/{optional → utilities}/analytics/utils/idGenerator.d.ts +0 -0
- /package/dist/{optional → utilities}/analytics/utils/idGenerator.js +0 -0
- /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("../
|
|
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
|
|
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
|
|
1050
|
-
|
|
1051
|
-
|
|
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
|
-
//
|
|
1194
|
-
const yOffset =
|
|
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("../
|
|
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;
|