@willwade/aac-processors 0.0.15 → 0.0.17
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 +17 -8
- package/dist/core/treeStructure.d.ts +1 -0
- package/dist/processors/gridset/commands.js +15 -0
- package/dist/processors/gridset/pluginTypes.js +4 -4
- package/dist/processors/gridsetProcessor.d.ts +4 -0
- package/dist/processors/gridsetProcessor.js +315 -47
- package/dist/processors/snapProcessor.js +105 -9
- package/dist/processors/touchchatProcessor.js +33 -13
- package/dist/types/aac.d.ts +13 -3
- package/dist/types/aac.js +6 -2
- package/dist/utilities/analytics/metrics/comparison.d.ts +3 -1
- package/dist/utilities/analytics/metrics/comparison.js +189 -42
- package/dist/utilities/analytics/metrics/core.d.ts +7 -2
- package/dist/utilities/analytics/metrics/core.js +323 -197
- package/dist/utilities/analytics/metrics/effort.d.ts +23 -3
- package/dist/utilities/analytics/metrics/effort.js +31 -5
- package/dist/utilities/analytics/metrics/sentence.js +17 -4
- package/dist/utilities/analytics/metrics/types.d.ts +61 -0
- package/dist/utilities/analytics/metrics/vocabulary.js +1 -1
- package/dist/utilities/analytics/reference/index.d.ts +6 -0
- package/dist/utilities/analytics/reference/index.js +29 -1
- package/dist/utilities/translation/translationProcessor.d.ts +2 -1
- package/dist/utilities/translation/translationProcessor.js +5 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ This step is only required for Electron apps; regular Node.js consumers do not n
|
|
|
94
94
|
|
|
95
95
|
## 🔧 Quick Start
|
|
96
96
|
|
|
97
|
-
### Basic Usage (TypeScript
|
|
97
|
+
### Basic Usage (TypeScript)
|
|
98
98
|
|
|
99
99
|
```typescript
|
|
100
100
|
import {
|
|
@@ -118,15 +118,20 @@ const aacTree = dotProcessor.loadIntoTree("examples/example.dot");
|
|
|
118
118
|
console.log("Pages:", Object.keys(aacTree.pages).length);
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
###
|
|
121
|
+
### Platform Support
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
const { getProcessor, DotProcessor } = require("aac-processors");
|
|
123
|
+
**AACProcessors is designed for Node.js environments only.** It requires Node.js v20+ and cannot run in browsers due to:
|
|
125
124
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
- **File system access** - Required for reading/writing AAC files
|
|
126
|
+
- **Native SQLite** - Used by Snap, TouchChat, and Analytics features
|
|
127
|
+
- **Binary format processing** - ZIP, encrypted formats, etc.
|
|
128
|
+
|
|
129
|
+
**For browser-based AAC display**, consider these alternatives:
|
|
130
|
+
- **obf-renderer** - Display OBF/OBZ files in web apps
|
|
131
|
+
- **Arc Core** - Browser-based AAC communication
|
|
132
|
+
- **Cboard** - Web-based AAC display system
|
|
133
|
+
|
|
134
|
+
This library focuses on **server-side file processing**, not client-side rendering.
|
|
130
135
|
|
|
131
136
|
### Button Filtering System
|
|
132
137
|
|
|
@@ -893,3 +898,7 @@ Inspired by the Python AACProcessors project
|
|
|
893
898
|
### Contributing
|
|
894
899
|
|
|
895
900
|
Want to help with any of these items? See our [Contributing Guidelines](#-contributing) and pick an issue that interests you!
|
|
901
|
+
|
|
902
|
+
### Credits
|
|
903
|
+
|
|
904
|
+
Some of the OBF work is directly from https://github.com/open-aac/obf and https://github.com/open-aac/aac-metrics - OBLA too https://www.openboardformat.org/logs
|
|
@@ -699,6 +699,21 @@ exports.GRID3_COMMANDS = {
|
|
|
699
699
|
description: 'Clear word prediction buffer',
|
|
700
700
|
platforms: ['desktop', 'ios', 'medicare', 'medicareBionics'],
|
|
701
701
|
},
|
|
702
|
+
'Prediction.PredictThis': {
|
|
703
|
+
id: 'Prediction.PredictThis',
|
|
704
|
+
category: Grid3CommandCategory.AUTO_CONTENT,
|
|
705
|
+
pluginId: 'prediction',
|
|
706
|
+
displayName: 'Predict This',
|
|
707
|
+
description: 'Provide suggestions based on word list',
|
|
708
|
+
parameters: [
|
|
709
|
+
{
|
|
710
|
+
key: 'wordlist',
|
|
711
|
+
type: 'string', // Actually highly structured, but string type is a placeholder
|
|
712
|
+
required: true,
|
|
713
|
+
description: 'Word list for prediction',
|
|
714
|
+
},
|
|
715
|
+
],
|
|
716
|
+
},
|
|
702
717
|
'Grammar.Change': {
|
|
703
718
|
id: 'Grammar.Change',
|
|
704
719
|
category: Grid3CommandCategory.AUTO_CONTENT,
|
|
@@ -128,12 +128,12 @@ function detectPluginCellType(content) {
|
|
|
128
128
|
}
|
|
129
129
|
// AutoContent detection - dynamic word/content suggestions
|
|
130
130
|
if (contentType === 'AutoContent' || content.Style?.BasedOnStyle === 'AutoContent') {
|
|
131
|
-
const autoContentType = extractAutoContentType(content);
|
|
131
|
+
const autoContentType = extractAutoContentType(content) || contentSubType;
|
|
132
132
|
return {
|
|
133
133
|
cellType: Grid3CellType.AutoContent,
|
|
134
|
-
autoContentType: autoContentType
|
|
135
|
-
pluginId: inferAutoContentPlugin(autoContentType),
|
|
136
|
-
displayName: autoContentType
|
|
134
|
+
autoContentType: autoContentType ? String(autoContentType) : undefined,
|
|
135
|
+
pluginId: inferAutoContentPlugin(autoContentType ? String(autoContentType) : undefined),
|
|
136
|
+
displayName: autoContentType ? String(autoContentType) : 'Auto Content',
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
139
|
// Regular cell
|
|
@@ -12,6 +12,10 @@ declare class GridsetProcessor extends BaseProcessor {
|
|
|
12
12
|
private decryptGridsetEntry;
|
|
13
13
|
private getGridsetPassword;
|
|
14
14
|
private ensureAlphaChannel;
|
|
15
|
+
/**
|
|
16
|
+
* Extract words from Grid3 WordList structure
|
|
17
|
+
*/
|
|
18
|
+
private _extractWordsFromWordList;
|
|
15
19
|
private generateCommandsFromSemanticAction;
|
|
16
20
|
private convertGrid3StyleToAACStyle;
|
|
17
21
|
private getStyleById;
|
|
@@ -74,6 +74,35 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
74
74
|
// Invalid or unknown format, return white
|
|
75
75
|
return '#FFFFFFFF';
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Extract words from Grid3 WordList structure
|
|
79
|
+
*/
|
|
80
|
+
_extractWordsFromWordList(param) {
|
|
81
|
+
if (!param)
|
|
82
|
+
return [];
|
|
83
|
+
// Sometimes the param itself is the WordList, sometimes it has a WordList property
|
|
84
|
+
const wordList = param.WordList || param.wordlist || (param.Items || param.items ? param : undefined);
|
|
85
|
+
if (!wordList || !(wordList.Items || wordList.items))
|
|
86
|
+
return [];
|
|
87
|
+
const items = wordList.Items?.WordListItem || wordList.items?.wordlistitem || [];
|
|
88
|
+
const itemArr = Array.isArray(items) ? items : [items];
|
|
89
|
+
const words = [];
|
|
90
|
+
for (const item of itemArr) {
|
|
91
|
+
const text = item.Text || item.text;
|
|
92
|
+
if (text) {
|
|
93
|
+
const val = this.textOf(text);
|
|
94
|
+
if (val)
|
|
95
|
+
words.push(val);
|
|
96
|
+
}
|
|
97
|
+
else if (item['#text'] !== undefined) {
|
|
98
|
+
words.push(String(item['#text']));
|
|
99
|
+
}
|
|
100
|
+
else if (typeof item === 'string') {
|
|
101
|
+
words.push(item);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return words;
|
|
105
|
+
}
|
|
77
106
|
// Helper function to generate Grid3 commands from semantic actions
|
|
78
107
|
generateCommandsFromSemanticAction(button, tree) {
|
|
79
108
|
const semanticAction = button.semanticAction;
|
|
@@ -280,8 +309,45 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
280
309
|
return undefined;
|
|
281
310
|
if (typeof val === 'string')
|
|
282
311
|
return val;
|
|
283
|
-
if (typeof val === '
|
|
284
|
-
return String(val
|
|
312
|
+
if (typeof val === 'number')
|
|
313
|
+
return String(val);
|
|
314
|
+
if (typeof val === 'object') {
|
|
315
|
+
if ('#text' in val)
|
|
316
|
+
return String(val['#text']);
|
|
317
|
+
// Handle Grid3 structured format <p><s><r>text</r></s></p>
|
|
318
|
+
// Can start at p, s, or r level
|
|
319
|
+
const parts = [];
|
|
320
|
+
const processS = (s) => {
|
|
321
|
+
if (!s)
|
|
322
|
+
return;
|
|
323
|
+
if (s.r !== undefined) {
|
|
324
|
+
const rElements = Array.isArray(s.r) ? s.r : [s.r];
|
|
325
|
+
for (const r of rElements) {
|
|
326
|
+
if (typeof r === 'object' && r !== null && '#text' in r) {
|
|
327
|
+
parts.push(String(r['#text']));
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
parts.push(String(r));
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
if (val.p) {
|
|
336
|
+
const p = val.p;
|
|
337
|
+
const sElements = Array.isArray(p.s) ? p.s : p.s ? [p.s] : [];
|
|
338
|
+
sElements.forEach(processS);
|
|
339
|
+
}
|
|
340
|
+
else if (val.s) {
|
|
341
|
+
const sElements = Array.isArray(val.s) ? val.s : [val.s];
|
|
342
|
+
sElements.forEach(processS);
|
|
343
|
+
}
|
|
344
|
+
else if (val.r !== undefined) {
|
|
345
|
+
processS(val);
|
|
346
|
+
}
|
|
347
|
+
if (parts.length > 0) {
|
|
348
|
+
return parts.join('').trim();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
285
351
|
return undefined;
|
|
286
352
|
}
|
|
287
353
|
extractTexts(filePathOrBuffer) {
|
|
@@ -404,15 +470,21 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
404
470
|
if (!grid)
|
|
405
471
|
return;
|
|
406
472
|
const gridId = this.textOf(grid.GridGuid || grid.gridGuid || grid.id);
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
473
|
+
const gridName = this.textOf(grid.Name) || this.textOf(grid.name) || this.textOf(grid['@_Name']);
|
|
474
|
+
const folderMatch = entry.entryName.match(/^Grids\/([^/]+)\//);
|
|
475
|
+
const folderName = folderMatch ? folderMatch[1] : undefined;
|
|
476
|
+
if (gridId) {
|
|
477
|
+
if (gridName) {
|
|
478
|
+
gridNameToIdMap.set(gridName, gridId);
|
|
479
|
+
gridIdToNameMap.set(gridId, gridName);
|
|
480
|
+
}
|
|
481
|
+
if (folderName) {
|
|
482
|
+
// Folder name is often used as the grid name in Jump.To commands
|
|
483
|
+
gridNameToIdMap.set(folderName, gridId);
|
|
484
|
+
if (!gridName) {
|
|
485
|
+
gridIdToNameMap.set(gridId, folderName);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
416
488
|
}
|
|
417
489
|
}
|
|
418
490
|
catch (e) {
|
|
@@ -483,6 +555,32 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
483
555
|
for (let r = 0; r < maxRows; r++) {
|
|
484
556
|
gridLayout[r] = new Array(maxCols).fill(null);
|
|
485
557
|
}
|
|
558
|
+
const pagePredictedWords = new Set();
|
|
559
|
+
// Extract words from grid-level AutoContentCommands (e.g., Prediction Bar)
|
|
560
|
+
if (grid.AutoContentCommands) {
|
|
561
|
+
const collections = grid.AutoContentCommands.AutoContentCommandCollection;
|
|
562
|
+
const collectionArr = Array.isArray(collections)
|
|
563
|
+
? collections
|
|
564
|
+
: collections
|
|
565
|
+
? [collections]
|
|
566
|
+
: [];
|
|
567
|
+
collectionArr.forEach((collection) => {
|
|
568
|
+
const commands = collection.Commands?.Command;
|
|
569
|
+
const commandArr = Array.isArray(commands) ? commands : commands ? [commands] : [];
|
|
570
|
+
commandArr.forEach((command) => {
|
|
571
|
+
const commandId = command['@_ID'] || command.ID || command.id;
|
|
572
|
+
if (commandId === 'Prediction.PredictThis') {
|
|
573
|
+
const params = command.Parameter;
|
|
574
|
+
const paramArr = Array.isArray(params) ? params : params ? [params] : [];
|
|
575
|
+
const wordListParam = paramArr.find((p) => (p['@_Key'] || p.Key || p.key) === 'wordlist');
|
|
576
|
+
if (wordListParam) {
|
|
577
|
+
const words = this._extractWordsFromWordList(wordListParam);
|
|
578
|
+
words.forEach((w) => pagePredictedWords.add(w));
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
}
|
|
486
584
|
cellArr.forEach((cell, idx) => {
|
|
487
585
|
if (!cell || !cell.Content)
|
|
488
586
|
return;
|
|
@@ -526,7 +624,7 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
526
624
|
// Extract label from CaptionAndImage/Caption
|
|
527
625
|
const content = cell.Content;
|
|
528
626
|
const captionAndImage = content.CaptionAndImage || content.captionAndImage;
|
|
529
|
-
let label = captionAndImage?.Caption || captionAndImage?.caption || '';
|
|
627
|
+
let label = this.textOf(captionAndImage?.Caption || captionAndImage?.caption) || '';
|
|
530
628
|
// Check if cell has an image/symbol (needed to decide if we should keep it)
|
|
531
629
|
const hasImageCandidate = !!(captionAndImage?.Image ||
|
|
532
630
|
captionAndImage?.image ||
|
|
@@ -586,6 +684,25 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
586
684
|
if (commands) {
|
|
587
685
|
const commandArr = Array.isArray(commands) ? commands : [commands];
|
|
588
686
|
detectedCommands = commandArr.map((cmd) => (0, commands_1.detectCommand)(cmd));
|
|
687
|
+
// Scan all commands for vocabulary (predictions) before identifying primary action
|
|
688
|
+
commandArr.forEach((cmd) => {
|
|
689
|
+
const id = cmd['@_ID'] || cmd.ID || cmd.id;
|
|
690
|
+
if (id === 'Prediction.PredictThis') {
|
|
691
|
+
const params = cmd.Parameter || cmd.parameter;
|
|
692
|
+
const pArr = params ? (Array.isArray(params) ? params : [params]) : [];
|
|
693
|
+
let wlP;
|
|
694
|
+
for (const p of pArr) {
|
|
695
|
+
if (p['@_Key'] === 'wordlist' || p.Key === 'wordlist' || p.key === 'wordlist') {
|
|
696
|
+
wlP = p;
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
if (wlP) {
|
|
701
|
+
const words = this._extractWordsFromWordList(wlP);
|
|
702
|
+
words.forEach((w) => pagePredictedWords.add(w));
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
});
|
|
589
706
|
for (const command of commandArr) {
|
|
590
707
|
const commandId = command['@_ID'] || command.ID || command.id;
|
|
591
708
|
const parameters = command.Parameter || command.parameter;
|
|
@@ -594,50 +711,52 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
594
711
|
? parameters
|
|
595
712
|
: [parameters]
|
|
596
713
|
: [];
|
|
597
|
-
// Helper to
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
// Handle p.s array or single s element
|
|
603
|
-
const sElements = Array.isArray(p.s) ? p.s : p.s ? [p.s] : [];
|
|
604
|
-
// Extract all r values and concatenate
|
|
605
|
-
const parts = [];
|
|
606
|
-
for (const s of sElements) {
|
|
607
|
-
if (s && s.r !== undefined) {
|
|
608
|
-
parts.push(String(s.r));
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
if (parts.length > 0) {
|
|
612
|
-
return parts.join('');
|
|
714
|
+
// Helper to get raw parameter object
|
|
715
|
+
const getRawParam = (key) => {
|
|
716
|
+
for (const param of paramArr) {
|
|
717
|
+
if (param['@_Key'] === key || param.Key === key || param.key === key) {
|
|
718
|
+
return param;
|
|
613
719
|
}
|
|
614
720
|
}
|
|
615
721
|
return undefined;
|
|
616
722
|
};
|
|
617
723
|
// Helper to get parameter value
|
|
618
724
|
const getParam = (key) => {
|
|
619
|
-
|
|
725
|
+
const param = getRawParam(key);
|
|
726
|
+
if (param === undefined)
|
|
620
727
|
return undefined;
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
return structuredValue;
|
|
632
|
-
}
|
|
633
|
-
// Fallback to string conversion
|
|
634
|
-
if (typeof param === 'string') {
|
|
635
|
-
return param;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
728
|
+
const simpleValue = param['#text'] ?? param.text ?? param.value;
|
|
729
|
+
if (typeof simpleValue === 'string')
|
|
730
|
+
return simpleValue;
|
|
731
|
+
if (typeof simpleValue === 'number')
|
|
732
|
+
return String(simpleValue);
|
|
733
|
+
const structuredValue = this.textOf(param);
|
|
734
|
+
if (structuredValue !== undefined)
|
|
735
|
+
return structuredValue;
|
|
736
|
+
if (typeof param === 'string')
|
|
737
|
+
return param;
|
|
639
738
|
return undefined;
|
|
640
739
|
};
|
|
740
|
+
// Skip PredictThis in primary action loop as it was handled in pre-pass
|
|
741
|
+
// unless we need a primary action and nothing else exists
|
|
742
|
+
if (commandId === 'Prediction.PredictThis') {
|
|
743
|
+
if (!semanticAction) {
|
|
744
|
+
const wlParam = getRawParam('wordlist');
|
|
745
|
+
if (wlParam) {
|
|
746
|
+
const words = this._extractWordsFromWordList(wlParam);
|
|
747
|
+
semanticAction = {
|
|
748
|
+
category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
|
|
749
|
+
intent: treeStructure_1.AACSemanticIntent.PLATFORM_SPECIFIC,
|
|
750
|
+
text: words.slice(0, 3).join(', '),
|
|
751
|
+
platformData: {
|
|
752
|
+
grid3: { commandId, parameters: { wordlist: words } },
|
|
753
|
+
},
|
|
754
|
+
fallback: { type: 'ACTION', message: 'Predict words' },
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
641
760
|
switch (commandId) {
|
|
642
761
|
case 'Jump.To': {
|
|
643
762
|
const gridTarget = getParam('grid');
|
|
@@ -689,10 +808,13 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
689
808
|
};
|
|
690
809
|
break;
|
|
691
810
|
case 'Jump.Home':
|
|
811
|
+
case 'Jump.SetHome':
|
|
692
812
|
// action
|
|
813
|
+
navigationTarget = tree.rootId || undefined;
|
|
693
814
|
semanticAction = {
|
|
694
815
|
category: treeStructure_1.AACSemanticCategory.NAVIGATION,
|
|
695
816
|
intent: treeStructure_1.AACSemanticIntent.GO_HOME,
|
|
817
|
+
targetId: tree.rootId || undefined,
|
|
696
818
|
platformData: {
|
|
697
819
|
grid3: {
|
|
698
820
|
commandId,
|
|
@@ -708,6 +830,79 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
708
830
|
type: 'GO_HOME',
|
|
709
831
|
};
|
|
710
832
|
break;
|
|
833
|
+
case 'Jump.ToKeyboard': {
|
|
834
|
+
// Navigate to the set keyboard if we found one in settings
|
|
835
|
+
const keyboardGridName = tree.keyboardGridName;
|
|
836
|
+
const keyboardPageId = gridNameToIdMap.get(keyboardGridName);
|
|
837
|
+
if (keyboardPageId) {
|
|
838
|
+
navigationTarget = keyboardPageId;
|
|
839
|
+
}
|
|
840
|
+
semanticAction = {
|
|
841
|
+
category: treeStructure_1.AACSemanticCategory.NAVIGATION,
|
|
842
|
+
intent: treeStructure_1.AACSemanticIntent.GO_HOME, // Close enough to 'navigation to keyboard'
|
|
843
|
+
targetId: keyboardPageId,
|
|
844
|
+
platformData: {
|
|
845
|
+
grid3: {
|
|
846
|
+
commandId,
|
|
847
|
+
parameters: {},
|
|
848
|
+
},
|
|
849
|
+
},
|
|
850
|
+
fallback: {
|
|
851
|
+
type: 'NAVIGATE',
|
|
852
|
+
targetPageId: keyboardPageId,
|
|
853
|
+
},
|
|
854
|
+
};
|
|
855
|
+
break;
|
|
856
|
+
}
|
|
857
|
+
case 'Action.InsertTextAndSpeak': {
|
|
858
|
+
const insertText = getParam('text');
|
|
859
|
+
semanticAction = {
|
|
860
|
+
category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
|
|
861
|
+
intent: treeStructure_1.AACSemanticIntent.SPEAK_IMMEDIATE,
|
|
862
|
+
text: insertText,
|
|
863
|
+
platformData: {
|
|
864
|
+
grid3: {
|
|
865
|
+
commandId,
|
|
866
|
+
parameters: { text: insertText },
|
|
867
|
+
},
|
|
868
|
+
},
|
|
869
|
+
fallback: {
|
|
870
|
+
type: 'SPEAK',
|
|
871
|
+
message: insertText,
|
|
872
|
+
},
|
|
873
|
+
};
|
|
874
|
+
break;
|
|
875
|
+
}
|
|
876
|
+
case 'Prediction.PredictThis': {
|
|
877
|
+
const wlParam = getRawParam('wordlist');
|
|
878
|
+
if (wlParam) {
|
|
879
|
+
const words = this._extractWordsFromWordList(wlParam);
|
|
880
|
+
// Add to page-wide set of predicted words
|
|
881
|
+
words.forEach((w) => pagePredictedWords.add(w));
|
|
882
|
+
// Store words in a way that analyzer can find them
|
|
883
|
+
// For now, we'll attach to semanticAction so it can be used later
|
|
884
|
+
// We only set this as the primary action if we don't have one yet
|
|
885
|
+
if (!semanticAction) {
|
|
886
|
+
semanticAction = {
|
|
887
|
+
category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
|
|
888
|
+
intent: treeStructure_1.AACSemanticIntent.PLATFORM_SPECIFIC,
|
|
889
|
+
text: words.slice(0, 3).join(', '), // Provide first few as preview
|
|
890
|
+
platformData: {
|
|
891
|
+
grid3: {
|
|
892
|
+
commandId,
|
|
893
|
+
parameters: { wordlist: words },
|
|
894
|
+
},
|
|
895
|
+
},
|
|
896
|
+
fallback: {
|
|
897
|
+
type: 'ACTION',
|
|
898
|
+
message: 'Predict words',
|
|
899
|
+
},
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
// Continue to check other commands (e.g. Action.InsertText)
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
711
906
|
case 'Action.Speak': {
|
|
712
907
|
// speak
|
|
713
908
|
const speakUnit = getParam('unit');
|
|
@@ -963,6 +1158,19 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
963
1158
|
if (content.Style.FontSize)
|
|
964
1159
|
inlineStyle.fontSize = parseInt(String(content.Style.FontSize));
|
|
965
1160
|
}
|
|
1161
|
+
// Extract grammar tags from commands (Smart Grammar)
|
|
1162
|
+
const grammar = {};
|
|
1163
|
+
detectedCommands.forEach((cmd) => {
|
|
1164
|
+
if (cmd.parameters.pos)
|
|
1165
|
+
grammar.pos = cmd.parameters.pos;
|
|
1166
|
+
if (cmd.parameters.person)
|
|
1167
|
+
grammar.person = cmd.parameters.person;
|
|
1168
|
+
if (cmd.parameters.number)
|
|
1169
|
+
grammar.number = cmd.parameters.number;
|
|
1170
|
+
if (cmd.parameters.feature)
|
|
1171
|
+
grammar.feature = cmd.parameters.feature;
|
|
1172
|
+
});
|
|
1173
|
+
const isSmartGrammarCell = Object.keys(grammar).length > 0;
|
|
966
1174
|
const button = new treeStructure_1.AACButton({
|
|
967
1175
|
id: `${gridId}_btn_${idx}`,
|
|
968
1176
|
label: String(label),
|
|
@@ -998,6 +1206,8 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
998
1206
|
pluginMetadata: pluginMetadata, // Store full plugin metadata for future use
|
|
999
1207
|
grid3Commands: detectedCommands, // Store detected command metadata
|
|
1000
1208
|
symbolLibraryRef: symbolLibraryRef, // Store full symbol reference
|
|
1209
|
+
grammar: isSmartGrammarCell ? grammar : undefined,
|
|
1210
|
+
isSmartGrammarCell: isSmartGrammarCell,
|
|
1001
1211
|
},
|
|
1002
1212
|
});
|
|
1003
1213
|
// Add button to page
|
|
@@ -1011,6 +1221,59 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1011
1221
|
}
|
|
1012
1222
|
}
|
|
1013
1223
|
});
|
|
1224
|
+
// Process predicted words: Populate AutoContent slots first, then add virtual buttons at bottom
|
|
1225
|
+
if (pagePredictedWords.size > 0) {
|
|
1226
|
+
const extraWords = Array.from(pagePredictedWords).filter((w) => w.trim().length > 0);
|
|
1227
|
+
if (extraWords.length > 0) {
|
|
1228
|
+
let wordIdx = 0;
|
|
1229
|
+
// Step 1: Fill dedicated AutoContent prediction slots (e.g. at the top)
|
|
1230
|
+
page.buttons.forEach((btn) => {
|
|
1231
|
+
if (btn.contentType === 'AutoContent' &&
|
|
1232
|
+
btn.contentSubType === 'Prediction' &&
|
|
1233
|
+
wordIdx < extraWords.length) {
|
|
1234
|
+
const word = extraWords[wordIdx++];
|
|
1235
|
+
btn.label = word;
|
|
1236
|
+
btn.message = word;
|
|
1237
|
+
btn.semanticAction = {
|
|
1238
|
+
category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
|
|
1239
|
+
intent: treeStructure_1.AACSemanticIntent.INSERT_TEXT,
|
|
1240
|
+
text: word,
|
|
1241
|
+
fallback: { type: 'SPEAK', message: word },
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
// Step 2: Add remaining words as virtual buttons at the bottom
|
|
1246
|
+
if (wordIdx < extraWords.length) {
|
|
1247
|
+
const remainingWords = extraWords.slice(wordIdx);
|
|
1248
|
+
const extraRowsCount = Math.ceil(remainingWords.length / maxCols);
|
|
1249
|
+
for (let r = 0; r < extraRowsCount; r++) {
|
|
1250
|
+
const row = new Array(maxCols).fill(null);
|
|
1251
|
+
for (let c = 0; c < maxCols; c++) {
|
|
1252
|
+
const idx = r * maxCols + c;
|
|
1253
|
+
if (idx < remainingWords.length) {
|
|
1254
|
+
const word = remainingWords[idx];
|
|
1255
|
+
const vBtn = new treeStructure_1.AACButton({
|
|
1256
|
+
id: `${gridId}_vpredict_${wordIdx + idx}`,
|
|
1257
|
+
label: word,
|
|
1258
|
+
message: word,
|
|
1259
|
+
x: c,
|
|
1260
|
+
y: maxRows + r,
|
|
1261
|
+
semanticAction: {
|
|
1262
|
+
category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
|
|
1263
|
+
intent: treeStructure_1.AACSemanticIntent.INSERT_TEXT,
|
|
1264
|
+
text: word,
|
|
1265
|
+
fallback: { type: 'SPEAK', message: word },
|
|
1266
|
+
},
|
|
1267
|
+
});
|
|
1268
|
+
row[c] = vBtn;
|
|
1269
|
+
page.addButton(vBtn);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
gridLayout.push(row);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1014
1277
|
// Set the page's grid layout
|
|
1015
1278
|
page.grid = gridLayout;
|
|
1016
1279
|
// Generate clone_id for each button in the grid
|
|
@@ -1068,6 +1331,11 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1068
1331
|
tree.rootId = homeGridId;
|
|
1069
1332
|
}
|
|
1070
1333
|
}
|
|
1334
|
+
const keyboardGridName = settingsData?.GridSetSettings?.KeyboardGrid ||
|
|
1335
|
+
settingsData?.gridSetSettings?.keyboardGrid;
|
|
1336
|
+
if (keyboardGridName && typeof keyboardGridName === 'string') {
|
|
1337
|
+
tree.keyboardGridName = keyboardGridName;
|
|
1338
|
+
}
|
|
1071
1339
|
}
|
|
1072
1340
|
}
|
|
1073
1341
|
catch (e) {
|