@speechos/client 0.2.8 → 0.2.10
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/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/index.cjs +694 -117
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.iife.js +715 -120
- package/dist/index.iife.js.map +1 -1
- package/dist/index.iife.min.js +136 -103
- package/dist/index.iife.min.js.map +1 -1
- package/dist/index.js +694 -119
- package/dist/index.js.map +1 -1
- package/dist/settings-sync.d.ts +66 -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, 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
|
/**
|
|
@@ -12,6 +12,7 @@ const defaultClientConfig = {
|
|
|
12
12
|
commands: [],
|
|
13
13
|
zIndex: 999999,
|
|
14
14
|
alwaysVisible: false,
|
|
15
|
+
useExternalSettings: false,
|
|
15
16
|
};
|
|
16
17
|
/**
|
|
17
18
|
* Current client configuration singleton
|
|
@@ -27,6 +28,7 @@ function validateClientConfig(config) {
|
|
|
27
28
|
commands: config.commands ?? defaultClientConfig.commands,
|
|
28
29
|
zIndex: config.zIndex ?? defaultClientConfig.zIndex,
|
|
29
30
|
alwaysVisible: config.alwaysVisible ?? defaultClientConfig.alwaysVisible,
|
|
31
|
+
useExternalSettings: config.useExternalSettings ?? defaultClientConfig.useExternalSettings,
|
|
30
32
|
};
|
|
31
33
|
// Validate zIndex
|
|
32
34
|
if (typeof resolved.zIndex !== "number" || resolved.zIndex < 0) {
|
|
@@ -78,6 +80,12 @@ function getZIndex() {
|
|
|
78
80
|
function isAlwaysVisible() {
|
|
79
81
|
return currentClientConfig.alwaysVisible;
|
|
80
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if external settings page should be used
|
|
85
|
+
*/
|
|
86
|
+
function useExternalSettings() {
|
|
87
|
+
return currentClientConfig.useExternalSettings;
|
|
88
|
+
}
|
|
81
89
|
|
|
82
90
|
/**
|
|
83
91
|
* Form field focus detection for SpeechOS Client SDK
|
|
@@ -481,15 +489,23 @@ function resetTextInputHandler() {
|
|
|
481
489
|
* Persists input language preferences to localStorage
|
|
482
490
|
*/
|
|
483
491
|
const STORAGE_KEY$4 = "speechos_language_settings";
|
|
492
|
+
/**
|
|
493
|
+
* In-memory cache for language settings. When server sync is enabled, this is the
|
|
494
|
+
* source of truth. localStorage is only used when server sync is disabled.
|
|
495
|
+
*/
|
|
496
|
+
let memoryCache$3 = null;
|
|
484
497
|
/**
|
|
485
498
|
* Supported input languages for speech recognition
|
|
486
499
|
* Each language has a name, primary code, and available variants
|
|
487
500
|
* Sorted alphabetically by name for dropdown display
|
|
488
501
|
*/
|
|
489
502
|
const SUPPORTED_LANGUAGES = [
|
|
503
|
+
{ name: "Belarusian", code: "be", variants: ["be"] },
|
|
504
|
+
{ name: "Bengali", code: "bn", variants: ["bn"] },
|
|
505
|
+
{ name: "Bosnian", code: "bs", variants: ["bs"] },
|
|
490
506
|
{ name: "Bulgarian", code: "bg", variants: ["bg"] },
|
|
491
507
|
{ name: "Catalan", code: "ca", variants: ["ca"] },
|
|
492
|
-
{ name: "
|
|
508
|
+
{ name: "Croatian", code: "hr", variants: ["hr"] },
|
|
493
509
|
{ name: "Czech", code: "cs", variants: ["cs"] },
|
|
494
510
|
{ name: "Danish", code: "da", variants: ["da", "da-DK"] },
|
|
495
511
|
{ name: "Dutch", code: "nl", variants: ["nl"] },
|
|
@@ -510,18 +526,26 @@ const SUPPORTED_LANGUAGES = [
|
|
|
510
526
|
{ name: "Indonesian", code: "id", variants: ["id"] },
|
|
511
527
|
{ name: "Italian", code: "it", variants: ["it"] },
|
|
512
528
|
{ name: "Japanese", code: "ja", variants: ["ja"] },
|
|
529
|
+
{ name: "Kannada", code: "kn", variants: ["kn"] },
|
|
513
530
|
{ name: "Korean", code: "ko", variants: ["ko", "ko-KR"] },
|
|
514
531
|
{ name: "Latvian", code: "lv", variants: ["lv"] },
|
|
515
532
|
{ name: "Lithuanian", code: "lt", variants: ["lt"] },
|
|
533
|
+
{ name: "Macedonian", code: "mk", variants: ["mk"] },
|
|
516
534
|
{ name: "Malay", code: "ms", variants: ["ms"] },
|
|
535
|
+
{ name: "Marathi", code: "mr", variants: ["mr"] },
|
|
517
536
|
{ name: "Norwegian", code: "no", variants: ["no"] },
|
|
518
537
|
{ name: "Polish", code: "pl", variants: ["pl"] },
|
|
519
538
|
{ name: "Portuguese", code: "pt", variants: ["pt", "pt-BR", "pt-PT"] },
|
|
520
539
|
{ name: "Romanian", code: "ro", variants: ["ro"] },
|
|
521
540
|
{ name: "Russian", code: "ru", variants: ["ru"] },
|
|
541
|
+
{ name: "Serbian", code: "sr", variants: ["sr"] },
|
|
522
542
|
{ name: "Slovak", code: "sk", variants: ["sk"] },
|
|
543
|
+
{ name: "Slovenian", code: "sl", variants: ["sl"] },
|
|
523
544
|
{ name: "Spanish", code: "es", variants: ["es", "es-419"] },
|
|
524
545
|
{ name: "Swedish", code: "sv", variants: ["sv", "sv-SE"] },
|
|
546
|
+
{ name: "Tagalog", code: "tl", variants: ["tl"] },
|
|
547
|
+
{ name: "Tamil", code: "ta", variants: ["ta"] },
|
|
548
|
+
{ name: "Telugu", code: "te", variants: ["te"] },
|
|
525
549
|
{ name: "Turkish", code: "tr", variants: ["tr"] },
|
|
526
550
|
{ name: "Ukrainian", code: "uk", variants: ["uk"] },
|
|
527
551
|
{ name: "Vietnamese", code: "vi", variants: ["vi"] },
|
|
@@ -532,9 +556,13 @@ const defaultSettings$1 = {
|
|
|
532
556
|
smartFormat: true,
|
|
533
557
|
};
|
|
534
558
|
/**
|
|
535
|
-
* Get current language settings from
|
|
559
|
+
* Get current language settings. Prefers in-memory cache (from server sync),
|
|
560
|
+
* then falls back to localStorage.
|
|
536
561
|
*/
|
|
537
562
|
function getLanguageSettings() {
|
|
563
|
+
if (memoryCache$3 !== null) {
|
|
564
|
+
return { ...memoryCache$3 };
|
|
565
|
+
}
|
|
538
566
|
try {
|
|
539
567
|
const stored = localStorage.getItem(STORAGE_KEY$4);
|
|
540
568
|
if (!stored)
|
|
@@ -546,14 +574,27 @@ function getLanguageSettings() {
|
|
|
546
574
|
}
|
|
547
575
|
}
|
|
548
576
|
/**
|
|
549
|
-
*
|
|
577
|
+
* Set language settings directly (used by settings sync from server data).
|
|
578
|
+
*/
|
|
579
|
+
function setLanguageSettings(settings) {
|
|
580
|
+
memoryCache$3 = { ...defaultSettings$1, ...settings };
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Reset memory cache (for testing only)
|
|
584
|
+
*/
|
|
585
|
+
function resetMemoryCache$2() {
|
|
586
|
+
memoryCache$3 = null;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Save language settings (updates memory cache and tries localStorage)
|
|
550
590
|
*/
|
|
551
591
|
function saveLanguageSettings(settings) {
|
|
592
|
+
memoryCache$3 = settings;
|
|
552
593
|
try {
|
|
553
594
|
localStorage.setItem(STORAGE_KEY$4, JSON.stringify(settings));
|
|
554
595
|
}
|
|
555
596
|
catch {
|
|
556
|
-
// localStorage full or unavailable -
|
|
597
|
+
// localStorage full or unavailable - memory cache still updated
|
|
557
598
|
}
|
|
558
599
|
}
|
|
559
600
|
/**
|
|
@@ -657,6 +698,7 @@ function getLanguageName() {
|
|
|
657
698
|
* Reset language settings to defaults
|
|
658
699
|
*/
|
|
659
700
|
function resetLanguageSettings() {
|
|
701
|
+
memoryCache$3 = null;
|
|
660
702
|
try {
|
|
661
703
|
localStorage.removeItem(STORAGE_KEY$4);
|
|
662
704
|
}
|
|
@@ -666,6 +708,7 @@ function resetLanguageSettings() {
|
|
|
666
708
|
}
|
|
667
709
|
const languageSettings = {
|
|
668
710
|
getLanguageSettings,
|
|
711
|
+
setLanguageSettings,
|
|
669
712
|
getInputLanguageCode,
|
|
670
713
|
setInputLanguageCode,
|
|
671
714
|
getOutputLanguageCode,
|
|
@@ -676,6 +719,7 @@ const languageSettings = {
|
|
|
676
719
|
getSmartFormatEnabled,
|
|
677
720
|
setSmartFormatEnabled,
|
|
678
721
|
resetLanguageSettings,
|
|
722
|
+
resetMemoryCache: resetMemoryCache$2,
|
|
679
723
|
SUPPORTED_LANGUAGES,
|
|
680
724
|
// Legacy aliases
|
|
681
725
|
getLanguageCode,
|
|
@@ -691,6 +735,11 @@ const STORAGE_KEY$3 = "speechos_snippets";
|
|
|
691
735
|
const MAX_SNIPPETS = 25;
|
|
692
736
|
const MAX_TRIGGER_LENGTH = 30;
|
|
693
737
|
const MAX_EXPANSION_LENGTH = 300;
|
|
738
|
+
/**
|
|
739
|
+
* In-memory cache for snippets. When server sync is enabled, this is the
|
|
740
|
+
* source of truth. localStorage is only used when server sync is disabled.
|
|
741
|
+
*/
|
|
742
|
+
let memoryCache$2 = null;
|
|
694
743
|
/**
|
|
695
744
|
* Generate a unique ID for snippet entries
|
|
696
745
|
*/
|
|
@@ -698,15 +747,18 @@ function generateId$2() {
|
|
|
698
747
|
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
699
748
|
}
|
|
700
749
|
/**
|
|
701
|
-
* Get all snippets from
|
|
750
|
+
* Get all snippets. Prefers in-memory cache (from server sync),
|
|
751
|
+
* then falls back to localStorage.
|
|
702
752
|
*/
|
|
703
753
|
function getSnippets() {
|
|
754
|
+
if (memoryCache$2 !== null) {
|
|
755
|
+
return [...memoryCache$2].sort((a, b) => b.createdAt - a.createdAt);
|
|
756
|
+
}
|
|
704
757
|
try {
|
|
705
758
|
const stored = localStorage.getItem(STORAGE_KEY$3);
|
|
706
759
|
if (!stored)
|
|
707
760
|
return [];
|
|
708
761
|
const entries = JSON.parse(stored);
|
|
709
|
-
// Return newest first
|
|
710
762
|
return entries.sort((a, b) => b.createdAt - a.createdAt);
|
|
711
763
|
}
|
|
712
764
|
catch {
|
|
@@ -714,14 +766,27 @@ function getSnippets() {
|
|
|
714
766
|
}
|
|
715
767
|
}
|
|
716
768
|
/**
|
|
717
|
-
*
|
|
769
|
+
* Set snippets directly (used by settings sync from server data).
|
|
770
|
+
*/
|
|
771
|
+
function setSnippets(snippets) {
|
|
772
|
+
memoryCache$2 = snippets.slice(0, MAX_SNIPPETS);
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Reset memory cache (for testing only)
|
|
776
|
+
*/
|
|
777
|
+
function resetMemoryCache$1() {
|
|
778
|
+
memoryCache$2 = null;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Save snippets (updates memory cache and tries localStorage)
|
|
718
782
|
*/
|
|
719
783
|
function saveSnippets(snippets) {
|
|
784
|
+
memoryCache$2 = snippets;
|
|
720
785
|
try {
|
|
721
786
|
localStorage.setItem(STORAGE_KEY$3, JSON.stringify(snippets));
|
|
722
787
|
}
|
|
723
788
|
catch {
|
|
724
|
-
// localStorage full or unavailable -
|
|
789
|
+
// localStorage full or unavailable - memory cache still updated
|
|
725
790
|
}
|
|
726
791
|
}
|
|
727
792
|
/**
|
|
@@ -839,13 +904,14 @@ function deleteSnippet(id) {
|
|
|
839
904
|
* Clear all snippets
|
|
840
905
|
*/
|
|
841
906
|
function clearSnippets() {
|
|
907
|
+
memoryCache$2 = [];
|
|
842
908
|
try {
|
|
843
909
|
localStorage.removeItem(STORAGE_KEY$3);
|
|
844
|
-
events.emit("settings:changed", { setting: "snippets" });
|
|
845
910
|
}
|
|
846
911
|
catch {
|
|
847
912
|
// Silently fail
|
|
848
913
|
}
|
|
914
|
+
events.emit("settings:changed", { setting: "snippets" });
|
|
849
915
|
}
|
|
850
916
|
/**
|
|
851
917
|
* Get snippet count info
|
|
@@ -861,12 +927,14 @@ function isAtSnippetLimit() {
|
|
|
861
927
|
}
|
|
862
928
|
const snippetsStore = {
|
|
863
929
|
getSnippets,
|
|
930
|
+
setSnippets,
|
|
864
931
|
addSnippet,
|
|
865
932
|
updateSnippet,
|
|
866
933
|
deleteSnippet,
|
|
867
934
|
clearSnippets,
|
|
868
935
|
getSnippetCount,
|
|
869
936
|
isAtSnippetLimit,
|
|
937
|
+
resetMemoryCache: resetMemoryCache$1,
|
|
870
938
|
MAX_SNIPPETS,
|
|
871
939
|
MAX_TRIGGER_LENGTH,
|
|
872
940
|
MAX_EXPANSION_LENGTH,
|
|
@@ -879,6 +947,11 @@ const snippetsStore = {
|
|
|
879
947
|
const STORAGE_KEY$2 = "speechos_vocabulary";
|
|
880
948
|
const MAX_TERMS = 50;
|
|
881
949
|
const MAX_TERM_LENGTH = 50;
|
|
950
|
+
/**
|
|
951
|
+
* In-memory cache for vocabulary. When server sync is enabled, this is the
|
|
952
|
+
* source of truth. localStorage is only used when server sync is disabled.
|
|
953
|
+
*/
|
|
954
|
+
let memoryCache$1 = null;
|
|
882
955
|
/**
|
|
883
956
|
* Generate a unique ID for vocabulary entries
|
|
884
957
|
*/
|
|
@@ -886,15 +959,18 @@ function generateId$1() {
|
|
|
886
959
|
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
887
960
|
}
|
|
888
961
|
/**
|
|
889
|
-
* Get all vocabulary terms from
|
|
962
|
+
* Get all vocabulary terms. Prefers in-memory cache (from server sync),
|
|
963
|
+
* then falls back to localStorage.
|
|
890
964
|
*/
|
|
891
965
|
function getVocabulary() {
|
|
966
|
+
if (memoryCache$1 !== null) {
|
|
967
|
+
return [...memoryCache$1].sort((a, b) => b.createdAt - a.createdAt);
|
|
968
|
+
}
|
|
892
969
|
try {
|
|
893
970
|
const stored = localStorage.getItem(STORAGE_KEY$2);
|
|
894
971
|
if (!stored)
|
|
895
972
|
return [];
|
|
896
973
|
const entries = JSON.parse(stored);
|
|
897
|
-
// Return newest first
|
|
898
974
|
return entries.sort((a, b) => b.createdAt - a.createdAt);
|
|
899
975
|
}
|
|
900
976
|
catch {
|
|
@@ -902,14 +978,27 @@ function getVocabulary() {
|
|
|
902
978
|
}
|
|
903
979
|
}
|
|
904
980
|
/**
|
|
905
|
-
*
|
|
981
|
+
* Set vocabulary directly (used by settings sync from server data).
|
|
982
|
+
*/
|
|
983
|
+
function setVocabulary(terms) {
|
|
984
|
+
memoryCache$1 = terms.slice(0, MAX_TERMS);
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Reset memory cache (for testing only)
|
|
988
|
+
*/
|
|
989
|
+
function resetMemoryCache() {
|
|
990
|
+
memoryCache$1 = null;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Save vocabulary (updates memory cache and tries localStorage)
|
|
906
994
|
*/
|
|
907
995
|
function saveVocabulary(terms) {
|
|
996
|
+
memoryCache$1 = terms;
|
|
908
997
|
try {
|
|
909
998
|
localStorage.setItem(STORAGE_KEY$2, JSON.stringify(terms));
|
|
910
999
|
}
|
|
911
1000
|
catch {
|
|
912
|
-
// localStorage full or unavailable -
|
|
1001
|
+
// localStorage full or unavailable - memory cache still updated
|
|
913
1002
|
}
|
|
914
1003
|
}
|
|
915
1004
|
/**
|
|
@@ -977,13 +1066,14 @@ function deleteTerm(id) {
|
|
|
977
1066
|
* Clear all vocabulary
|
|
978
1067
|
*/
|
|
979
1068
|
function clearVocabulary() {
|
|
1069
|
+
memoryCache$1 = [];
|
|
980
1070
|
try {
|
|
981
1071
|
localStorage.removeItem(STORAGE_KEY$2);
|
|
982
|
-
events.emit("settings:changed", { setting: "vocabulary" });
|
|
983
1072
|
}
|
|
984
1073
|
catch {
|
|
985
1074
|
// Silently fail
|
|
986
1075
|
}
|
|
1076
|
+
events.emit("settings:changed", { setting: "vocabulary" });
|
|
987
1077
|
}
|
|
988
1078
|
/**
|
|
989
1079
|
* Get vocabulary count info
|
|
@@ -999,11 +1089,13 @@ function isAtVocabularyLimit() {
|
|
|
999
1089
|
}
|
|
1000
1090
|
const vocabularyStore = {
|
|
1001
1091
|
getVocabulary,
|
|
1092
|
+
setVocabulary,
|
|
1002
1093
|
addTerm,
|
|
1003
1094
|
deleteTerm,
|
|
1004
1095
|
clearVocabulary,
|
|
1005
1096
|
getVocabularyCount,
|
|
1006
1097
|
isAtVocabularyLimit,
|
|
1098
|
+
resetMemoryCache,
|
|
1007
1099
|
MAX_TERMS,
|
|
1008
1100
|
MAX_TERM_LENGTH,
|
|
1009
1101
|
};
|
|
@@ -1101,6 +1193,451 @@ const audioSettings = {
|
|
|
1101
1193
|
resetAudioSettings,
|
|
1102
1194
|
};
|
|
1103
1195
|
|
|
1196
|
+
/**
|
|
1197
|
+
* Transcript history store
|
|
1198
|
+
* Persists transcripts to localStorage for viewing in the settings modal
|
|
1199
|
+
*/
|
|
1200
|
+
const STORAGE_KEY = "speechos_transcripts";
|
|
1201
|
+
const MAX_ENTRIES = 50;
|
|
1202
|
+
/**
|
|
1203
|
+
* In-memory cache for transcripts. When server sync is enabled, this is the
|
|
1204
|
+
* source of truth. localStorage is only used when server sync is disabled.
|
|
1205
|
+
*/
|
|
1206
|
+
let memoryCache = null;
|
|
1207
|
+
/**
|
|
1208
|
+
* Generate a unique ID for transcript entries
|
|
1209
|
+
*/
|
|
1210
|
+
function generateId() {
|
|
1211
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Get all transcripts. Prefers in-memory cache (from server sync),
|
|
1215
|
+
* then falls back to localStorage.
|
|
1216
|
+
*/
|
|
1217
|
+
function getTranscripts() {
|
|
1218
|
+
// If we have in-memory data (from server sync), use it
|
|
1219
|
+
if (memoryCache !== null) {
|
|
1220
|
+
return [...memoryCache].sort((a, b) => b.timestamp - a.timestamp);
|
|
1221
|
+
}
|
|
1222
|
+
// Fall back to localStorage (when server sync is disabled)
|
|
1223
|
+
try {
|
|
1224
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
1225
|
+
if (!stored)
|
|
1226
|
+
return [];
|
|
1227
|
+
const entries = JSON.parse(stored);
|
|
1228
|
+
return entries.sort((a, b) => b.timestamp - a.timestamp);
|
|
1229
|
+
}
|
|
1230
|
+
catch {
|
|
1231
|
+
return [];
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Set transcripts directly (used by settings sync from server data).
|
|
1236
|
+
* Server data is the source of truth - just update memory cache.
|
|
1237
|
+
*/
|
|
1238
|
+
function setTranscripts(entries) {
|
|
1239
|
+
memoryCache = entries.slice(0, MAX_ENTRIES);
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Save a new transcript entry
|
|
1243
|
+
*/
|
|
1244
|
+
function saveTranscript(text, action, originalTextOrOptions) {
|
|
1245
|
+
const entry = {
|
|
1246
|
+
id: generateId(),
|
|
1247
|
+
text,
|
|
1248
|
+
timestamp: Date.now(),
|
|
1249
|
+
action,
|
|
1250
|
+
};
|
|
1251
|
+
// Handle edit action with originalText string
|
|
1252
|
+
if (action === "edit" && typeof originalTextOrOptions === "string") {
|
|
1253
|
+
entry.originalText = originalTextOrOptions;
|
|
1254
|
+
}
|
|
1255
|
+
// Handle command action with options object
|
|
1256
|
+
if (action === "command" && typeof originalTextOrOptions === "object") {
|
|
1257
|
+
const options = originalTextOrOptions;
|
|
1258
|
+
if (options.inputText !== undefined)
|
|
1259
|
+
entry.inputText = options.inputText;
|
|
1260
|
+
if (options.commandResult !== undefined)
|
|
1261
|
+
entry.commandResult = options.commandResult;
|
|
1262
|
+
if (options.commandConfig !== undefined)
|
|
1263
|
+
entry.commandConfig = options.commandConfig;
|
|
1264
|
+
}
|
|
1265
|
+
const entries = getTranscripts();
|
|
1266
|
+
entries.unshift(entry);
|
|
1267
|
+
// Prune to max entries
|
|
1268
|
+
const pruned = entries.slice(0, MAX_ENTRIES);
|
|
1269
|
+
// Update memory cache (always)
|
|
1270
|
+
memoryCache = pruned;
|
|
1271
|
+
// Try to persist to localStorage (for when server sync is disabled)
|
|
1272
|
+
try {
|
|
1273
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(pruned));
|
|
1274
|
+
}
|
|
1275
|
+
catch {
|
|
1276
|
+
// Quota exceeded - memory cache is still updated
|
|
1277
|
+
}
|
|
1278
|
+
// Emit settings change event to trigger sync
|
|
1279
|
+
events.emit("settings:changed", { setting: "history" });
|
|
1280
|
+
return entry;
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Clear all transcript history
|
|
1284
|
+
*/
|
|
1285
|
+
function clearTranscripts() {
|
|
1286
|
+
memoryCache = [];
|
|
1287
|
+
try {
|
|
1288
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
1289
|
+
}
|
|
1290
|
+
catch {
|
|
1291
|
+
// Silently fail
|
|
1292
|
+
}
|
|
1293
|
+
events.emit("settings:changed", { setting: "history" });
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Delete a single transcript by ID
|
|
1297
|
+
*/
|
|
1298
|
+
function deleteTranscript(id) {
|
|
1299
|
+
const entries = getTranscripts().filter((e) => e.id !== id);
|
|
1300
|
+
memoryCache = entries;
|
|
1301
|
+
try {
|
|
1302
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));
|
|
1303
|
+
}
|
|
1304
|
+
catch {
|
|
1305
|
+
// Silently fail
|
|
1306
|
+
}
|
|
1307
|
+
events.emit("settings:changed", { setting: "history" });
|
|
1308
|
+
}
|
|
1309
|
+
const transcriptStore = {
|
|
1310
|
+
getTranscripts,
|
|
1311
|
+
setTranscripts,
|
|
1312
|
+
saveTranscript,
|
|
1313
|
+
clearTranscripts,
|
|
1314
|
+
deleteTranscript,
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
/**
|
|
1318
|
+
* Settings sync manager
|
|
1319
|
+
* Syncs user settings (language, vocabulary, snippets, history) with the server
|
|
1320
|
+
*/
|
|
1321
|
+
// Sync debounce delay in milliseconds
|
|
1322
|
+
const SYNC_DEBOUNCE_MS = 2000;
|
|
1323
|
+
// Maximum retry attempts
|
|
1324
|
+
const MAX_RETRIES = 3;
|
|
1325
|
+
// Base retry delay in milliseconds (exponential backoff)
|
|
1326
|
+
const BASE_RETRY_DELAY_MS = 2000;
|
|
1327
|
+
/**
|
|
1328
|
+
* Settings sync manager singleton
|
|
1329
|
+
*/
|
|
1330
|
+
class SettingsSync {
|
|
1331
|
+
constructor() {
|
|
1332
|
+
this.syncTimer = null;
|
|
1333
|
+
this.isSyncing = false;
|
|
1334
|
+
this.retryCount = 0;
|
|
1335
|
+
this.isInitialized = false;
|
|
1336
|
+
this.unsubscribe = null;
|
|
1337
|
+
/** When true, sync is disabled due to CSP or network restrictions */
|
|
1338
|
+
this.syncDisabled = false;
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Make a fetch request using native fetch.
|
|
1342
|
+
*/
|
|
1343
|
+
async doFetch(url, options) {
|
|
1344
|
+
const config = getConfig();
|
|
1345
|
+
if (config.debug) {
|
|
1346
|
+
console.log("[SpeechOS] Using native fetch", options.method, url);
|
|
1347
|
+
}
|
|
1348
|
+
return fetch(url, options);
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Initialize the settings sync manager
|
|
1352
|
+
* If a settingsToken is configured, loads settings from server
|
|
1353
|
+
*/
|
|
1354
|
+
async init() {
|
|
1355
|
+
const token = getSettingsToken();
|
|
1356
|
+
if (!token) {
|
|
1357
|
+
// No token configured, sync is disabled
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
if (this.isInitialized) {
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
this.isInitialized = true;
|
|
1364
|
+
// Subscribe to settings changes
|
|
1365
|
+
this.unsubscribe = events.on("settings:changed", () => {
|
|
1366
|
+
this.scheduleSyncToServer();
|
|
1367
|
+
});
|
|
1368
|
+
// Load settings from server
|
|
1369
|
+
await this.loadFromServer();
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Stop the sync manager and clean up
|
|
1373
|
+
*/
|
|
1374
|
+
destroy() {
|
|
1375
|
+
if (this.syncTimer) {
|
|
1376
|
+
clearTimeout(this.syncTimer);
|
|
1377
|
+
this.syncTimer = null;
|
|
1378
|
+
}
|
|
1379
|
+
if (this.unsubscribe) {
|
|
1380
|
+
this.unsubscribe();
|
|
1381
|
+
this.unsubscribe = null;
|
|
1382
|
+
}
|
|
1383
|
+
this.isInitialized = false;
|
|
1384
|
+
this.retryCount = 0;
|
|
1385
|
+
this.syncDisabled = false;
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Load settings from the server and merge with local
|
|
1389
|
+
*/
|
|
1390
|
+
async loadFromServer() {
|
|
1391
|
+
const token = getSettingsToken();
|
|
1392
|
+
if (!token) {
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
const config = getConfig();
|
|
1396
|
+
try {
|
|
1397
|
+
const response = await this.doFetch(`${config.host}/api/user-settings/`, {
|
|
1398
|
+
method: "GET",
|
|
1399
|
+
headers: {
|
|
1400
|
+
Authorization: `Bearer ${token}`,
|
|
1401
|
+
"Content-Type": "application/json",
|
|
1402
|
+
},
|
|
1403
|
+
});
|
|
1404
|
+
if (config.debug) {
|
|
1405
|
+
console.log("[SpeechOS] Settings fetch response:", response.status, response.ok ? "OK" : response.statusText);
|
|
1406
|
+
}
|
|
1407
|
+
if (response.status === 404) {
|
|
1408
|
+
// No settings on server yet (new user) - sync local settings to server
|
|
1409
|
+
if (config.debug) {
|
|
1410
|
+
console.log("[SpeechOS] No server settings found, syncing local to server");
|
|
1411
|
+
}
|
|
1412
|
+
await this.syncToServer();
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
if (response.status === 401 || response.status === 403) {
|
|
1416
|
+
// Token expired or invalid
|
|
1417
|
+
this.handleTokenExpired();
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
if (!response.ok) {
|
|
1421
|
+
throw new Error(`Server returned ${response.status}`);
|
|
1422
|
+
}
|
|
1423
|
+
const serverSettings = (await response.json());
|
|
1424
|
+
if (config.debug) {
|
|
1425
|
+
console.log("[SpeechOS] Settings received from server:", {
|
|
1426
|
+
language: serverSettings.language,
|
|
1427
|
+
vocabularyCount: serverSettings.vocabulary?.length ?? 0,
|
|
1428
|
+
snippetsCount: serverSettings.snippets?.length ?? 0,
|
|
1429
|
+
historyCount: serverSettings.history?.length ?? 0,
|
|
1430
|
+
lastSyncedAt: serverSettings.lastSyncedAt,
|
|
1431
|
+
});
|
|
1432
|
+
if (serverSettings.history?.length > 0) {
|
|
1433
|
+
console.log("[SpeechOS] History entries:", serverSettings.history);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
this.mergeSettings(serverSettings);
|
|
1437
|
+
events.emit("settings:loaded", undefined);
|
|
1438
|
+
if (config.debug) {
|
|
1439
|
+
console.log("[SpeechOS] Settings merged and loaded");
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
catch (error) {
|
|
1443
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1444
|
+
// Check if this is a CSP/network restriction - disable sync permanently for this session
|
|
1445
|
+
if (this.isNetworkRestrictionError(error)) {
|
|
1446
|
+
this.syncDisabled = true;
|
|
1447
|
+
if (config.debug) {
|
|
1448
|
+
console.log("[SpeechOS] Settings sync disabled (CSP/network restriction), using localStorage only");
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
else if (config.debug) {
|
|
1452
|
+
console.warn("[SpeechOS] Failed to load settings from server:", errorMessage);
|
|
1453
|
+
}
|
|
1454
|
+
events.emit("settings:syncFailed", { error: errorMessage });
|
|
1455
|
+
// Continue with local settings on error
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Merge server settings with local (server wins).
|
|
1460
|
+
* Uses store setters to update memory cache - localStorage is a fallback.
|
|
1461
|
+
*/
|
|
1462
|
+
mergeSettings(serverSettings) {
|
|
1463
|
+
// Language settings - server wins
|
|
1464
|
+
if (serverSettings.language) {
|
|
1465
|
+
setLanguageSettings(serverSettings.language);
|
|
1466
|
+
}
|
|
1467
|
+
// Vocabulary - server wins
|
|
1468
|
+
if (serverSettings.vocabulary) {
|
|
1469
|
+
setVocabulary(serverSettings.vocabulary);
|
|
1470
|
+
}
|
|
1471
|
+
// Snippets - server wins
|
|
1472
|
+
if (serverSettings.snippets) {
|
|
1473
|
+
setSnippets(serverSettings.snippets);
|
|
1474
|
+
}
|
|
1475
|
+
// History - server wins
|
|
1476
|
+
if (serverSettings.history) {
|
|
1477
|
+
setTranscripts(serverSettings.history);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Schedule a debounced sync to server
|
|
1482
|
+
*/
|
|
1483
|
+
scheduleSyncToServer() {
|
|
1484
|
+
const token = getSettingsToken();
|
|
1485
|
+
if (!token || this.syncDisabled) {
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
// Cancel any pending sync
|
|
1489
|
+
if (this.syncTimer) {
|
|
1490
|
+
clearTimeout(this.syncTimer);
|
|
1491
|
+
}
|
|
1492
|
+
// Schedule new sync
|
|
1493
|
+
this.syncTimer = setTimeout(() => {
|
|
1494
|
+
this.syncToServer();
|
|
1495
|
+
}, SYNC_DEBOUNCE_MS);
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Sync current settings to server
|
|
1499
|
+
*/
|
|
1500
|
+
async syncToServer() {
|
|
1501
|
+
const token = getSettingsToken();
|
|
1502
|
+
if (!token || this.isSyncing || this.syncDisabled) {
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
this.isSyncing = true;
|
|
1506
|
+
const config = getConfig();
|
|
1507
|
+
try {
|
|
1508
|
+
const languageSettings = getLanguageSettings();
|
|
1509
|
+
const vocabulary = getVocabulary();
|
|
1510
|
+
const snippets = getSnippets();
|
|
1511
|
+
const transcripts = getTranscripts();
|
|
1512
|
+
const payload = {
|
|
1513
|
+
language: {
|
|
1514
|
+
inputLanguageCode: languageSettings.inputLanguageCode,
|
|
1515
|
+
outputLanguageCode: languageSettings.outputLanguageCode,
|
|
1516
|
+
smartFormat: languageSettings.smartFormat,
|
|
1517
|
+
},
|
|
1518
|
+
vocabulary: vocabulary.map((v) => ({
|
|
1519
|
+
id: v.id,
|
|
1520
|
+
term: v.term,
|
|
1521
|
+
createdAt: v.createdAt,
|
|
1522
|
+
})),
|
|
1523
|
+
snippets: snippets.map((s) => ({
|
|
1524
|
+
id: s.id,
|
|
1525
|
+
trigger: s.trigger,
|
|
1526
|
+
expansion: s.expansion,
|
|
1527
|
+
createdAt: s.createdAt,
|
|
1528
|
+
})),
|
|
1529
|
+
// Sync history (excluding commandConfig to reduce payload size)
|
|
1530
|
+
history: transcripts.map((t) => ({
|
|
1531
|
+
id: t.id,
|
|
1532
|
+
text: t.text,
|
|
1533
|
+
timestamp: t.timestamp,
|
|
1534
|
+
action: t.action,
|
|
1535
|
+
...(t.originalText && { originalText: t.originalText }),
|
|
1536
|
+
...(t.inputText && { inputText: t.inputText }),
|
|
1537
|
+
...(t.commandResult !== undefined && { commandResult: t.commandResult }),
|
|
1538
|
+
})),
|
|
1539
|
+
};
|
|
1540
|
+
const response = await this.doFetch(`${config.host}/api/user-settings/`, {
|
|
1541
|
+
method: "PUT",
|
|
1542
|
+
headers: {
|
|
1543
|
+
Authorization: `Bearer ${token}`,
|
|
1544
|
+
"Content-Type": "application/json",
|
|
1545
|
+
},
|
|
1546
|
+
body: JSON.stringify(payload),
|
|
1547
|
+
});
|
|
1548
|
+
if (response.status === 401 || response.status === 403) {
|
|
1549
|
+
this.handleTokenExpired();
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
if (!response.ok) {
|
|
1553
|
+
throw new Error(`Server returned ${response.status}`);
|
|
1554
|
+
}
|
|
1555
|
+
// Reset retry count on success
|
|
1556
|
+
this.retryCount = 0;
|
|
1557
|
+
events.emit("settings:synced", undefined);
|
|
1558
|
+
if (config.debug) {
|
|
1559
|
+
console.log("[SpeechOS] Settings synced to server");
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
catch (error) {
|
|
1563
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1564
|
+
// Check if this is a CSP/network restriction - disable sync permanently for this session
|
|
1565
|
+
if (this.isNetworkRestrictionError(error)) {
|
|
1566
|
+
this.syncDisabled = true;
|
|
1567
|
+
if (config.debug) {
|
|
1568
|
+
console.log("[SpeechOS] Settings sync disabled (CSP/network restriction), using localStorage only");
|
|
1569
|
+
}
|
|
1570
|
+
events.emit("settings:syncFailed", { error: errorMessage });
|
|
1571
|
+
// Don't retry - CSP errors are permanent
|
|
1572
|
+
}
|
|
1573
|
+
else {
|
|
1574
|
+
if (config.debug) {
|
|
1575
|
+
console.warn("[SpeechOS] Failed to sync settings to server:", errorMessage);
|
|
1576
|
+
}
|
|
1577
|
+
events.emit("settings:syncFailed", { error: errorMessage });
|
|
1578
|
+
// Retry with exponential backoff (only for transient errors)
|
|
1579
|
+
this.scheduleRetry();
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
finally {
|
|
1583
|
+
this.isSyncing = false;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Schedule a retry with exponential backoff
|
|
1588
|
+
*/
|
|
1589
|
+
scheduleRetry() {
|
|
1590
|
+
if (this.retryCount >= MAX_RETRIES) {
|
|
1591
|
+
const config = getConfig();
|
|
1592
|
+
if (config.debug) {
|
|
1593
|
+
console.warn("[SpeechOS] Max retries reached, giving up sync");
|
|
1594
|
+
}
|
|
1595
|
+
this.retryCount = 0;
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
this.retryCount++;
|
|
1599
|
+
const delay = BASE_RETRY_DELAY_MS * Math.pow(2, this.retryCount - 1);
|
|
1600
|
+
this.syncTimer = setTimeout(() => {
|
|
1601
|
+
this.syncToServer();
|
|
1602
|
+
}, delay);
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* Check if an error is a CSP or network restriction error
|
|
1606
|
+
* These errors are permanent and shouldn't trigger retries
|
|
1607
|
+
*/
|
|
1608
|
+
isNetworkRestrictionError(error) {
|
|
1609
|
+
if (error instanceof TypeError) {
|
|
1610
|
+
const message = error.message.toLowerCase();
|
|
1611
|
+
// Common CSP/network error messages
|
|
1612
|
+
return (message.includes("failed to fetch") ||
|
|
1613
|
+
message.includes("network request failed") ||
|
|
1614
|
+
message.includes("content security policy") ||
|
|
1615
|
+
message.includes("csp") ||
|
|
1616
|
+
message.includes("blocked"));
|
|
1617
|
+
}
|
|
1618
|
+
return false;
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Handle token expiration
|
|
1622
|
+
*/
|
|
1623
|
+
handleTokenExpired() {
|
|
1624
|
+
clearSettingsToken();
|
|
1625
|
+
events.emit("settings:tokenExpired", undefined);
|
|
1626
|
+
const config = getConfig();
|
|
1627
|
+
if (config.debug) {
|
|
1628
|
+
console.warn("[SpeechOS] Settings token expired");
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
/**
|
|
1632
|
+
* Check if sync is enabled (token is configured)
|
|
1633
|
+
*/
|
|
1634
|
+
isEnabled() {
|
|
1635
|
+
return !!getSettingsToken();
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
// Singleton instance
|
|
1639
|
+
const settingsSync = new SettingsSync();
|
|
1640
|
+
|
|
1104
1641
|
/******************************************************************************
|
|
1105
1642
|
Copyright (c) Microsoft Corporation.
|
|
1106
1643
|
|
|
@@ -1184,6 +1721,9 @@ const t$1=t=>(e,o)=>{ void 0!==o?o.addInitializer(()=>{customElements.define(t,e
|
|
|
1184
1721
|
*/
|
|
1185
1722
|
const themeStyles = i$4 `
|
|
1186
1723
|
:host {
|
|
1724
|
+
/* Font stack - system fonts for consistent rendering across sites */
|
|
1725
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
1726
|
+
|
|
1187
1727
|
/* Color tokens */
|
|
1188
1728
|
--speechos-primary: #10B981;
|
|
1189
1729
|
--speechos-primary-hover: #059669;
|
|
@@ -1307,100 +1847,6 @@ i$4 `
|
|
|
1307
1847
|
}
|
|
1308
1848
|
`;
|
|
1309
1849
|
|
|
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
1850
|
function isNativeField(field) {
|
|
1405
1851
|
return field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement;
|
|
1406
1852
|
}
|
|
@@ -3398,6 +3844,8 @@ const modalLayoutStyles = i$4 `
|
|
|
3398
3844
|
inset: 0;
|
|
3399
3845
|
pointer-events: none;
|
|
3400
3846
|
z-index: calc(var(--speechos-z-base) + 100);
|
|
3847
|
+
/* Ensure consistent font rendering across all sites */
|
|
3848
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
3401
3849
|
}
|
|
3402
3850
|
|
|
3403
3851
|
.modal-overlay {
|
|
@@ -3886,6 +4334,7 @@ let SpeechOSHistoryTab = class SpeechOSHistoryTab extends i$1 {
|
|
|
3886
4334
|
constructor() {
|
|
3887
4335
|
super(...arguments);
|
|
3888
4336
|
this.transcripts = [];
|
|
4337
|
+
this.unsubscribeSettingsLoaded = null;
|
|
3889
4338
|
}
|
|
3890
4339
|
static { this.styles = [
|
|
3891
4340
|
themeStyles,
|
|
@@ -4022,11 +4471,36 @@ let SpeechOSHistoryTab = class SpeechOSHistoryTab extends i$1 {
|
|
|
4022
4471
|
background: rgba(239, 68, 68, 0.18);
|
|
4023
4472
|
border-color: rgba(239, 68, 68, 0.25);
|
|
4024
4473
|
}
|
|
4474
|
+
|
|
4475
|
+
.command-matched {
|
|
4476
|
+
font-size: 12px;
|
|
4477
|
+
color: rgba(255, 255, 255, 0.5);
|
|
4478
|
+
margin-top: 6px;
|
|
4479
|
+
}
|
|
4480
|
+
|
|
4481
|
+
.command-matched code {
|
|
4482
|
+
background: rgba(245, 158, 11, 0.15);
|
|
4483
|
+
color: #fbbf24;
|
|
4484
|
+
padding: 2px 6px;
|
|
4485
|
+
border-radius: 4px;
|
|
4486
|
+
font-family: monospace;
|
|
4487
|
+
}
|
|
4025
4488
|
`,
|
|
4026
4489
|
]; }
|
|
4027
4490
|
connectedCallback() {
|
|
4028
4491
|
super.connectedCallback();
|
|
4029
4492
|
this.loadTranscripts();
|
|
4493
|
+
// Refresh when settings are loaded from the server (history may have changed)
|
|
4494
|
+
this.unsubscribeSettingsLoaded = events.on("settings:loaded", () => {
|
|
4495
|
+
this.loadTranscripts();
|
|
4496
|
+
});
|
|
4497
|
+
}
|
|
4498
|
+
disconnectedCallback() {
|
|
4499
|
+
super.disconnectedCallback();
|
|
4500
|
+
if (this.unsubscribeSettingsLoaded) {
|
|
4501
|
+
this.unsubscribeSettingsLoaded();
|
|
4502
|
+
this.unsubscribeSettingsLoaded = null;
|
|
4503
|
+
}
|
|
4030
4504
|
}
|
|
4031
4505
|
/** Reload transcripts from store */
|
|
4032
4506
|
refresh() {
|
|
@@ -4097,7 +4571,13 @@ let SpeechOSHistoryTab = class SpeechOSHistoryTab extends i$1 {
|
|
|
4097
4571
|
renderCommandDetails(entry) {
|
|
4098
4572
|
// Show the transcript text (what the user said)
|
|
4099
4573
|
const displayText = entry.inputText || entry.text;
|
|
4100
|
-
|
|
4574
|
+
const commandName = entry.commandResult?.name;
|
|
4575
|
+
return b `
|
|
4576
|
+
<div class="transcript-text">${displayText}</div>
|
|
4577
|
+
${commandName
|
|
4578
|
+
? b `<div class="command-matched">Matched: <code>${commandName}</code></div>`
|
|
4579
|
+
: null}
|
|
4580
|
+
`;
|
|
4101
4581
|
}
|
|
4102
4582
|
getCopyText(entry) {
|
|
4103
4583
|
if (entry.action === "command") {
|
|
@@ -4647,6 +5127,7 @@ let SpeechOSSettingsTab = class SpeechOSSettingsTab extends i$1 {
|
|
|
4647
5127
|
this.permissionGranted = false;
|
|
4648
5128
|
this.smartFormatEnabled = true;
|
|
4649
5129
|
this.savedIndicatorTimeout = null;
|
|
5130
|
+
this.unsubscribeSettingsLoaded = null;
|
|
4650
5131
|
}
|
|
4651
5132
|
static { this.styles = [
|
|
4652
5133
|
themeStyles,
|
|
@@ -4740,6 +5221,16 @@ let SpeechOSSettingsTab = class SpeechOSSettingsTab extends i$1 {
|
|
|
4740
5221
|
padding: 8px;
|
|
4741
5222
|
}
|
|
4742
5223
|
|
|
5224
|
+
.settings-select:disabled {
|
|
5225
|
+
opacity: 0.4;
|
|
5226
|
+
cursor: not-allowed;
|
|
5227
|
+
}
|
|
5228
|
+
|
|
5229
|
+
.settings-select:disabled:hover {
|
|
5230
|
+
border-color: rgba(255, 255, 255, 0.08);
|
|
5231
|
+
background: rgba(0, 0, 0, 0.3);
|
|
5232
|
+
}
|
|
5233
|
+
|
|
4743
5234
|
.settings-select-arrow {
|
|
4744
5235
|
position: absolute;
|
|
4745
5236
|
right: 14px;
|
|
@@ -4821,12 +5312,20 @@ let SpeechOSSettingsTab = class SpeechOSSettingsTab extends i$1 {
|
|
|
4821
5312
|
connectedCallback() {
|
|
4822
5313
|
super.connectedCallback();
|
|
4823
5314
|
this.loadSettings();
|
|
5315
|
+
// Refresh when settings are loaded from the server
|
|
5316
|
+
this.unsubscribeSettingsLoaded = events.on("settings:loaded", () => {
|
|
5317
|
+
this.loadSettings();
|
|
5318
|
+
});
|
|
4824
5319
|
}
|
|
4825
5320
|
disconnectedCallback() {
|
|
4826
5321
|
super.disconnectedCallback();
|
|
4827
5322
|
if (this.savedIndicatorTimeout) {
|
|
4828
5323
|
clearTimeout(this.savedIndicatorTimeout);
|
|
4829
5324
|
}
|
|
5325
|
+
if (this.unsubscribeSettingsLoaded) {
|
|
5326
|
+
this.unsubscribeSettingsLoaded();
|
|
5327
|
+
this.unsubscribeSettingsLoaded = null;
|
|
5328
|
+
}
|
|
4830
5329
|
this.isTestingMic = false;
|
|
4831
5330
|
}
|
|
4832
5331
|
/** Called when tab becomes active */
|
|
@@ -4913,13 +5412,14 @@ let SpeechOSSettingsTab = class SpeechOSSettingsTab extends i$1 {
|
|
|
4913
5412
|
setSmartFormatEnabled(this.smartFormatEnabled);
|
|
4914
5413
|
this.showSaved();
|
|
4915
5414
|
}
|
|
4916
|
-
renderLanguageSelector(selectedCode, onChange) {
|
|
5415
|
+
renderLanguageSelector(selectedCode, onChange, disabled = false) {
|
|
4917
5416
|
return b `
|
|
4918
5417
|
<div class="settings-select-wrapper">
|
|
4919
5418
|
<select
|
|
4920
5419
|
class="settings-select"
|
|
4921
5420
|
.value="${selectedCode}"
|
|
4922
5421
|
@change="${onChange}"
|
|
5422
|
+
?disabled="${disabled}"
|
|
4923
5423
|
>
|
|
4924
5424
|
${SUPPORTED_LANGUAGES.map((lang) => b `
|
|
4925
5425
|
<option
|
|
@@ -5037,7 +5537,7 @@ let SpeechOSSettingsTab = class SpeechOSSettingsTab extends i$1 {
|
|
|
5037
5537
|
<div class="settings-section-description">
|
|
5038
5538
|
AI automatically removes filler words, adds punctuation, and polishes
|
|
5039
5539
|
your text. Disable for raw transcription output. Note: disabling also
|
|
5040
|
-
turns off text snippets.
|
|
5540
|
+
turns off text snippets and output language translation.
|
|
5041
5541
|
</div>
|
|
5042
5542
|
<div class="settings-toggle-row">
|
|
5043
5543
|
<span class="settings-toggle-label">Enable AI formatting</span>
|
|
@@ -5078,9 +5578,13 @@ let SpeechOSSettingsTab = class SpeechOSSettingsTab extends i$1 {
|
|
|
5078
5578
|
</div>
|
|
5079
5579
|
<div class="settings-section-description">
|
|
5080
5580
|
The language for your transcribed text. Usually the same as input, but
|
|
5081
|
-
can differ for translation.
|
|
5581
|
+
can differ for translation.${!this.smartFormatEnabled
|
|
5582
|
+
? " Requires Smart Format to be enabled."
|
|
5583
|
+
: ""}
|
|
5082
5584
|
</div>
|
|
5083
|
-
${this.renderLanguageSelector(this.
|
|
5585
|
+
${this.renderLanguageSelector(this.smartFormatEnabled
|
|
5586
|
+
? this.selectedOutputLanguageCode
|
|
5587
|
+
: this.selectedInputLanguageCode, this.handleOutputLanguageChange.bind(this), !this.smartFormatEnabled)}
|
|
5084
5588
|
</div>
|
|
5085
5589
|
`;
|
|
5086
5590
|
}
|
|
@@ -5125,6 +5629,7 @@ let SpeechOSSnippetsTab = class SpeechOSSnippetsTab extends i$1 {
|
|
|
5125
5629
|
this.trigger = "";
|
|
5126
5630
|
this.expansion = "";
|
|
5127
5631
|
this.error = "";
|
|
5632
|
+
this.unsubscribeSettingsLoaded = null;
|
|
5128
5633
|
}
|
|
5129
5634
|
static { this.styles = [
|
|
5130
5635
|
themeStyles,
|
|
@@ -5230,6 +5735,17 @@ let SpeechOSSnippetsTab = class SpeechOSSnippetsTab extends i$1 {
|
|
|
5230
5735
|
connectedCallback() {
|
|
5231
5736
|
super.connectedCallback();
|
|
5232
5737
|
this.loadSnippets();
|
|
5738
|
+
// Refresh when settings are loaded from the server
|
|
5739
|
+
this.unsubscribeSettingsLoaded = events.on("settings:loaded", () => {
|
|
5740
|
+
this.loadSnippets();
|
|
5741
|
+
});
|
|
5742
|
+
}
|
|
5743
|
+
disconnectedCallback() {
|
|
5744
|
+
super.disconnectedCallback();
|
|
5745
|
+
if (this.unsubscribeSettingsLoaded) {
|
|
5746
|
+
this.unsubscribeSettingsLoaded();
|
|
5747
|
+
this.unsubscribeSettingsLoaded = null;
|
|
5748
|
+
}
|
|
5233
5749
|
}
|
|
5234
5750
|
/** Reload snippets from store */
|
|
5235
5751
|
refresh() {
|
|
@@ -5472,6 +5988,7 @@ let SpeechOSVocabularyTab = class SpeechOSVocabularyTab extends i$1 {
|
|
|
5472
5988
|
this.showForm = false;
|
|
5473
5989
|
this.term = "";
|
|
5474
5990
|
this.error = "";
|
|
5991
|
+
this.unsubscribeSettingsLoaded = null;
|
|
5475
5992
|
}
|
|
5476
5993
|
static { this.styles = [
|
|
5477
5994
|
themeStyles,
|
|
@@ -5524,6 +6041,17 @@ let SpeechOSVocabularyTab = class SpeechOSVocabularyTab extends i$1 {
|
|
|
5524
6041
|
connectedCallback() {
|
|
5525
6042
|
super.connectedCallback();
|
|
5526
6043
|
this.loadVocabulary();
|
|
6044
|
+
// Refresh when settings are loaded from the server
|
|
6045
|
+
this.unsubscribeSettingsLoaded = events.on("settings:loaded", () => {
|
|
6046
|
+
this.loadVocabulary();
|
|
6047
|
+
});
|
|
6048
|
+
}
|
|
6049
|
+
disconnectedCallback() {
|
|
6050
|
+
super.disconnectedCallback();
|
|
6051
|
+
if (this.unsubscribeSettingsLoaded) {
|
|
6052
|
+
this.unsubscribeSettingsLoaded();
|
|
6053
|
+
this.unsubscribeSettingsLoaded = null;
|
|
6054
|
+
}
|
|
5527
6055
|
}
|
|
5528
6056
|
/** Reload vocabulary from store */
|
|
5529
6057
|
refresh() {
|
|
@@ -7048,7 +7576,14 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7048
7576
|
state.hide();
|
|
7049
7577
|
}
|
|
7050
7578
|
handleSettingsClick() {
|
|
7051
|
-
|
|
7579
|
+
if (useExternalSettings()) {
|
|
7580
|
+
const host = getConfig().host;
|
|
7581
|
+
const fullUrl = `${host}/a/extension-settings`;
|
|
7582
|
+
window.open(fullUrl, '_blank', 'noopener,noreferrer');
|
|
7583
|
+
}
|
|
7584
|
+
else {
|
|
7585
|
+
this.settingsOpen = true;
|
|
7586
|
+
}
|
|
7052
7587
|
}
|
|
7053
7588
|
handleDragStart(e) {
|
|
7054
7589
|
if (e.button !== 0)
|
|
@@ -7589,10 +8124,17 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7589
8124
|
this.editSelectionStart = null;
|
|
7590
8125
|
this.editSelectionEnd = null;
|
|
7591
8126
|
this.editSelectedText = "";
|
|
7592
|
-
// Open settings modal
|
|
7593
|
-
|
|
8127
|
+
// Open settings - either external URL or modal
|
|
8128
|
+
if (useExternalSettings()) {
|
|
8129
|
+
const host = getConfig().host;
|
|
8130
|
+
const fullUrl = `${host}/a/extension-settings`;
|
|
8131
|
+
window.open(fullUrl, '_blank', 'noopener,noreferrer');
|
|
8132
|
+
}
|
|
8133
|
+
else {
|
|
8134
|
+
this.settingsOpen = true;
|
|
8135
|
+
}
|
|
7594
8136
|
if (getConfig().debug) {
|
|
7595
|
-
console.log("[SpeechOS] Settings
|
|
8137
|
+
console.log("[SpeechOS] Settings opened from no-audio warning", { useExternalSettings: useExternalSettings() });
|
|
7596
8138
|
}
|
|
7597
8139
|
await disconnectPromise;
|
|
7598
8140
|
}
|
|
@@ -7801,6 +8343,30 @@ SpeechOSWidget = SpeechOSWidget_1 = __decorate([
|
|
|
7801
8343
|
t$1("speechos-widget")
|
|
7802
8344
|
], SpeechOSWidget);
|
|
7803
8345
|
|
|
8346
|
+
/**
|
|
8347
|
+
* UI module exports
|
|
8348
|
+
* Lit-based Shadow DOM components
|
|
8349
|
+
*/
|
|
8350
|
+
// Patch customElements.define to silently ignore duplicate registrations for speechos-* elements.
|
|
8351
|
+
// This prevents errors when the extension loads on a page that already has SpeechOS.
|
|
8352
|
+
// The patch is scoped to only affect speechos-* tags to avoid unintended effects on host pages.
|
|
8353
|
+
const originalDefine = customElements.define.bind(customElements);
|
|
8354
|
+
customElements.define = (name, constructor, options) => {
|
|
8355
|
+
// Only intercept speechos-* elements
|
|
8356
|
+
if (name.startsWith("speechos-")) {
|
|
8357
|
+
if (customElements.get(name) === undefined) {
|
|
8358
|
+
originalDefine(name, constructor, options);
|
|
8359
|
+
}
|
|
8360
|
+
// Skip silently if already registered
|
|
8361
|
+
}
|
|
8362
|
+
else {
|
|
8363
|
+
// Pass through for non-speechos elements
|
|
8364
|
+
originalDefine(name, constructor, options);
|
|
8365
|
+
}
|
|
8366
|
+
};
|
|
8367
|
+
// Restore original customElements.define after our components are registered
|
|
8368
|
+
customElements.define = originalDefine;
|
|
8369
|
+
|
|
7804
8370
|
/**
|
|
7805
8371
|
* Main SpeechOS Client SDK class
|
|
7806
8372
|
* Composes core logic with UI components
|
|
@@ -7882,6 +8448,13 @@ class SpeechOS {
|
|
|
7882
8448
|
state.show();
|
|
7883
8449
|
}
|
|
7884
8450
|
this.isInitialized = true;
|
|
8451
|
+
// Initialize settings sync if token is configured
|
|
8452
|
+
// This loads settings from server and subscribes to changes
|
|
8453
|
+
settingsSync.init().catch((error) => {
|
|
8454
|
+
if (finalConfig.debug) {
|
|
8455
|
+
console.warn("[SpeechOS] Settings sync initialization failed:", error);
|
|
8456
|
+
}
|
|
8457
|
+
});
|
|
7885
8458
|
// Log initialization in debug mode
|
|
7886
8459
|
if (finalConfig.debug) {
|
|
7887
8460
|
console.log("[SpeechOS] Initialized with config:", finalConfig);
|
|
@@ -7912,6 +8485,8 @@ class SpeechOS {
|
|
|
7912
8485
|
resetClientConfig();
|
|
7913
8486
|
// Reset text input handler to default
|
|
7914
8487
|
resetTextInputHandler();
|
|
8488
|
+
// Stop settings sync
|
|
8489
|
+
settingsSync.destroy();
|
|
7915
8490
|
// Clear instance
|
|
7916
8491
|
this.instance = null;
|
|
7917
8492
|
this.isInitialized = false;
|
|
@@ -8073,5 +8648,5 @@ class SpeechOS {
|
|
|
8073
8648
|
// Version
|
|
8074
8649
|
const VERSION = "0.1.0";
|
|
8075
8650
|
|
|
8076
|
-
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 };
|
|
8651
|
+
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, useExternalSettings, vocabularyStore };
|
|
8077
8652
|
//# sourceMappingURL=index.js.map
|