@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
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.SnapProcessor = 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 better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
11
11
|
const path_1 = __importDefault(require("path"));
|
|
12
12
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -17,8 +17,11 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
17
17
|
super(options);
|
|
18
18
|
this.symbolResolver = null;
|
|
19
19
|
this.loadAudio = false;
|
|
20
|
+
this.pageLayoutPreference = 'scanning'; // Default to scanning for metrics
|
|
20
21
|
this.symbolResolver = symbolResolver;
|
|
21
22
|
this.loadAudio = options.loadAudio !== undefined ? options.loadAudio : true;
|
|
23
|
+
this.pageLayoutPreference =
|
|
24
|
+
options.pageLayoutPreference !== undefined ? options.pageLayoutPreference : 'scanning'; // Default to scanning
|
|
22
25
|
}
|
|
23
26
|
extractTexts(filePathOrBuffer) {
|
|
24
27
|
const tree = this.loadIntoTree(filePathOrBuffer);
|
|
@@ -79,11 +82,127 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
79
82
|
});
|
|
80
83
|
tree.addPage(page);
|
|
81
84
|
});
|
|
85
|
+
const scanGroupsByPageLayout = new Map();
|
|
86
|
+
try {
|
|
87
|
+
const scanGroupRows = db
|
|
88
|
+
.prepare('SELECT Id, SerializedGridPositions, PageLayoutId FROM ScanGroup ORDER BY Id')
|
|
89
|
+
.all();
|
|
90
|
+
if (scanGroupRows && scanGroupRows.length > 0) {
|
|
91
|
+
// Group by PageLayoutId first
|
|
92
|
+
const groupsByLayout = new Map();
|
|
93
|
+
scanGroupRows.forEach((sg) => {
|
|
94
|
+
if (!groupsByLayout.has(sg.PageLayoutId)) {
|
|
95
|
+
groupsByLayout.set(sg.PageLayoutId, []);
|
|
96
|
+
}
|
|
97
|
+
const layoutGroups = groupsByLayout.get(sg.PageLayoutId);
|
|
98
|
+
if (layoutGroups) {
|
|
99
|
+
layoutGroups.push(sg);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
// For each PageLayout, assign scan block numbers based on order (1-based index)
|
|
103
|
+
groupsByLayout.forEach((groups, layoutId) => {
|
|
104
|
+
groups.forEach((sg, index) => {
|
|
105
|
+
// Parse SerializedGridPositions JSON
|
|
106
|
+
let positions = [];
|
|
107
|
+
try {
|
|
108
|
+
positions = JSON.parse(sg.SerializedGridPositions);
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
// Invalid JSON, skip this group
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const scanGroup = {
|
|
115
|
+
id: sg.Id,
|
|
116
|
+
scanBlock: index + 1, // Scan block is 1-based index
|
|
117
|
+
positions: positions,
|
|
118
|
+
};
|
|
119
|
+
if (!scanGroupsByPageLayout.has(layoutId)) {
|
|
120
|
+
scanGroupsByPageLayout.set(layoutId, []);
|
|
121
|
+
}
|
|
122
|
+
const layoutGroups = scanGroupsByPageLayout.get(layoutId);
|
|
123
|
+
if (layoutGroups) {
|
|
124
|
+
layoutGroups.push(scanGroup);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
// No ScanGroups table or error loading, continue without scan blocks
|
|
132
|
+
console.warn('[SnapProcessor] Failed to load ScanGroups:', e);
|
|
133
|
+
}
|
|
82
134
|
// Load buttons per page, using UniqueId for page id
|
|
83
135
|
for (const pageRow of pages) {
|
|
84
|
-
let buttons = [];
|
|
85
136
|
// Create a map to track page grid layouts
|
|
86
137
|
const pageGrids = new Map();
|
|
138
|
+
// Select PageLayout for this page based on preference
|
|
139
|
+
let selectedPageLayoutId = null;
|
|
140
|
+
try {
|
|
141
|
+
const pageLayouts = db
|
|
142
|
+
.prepare('SELECT Id, PageLayoutSetting FROM PageLayout WHERE PageId = ?')
|
|
143
|
+
.all(pageRow.Id);
|
|
144
|
+
if (pageLayouts && pageLayouts.length > 0) {
|
|
145
|
+
// Parse PageLayoutSetting: "columns,rows,hasScanGroups,?"
|
|
146
|
+
const layoutsWithInfo = pageLayouts.map((pl) => {
|
|
147
|
+
const parts = pl.PageLayoutSetting.split(',');
|
|
148
|
+
const cols = parseInt(parts[0], 10) || 0;
|
|
149
|
+
const rows = parseInt(parts[1], 10) || 0;
|
|
150
|
+
const hasScanning = parts[2] === 'True';
|
|
151
|
+
const size = cols * rows;
|
|
152
|
+
return { id: pl.Id, cols, rows, size, hasScanning };
|
|
153
|
+
});
|
|
154
|
+
// Select based on preference
|
|
155
|
+
if (typeof this.pageLayoutPreference === 'number') {
|
|
156
|
+
// Specific PageLayoutId
|
|
157
|
+
selectedPageLayoutId = this.pageLayoutPreference;
|
|
158
|
+
}
|
|
159
|
+
else if (this.pageLayoutPreference === 'largest') {
|
|
160
|
+
// Select layout with largest grid size, prefer layouts with ScanGroups
|
|
161
|
+
layoutsWithInfo.sort((a, b) => {
|
|
162
|
+
const sizeDiff = b.size - a.size;
|
|
163
|
+
if (sizeDiff !== 0)
|
|
164
|
+
return sizeDiff;
|
|
165
|
+
// Same size, prefer one with ScanGroups
|
|
166
|
+
const aHasScanning = scanGroupsByPageLayout.has(a.id);
|
|
167
|
+
const bHasScanning = scanGroupsByPageLayout.has(b.id);
|
|
168
|
+
return (bHasScanning ? 1 : 0) - (aHasScanning ? 1 : 0);
|
|
169
|
+
});
|
|
170
|
+
selectedPageLayoutId = layoutsWithInfo[0].id;
|
|
171
|
+
}
|
|
172
|
+
else if (this.pageLayoutPreference === 'smallest') {
|
|
173
|
+
// Select layout with smallest grid size, prefer layouts with ScanGroups
|
|
174
|
+
layoutsWithInfo.sort((a, b) => {
|
|
175
|
+
const sizeDiff = a.size - b.size;
|
|
176
|
+
if (sizeDiff !== 0)
|
|
177
|
+
return sizeDiff;
|
|
178
|
+
// Same size, prefer one with ScanGroups
|
|
179
|
+
const aHasScanning = scanGroupsByPageLayout.has(a.id);
|
|
180
|
+
const bHasScanning = scanGroupsByPageLayout.has(b.id);
|
|
181
|
+
return (bHasScanning ? 1 : 0) - (aHasScanning ? 1 : 0);
|
|
182
|
+
});
|
|
183
|
+
selectedPageLayoutId = layoutsWithInfo[0].id;
|
|
184
|
+
}
|
|
185
|
+
else if (this.pageLayoutPreference === 'scanning') {
|
|
186
|
+
// Select layout with scanning enabled (check against actual ScanGroups)
|
|
187
|
+
const scanningLayouts = layoutsWithInfo.filter((l) => scanGroupsByPageLayout.has(l.id));
|
|
188
|
+
if (scanningLayouts.length > 0) {
|
|
189
|
+
scanningLayouts.sort((a, b) => b.size - a.size);
|
|
190
|
+
selectedPageLayoutId = scanningLayouts[0].id;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
// Fallback to largest
|
|
194
|
+
layoutsWithInfo.sort((a, b) => b.size - a.size);
|
|
195
|
+
selectedPageLayoutId = layoutsWithInfo[0].id;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch (e) {
|
|
201
|
+
// Error selecting PageLayout, will load all buttons
|
|
202
|
+
console.warn(`[SnapProcessor] Failed to select PageLayout for page ${pageRow.Id}:`, e);
|
|
203
|
+
}
|
|
204
|
+
// Load buttons
|
|
205
|
+
let buttons = [];
|
|
87
206
|
try {
|
|
88
207
|
const buttonColumns = getTableColumns('Button');
|
|
89
208
|
const selectFields = [
|
|
@@ -112,15 +231,19 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
112
231
|
? 'b.SerializedMessageSoundMetadata'
|
|
113
232
|
: 'NULL AS SerializedMessageSoundMetadata');
|
|
114
233
|
}
|
|
115
|
-
|
|
234
|
+
const placementColumns = getTableColumns('ElementPlacement');
|
|
235
|
+
selectFields.push(placementColumns.has('GridPosition') ? 'ep.GridPosition' : 'NULL AS GridPosition', placementColumns.has('PageLayoutId') ? 'ep.PageLayoutId' : 'NULL AS PageLayoutId', 'er.PageId as ButtonPageId');
|
|
116
236
|
const buttonQuery = `
|
|
117
237
|
SELECT ${selectFields.join(', ')}
|
|
118
238
|
FROM Button b
|
|
119
239
|
INNER JOIN ElementReference er ON b.ElementReferenceId = er.Id
|
|
120
240
|
LEFT JOIN ElementPlacement ep ON ep.ElementReferenceId = er.Id
|
|
121
|
-
WHERE er.PageId = ?
|
|
241
|
+
WHERE er.PageId = ? ${selectedPageLayoutId ? 'AND ep.PageLayoutId = ?' : ''}
|
|
122
242
|
`;
|
|
123
|
-
|
|
243
|
+
const queryParams = selectedPageLayoutId
|
|
244
|
+
? [pageRow.Id, selectedPageLayoutId]
|
|
245
|
+
: [pageRow.Id];
|
|
246
|
+
buttons = db.prepare(buttonQuery).all(...queryParams);
|
|
124
247
|
}
|
|
125
248
|
catch (err) {
|
|
126
249
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -252,6 +375,34 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
252
375
|
const [xStr, yStr] = gridPositionStr.split(',');
|
|
253
376
|
const gridX = parseInt(xStr, 10);
|
|
254
377
|
const gridY = parseInt(yStr, 10);
|
|
378
|
+
// Set button x,y properties (critical for metrics!)
|
|
379
|
+
if (!isNaN(gridX) && !isNaN(gridY)) {
|
|
380
|
+
button.x = gridX;
|
|
381
|
+
button.y = gridY;
|
|
382
|
+
// Determine scan block from ScanGroups (TD Snap "Group Scan")
|
|
383
|
+
// IMPORTANT: Only match against ScanGroups from the SAME PageLayout
|
|
384
|
+
// A button can exist in multiple layouts with different positions
|
|
385
|
+
const buttonPageLayoutId = btnRow.PageLayoutId;
|
|
386
|
+
if (buttonPageLayoutId && scanGroupsByPageLayout.has(buttonPageLayoutId)) {
|
|
387
|
+
const scanGroups = scanGroupsByPageLayout.get(buttonPageLayoutId);
|
|
388
|
+
if (scanGroups && scanGroups.length > 0) {
|
|
389
|
+
// Find which ScanGroup contains this button's position
|
|
390
|
+
for (const scanGroup of scanGroups) {
|
|
391
|
+
// Skip if positions array is null or undefined
|
|
392
|
+
if (!scanGroup.positions || !Array.isArray(scanGroup.positions)) {
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
const foundInGroup = scanGroup.positions.some((pos) => pos.Column === gridX && pos.Row === gridY);
|
|
396
|
+
if (foundInGroup) {
|
|
397
|
+
// Use the scan block number from the ScanGroup
|
|
398
|
+
// ScanGroup scanBlock is already 1-based (index + 1)
|
|
399
|
+
button.scanBlock = scanGroup.scanBlock;
|
|
400
|
+
break; // Found the scan block, stop looking
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
255
406
|
// Place button in grid if within bounds and coordinates are valid
|
|
256
407
|
if (!isNaN(gridX) &&
|
|
257
408
|
!isNaN(gridY) &&
|
|
@@ -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 TouchChatProcessor extends BaseProcessor {
|
|
5
6
|
private tree;
|
|
6
7
|
private sourceFile;
|
|
@@ -30,5 +31,30 @@ declare class TouchChatProcessor extends BaseProcessor {
|
|
|
30
31
|
* @returns Promise with validation result
|
|
31
32
|
*/
|
|
32
33
|
validate(filePath: string): Promise<ValidationResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Extract symbol information from a TouchChat file for LLM-based translation.
|
|
36
|
+
* Returns a structured format showing which buttons have symbols and their context.
|
|
37
|
+
*
|
|
38
|
+
* This method uses shared translation utilities that work across all AAC formats.
|
|
39
|
+
*
|
|
40
|
+
* @param filePathOrBuffer - Path to TouchChat .ce file or buffer
|
|
41
|
+
* @returns Array of symbol information for LLM processing
|
|
42
|
+
*/
|
|
43
|
+
extractSymbolsForLLM(filePathOrBuffer: string | Buffer): ButtonForTranslation[];
|
|
44
|
+
/**
|
|
45
|
+
* Apply LLM translations with symbol information.
|
|
46
|
+
* The LLM should provide translations with symbol attachments in the correct positions.
|
|
47
|
+
*
|
|
48
|
+
* This method uses shared translation utilities that work across all AAC formats.
|
|
49
|
+
*
|
|
50
|
+
* @param filePathOrBuffer - Path to TouchChat .ce file or buffer
|
|
51
|
+
* @param llmTranslations - Array of LLM translations with symbol info
|
|
52
|
+
* @param outputPath - Where to save the translated TouchChat file
|
|
53
|
+
* @param options - Translation options (e.g., allowPartial for testing)
|
|
54
|
+
* @returns Buffer of the translated TouchChat file
|
|
55
|
+
*/
|
|
56
|
+
processLLMTranslations(filePathOrBuffer: string | Buffer, llmTranslations: LLMLTranslationResult[], outputPath: string, options?: {
|
|
57
|
+
allowPartial?: boolean;
|
|
58
|
+
}): Buffer;
|
|
33
59
|
}
|
|
34
60
|
export { TouchChatProcessor };
|
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.TouchChatProcessor = 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 stringCasing_1 = require("../core/stringCasing");
|
|
11
11
|
const adm_zip_1 = __importDefault(require("adm-zip"));
|
|
12
12
|
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
@@ -14,6 +14,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
14
14
|
const fs_1 = __importDefault(require("fs"));
|
|
15
15
|
const os_1 = __importDefault(require("os"));
|
|
16
16
|
const touchChatValidator_1 = require("../validation/touchChatValidator");
|
|
17
|
+
const translationProcessor_1 = require("../utilities/translation/translationProcessor");
|
|
17
18
|
const toNumberOrUndefined = (value) => typeof value === 'number' ? value : undefined;
|
|
18
19
|
const toStringOrUndefined = (value) => typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
19
20
|
const toBooleanOrUndefined = (value) => typeof value === 'number' ? value !== 0 : undefined;
|
|
@@ -24,6 +25,17 @@ function intToHex(colorInt) {
|
|
|
24
25
|
// Assuming the color is in ARGB format, we mask out the alpha channel
|
|
25
26
|
return `#${(colorInt & 0x00ffffff).toString(16).padStart(6, '0')}`;
|
|
26
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Map TouchChat visible value to AAC standard visibility
|
|
30
|
+
* TouchChat: 0 = Hidden, 1 = Visible
|
|
31
|
+
* Maps to: 'Hidden' | 'Visible' | undefined
|
|
32
|
+
*/
|
|
33
|
+
function mapTouchChatVisibility(visible) {
|
|
34
|
+
if (visible === null || visible === undefined) {
|
|
35
|
+
return undefined; // Default to visible
|
|
36
|
+
}
|
|
37
|
+
return visible === 0 ? 'Hidden' : 'Visible';
|
|
38
|
+
}
|
|
27
39
|
class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
|
|
28
40
|
constructor(options) {
|
|
29
41
|
super(options);
|
|
@@ -132,7 +144,7 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
132
144
|
});
|
|
133
145
|
// Load button boxes and their cells
|
|
134
146
|
const buttonBoxQuery = `
|
|
135
|
-
SELECT bbc.*, b.*, bb.id as box_id
|
|
147
|
+
SELECT bbc.*, b.*, bb.id as box_id
|
|
136
148
|
FROM button_box_cells bbc
|
|
137
149
|
JOIN buttons b ON b.resource_id = bbc.resource_id
|
|
138
150
|
JOIN button_boxes bb ON bb.id = bbc.button_box_id
|
|
@@ -166,7 +178,11 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
166
178
|
label: cell.label || '',
|
|
167
179
|
message: cell.message || '',
|
|
168
180
|
semanticAction: semanticAction,
|
|
169
|
-
semantic_id: cell.symbol_link_id || cell.symbolLinkId || undefined, // Extract semantic_id from symbol_link_id
|
|
181
|
+
semantic_id: (cell.symbol_link_id || cell.symbolLinkId) || undefined, // Extract semantic_id from symbol_link_id
|
|
182
|
+
visibility: mapTouchChatVisibility(cell.visible || undefined),
|
|
183
|
+
// Note: TouchChat does not use scan blocks in the file
|
|
184
|
+
// Scanning is a runtime feature (linear/row-column patterns)
|
|
185
|
+
// scanBlock defaults to 1 (no grouping)
|
|
170
186
|
style: {
|
|
171
187
|
backgroundColor: intToHex(style?.body_color),
|
|
172
188
|
borderColor: intToHex(style?.border_color),
|
|
@@ -183,9 +199,9 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
183
199
|
});
|
|
184
200
|
buttonBoxes.get(cell.box_id)?.push({
|
|
185
201
|
button,
|
|
186
|
-
location: cell.location,
|
|
187
|
-
spanX: cell.span_x,
|
|
188
|
-
spanY: cell.span_y,
|
|
202
|
+
location: cell.location || 0,
|
|
203
|
+
spanX: cell.span_x || 1,
|
|
204
|
+
spanY: cell.span_y || 1,
|
|
189
205
|
});
|
|
190
206
|
});
|
|
191
207
|
// Map button boxes to pages
|
|
@@ -306,6 +322,10 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
306
322
|
label: btnRow.label || '',
|
|
307
323
|
message: btnRow.message || '',
|
|
308
324
|
semanticAction: semanticAction,
|
|
325
|
+
visibility: mapTouchChatVisibility(btnRow.visible),
|
|
326
|
+
// Note: TouchChat does not use scan blocks in the file
|
|
327
|
+
// Scanning is a runtime feature (linear/row-column patterns)
|
|
328
|
+
// scanBlock defaults to 1 (no grouping)
|
|
309
329
|
style: {
|
|
310
330
|
backgroundColor: intToHex(style?.body_color),
|
|
311
331
|
borderColor: intToHex(style?.border_color),
|
|
@@ -823,5 +843,85 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
823
843
|
async validate(filePath) {
|
|
824
844
|
return touchChatValidator_1.TouchChatValidator.validateFile(filePath);
|
|
825
845
|
}
|
|
846
|
+
/**
|
|
847
|
+
* Extract symbol information from a TouchChat file for LLM-based translation.
|
|
848
|
+
* Returns a structured format showing which buttons have symbols and their context.
|
|
849
|
+
*
|
|
850
|
+
* This method uses shared translation utilities that work across all AAC formats.
|
|
851
|
+
*
|
|
852
|
+
* @param filePathOrBuffer - Path to TouchChat .ce file or buffer
|
|
853
|
+
* @returns Array of symbol information for LLM processing
|
|
854
|
+
*/
|
|
855
|
+
extractSymbolsForLLM(filePathOrBuffer) {
|
|
856
|
+
const tree = this.loadIntoTree(filePathOrBuffer);
|
|
857
|
+
// Collect all buttons from all pages
|
|
858
|
+
const allButtons = [];
|
|
859
|
+
Object.values(tree.pages).forEach((page) => {
|
|
860
|
+
page.buttons.forEach((button) => {
|
|
861
|
+
// Add page context to each button
|
|
862
|
+
button.pageId = page.id;
|
|
863
|
+
button.pageName = page.name || page.id;
|
|
864
|
+
allButtons.push(button);
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
// Use shared utility to extract buttons with translation context
|
|
868
|
+
return (0, translationProcessor_1.extractAllButtonsForTranslation)(allButtons, (button) => ({
|
|
869
|
+
pageId: button.pageId,
|
|
870
|
+
pageName: button.pageName,
|
|
871
|
+
}));
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Apply LLM translations with symbol information.
|
|
875
|
+
* The LLM should provide translations with symbol attachments in the correct positions.
|
|
876
|
+
*
|
|
877
|
+
* This method uses shared translation utilities that work across all AAC formats.
|
|
878
|
+
*
|
|
879
|
+
* @param filePathOrBuffer - Path to TouchChat .ce file or buffer
|
|
880
|
+
* @param llmTranslations - Array of LLM translations with symbol info
|
|
881
|
+
* @param outputPath - Where to save the translated TouchChat file
|
|
882
|
+
* @param options - Translation options (e.g., allowPartial for testing)
|
|
883
|
+
* @returns Buffer of the translated TouchChat file
|
|
884
|
+
*/
|
|
885
|
+
processLLMTranslations(filePathOrBuffer, llmTranslations, outputPath, options) {
|
|
886
|
+
const tree = this.loadIntoTree(filePathOrBuffer);
|
|
887
|
+
// Validate translations using shared utility
|
|
888
|
+
const buttonIds = Object.values(tree.pages).flatMap((page) => page.buttons.map((b) => b.id));
|
|
889
|
+
(0, translationProcessor_1.validateTranslationResults)(llmTranslations, buttonIds, options);
|
|
890
|
+
// Create a map for quick lookup
|
|
891
|
+
const translationMap = new Map(llmTranslations.map((t) => [t.buttonId, t]));
|
|
892
|
+
// Apply translations
|
|
893
|
+
Object.values(tree.pages).forEach((page) => {
|
|
894
|
+
page.buttons.forEach((button) => {
|
|
895
|
+
const translation = translationMap.get(button.id);
|
|
896
|
+
if (!translation)
|
|
897
|
+
return;
|
|
898
|
+
// Apply label translation
|
|
899
|
+
if (translation.translatedLabel) {
|
|
900
|
+
button.label = translation.translatedLabel;
|
|
901
|
+
}
|
|
902
|
+
// Apply message translation
|
|
903
|
+
if (translation.translatedMessage) {
|
|
904
|
+
button.message = translation.translatedMessage;
|
|
905
|
+
// Update semantic action if symbols provided
|
|
906
|
+
if (translation.symbols && translation.symbols.length > 0) {
|
|
907
|
+
if (!button.semanticAction) {
|
|
908
|
+
button.semanticAction = {
|
|
909
|
+
category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
|
|
910
|
+
intent: treeStructure_1.AACSemanticIntent.SPEAK_TEXT,
|
|
911
|
+
text: translation.translatedMessage,
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
button.semanticAction.richText = {
|
|
915
|
+
text: translation.translatedMessage,
|
|
916
|
+
symbols: translation.symbols,
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
});
|
|
922
|
+
// Save and return
|
|
923
|
+
this.saveFromTree(tree, outputPath);
|
|
924
|
+
return fs_1.default.readFileSync(outputPath);
|
|
925
|
+
}
|
|
826
926
|
}
|
|
827
927
|
exports.TouchChatProcessor = TouchChatProcessor;
|
package/dist/types/aac.d.ts
CHANGED
|
@@ -1,4 +1,54 @@
|
|
|
1
1
|
import { AACSemanticAction } from '../core/treeStructure';
|
|
2
|
+
/**
|
|
3
|
+
* Scanning selection methods for switch access
|
|
4
|
+
* Determines how the scanning advances through items
|
|
5
|
+
*/
|
|
6
|
+
export declare enum ScanningSelectionMethod {
|
|
7
|
+
/** Automatically advance through items at timed intervals */
|
|
8
|
+
AutoScan = "AutoScan",
|
|
9
|
+
/** Automatic scanning with overscan (two-stage scanning) */
|
|
10
|
+
AutoScanWithOverscan = "AutoScanWithOverscan",
|
|
11
|
+
/** Hold switch to advance, release to select */
|
|
12
|
+
HoldToAdvance = "HoldToAdvance",
|
|
13
|
+
/** Hold to advance with overscan */
|
|
14
|
+
HoldToAdvanceWithOverscan = "HoldToAdvanceWithOverscan",
|
|
15
|
+
/** Tap switch to advance, tap again to select */
|
|
16
|
+
TapToAdvance = "TapToAdvance"
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Cell scanning order patterns
|
|
20
|
+
* Determines the sequence in which cells are highlighted
|
|
21
|
+
*/
|
|
22
|
+
export declare enum CellScanningOrder {
|
|
23
|
+
/** Simple linear scan across rows (left-to-right, top-to-bottom) */
|
|
24
|
+
SimpleScan = "SimpleScan",
|
|
25
|
+
/** Simple linear scan down columns (top-to-bottom, left-to-right) */
|
|
26
|
+
SimpleScanColumnsFirst = "SimpleScanColumnsFirst",
|
|
27
|
+
/** Row-group scanning: highlight rows first, then cells within selected row */
|
|
28
|
+
RowColumnScan = "RowColumnScan",
|
|
29
|
+
/** Column-group scanning: highlight columns first, then cells within selected column */
|
|
30
|
+
ColumnRowScan = "ColumnRowScan"
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Scanning configuration for a page or pageset
|
|
34
|
+
* Controls how switch scanning operates
|
|
35
|
+
*/
|
|
36
|
+
export interface ScanningConfig {
|
|
37
|
+
/** Method for advancing through items */
|
|
38
|
+
selectionMethod?: ScanningSelectionMethod;
|
|
39
|
+
/** Order in which cells are scanned */
|
|
40
|
+
cellScanningOrder?: CellScanningOrder;
|
|
41
|
+
/** Whether block scanning is enabled (group cells by scanBlock number) */
|
|
42
|
+
blockScanEnabled?: boolean;
|
|
43
|
+
/** Whether to include the workspace/message bar in scanning */
|
|
44
|
+
scanWorkspace?: boolean;
|
|
45
|
+
/** Time in milliseconds to highlight each item */
|
|
46
|
+
forwardScanSpeed?: number;
|
|
47
|
+
/** Time in milliseconds to wait before auto-accepting selection */
|
|
48
|
+
dwellTime?: number;
|
|
49
|
+
/** How the selection is accepted */
|
|
50
|
+
acceptScanMethod?: 'Switch' | 'Timeout' | 'Hold';
|
|
51
|
+
}
|
|
2
52
|
export interface AACStyle {
|
|
3
53
|
backgroundColor?: string;
|
|
4
54
|
fontColor?: string;
|
|
@@ -35,7 +85,18 @@ export interface AACButton {
|
|
|
35
85
|
y?: number;
|
|
36
86
|
columnSpan?: number;
|
|
37
87
|
rowSpan?: number;
|
|
88
|
+
/**
|
|
89
|
+
* Scan block number (1-8) for block scanning
|
|
90
|
+
* Buttons with the same scanBlock number are highlighted together
|
|
91
|
+
* @deprecated Use scanBlock instead (singular, not array)
|
|
92
|
+
*/
|
|
38
93
|
scanBlocks?: number[];
|
|
94
|
+
/**
|
|
95
|
+
* Scan block number (1-8) for block scanning
|
|
96
|
+
* Buttons with the same scanBlock number are highlighted together
|
|
97
|
+
* Reduces scanning effort by grouping buttons
|
|
98
|
+
*/
|
|
99
|
+
scanBlock?: number;
|
|
39
100
|
visibility?: 'Visible' | 'Hidden' | 'Disabled' | 'PointerAndTouchOnly' | 'Empty';
|
|
40
101
|
directActivate?: boolean;
|
|
41
102
|
audioDescription?: string;
|
|
@@ -58,6 +119,8 @@ export interface AACPage {
|
|
|
58
119
|
sounds?: any[];
|
|
59
120
|
semantic_ids?: string[];
|
|
60
121
|
clone_ids?: string[];
|
|
122
|
+
scanningConfig?: ScanningConfig;
|
|
123
|
+
scanBlocksConfig?: any[];
|
|
61
124
|
}
|
|
62
125
|
export interface AACTree {
|
|
63
126
|
pages: {
|
package/dist/types/aac.js
CHANGED
|
@@ -1,2 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CellScanningOrder = exports.ScanningSelectionMethod = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Scanning selection methods for switch access
|
|
6
|
+
* Determines how the scanning advances through items
|
|
7
|
+
*/
|
|
8
|
+
var ScanningSelectionMethod;
|
|
9
|
+
(function (ScanningSelectionMethod) {
|
|
10
|
+
/** Automatically advance through items at timed intervals */
|
|
11
|
+
ScanningSelectionMethod["AutoScan"] = "AutoScan";
|
|
12
|
+
/** Automatic scanning with overscan (two-stage scanning) */
|
|
13
|
+
ScanningSelectionMethod["AutoScanWithOverscan"] = "AutoScanWithOverscan";
|
|
14
|
+
/** Hold switch to advance, release to select */
|
|
15
|
+
ScanningSelectionMethod["HoldToAdvance"] = "HoldToAdvance";
|
|
16
|
+
/** Hold to advance with overscan */
|
|
17
|
+
ScanningSelectionMethod["HoldToAdvanceWithOverscan"] = "HoldToAdvanceWithOverscan";
|
|
18
|
+
/** Tap switch to advance, tap again to select */
|
|
19
|
+
ScanningSelectionMethod["TapToAdvance"] = "TapToAdvance";
|
|
20
|
+
})(ScanningSelectionMethod || (exports.ScanningSelectionMethod = ScanningSelectionMethod = {}));
|
|
21
|
+
/**
|
|
22
|
+
* Cell scanning order patterns
|
|
23
|
+
* Determines the sequence in which cells are highlighted
|
|
24
|
+
*/
|
|
25
|
+
var CellScanningOrder;
|
|
26
|
+
(function (CellScanningOrder) {
|
|
27
|
+
/** Simple linear scan across rows (left-to-right, top-to-bottom) */
|
|
28
|
+
CellScanningOrder["SimpleScan"] = "SimpleScan";
|
|
29
|
+
/** Simple linear scan down columns (top-to-bottom, left-to-right) */
|
|
30
|
+
CellScanningOrder["SimpleScanColumnsFirst"] = "SimpleScanColumnsFirst";
|
|
31
|
+
/** Row-group scanning: highlight rows first, then cells within selected row */
|
|
32
|
+
CellScanningOrder["RowColumnScan"] = "RowColumnScan";
|
|
33
|
+
/** Column-group scanning: highlight columns first, then cells within selected column */
|
|
34
|
+
CellScanningOrder["ColumnRowScan"] = "ColumnRowScan";
|
|
35
|
+
})(CellScanningOrder || (exports.CellScanningOrder = CellScanningOrder = {}));
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { dotNetTicksToDate } from '../../utils/dotnetTicks';
|
|
2
2
|
import { Grid3UserPath } from '../../processors/gridset/helpers';
|
|
3
3
|
import { SnapUserInfo } from '../../processors/snap/helpers';
|
|
4
|
-
|
|
4
|
+
import { AACSemanticCategory, AACSemanticIntent } from '../../core/treeStructure';
|
|
5
|
+
export type HistorySource = 'Grid' | 'Snap' | 'OBL' | string;
|
|
5
6
|
export interface HistoryOccurrence {
|
|
6
7
|
timestamp: Date;
|
|
7
8
|
latitude?: number | null;
|
|
@@ -9,12 +10,22 @@ export interface HistoryOccurrence {
|
|
|
9
10
|
modeling?: boolean;
|
|
10
11
|
accessMethod?: number | null;
|
|
11
12
|
pageId?: string | null;
|
|
13
|
+
buttonId?: string | null;
|
|
14
|
+
boardId?: string | null;
|
|
15
|
+
spoken?: boolean;
|
|
16
|
+
vocalization?: string;
|
|
17
|
+
imageUrl?: string;
|
|
18
|
+
actions?: any[];
|
|
19
|
+
type?: 'button' | 'action' | 'utterance' | 'note' | 'other';
|
|
20
|
+
intent?: AACSemanticIntent | string;
|
|
21
|
+
category?: AACSemanticCategory;
|
|
12
22
|
}
|
|
13
23
|
export interface HistoryPlatformExtras {
|
|
14
24
|
label?: string;
|
|
15
25
|
message?: string;
|
|
16
26
|
buttonId?: string;
|
|
17
27
|
contentXml?: string;
|
|
28
|
+
[key: string]: any;
|
|
18
29
|
}
|
|
19
30
|
export interface HistoryEntry {
|
|
20
31
|
id: string;
|
|
@@ -13,6 +13,8 @@ export * from './metrics/types';
|
|
|
13
13
|
export * from './metrics/effort';
|
|
14
14
|
export * from './utils/idGenerator';
|
|
15
15
|
export * from './history';
|
|
16
|
+
export * from './metrics/obl-types';
|
|
17
|
+
export { OblUtil, OblAnonymizer } from './metrics/obl';
|
|
16
18
|
export { MetricsCalculator } from './metrics/core';
|
|
17
19
|
export { VocabularyAnalyzer } from './metrics/vocabulary';
|
|
18
20
|
export { SentenceAnalyzer } from './metrics/sentence';
|
|
@@ -28,7 +28,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
28
28
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
29
29
|
};
|
|
30
30
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
-
exports.ReferenceLoader = exports.ComparisonAnalyzer = exports.SentenceAnalyzer = exports.VocabularyAnalyzer = exports.MetricsCalculator = void 0;
|
|
31
|
+
exports.ReferenceLoader = exports.ComparisonAnalyzer = exports.SentenceAnalyzer = exports.VocabularyAnalyzer = exports.MetricsCalculator = exports.OblAnonymizer = exports.OblUtil = void 0;
|
|
32
32
|
exports.getReferenceDataPath = getReferenceDataPath;
|
|
33
33
|
exports.hasReferenceData = hasReferenceData;
|
|
34
34
|
const path_1 = __importDefault(require("path"));
|
|
@@ -39,6 +39,11 @@ __exportStar(require("./metrics/effort"), exports);
|
|
|
39
39
|
__exportStar(require("./utils/idGenerator"), exports);
|
|
40
40
|
// Export history functionality
|
|
41
41
|
__exportStar(require("./history"), exports);
|
|
42
|
+
// Export OBL logging support
|
|
43
|
+
__exportStar(require("./metrics/obl-types"), exports);
|
|
44
|
+
var obl_1 = require("./metrics/obl");
|
|
45
|
+
Object.defineProperty(exports, "OblUtil", { enumerable: true, get: function () { return obl_1.OblUtil; } });
|
|
46
|
+
Object.defineProperty(exports, "OblAnonymizer", { enumerable: true, get: function () { return obl_1.OblAnonymizer; } });
|
|
42
47
|
// Export core metrics calculator
|
|
43
48
|
var core_1 = require("./metrics/core");
|
|
44
49
|
Object.defineProperty(exports, "MetricsCalculator", { enumerable: true, get: function () { return core_1.MetricsCalculator; } });
|
|
@@ -87,13 +87,17 @@ class ComparisonAnalyzer {
|
|
|
87
87
|
}
|
|
88
88
|
});
|
|
89
89
|
highEffortWords.sort((a, b) => {
|
|
90
|
-
const
|
|
91
|
-
const
|
|
90
|
+
const targetBtnA = targetWords.get(a);
|
|
91
|
+
const targetBtnB = targetWords.get(b);
|
|
92
|
+
const diffA = (targetBtnA?.effort || 0) - (compareWords.get(a)?.effort || 0);
|
|
93
|
+
const diffB = (targetBtnB?.effort || 0) - (compareWords.get(b)?.effort || 0);
|
|
92
94
|
return diffB - diffA;
|
|
93
95
|
});
|
|
94
96
|
lowEffortWords.sort((a, b) => {
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
+
const targetBtnA = targetWords.get(a);
|
|
98
|
+
const targetBtnB = targetWords.get(b);
|
|
99
|
+
const diffA = (compareWords.get(a)?.effort || 0) - (targetBtnA?.effort || 0);
|
|
100
|
+
const diffB = (compareWords.get(b)?.effort || 0) - (targetBtnB?.effort || 0);
|
|
97
101
|
return diffB - diffA;
|
|
98
102
|
});
|
|
99
103
|
// Sentence analysis
|
|
@@ -21,6 +21,11 @@ export declare class MetricsCalculator {
|
|
|
21
21
|
* Build reference maps for semantic_id and clone_id frequencies
|
|
22
22
|
*/
|
|
23
23
|
private buildReferenceMaps;
|
|
24
|
+
/**
|
|
25
|
+
* Count scan items for visual scanning effort
|
|
26
|
+
* When block scanning is enabled, count unique scan blocks instead of individual buttons
|
|
27
|
+
*/
|
|
28
|
+
private countScanItems;
|
|
24
29
|
/**
|
|
25
30
|
* Analyze starting from a specific board
|
|
26
31
|
*/
|
|
@@ -33,4 +38,8 @@ export declare class MetricsCalculator {
|
|
|
33
38
|
* Calculate grid dimensions from the tree
|
|
34
39
|
*/
|
|
35
40
|
private calculateGridDimensions;
|
|
41
|
+
/**
|
|
42
|
+
* Calculate scanning steps and selections for a button based on access method
|
|
43
|
+
*/
|
|
44
|
+
private calculateScanSteps;
|
|
36
45
|
}
|