@speechos/client 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +651 -110
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.iife.js +684 -114
- package/dist/index.iife.js.map +1 -1
- package/dist/index.iife.min.js +122 -100
- package/dist/index.iife.min.js.map +1 -1
- package/dist/index.js +652 -112
- package/dist/index.js.map +1 -1
- package/dist/settings-sync.d.ts +68 -0
- package/dist/settings-sync.d.ts.map +1 -0
- package/dist/settings-sync.test.d.ts +5 -0
- package/dist/settings-sync.test.d.ts.map +1 -0
- package/dist/speechos.d.ts.map +1 -1
- package/dist/stores/language-settings.d.ts +12 -1
- package/dist/stores/language-settings.d.ts.map +1 -1
- package/dist/stores/language-settings.test.d.ts +5 -0
- package/dist/stores/language-settings.test.d.ts.map +1 -0
- package/dist/stores/snippets-store.d.ts +12 -1
- package/dist/stores/snippets-store.d.ts.map +1 -1
- package/dist/stores/transcript-store.d.ts +13 -2
- package/dist/stores/transcript-store.d.ts.map +1 -1
- package/dist/stores/vocabulary-store.d.ts +12 -1
- package/dist/stores/vocabulary-store.d.ts.map +1 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/styles/modal-styles.d.ts.map +1 -1
- package/dist/ui/styles/theme.d.ts.map +1 -1
- package/dist/ui/tabs/history-tab.d.ts +2 -0
- package/dist/ui/tabs/history-tab.d.ts.map +1 -1
- package/dist/ui/tabs/settings-tab.d.ts +1 -0
- package/dist/ui/tabs/settings-tab.d.ts.map +1 -1
- package/dist/ui/tabs/snippets-tab.d.ts +2 -0
- package/dist/ui/tabs/snippets-tab.d.ts.map +1 -1
- package/dist/ui/tabs/vocabulary-tab.d.ts +2 -0
- package/dist/ui/tabs/vocabulary-tab.d.ts.map +1 -1
- package/dist/ui/widget.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -484,6 +484,11 @@ function resetTextInputHandler() {
|
|
|
484
484
|
* Persists input language preferences to localStorage
|
|
485
485
|
*/
|
|
486
486
|
const STORAGE_KEY$4 = "speechos_language_settings";
|
|
487
|
+
/**
|
|
488
|
+
* In-memory cache for language settings. When server sync is enabled, this is the
|
|
489
|
+
* source of truth. localStorage is only used when server sync is disabled.
|
|
490
|
+
*/
|
|
491
|
+
let memoryCache$3 = null;
|
|
487
492
|
/**
|
|
488
493
|
* Supported input languages for speech recognition
|
|
489
494
|
* Each language has a name, primary code, and available variants
|
|
@@ -535,9 +540,13 @@ const defaultSettings$1 = {
|
|
|
535
540
|
smartFormat: true,
|
|
536
541
|
};
|
|
537
542
|
/**
|
|
538
|
-
* Get current language settings from
|
|
543
|
+
* Get current language settings. Prefers in-memory cache (from server sync),
|
|
544
|
+
* then falls back to localStorage.
|
|
539
545
|
*/
|
|
540
546
|
function getLanguageSettings() {
|
|
547
|
+
if (memoryCache$3 !== null) {
|
|
548
|
+
return { ...memoryCache$3 };
|
|
549
|
+
}
|
|
541
550
|
try {
|
|
542
551
|
const stored = localStorage.getItem(STORAGE_KEY$4);
|
|
543
552
|
if (!stored)
|
|
@@ -549,14 +558,27 @@ function getLanguageSettings() {
|
|
|
549
558
|
}
|
|
550
559
|
}
|
|
551
560
|
/**
|
|
552
|
-
*
|
|
561
|
+
* Set language settings directly (used by settings sync from server data).
|
|
562
|
+
*/
|
|
563
|
+
function setLanguageSettings(settings) {
|
|
564
|
+
memoryCache$3 = { ...defaultSettings$1, ...settings };
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Reset memory cache (for testing only)
|
|
568
|
+
*/
|
|
569
|
+
function resetMemoryCache$2() {
|
|
570
|
+
memoryCache$3 = null;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Save language settings (updates memory cache and tries localStorage)
|
|
553
574
|
*/
|
|
554
575
|
function saveLanguageSettings(settings) {
|
|
576
|
+
memoryCache$3 = settings;
|
|
555
577
|
try {
|
|
556
578
|
localStorage.setItem(STORAGE_KEY$4, JSON.stringify(settings));
|
|
557
579
|
}
|
|
558
580
|
catch {
|
|
559
|
-
// localStorage full or unavailable -
|
|
581
|
+
// localStorage full or unavailable - memory cache still updated
|
|
560
582
|
}
|
|
561
583
|
}
|
|
562
584
|
/**
|
|
@@ -660,6 +682,7 @@ function getLanguageName() {
|
|
|
660
682
|
* Reset language settings to defaults
|
|
661
683
|
*/
|
|
662
684
|
function resetLanguageSettings() {
|
|
685
|
+
memoryCache$3 = null;
|
|
663
686
|
try {
|
|
664
687
|
localStorage.removeItem(STORAGE_KEY$4);
|
|
665
688
|
}
|
|
@@ -669,6 +692,7 @@ function resetLanguageSettings() {
|
|
|
669
692
|
}
|
|
670
693
|
const languageSettings = {
|
|
671
694
|
getLanguageSettings,
|
|
695
|
+
setLanguageSettings,
|
|
672
696
|
getInputLanguageCode,
|
|
673
697
|
setInputLanguageCode,
|
|
674
698
|
getOutputLanguageCode,
|
|
@@ -679,6 +703,7 @@ const languageSettings = {
|
|
|
679
703
|
getSmartFormatEnabled,
|
|
680
704
|
setSmartFormatEnabled,
|
|
681
705
|
resetLanguageSettings,
|
|
706
|
+
resetMemoryCache: resetMemoryCache$2,
|
|
682
707
|
SUPPORTED_LANGUAGES,
|
|
683
708
|
// Legacy aliases
|
|
684
709
|
getLanguageCode,
|
|
@@ -694,6 +719,11 @@ const STORAGE_KEY$3 = "speechos_snippets";
|
|
|
694
719
|
const MAX_SNIPPETS = 25;
|
|
695
720
|
const MAX_TRIGGER_LENGTH = 30;
|
|
696
721
|
const MAX_EXPANSION_LENGTH = 300;
|
|
722
|
+
/**
|
|
723
|
+
* In-memory cache for snippets. When server sync is enabled, this is the
|
|
724
|
+
* source of truth. localStorage is only used when server sync is disabled.
|
|
725
|
+
*/
|
|
726
|
+
let memoryCache$2 = null;
|
|
697
727
|
/**
|
|
698
728
|
* Generate a unique ID for snippet entries
|
|
699
729
|
*/
|
|
@@ -701,15 +731,18 @@ function generateId$2() {
|
|
|
701
731
|
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
702
732
|
}
|
|
703
733
|
/**
|
|
704
|
-
* Get all snippets from
|
|
734
|
+
* Get all snippets. Prefers in-memory cache (from server sync),
|
|
735
|
+
* then falls back to localStorage.
|
|
705
736
|
*/
|
|
706
737
|
function getSnippets() {
|
|
738
|
+
if (memoryCache$2 !== null) {
|
|
739
|
+
return [...memoryCache$2].sort((a, b) => b.createdAt - a.createdAt);
|
|
740
|
+
}
|
|
707
741
|
try {
|
|
708
742
|
const stored = localStorage.getItem(STORAGE_KEY$3);
|
|
709
743
|
if (!stored)
|
|
710
744
|
return [];
|
|
711
745
|
const entries = JSON.parse(stored);
|
|
712
|
-
// Return newest first
|
|
713
746
|
return entries.sort((a, b) => b.createdAt - a.createdAt);
|
|
714
747
|
}
|
|
715
748
|
catch {
|
|
@@ -717,14 +750,27 @@ function getSnippets() {
|
|
|
717
750
|
}
|
|
718
751
|
}
|
|
719
752
|
/**
|
|
720
|
-
*
|
|
753
|
+
* Set snippets directly (used by settings sync from server data).
|
|
754
|
+
*/
|
|
755
|
+
function setSnippets(snippets) {
|
|
756
|
+
memoryCache$2 = snippets.slice(0, MAX_SNIPPETS);
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Reset memory cache (for testing only)
|
|
760
|
+
*/
|
|
761
|
+
function resetMemoryCache$1() {
|
|
762
|
+
memoryCache$2 = null;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Save snippets (updates memory cache and tries localStorage)
|
|
721
766
|
*/
|
|
722
767
|
function saveSnippets(snippets) {
|
|
768
|
+
memoryCache$2 = snippets;
|
|
723
769
|
try {
|
|
724
770
|
localStorage.setItem(STORAGE_KEY$3, JSON.stringify(snippets));
|
|
725
771
|
}
|
|
726
772
|
catch {
|
|
727
|
-
// localStorage full or unavailable -
|
|
773
|
+
// localStorage full or unavailable - memory cache still updated
|
|
728
774
|
}
|
|
729
775
|
}
|
|
730
776
|
/**
|
|
@@ -842,13 +888,14 @@ function deleteSnippet(id) {
|
|
|
842
888
|
* Clear all snippets
|
|
843
889
|
*/
|
|
844
890
|
function clearSnippets() {
|
|
891
|
+
memoryCache$2 = [];
|
|
845
892
|
try {
|
|
846
893
|
localStorage.removeItem(STORAGE_KEY$3);
|
|
847
|
-
core.events.emit("settings:changed", { setting: "snippets" });
|
|
848
894
|
}
|
|
849
895
|
catch {
|
|
850
896
|
// Silently fail
|
|
851
897
|
}
|
|
898
|
+
core.events.emit("settings:changed", { setting: "snippets" });
|
|
852
899
|
}
|
|
853
900
|
/**
|
|
854
901
|
* Get snippet count info
|
|
@@ -864,12 +911,14 @@ function isAtSnippetLimit() {
|
|
|
864
911
|
}
|
|
865
912
|
const snippetsStore = {
|
|
866
913
|
getSnippets,
|
|
914
|
+
setSnippets,
|
|
867
915
|
addSnippet,
|
|
868
916
|
updateSnippet,
|
|
869
917
|
deleteSnippet,
|
|
870
918
|
clearSnippets,
|
|
871
919
|
getSnippetCount,
|
|
872
920
|
isAtSnippetLimit,
|
|
921
|
+
resetMemoryCache: resetMemoryCache$1,
|
|
873
922
|
MAX_SNIPPETS,
|
|
874
923
|
MAX_TRIGGER_LENGTH,
|
|
875
924
|
MAX_EXPANSION_LENGTH,
|
|
@@ -882,6 +931,11 @@ const snippetsStore = {
|
|
|
882
931
|
const STORAGE_KEY$2 = "speechos_vocabulary";
|
|
883
932
|
const MAX_TERMS = 50;
|
|
884
933
|
const MAX_TERM_LENGTH = 50;
|
|
934
|
+
/**
|
|
935
|
+
* In-memory cache for vocabulary. When server sync is enabled, this is the
|
|
936
|
+
* source of truth. localStorage is only used when server sync is disabled.
|
|
937
|
+
*/
|
|
938
|
+
let memoryCache$1 = null;
|
|
885
939
|
/**
|
|
886
940
|
* Generate a unique ID for vocabulary entries
|
|
887
941
|
*/
|
|
@@ -889,15 +943,18 @@ function generateId$1() {
|
|
|
889
943
|
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
890
944
|
}
|
|
891
945
|
/**
|
|
892
|
-
* Get all vocabulary terms from
|
|
946
|
+
* Get all vocabulary terms. Prefers in-memory cache (from server sync),
|
|
947
|
+
* then falls back to localStorage.
|
|
893
948
|
*/
|
|
894
949
|
function getVocabulary() {
|
|
950
|
+
if (memoryCache$1 !== null) {
|
|
951
|
+
return [...memoryCache$1].sort((a, b) => b.createdAt - a.createdAt);
|
|
952
|
+
}
|
|
895
953
|
try {
|
|
896
954
|
const stored = localStorage.getItem(STORAGE_KEY$2);
|
|
897
955
|
if (!stored)
|
|
898
956
|
return [];
|
|
899
957
|
const entries = JSON.parse(stored);
|
|
900
|
-
// Return newest first
|
|
901
958
|
return entries.sort((a, b) => b.createdAt - a.createdAt);
|
|
902
959
|
}
|
|
903
960
|
catch {
|
|
@@ -905,14 +962,27 @@ function getVocabulary() {
|
|
|
905
962
|
}
|
|
906
963
|
}
|
|
907
964
|
/**
|
|
908
|
-
*
|
|
965
|
+
* Set vocabulary directly (used by settings sync from server data).
|
|
966
|
+
*/
|
|
967
|
+
function setVocabulary(terms) {
|
|
968
|
+
memoryCache$1 = terms.slice(0, MAX_TERMS);
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Reset memory cache (for testing only)
|
|
972
|
+
*/
|
|
973
|
+
function resetMemoryCache() {
|
|
974
|
+
memoryCache$1 = null;
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Save vocabulary (updates memory cache and tries localStorage)
|
|
909
978
|
*/
|
|
910
979
|
function saveVocabulary(terms) {
|
|
980
|
+
memoryCache$1 = terms;
|
|
911
981
|
try {
|
|
912
982
|
localStorage.setItem(STORAGE_KEY$2, JSON.stringify(terms));
|
|
913
983
|
}
|
|
914
984
|
catch {
|
|
915
|
-
// localStorage full or unavailable -
|
|
985
|
+
// localStorage full or unavailable - memory cache still updated
|
|
916
986
|
}
|
|
917
987
|
}
|
|
918
988
|
/**
|
|
@@ -980,13 +1050,14 @@ function deleteTerm(id) {
|
|
|
980
1050
|
* Clear all vocabulary
|
|
981
1051
|
*/
|
|
982
1052
|
function clearVocabulary() {
|
|
1053
|
+
memoryCache$1 = [];
|
|
983
1054
|
try {
|
|
984
1055
|
localStorage.removeItem(STORAGE_KEY$2);
|
|
985
|
-
core.events.emit("settings:changed", { setting: "vocabulary" });
|
|
986
1056
|
}
|
|
987
1057
|
catch {
|
|
988
1058
|
// Silently fail
|
|
989
1059
|
}
|
|
1060
|
+
core.events.emit("settings:changed", { setting: "vocabulary" });
|
|
990
1061
|
}
|
|
991
1062
|
/**
|
|
992
1063
|
* Get vocabulary count info
|
|
@@ -1002,11 +1073,13 @@ function isAtVocabularyLimit() {
|
|
|
1002
1073
|
}
|
|
1003
1074
|
const vocabularyStore = {
|
|
1004
1075
|
getVocabulary,
|
|
1076
|
+
setVocabulary,
|
|
1005
1077
|
addTerm,
|
|
1006
1078
|
deleteTerm,
|
|
1007
1079
|
clearVocabulary,
|
|
1008
1080
|
getVocabularyCount,
|
|
1009
1081
|
isAtVocabularyLimit,
|
|
1082
|
+
resetMemoryCache,
|
|
1010
1083
|
MAX_TERMS,
|
|
1011
1084
|
MAX_TERM_LENGTH,
|
|
1012
1085
|
};
|
|
@@ -1104,6 +1177,462 @@ const audioSettings = {
|
|
|
1104
1177
|
resetAudioSettings,
|
|
1105
1178
|
};
|
|
1106
1179
|
|
|
1180
|
+
/**
|
|
1181
|
+
* Transcript history store
|
|
1182
|
+
* Persists transcripts to localStorage for viewing in the settings modal
|
|
1183
|
+
*/
|
|
1184
|
+
const STORAGE_KEY = "speechos_transcripts";
|
|
1185
|
+
const MAX_ENTRIES = 50;
|
|
1186
|
+
/**
|
|
1187
|
+
* In-memory cache for transcripts. When server sync is enabled, this is the
|
|
1188
|
+
* source of truth. localStorage is only used when server sync is disabled.
|
|
1189
|
+
*/
|
|
1190
|
+
let memoryCache = null;
|
|
1191
|
+
/**
|
|
1192
|
+
* Generate a unique ID for transcript entries
|
|
1193
|
+
*/
|
|
1194
|
+
function generateId() {
|
|
1195
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Get all transcripts. Prefers in-memory cache (from server sync),
|
|
1199
|
+
* then falls back to localStorage.
|
|
1200
|
+
*/
|
|
1201
|
+
function getTranscripts() {
|
|
1202
|
+
// If we have in-memory data (from server sync), use it
|
|
1203
|
+
if (memoryCache !== null) {
|
|
1204
|
+
return [...memoryCache].sort((a, b) => b.timestamp - a.timestamp);
|
|
1205
|
+
}
|
|
1206
|
+
// Fall back to localStorage (when server sync is disabled)
|
|
1207
|
+
try {
|
|
1208
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
1209
|
+
if (!stored)
|
|
1210
|
+
return [];
|
|
1211
|
+
const entries = JSON.parse(stored);
|
|
1212
|
+
return entries.sort((a, b) => b.timestamp - a.timestamp);
|
|
1213
|
+
}
|
|
1214
|
+
catch {
|
|
1215
|
+
return [];
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Set transcripts directly (used by settings sync from server data).
|
|
1220
|
+
* Server data is the source of truth - just update memory cache.
|
|
1221
|
+
*/
|
|
1222
|
+
function setTranscripts(entries) {
|
|
1223
|
+
memoryCache = entries.slice(0, MAX_ENTRIES);
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Save a new transcript entry
|
|
1227
|
+
*/
|
|
1228
|
+
function saveTranscript(text, action, originalTextOrOptions) {
|
|
1229
|
+
const entry = {
|
|
1230
|
+
id: generateId(),
|
|
1231
|
+
text,
|
|
1232
|
+
timestamp: Date.now(),
|
|
1233
|
+
action,
|
|
1234
|
+
};
|
|
1235
|
+
// Handle edit action with originalText string
|
|
1236
|
+
if (action === "edit" && typeof originalTextOrOptions === "string") {
|
|
1237
|
+
entry.originalText = originalTextOrOptions;
|
|
1238
|
+
}
|
|
1239
|
+
// Handle command action with options object
|
|
1240
|
+
if (action === "command" && typeof originalTextOrOptions === "object") {
|
|
1241
|
+
const options = originalTextOrOptions;
|
|
1242
|
+
if (options.inputText !== undefined)
|
|
1243
|
+
entry.inputText = options.inputText;
|
|
1244
|
+
if (options.commandResult !== undefined)
|
|
1245
|
+
entry.commandResult = options.commandResult;
|
|
1246
|
+
if (options.commandConfig !== undefined)
|
|
1247
|
+
entry.commandConfig = options.commandConfig;
|
|
1248
|
+
}
|
|
1249
|
+
const entries = getTranscripts();
|
|
1250
|
+
entries.unshift(entry);
|
|
1251
|
+
// Prune to max entries
|
|
1252
|
+
const pruned = entries.slice(0, MAX_ENTRIES);
|
|
1253
|
+
// Update memory cache (always)
|
|
1254
|
+
memoryCache = pruned;
|
|
1255
|
+
// Try to persist to localStorage (for when server sync is disabled)
|
|
1256
|
+
try {
|
|
1257
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(pruned));
|
|
1258
|
+
}
|
|
1259
|
+
catch {
|
|
1260
|
+
// Quota exceeded - memory cache is still updated
|
|
1261
|
+
}
|
|
1262
|
+
// Emit settings change event to trigger sync
|
|
1263
|
+
core.events.emit("settings:changed", { setting: "history" });
|
|
1264
|
+
return entry;
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Clear all transcript history
|
|
1268
|
+
*/
|
|
1269
|
+
function clearTranscripts() {
|
|
1270
|
+
memoryCache = [];
|
|
1271
|
+
try {
|
|
1272
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
1273
|
+
}
|
|
1274
|
+
catch {
|
|
1275
|
+
// Silently fail
|
|
1276
|
+
}
|
|
1277
|
+
core.events.emit("settings:changed", { setting: "history" });
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Delete a single transcript by ID
|
|
1281
|
+
*/
|
|
1282
|
+
function deleteTranscript(id) {
|
|
1283
|
+
const entries = getTranscripts().filter((e) => e.id !== id);
|
|
1284
|
+
memoryCache = entries;
|
|
1285
|
+
try {
|
|
1286
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));
|
|
1287
|
+
}
|
|
1288
|
+
catch {
|
|
1289
|
+
// Silently fail
|
|
1290
|
+
}
|
|
1291
|
+
core.events.emit("settings:changed", { setting: "history" });
|
|
1292
|
+
}
|
|
1293
|
+
const transcriptStore = {
|
|
1294
|
+
getTranscripts,
|
|
1295
|
+
setTranscripts,
|
|
1296
|
+
saveTranscript,
|
|
1297
|
+
clearTranscripts,
|
|
1298
|
+
deleteTranscript,
|
|
1299
|
+
};
|
|
1300
|
+
|
|
1301
|
+
/**
|
|
1302
|
+
* Settings sync manager
|
|
1303
|
+
* Syncs user settings (language, vocabulary, snippets, history) with the server
|
|
1304
|
+
*/
|
|
1305
|
+
// Sync debounce delay in milliseconds
|
|
1306
|
+
const SYNC_DEBOUNCE_MS = 2000;
|
|
1307
|
+
// Maximum retry attempts
|
|
1308
|
+
const MAX_RETRIES = 3;
|
|
1309
|
+
// Base retry delay in milliseconds (exponential backoff)
|
|
1310
|
+
const BASE_RETRY_DELAY_MS = 2000;
|
|
1311
|
+
/**
|
|
1312
|
+
* Settings sync manager singleton
|
|
1313
|
+
*/
|
|
1314
|
+
class SettingsSync {
|
|
1315
|
+
constructor() {
|
|
1316
|
+
this.syncTimer = null;
|
|
1317
|
+
this.isSyncing = false;
|
|
1318
|
+
this.retryCount = 0;
|
|
1319
|
+
this.isInitialized = false;
|
|
1320
|
+
this.unsubscribe = null;
|
|
1321
|
+
/** When true, sync is disabled due to CSP or network restrictions */
|
|
1322
|
+
this.syncDisabled = false;
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Make a fetch request using custom fetchHandler if configured, otherwise native fetch.
|
|
1326
|
+
* This allows the Chrome extension to route fetch traffic through the service worker
|
|
1327
|
+
* to bypass page CSP restrictions.
|
|
1328
|
+
*/
|
|
1329
|
+
async doFetch(url, options) {
|
|
1330
|
+
const config = core.getConfig();
|
|
1331
|
+
const customHandler = core.getFetchHandler();
|
|
1332
|
+
if (customHandler) {
|
|
1333
|
+
if (config.debug) {
|
|
1334
|
+
console.log("[SpeechOS] Using custom fetch handler (extension proxy)", options.method, url);
|
|
1335
|
+
}
|
|
1336
|
+
return customHandler(url, options);
|
|
1337
|
+
}
|
|
1338
|
+
if (config.debug) {
|
|
1339
|
+
console.log("[SpeechOS] Using native fetch", options.method, url);
|
|
1340
|
+
}
|
|
1341
|
+
// Use native fetch and wrap response to match FetchResponse interface
|
|
1342
|
+
const response = await fetch(url, options);
|
|
1343
|
+
return response;
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Initialize the settings sync manager
|
|
1347
|
+
* If a settingsToken is configured, loads settings from server
|
|
1348
|
+
*/
|
|
1349
|
+
async init() {
|
|
1350
|
+
const token = core.getSettingsToken();
|
|
1351
|
+
if (!token) {
|
|
1352
|
+
// No token configured, sync is disabled
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
if (this.isInitialized) {
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
this.isInitialized = true;
|
|
1359
|
+
// Subscribe to settings changes
|
|
1360
|
+
this.unsubscribe = core.events.on("settings:changed", () => {
|
|
1361
|
+
this.scheduleSyncToServer();
|
|
1362
|
+
});
|
|
1363
|
+
// Load settings from server
|
|
1364
|
+
await this.loadFromServer();
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Stop the sync manager and clean up
|
|
1368
|
+
*/
|
|
1369
|
+
destroy() {
|
|
1370
|
+
if (this.syncTimer) {
|
|
1371
|
+
clearTimeout(this.syncTimer);
|
|
1372
|
+
this.syncTimer = null;
|
|
1373
|
+
}
|
|
1374
|
+
if (this.unsubscribe) {
|
|
1375
|
+
this.unsubscribe();
|
|
1376
|
+
this.unsubscribe = null;
|
|
1377
|
+
}
|
|
1378
|
+
this.isInitialized = false;
|
|
1379
|
+
this.retryCount = 0;
|
|
1380
|
+
this.syncDisabled = false;
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Load settings from the server and merge with local
|
|
1384
|
+
*/
|
|
1385
|
+
async loadFromServer() {
|
|
1386
|
+
const token = core.getSettingsToken();
|
|
1387
|
+
if (!token) {
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
const config = core.getConfig();
|
|
1391
|
+
try {
|
|
1392
|
+
const response = await this.doFetch(`${config.host}/api/user-settings/`, {
|
|
1393
|
+
method: "GET",
|
|
1394
|
+
headers: {
|
|
1395
|
+
Authorization: `Bearer ${token}`,
|
|
1396
|
+
"Content-Type": "application/json",
|
|
1397
|
+
},
|
|
1398
|
+
});
|
|
1399
|
+
if (config.debug) {
|
|
1400
|
+
console.log("[SpeechOS] Settings fetch response:", response.status, response.ok ? "OK" : response.statusText);
|
|
1401
|
+
}
|
|
1402
|
+
if (response.status === 404) {
|
|
1403
|
+
// No settings on server yet (new user) - sync local settings to server
|
|
1404
|
+
if (config.debug) {
|
|
1405
|
+
console.log("[SpeechOS] No server settings found, syncing local to server");
|
|
1406
|
+
}
|
|
1407
|
+
await this.syncToServer();
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
if (response.status === 401 || response.status === 403) {
|
|
1411
|
+
// Token expired or invalid
|
|
1412
|
+
this.handleTokenExpired();
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
if (!response.ok) {
|
|
1416
|
+
throw new Error(`Server returned ${response.status}`);
|
|
1417
|
+
}
|
|
1418
|
+
const serverSettings = (await response.json());
|
|
1419
|
+
if (config.debug) {
|
|
1420
|
+
console.log("[SpeechOS] Settings received from server:", {
|
|
1421
|
+
language: serverSettings.language,
|
|
1422
|
+
vocabularyCount: serverSettings.vocabulary?.length ?? 0,
|
|
1423
|
+
snippetsCount: serverSettings.snippets?.length ?? 0,
|
|
1424
|
+
historyCount: serverSettings.history?.length ?? 0,
|
|
1425
|
+
lastSyncedAt: serverSettings.lastSyncedAt,
|
|
1426
|
+
});
|
|
1427
|
+
if (serverSettings.history?.length > 0) {
|
|
1428
|
+
console.log("[SpeechOS] History entries:", serverSettings.history);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
this.mergeSettings(serverSettings);
|
|
1432
|
+
core.events.emit("settings:loaded", undefined);
|
|
1433
|
+
if (config.debug) {
|
|
1434
|
+
console.log("[SpeechOS] Settings merged and loaded");
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
catch (error) {
|
|
1438
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1439
|
+
// Check if this is a CSP/network restriction - disable sync permanently for this session
|
|
1440
|
+
if (this.isNetworkRestrictionError(error)) {
|
|
1441
|
+
this.syncDisabled = true;
|
|
1442
|
+
if (config.debug) {
|
|
1443
|
+
console.log("[SpeechOS] Settings sync disabled (CSP/network restriction), using localStorage only");
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
else if (config.debug) {
|
|
1447
|
+
console.warn("[SpeechOS] Failed to load settings from server:", errorMessage);
|
|
1448
|
+
}
|
|
1449
|
+
core.events.emit("settings:syncFailed", { error: errorMessage });
|
|
1450
|
+
// Continue with local settings on error
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
/**
|
|
1454
|
+
* Merge server settings with local (server wins).
|
|
1455
|
+
* Uses store setters to update memory cache - localStorage is a fallback.
|
|
1456
|
+
*/
|
|
1457
|
+
mergeSettings(serverSettings) {
|
|
1458
|
+
// Language settings - server wins
|
|
1459
|
+
if (serverSettings.language) {
|
|
1460
|
+
setLanguageSettings(serverSettings.language);
|
|
1461
|
+
}
|
|
1462
|
+
// Vocabulary - server wins
|
|
1463
|
+
if (serverSettings.vocabulary) {
|
|
1464
|
+
setVocabulary(serverSettings.vocabulary);
|
|
1465
|
+
}
|
|
1466
|
+
// Snippets - server wins
|
|
1467
|
+
if (serverSettings.snippets) {
|
|
1468
|
+
setSnippets(serverSettings.snippets);
|
|
1469
|
+
}
|
|
1470
|
+
// History - server wins
|
|
1471
|
+
if (serverSettings.history) {
|
|
1472
|
+
setTranscripts(serverSettings.history);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Schedule a debounced sync to server
|
|
1477
|
+
*/
|
|
1478
|
+
scheduleSyncToServer() {
|
|
1479
|
+
const token = core.getSettingsToken();
|
|
1480
|
+
if (!token || this.syncDisabled) {
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
// Cancel any pending sync
|
|
1484
|
+
if (this.syncTimer) {
|
|
1485
|
+
clearTimeout(this.syncTimer);
|
|
1486
|
+
}
|
|
1487
|
+
// Schedule new sync
|
|
1488
|
+
this.syncTimer = setTimeout(() => {
|
|
1489
|
+
this.syncToServer();
|
|
1490
|
+
}, SYNC_DEBOUNCE_MS);
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Sync current settings to server
|
|
1494
|
+
*/
|
|
1495
|
+
async syncToServer() {
|
|
1496
|
+
const token = core.getSettingsToken();
|
|
1497
|
+
if (!token || this.isSyncing || this.syncDisabled) {
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
this.isSyncing = true;
|
|
1501
|
+
const config = core.getConfig();
|
|
1502
|
+
try {
|
|
1503
|
+
const languageSettings = getLanguageSettings();
|
|
1504
|
+
const vocabulary = getVocabulary();
|
|
1505
|
+
const snippets = getSnippets();
|
|
1506
|
+
const transcripts = getTranscripts();
|
|
1507
|
+
const payload = {
|
|
1508
|
+
language: {
|
|
1509
|
+
inputLanguageCode: languageSettings.inputLanguageCode,
|
|
1510
|
+
outputLanguageCode: languageSettings.outputLanguageCode,
|
|
1511
|
+
smartFormat: languageSettings.smartFormat,
|
|
1512
|
+
},
|
|
1513
|
+
vocabulary: vocabulary.map((v) => ({
|
|
1514
|
+
id: v.id,
|
|
1515
|
+
term: v.term,
|
|
1516
|
+
createdAt: v.createdAt,
|
|
1517
|
+
})),
|
|
1518
|
+
snippets: snippets.map((s) => ({
|
|
1519
|
+
id: s.id,
|
|
1520
|
+
trigger: s.trigger,
|
|
1521
|
+
expansion: s.expansion,
|
|
1522
|
+
createdAt: s.createdAt,
|
|
1523
|
+
})),
|
|
1524
|
+
// Sync history (excluding commandConfig to reduce payload size)
|
|
1525
|
+
history: transcripts.map((t) => ({
|
|
1526
|
+
id: t.id,
|
|
1527
|
+
text: t.text,
|
|
1528
|
+
timestamp: t.timestamp,
|
|
1529
|
+
action: t.action,
|
|
1530
|
+
...(t.originalText && { originalText: t.originalText }),
|
|
1531
|
+
...(t.inputText && { inputText: t.inputText }),
|
|
1532
|
+
...(t.commandResult !== undefined && { commandResult: t.commandResult }),
|
|
1533
|
+
})),
|
|
1534
|
+
};
|
|
1535
|
+
const response = await this.doFetch(`${config.host}/api/user-settings/`, {
|
|
1536
|
+
method: "PUT",
|
|
1537
|
+
headers: {
|
|
1538
|
+
Authorization: `Bearer ${token}`,
|
|
1539
|
+
"Content-Type": "application/json",
|
|
1540
|
+
},
|
|
1541
|
+
body: JSON.stringify(payload),
|
|
1542
|
+
});
|
|
1543
|
+
if (response.status === 401 || response.status === 403) {
|
|
1544
|
+
this.handleTokenExpired();
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
if (!response.ok) {
|
|
1548
|
+
throw new Error(`Server returned ${response.status}`);
|
|
1549
|
+
}
|
|
1550
|
+
// Reset retry count on success
|
|
1551
|
+
this.retryCount = 0;
|
|
1552
|
+
core.events.emit("settings:synced", undefined);
|
|
1553
|
+
if (config.debug) {
|
|
1554
|
+
console.log("[SpeechOS] Settings synced to server");
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
catch (error) {
|
|
1558
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1559
|
+
// Check if this is a CSP/network restriction - disable sync permanently for this session
|
|
1560
|
+
if (this.isNetworkRestrictionError(error)) {
|
|
1561
|
+
this.syncDisabled = true;
|
|
1562
|
+
if (config.debug) {
|
|
1563
|
+
console.log("[SpeechOS] Settings sync disabled (CSP/network restriction), using localStorage only");
|
|
1564
|
+
}
|
|
1565
|
+
core.events.emit("settings:syncFailed", { error: errorMessage });
|
|
1566
|
+
// Don't retry - CSP errors are permanent
|
|
1567
|
+
}
|
|
1568
|
+
else {
|
|
1569
|
+
if (config.debug) {
|
|
1570
|
+
console.warn("[SpeechOS] Failed to sync settings to server:", errorMessage);
|
|
1571
|
+
}
|
|
1572
|
+
core.events.emit("settings:syncFailed", { error: errorMessage });
|
|
1573
|
+
// Retry with exponential backoff (only for transient errors)
|
|
1574
|
+
this.scheduleRetry();
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
finally {
|
|
1578
|
+
this.isSyncing = false;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* Schedule a retry with exponential backoff
|
|
1583
|
+
*/
|
|
1584
|
+
scheduleRetry() {
|
|
1585
|
+
if (this.retryCount >= MAX_RETRIES) {
|
|
1586
|
+
const config = core.getConfig();
|
|
1587
|
+
if (config.debug) {
|
|
1588
|
+
console.warn("[SpeechOS] Max retries reached, giving up sync");
|
|
1589
|
+
}
|
|
1590
|
+
this.retryCount = 0;
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
this.retryCount++;
|
|
1594
|
+
const delay = BASE_RETRY_DELAY_MS * Math.pow(2, this.retryCount - 1);
|
|
1595
|
+
this.syncTimer = setTimeout(() => {
|
|
1596
|
+
this.syncToServer();
|
|
1597
|
+
}, delay);
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Check if an error is a CSP or network restriction error
|
|
1601
|
+
* These errors are permanent and shouldn't trigger retries
|
|
1602
|
+
*/
|
|
1603
|
+
isNetworkRestrictionError(error) {
|
|
1604
|
+
if (error instanceof TypeError) {
|
|
1605
|
+
const message = error.message.toLowerCase();
|
|
1606
|
+
// Common CSP/network error messages
|
|
1607
|
+
return (message.includes("failed to fetch") ||
|
|
1608
|
+
message.includes("network request failed") ||
|
|
1609
|
+
message.includes("content security policy") ||
|
|
1610
|
+
message.includes("csp") ||
|
|
1611
|
+
message.includes("blocked"));
|
|
1612
|
+
}
|
|
1613
|
+
return false;
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Handle token expiration
|
|
1617
|
+
*/
|
|
1618
|
+
handleTokenExpired() {
|
|
1619
|
+
core.clearSettingsToken();
|
|
1620
|
+
core.events.emit("settings:tokenExpired", undefined);
|
|
1621
|
+
const config = core.getConfig();
|
|
1622
|
+
if (config.debug) {
|
|
1623
|
+
console.warn("[SpeechOS] Settings token expired");
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Check if sync is enabled (token is configured)
|
|
1628
|
+
*/
|
|
1629
|
+
isEnabled() {
|
|
1630
|
+
return !!core.getSettingsToken();
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
// Singleton instance
|
|
1634
|
+
const settingsSync = new SettingsSync();
|
|
1635
|
+
|
|
1107
1636
|
/******************************************************************************
|
|
1108
1637
|
Copyright (c) Microsoft Corporation.
|
|
1109
1638
|
|
|
@@ -1187,6 +1716,9 @@ const t$1=t=>(e,o)=>{ void 0!==o?o.addInitializer(()=>{customElements.define(t,e
|
|
|
1187
1716
|
*/
|
|
1188
1717
|
const themeStyles = i$4 `
|
|
1189
1718
|
:host {
|
|
1719
|
+
/* Font stack - system fonts for consistent rendering across sites */
|
|
1720
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
1721
|
+
|
|
1190
1722
|
/* Color tokens */
|
|
1191
1723
|
--speechos-primary: #10B981;
|
|
1192
1724
|
--speechos-primary-hover: #059669;
|
|
@@ -1310,100 +1842,6 @@ i$4 `
|
|
|
1310
1842
|
}
|
|
1311
1843
|
`;
|
|
1312
1844
|
|
|
1313
|
-
/**
|
|
1314
|
-
* Transcript history store
|
|
1315
|
-
* Persists transcripts to localStorage for viewing in the settings modal
|
|
1316
|
-
*/
|
|
1317
|
-
const STORAGE_KEY = "speechos_transcripts";
|
|
1318
|
-
const MAX_ENTRIES = 50;
|
|
1319
|
-
/**
|
|
1320
|
-
* Generate a unique ID for transcript entries
|
|
1321
|
-
*/
|
|
1322
|
-
function generateId() {
|
|
1323
|
-
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
1324
|
-
}
|
|
1325
|
-
/**
|
|
1326
|
-
* Get all transcripts from localStorage
|
|
1327
|
-
*/
|
|
1328
|
-
function getTranscripts() {
|
|
1329
|
-
try {
|
|
1330
|
-
const stored = localStorage.getItem(STORAGE_KEY);
|
|
1331
|
-
if (!stored)
|
|
1332
|
-
return [];
|
|
1333
|
-
const entries = JSON.parse(stored);
|
|
1334
|
-
// Return newest first
|
|
1335
|
-
return entries.sort((a, b) => b.timestamp - a.timestamp);
|
|
1336
|
-
}
|
|
1337
|
-
catch {
|
|
1338
|
-
return [];
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
/**
|
|
1342
|
-
* Save a new transcript entry
|
|
1343
|
-
*/
|
|
1344
|
-
function saveTranscript(text, action, originalTextOrOptions) {
|
|
1345
|
-
const entry = {
|
|
1346
|
-
id: generateId(),
|
|
1347
|
-
text,
|
|
1348
|
-
timestamp: Date.now(),
|
|
1349
|
-
action,
|
|
1350
|
-
};
|
|
1351
|
-
// Handle edit action with originalText string
|
|
1352
|
-
if (action === "edit" && typeof originalTextOrOptions === "string") {
|
|
1353
|
-
entry.originalText = originalTextOrOptions;
|
|
1354
|
-
}
|
|
1355
|
-
// Handle command action with options object
|
|
1356
|
-
if (action === "command" && typeof originalTextOrOptions === "object") {
|
|
1357
|
-
const options = originalTextOrOptions;
|
|
1358
|
-
if (options.inputText !== undefined)
|
|
1359
|
-
entry.inputText = options.inputText;
|
|
1360
|
-
if (options.commandResult !== undefined)
|
|
1361
|
-
entry.commandResult = options.commandResult;
|
|
1362
|
-
if (options.commandConfig !== undefined)
|
|
1363
|
-
entry.commandConfig = options.commandConfig;
|
|
1364
|
-
}
|
|
1365
|
-
const entries = getTranscripts();
|
|
1366
|
-
entries.unshift(entry);
|
|
1367
|
-
// Prune to max entries
|
|
1368
|
-
const pruned = entries.slice(0, MAX_ENTRIES);
|
|
1369
|
-
try {
|
|
1370
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(pruned));
|
|
1371
|
-
}
|
|
1372
|
-
catch {
|
|
1373
|
-
// localStorage full or unavailable - silently fail
|
|
1374
|
-
}
|
|
1375
|
-
return entry;
|
|
1376
|
-
}
|
|
1377
|
-
/**
|
|
1378
|
-
* Clear all transcript history
|
|
1379
|
-
*/
|
|
1380
|
-
function clearTranscripts() {
|
|
1381
|
-
try {
|
|
1382
|
-
localStorage.removeItem(STORAGE_KEY);
|
|
1383
|
-
}
|
|
1384
|
-
catch {
|
|
1385
|
-
// Silently fail
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
/**
|
|
1389
|
-
* Delete a single transcript by ID
|
|
1390
|
-
*/
|
|
1391
|
-
function deleteTranscript(id) {
|
|
1392
|
-
const entries = getTranscripts().filter((e) => e.id !== id);
|
|
1393
|
-
try {
|
|
1394
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));
|
|
1395
|
-
}
|
|
1396
|
-
catch {
|
|
1397
|
-
// Silently fail
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
const transcriptStore = {
|
|
1401
|
-
getTranscripts: getTranscripts,
|
|
1402
|
-
saveTranscript: saveTranscript,
|
|
1403
|
-
clearTranscripts: clearTranscripts,
|
|
1404
|
-
deleteTranscript: deleteTranscript,
|
|
1405
|
-
};
|
|
1406
|
-
|
|
1407
1845
|
function isNativeField(field) {
|
|
1408
1846
|
return field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement;
|
|
1409
1847
|
}
|
|
@@ -3401,6 +3839,8 @@ const modalLayoutStyles = i$4 `
|
|
|
3401
3839
|
inset: 0;
|
|
3402
3840
|
pointer-events: none;
|
|
3403
3841
|
z-index: calc(var(--speechos-z-base) + 100);
|
|
3842
|
+
/* Ensure consistent font rendering across all sites */
|
|
3843
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
3404
3844
|
}
|
|
3405
3845
|
|
|
3406
3846
|
.modal-overlay {
|
|
@@ -3889,6 +4329,7 @@ let SpeechOSHistoryTab = class SpeechOSHistoryTab extends i$1 {
|
|
|
3889
4329
|
constructor() {
|
|
3890
4330
|
super(...arguments);
|
|
3891
4331
|
this.transcripts = [];
|
|
4332
|
+
this.unsubscribeSettingsLoaded = null;
|
|
3892
4333
|
}
|
|
3893
4334
|
static { this.styles = [
|
|
3894
4335
|
themeStyles,
|
|
@@ -4025,11 +4466,36 @@ let SpeechOSHistoryTab = class SpeechOSHistoryTab extends i$1 {
|
|
|
4025
4466
|
background: rgba(239, 68, 68, 0.18);
|
|
4026
4467
|
border-color: rgba(239, 68, 68, 0.25);
|
|
4027
4468
|
}
|
|
4469
|
+
|
|
4470
|
+
.command-matched {
|
|
4471
|
+
font-size: 12px;
|
|
4472
|
+
color: rgba(255, 255, 255, 0.5);
|
|
4473
|
+
margin-top: 6px;
|
|
4474
|
+
}
|
|
4475
|
+
|
|
4476
|
+
.command-matched code {
|
|
4477
|
+
background: rgba(245, 158, 11, 0.15);
|
|
4478
|
+
color: #fbbf24;
|
|
4479
|
+
padding: 2px 6px;
|
|
4480
|
+
border-radius: 4px;
|
|
4481
|
+
font-family: monospace;
|
|
4482
|
+
}
|
|
4028
4483
|
`,
|
|
4029
4484
|
]; }
|
|
4030
4485
|
connectedCallback() {
|
|
4031
4486
|
super.connectedCallback();
|
|
4032
4487
|
this.loadTranscripts();
|
|
4488
|
+
// Refresh when settings are loaded from the server (history may have changed)
|
|
4489
|
+
this.unsubscribeSettingsLoaded = core.events.on("settings:loaded", () => {
|
|
4490
|
+
this.loadTranscripts();
|
|
4491
|
+
});
|
|
4492
|
+
}
|
|
4493
|
+
disconnectedCallback() {
|
|
4494
|
+
super.disconnectedCallback();
|
|
4495
|
+
if (this.unsubscribeSettingsLoaded) {
|
|
4496
|
+
this.unsubscribeSettingsLoaded();
|
|
4497
|
+
this.unsubscribeSettingsLoaded = null;
|
|
4498
|
+
}
|
|
4033
4499
|
}
|
|
4034
4500
|
/** Reload transcripts from store */
|
|
4035
4501
|
refresh() {
|
|
@@ -4100,7 +4566,13 @@ let SpeechOSHistoryTab = class SpeechOSHistoryTab extends i$1 {
|
|
|
4100
4566
|
renderCommandDetails(entry) {
|
|
4101
4567
|
// Show the transcript text (what the user said)
|
|
4102
4568
|
const displayText = entry.inputText || entry.text;
|
|
4103
|
-
|
|
4569
|
+
const commandName = entry.commandResult?.name;
|
|
4570
|
+
return b `
|
|
4571
|
+
<div class="transcript-text">${displayText}</div>
|
|
4572
|
+
${commandName
|
|
4573
|
+
? b `<div class="command-matched">Matched: <code>${commandName}</code></div>`
|
|
4574
|
+
: null}
|
|
4575
|
+
`;
|
|
4104
4576
|
}
|
|
4105
4577
|
getCopyText(entry) {
|
|
4106
4578
|
if (entry.action === "command") {
|
|
@@ -4650,6 +5122,7 @@ let SpeechOSSettingsTab = class SpeechOSSettingsTab extends i$1 {
|
|
|
4650
5122
|
this.permissionGranted = false;
|
|
4651
5123
|
this.smartFormatEnabled = true;
|
|
4652
5124
|
this.savedIndicatorTimeout = null;
|
|
5125
|
+
this.unsubscribeSettingsLoaded = null;
|
|
4653
5126
|
}
|
|
4654
5127
|
static { this.styles = [
|
|
4655
5128
|
themeStyles,
|
|
@@ -4824,12 +5297,20 @@ let SpeechOSSettingsTab = class SpeechOSSettingsTab extends i$1 {
|
|
|
4824
5297
|
connectedCallback() {
|
|
4825
5298
|
super.connectedCallback();
|
|
4826
5299
|
this.loadSettings();
|
|
5300
|
+
// Refresh when settings are loaded from the server
|
|
5301
|
+
this.unsubscribeSettingsLoaded = core.events.on("settings:loaded", () => {
|
|
5302
|
+
this.loadSettings();
|
|
5303
|
+
});
|
|
4827
5304
|
}
|
|
4828
5305
|
disconnectedCallback() {
|
|
4829
5306
|
super.disconnectedCallback();
|
|
4830
5307
|
if (this.savedIndicatorTimeout) {
|
|
4831
5308
|
clearTimeout(this.savedIndicatorTimeout);
|
|
4832
5309
|
}
|
|
5310
|
+
if (this.unsubscribeSettingsLoaded) {
|
|
5311
|
+
this.unsubscribeSettingsLoaded();
|
|
5312
|
+
this.unsubscribeSettingsLoaded = null;
|
|
5313
|
+
}
|
|
4833
5314
|
this.isTestingMic = false;
|
|
4834
5315
|
}
|
|
4835
5316
|
/** Called when tab becomes active */
|
|
@@ -5128,6 +5609,7 @@ let SpeechOSSnippetsTab = class SpeechOSSnippetsTab extends i$1 {
|
|
|
5128
5609
|
this.trigger = "";
|
|
5129
5610
|
this.expansion = "";
|
|
5130
5611
|
this.error = "";
|
|
5612
|
+
this.unsubscribeSettingsLoaded = null;
|
|
5131
5613
|
}
|
|
5132
5614
|
static { this.styles = [
|
|
5133
5615
|
themeStyles,
|
|
@@ -5233,6 +5715,17 @@ let SpeechOSSnippetsTab = class SpeechOSSnippetsTab extends i$1 {
|
|
|
5233
5715
|
connectedCallback() {
|
|
5234
5716
|
super.connectedCallback();
|
|
5235
5717
|
this.loadSnippets();
|
|
5718
|
+
// Refresh when settings are loaded from the server
|
|
5719
|
+
this.unsubscribeSettingsLoaded = core.events.on("settings:loaded", () => {
|
|
5720
|
+
this.loadSnippets();
|
|
5721
|
+
});
|
|
5722
|
+
}
|
|
5723
|
+
disconnectedCallback() {
|
|
5724
|
+
super.disconnectedCallback();
|
|
5725
|
+
if (this.unsubscribeSettingsLoaded) {
|
|
5726
|
+
this.unsubscribeSettingsLoaded();
|
|
5727
|
+
this.unsubscribeSettingsLoaded = null;
|
|
5728
|
+
}
|
|
5236
5729
|
}
|
|
5237
5730
|
/** Reload snippets from store */
|
|
5238
5731
|
refresh() {
|
|
@@ -5475,6 +5968,7 @@ let SpeechOSVocabularyTab = class SpeechOSVocabularyTab extends i$1 {
|
|
|
5475
5968
|
this.showForm = false;
|
|
5476
5969
|
this.term = "";
|
|
5477
5970
|
this.error = "";
|
|
5971
|
+
this.unsubscribeSettingsLoaded = null;
|
|
5478
5972
|
}
|
|
5479
5973
|
static { this.styles = [
|
|
5480
5974
|
themeStyles,
|
|
@@ -5527,6 +6021,17 @@ let SpeechOSVocabularyTab = class SpeechOSVocabularyTab extends i$1 {
|
|
|
5527
6021
|
connectedCallback() {
|
|
5528
6022
|
super.connectedCallback();
|
|
5529
6023
|
this.loadVocabulary();
|
|
6024
|
+
// Refresh when settings are loaded from the server
|
|
6025
|
+
this.unsubscribeSettingsLoaded = core.events.on("settings:loaded", () => {
|
|
6026
|
+
this.loadVocabulary();
|
|
6027
|
+
});
|
|
6028
|
+
}
|
|
6029
|
+
disconnectedCallback() {
|
|
6030
|
+
super.disconnectedCallback();
|
|
6031
|
+
if (this.unsubscribeSettingsLoaded) {
|
|
6032
|
+
this.unsubscribeSettingsLoaded();
|
|
6033
|
+
this.unsubscribeSettingsLoaded = null;
|
|
6034
|
+
}
|
|
5530
6035
|
}
|
|
5531
6036
|
/** Reload vocabulary from store */
|
|
5532
6037
|
refresh() {
|
|
@@ -7140,6 +7645,8 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7140
7645
|
const originalContent = this.getElementContent(target) || "";
|
|
7141
7646
|
if (tagName === "input" || tagName === "textarea") {
|
|
7142
7647
|
const inputEl = target;
|
|
7648
|
+
// Ensure DOM focus is on the input before inserting
|
|
7649
|
+
inputEl.focus();
|
|
7143
7650
|
// Restore cursor position before inserting
|
|
7144
7651
|
const start = this.dictationCursorStart ?? inputEl.value.length;
|
|
7145
7652
|
const end = this.dictationCursorEnd ?? inputEl.value.length;
|
|
@@ -7476,8 +7983,6 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7476
7983
|
// Note: command:complete event is already emitted by the backend
|
|
7477
7984
|
// when the command_result message is received, so we don't emit here
|
|
7478
7985
|
core.state.completeRecording();
|
|
7479
|
-
// Keep widget visible but collapsed (just mic button, no action bubbles)
|
|
7480
|
-
core.state.setState({ isExpanded: false });
|
|
7481
7986
|
// Show command feedback
|
|
7482
7987
|
this.showActionFeedback(result ? "command-success" : "command-none");
|
|
7483
7988
|
backend.disconnect().catch(() => { });
|
|
@@ -7635,6 +8140,8 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7635
8140
|
if (tagName === "input" || tagName === "textarea") {
|
|
7636
8141
|
const inputEl = target;
|
|
7637
8142
|
originalContent = inputEl.value;
|
|
8143
|
+
// Ensure DOM focus is on the input before editing
|
|
8144
|
+
inputEl.focus();
|
|
7638
8145
|
// Restore the original selection/cursor position
|
|
7639
8146
|
const selectionStart = this.editSelectionStart ?? 0;
|
|
7640
8147
|
const selectionEnd = this.editSelectionEnd ?? inputEl.value.length;
|
|
@@ -7802,6 +8309,30 @@ SpeechOSWidget = SpeechOSWidget_1 = __decorate([
|
|
|
7802
8309
|
t$1("speechos-widget")
|
|
7803
8310
|
], SpeechOSWidget);
|
|
7804
8311
|
|
|
8312
|
+
/**
|
|
8313
|
+
* UI module exports
|
|
8314
|
+
* Lit-based Shadow DOM components
|
|
8315
|
+
*/
|
|
8316
|
+
// Patch customElements.define to silently ignore duplicate registrations for speechos-* elements.
|
|
8317
|
+
// This prevents errors when the extension loads on a page that already has SpeechOS.
|
|
8318
|
+
// The patch is scoped to only affect speechos-* tags to avoid unintended effects on host pages.
|
|
8319
|
+
const originalDefine = customElements.define.bind(customElements);
|
|
8320
|
+
customElements.define = (name, constructor, options) => {
|
|
8321
|
+
// Only intercept speechos-* elements
|
|
8322
|
+
if (name.startsWith("speechos-")) {
|
|
8323
|
+
if (customElements.get(name) === undefined) {
|
|
8324
|
+
originalDefine(name, constructor, options);
|
|
8325
|
+
}
|
|
8326
|
+
// Skip silently if already registered
|
|
8327
|
+
}
|
|
8328
|
+
else {
|
|
8329
|
+
// Pass through for non-speechos elements
|
|
8330
|
+
originalDefine(name, constructor, options);
|
|
8331
|
+
}
|
|
8332
|
+
};
|
|
8333
|
+
// Restore original customElements.define after our components are registered
|
|
8334
|
+
customElements.define = originalDefine;
|
|
8335
|
+
|
|
7805
8336
|
/**
|
|
7806
8337
|
* Main SpeechOS Client SDK class
|
|
7807
8338
|
* Composes core logic with UI components
|
|
@@ -7883,6 +8414,13 @@ class SpeechOS {
|
|
|
7883
8414
|
core.state.show();
|
|
7884
8415
|
}
|
|
7885
8416
|
this.isInitialized = true;
|
|
8417
|
+
// Initialize settings sync if token is configured
|
|
8418
|
+
// This loads settings from server and subscribes to changes
|
|
8419
|
+
settingsSync.init().catch((error) => {
|
|
8420
|
+
if (finalConfig.debug) {
|
|
8421
|
+
console.warn("[SpeechOS] Settings sync initialization failed:", error);
|
|
8422
|
+
}
|
|
8423
|
+
});
|
|
7886
8424
|
// Log initialization in debug mode
|
|
7887
8425
|
if (finalConfig.debug) {
|
|
7888
8426
|
console.log("[SpeechOS] Initialized with config:", finalConfig);
|
|
@@ -7913,6 +8451,8 @@ class SpeechOS {
|
|
|
7913
8451
|
resetClientConfig();
|
|
7914
8452
|
// Reset text input handler to default
|
|
7915
8453
|
resetTextInputHandler();
|
|
8454
|
+
// Stop settings sync
|
|
8455
|
+
settingsSync.destroy();
|
|
7916
8456
|
// Clear instance
|
|
7917
8457
|
this.instance = null;
|
|
7918
8458
|
this.isInitialized = false;
|
|
@@ -8158,6 +8698,7 @@ exports.setLanguageCode = setLanguageCode;
|
|
|
8158
8698
|
exports.setOutputLanguageCode = setOutputLanguageCode;
|
|
8159
8699
|
exports.setSmartFormatEnabled = setSmartFormatEnabled;
|
|
8160
8700
|
exports.setTextInputHandler = setTextInputHandler;
|
|
8701
|
+
exports.settingsSync = settingsSync;
|
|
8161
8702
|
exports.snippetsStore = snippetsStore;
|
|
8162
8703
|
exports.transcriptStore = transcriptStore;
|
|
8163
8704
|
exports.updateSnippet = updateSnippet;
|