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