@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.iife.js
CHANGED
|
@@ -26911,7 +26911,9 @@
|
|
|
26911
26911
|
userId: "",
|
|
26912
26912
|
host: DEFAULT_HOST,
|
|
26913
26913
|
debug: false,
|
|
26914
|
-
webSocketFactory: void 0
|
|
26914
|
+
webSocketFactory: void 0,
|
|
26915
|
+
fetchHandler: void 0,
|
|
26916
|
+
settingsToken: void 0
|
|
26915
26917
|
};
|
|
26916
26918
|
/**
|
|
26917
26919
|
* Validates and merges user config with defaults
|
|
@@ -26925,7 +26927,9 @@
|
|
|
26925
26927
|
userId: userConfig.userId ?? defaultConfig.userId,
|
|
26926
26928
|
host: userConfig.host ?? defaultConfig.host,
|
|
26927
26929
|
debug: userConfig.debug ?? defaultConfig.debug,
|
|
26928
|
-
webSocketFactory: userConfig.webSocketFactory ?? defaultConfig.webSocketFactory
|
|
26930
|
+
webSocketFactory: userConfig.webSocketFactory ?? defaultConfig.webSocketFactory,
|
|
26931
|
+
fetchHandler: userConfig.fetchHandler ?? defaultConfig.fetchHandler,
|
|
26932
|
+
settingsToken: userConfig.settingsToken ?? defaultConfig.settingsToken
|
|
26929
26933
|
};
|
|
26930
26934
|
}
|
|
26931
26935
|
/**
|
|
@@ -26962,6 +26966,29 @@
|
|
|
26962
26966
|
};
|
|
26963
26967
|
}
|
|
26964
26968
|
/**
|
|
26969
|
+
* Get the settings token from the current configuration
|
|
26970
|
+
* @returns The settings token or undefined if not configured
|
|
26971
|
+
*/
|
|
26972
|
+
function getSettingsToken() {
|
|
26973
|
+
return currentConfig.settingsToken;
|
|
26974
|
+
}
|
|
26975
|
+
/**
|
|
26976
|
+
* Clear the settings token (e.g., when it expires)
|
|
26977
|
+
*/
|
|
26978
|
+
function clearSettingsToken() {
|
|
26979
|
+
currentConfig = {
|
|
26980
|
+
...currentConfig,
|
|
26981
|
+
settingsToken: void 0
|
|
26982
|
+
};
|
|
26983
|
+
}
|
|
26984
|
+
/**
|
|
26985
|
+
* Get the fetch handler from the current configuration
|
|
26986
|
+
* @returns The fetch handler or undefined if not configured
|
|
26987
|
+
*/
|
|
26988
|
+
function getFetchHandler() {
|
|
26989
|
+
return currentConfig.fetchHandler;
|
|
26990
|
+
}
|
|
26991
|
+
/**
|
|
26965
26992
|
* LocalStorage key for anonymous ID persistence
|
|
26966
26993
|
*/
|
|
26967
26994
|
const ANONYMOUS_ID_KEY = "speechos_anonymous_id";
|
|
@@ -27214,14 +27241,16 @@
|
|
|
27214
27241
|
});
|
|
27215
27242
|
}
|
|
27216
27243
|
/**
|
|
27217
|
-
* Complete the recording flow and return to idle
|
|
27244
|
+
* Complete the recording flow and return to idle.
|
|
27245
|
+
* Keeps widget visible but collapsed (just mic button, no action bubbles).
|
|
27218
27246
|
*/
|
|
27219
27247
|
completeRecording() {
|
|
27220
27248
|
this.setState({
|
|
27221
27249
|
recordingState: "idle",
|
|
27222
27250
|
activeAction: null,
|
|
27223
27251
|
isConnected: false,
|
|
27224
|
-
isMicEnabled: false
|
|
27252
|
+
isMicEnabled: false,
|
|
27253
|
+
isExpanded: false
|
|
27225
27254
|
});
|
|
27226
27255
|
}
|
|
27227
27256
|
/**
|
|
@@ -29245,6 +29274,11 @@
|
|
|
29245
29274
|
* Persists input language preferences to localStorage
|
|
29246
29275
|
*/
|
|
29247
29276
|
const STORAGE_KEY$4 = "speechos_language_settings";
|
|
29277
|
+
/**
|
|
29278
|
+
* In-memory cache for language settings. When server sync is enabled, this is the
|
|
29279
|
+
* source of truth. localStorage is only used when server sync is disabled.
|
|
29280
|
+
*/
|
|
29281
|
+
let memoryCache$3 = null;
|
|
29248
29282
|
/**
|
|
29249
29283
|
* Supported input languages for speech recognition
|
|
29250
29284
|
* Each language has a name, primary code, and available variants
|
|
@@ -29296,9 +29330,13 @@
|
|
|
29296
29330
|
smartFormat: true,
|
|
29297
29331
|
};
|
|
29298
29332
|
/**
|
|
29299
|
-
* Get current language settings from
|
|
29333
|
+
* Get current language settings. Prefers in-memory cache (from server sync),
|
|
29334
|
+
* then falls back to localStorage.
|
|
29300
29335
|
*/
|
|
29301
29336
|
function getLanguageSettings() {
|
|
29337
|
+
if (memoryCache$3 !== null) {
|
|
29338
|
+
return { ...memoryCache$3 };
|
|
29339
|
+
}
|
|
29302
29340
|
try {
|
|
29303
29341
|
const stored = localStorage.getItem(STORAGE_KEY$4);
|
|
29304
29342
|
if (!stored)
|
|
@@ -29310,14 +29348,27 @@
|
|
|
29310
29348
|
}
|
|
29311
29349
|
}
|
|
29312
29350
|
/**
|
|
29313
|
-
*
|
|
29351
|
+
* Set language settings directly (used by settings sync from server data).
|
|
29352
|
+
*/
|
|
29353
|
+
function setLanguageSettings(settings) {
|
|
29354
|
+
memoryCache$3 = { ...defaultSettings$1, ...settings };
|
|
29355
|
+
}
|
|
29356
|
+
/**
|
|
29357
|
+
* Reset memory cache (for testing only)
|
|
29358
|
+
*/
|
|
29359
|
+
function resetMemoryCache$2() {
|
|
29360
|
+
memoryCache$3 = null;
|
|
29361
|
+
}
|
|
29362
|
+
/**
|
|
29363
|
+
* Save language settings (updates memory cache and tries localStorage)
|
|
29314
29364
|
*/
|
|
29315
29365
|
function saveLanguageSettings(settings) {
|
|
29366
|
+
memoryCache$3 = settings;
|
|
29316
29367
|
try {
|
|
29317
29368
|
localStorage.setItem(STORAGE_KEY$4, JSON.stringify(settings));
|
|
29318
29369
|
}
|
|
29319
29370
|
catch {
|
|
29320
|
-
// localStorage full or unavailable -
|
|
29371
|
+
// localStorage full or unavailable - memory cache still updated
|
|
29321
29372
|
}
|
|
29322
29373
|
}
|
|
29323
29374
|
/**
|
|
@@ -29421,6 +29472,7 @@
|
|
|
29421
29472
|
* Reset language settings to defaults
|
|
29422
29473
|
*/
|
|
29423
29474
|
function resetLanguageSettings() {
|
|
29475
|
+
memoryCache$3 = null;
|
|
29424
29476
|
try {
|
|
29425
29477
|
localStorage.removeItem(STORAGE_KEY$4);
|
|
29426
29478
|
}
|
|
@@ -29430,6 +29482,7 @@
|
|
|
29430
29482
|
}
|
|
29431
29483
|
const languageSettings = {
|
|
29432
29484
|
getLanguageSettings,
|
|
29485
|
+
setLanguageSettings,
|
|
29433
29486
|
getInputLanguageCode,
|
|
29434
29487
|
setInputLanguageCode,
|
|
29435
29488
|
getOutputLanguageCode,
|
|
@@ -29440,6 +29493,7 @@
|
|
|
29440
29493
|
getSmartFormatEnabled,
|
|
29441
29494
|
setSmartFormatEnabled,
|
|
29442
29495
|
resetLanguageSettings,
|
|
29496
|
+
resetMemoryCache: resetMemoryCache$2,
|
|
29443
29497
|
SUPPORTED_LANGUAGES,
|
|
29444
29498
|
// Legacy aliases
|
|
29445
29499
|
getLanguageCode,
|
|
@@ -29455,6 +29509,11 @@
|
|
|
29455
29509
|
const MAX_SNIPPETS = 25;
|
|
29456
29510
|
const MAX_TRIGGER_LENGTH = 30;
|
|
29457
29511
|
const MAX_EXPANSION_LENGTH = 300;
|
|
29512
|
+
/**
|
|
29513
|
+
* In-memory cache for snippets. When server sync is enabled, this is the
|
|
29514
|
+
* source of truth. localStorage is only used when server sync is disabled.
|
|
29515
|
+
*/
|
|
29516
|
+
let memoryCache$2 = null;
|
|
29458
29517
|
/**
|
|
29459
29518
|
* Generate a unique ID for snippet entries
|
|
29460
29519
|
*/
|
|
@@ -29462,15 +29521,18 @@
|
|
|
29462
29521
|
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
29463
29522
|
}
|
|
29464
29523
|
/**
|
|
29465
|
-
* Get all snippets from
|
|
29524
|
+
* Get all snippets. Prefers in-memory cache (from server sync),
|
|
29525
|
+
* then falls back to localStorage.
|
|
29466
29526
|
*/
|
|
29467
29527
|
function getSnippets() {
|
|
29528
|
+
if (memoryCache$2 !== null) {
|
|
29529
|
+
return [...memoryCache$2].sort((a, b) => b.createdAt - a.createdAt);
|
|
29530
|
+
}
|
|
29468
29531
|
try {
|
|
29469
29532
|
const stored = localStorage.getItem(STORAGE_KEY$3);
|
|
29470
29533
|
if (!stored)
|
|
29471
29534
|
return [];
|
|
29472
29535
|
const entries = JSON.parse(stored);
|
|
29473
|
-
// Return newest first
|
|
29474
29536
|
return entries.sort((a, b) => b.createdAt - a.createdAt);
|
|
29475
29537
|
}
|
|
29476
29538
|
catch {
|
|
@@ -29478,14 +29540,27 @@
|
|
|
29478
29540
|
}
|
|
29479
29541
|
}
|
|
29480
29542
|
/**
|
|
29481
|
-
*
|
|
29543
|
+
* Set snippets directly (used by settings sync from server data).
|
|
29544
|
+
*/
|
|
29545
|
+
function setSnippets(snippets) {
|
|
29546
|
+
memoryCache$2 = snippets.slice(0, MAX_SNIPPETS);
|
|
29547
|
+
}
|
|
29548
|
+
/**
|
|
29549
|
+
* Reset memory cache (for testing only)
|
|
29550
|
+
*/
|
|
29551
|
+
function resetMemoryCache$1() {
|
|
29552
|
+
memoryCache$2 = null;
|
|
29553
|
+
}
|
|
29554
|
+
/**
|
|
29555
|
+
* Save snippets (updates memory cache and tries localStorage)
|
|
29482
29556
|
*/
|
|
29483
29557
|
function saveSnippets(snippets) {
|
|
29558
|
+
memoryCache$2 = snippets;
|
|
29484
29559
|
try {
|
|
29485
29560
|
localStorage.setItem(STORAGE_KEY$3, JSON.stringify(snippets));
|
|
29486
29561
|
}
|
|
29487
29562
|
catch {
|
|
29488
|
-
// localStorage full or unavailable -
|
|
29563
|
+
// localStorage full or unavailable - memory cache still updated
|
|
29489
29564
|
}
|
|
29490
29565
|
}
|
|
29491
29566
|
/**
|
|
@@ -29603,13 +29678,14 @@
|
|
|
29603
29678
|
* Clear all snippets
|
|
29604
29679
|
*/
|
|
29605
29680
|
function clearSnippets() {
|
|
29681
|
+
memoryCache$2 = [];
|
|
29606
29682
|
try {
|
|
29607
29683
|
localStorage.removeItem(STORAGE_KEY$3);
|
|
29608
|
-
events.emit("settings:changed", { setting: "snippets" });
|
|
29609
29684
|
}
|
|
29610
29685
|
catch {
|
|
29611
29686
|
// Silently fail
|
|
29612
29687
|
}
|
|
29688
|
+
events.emit("settings:changed", { setting: "snippets" });
|
|
29613
29689
|
}
|
|
29614
29690
|
/**
|
|
29615
29691
|
* Get snippet count info
|
|
@@ -29625,12 +29701,14 @@
|
|
|
29625
29701
|
}
|
|
29626
29702
|
const snippetsStore = {
|
|
29627
29703
|
getSnippets,
|
|
29704
|
+
setSnippets,
|
|
29628
29705
|
addSnippet,
|
|
29629
29706
|
updateSnippet,
|
|
29630
29707
|
deleteSnippet,
|
|
29631
29708
|
clearSnippets,
|
|
29632
29709
|
getSnippetCount,
|
|
29633
29710
|
isAtSnippetLimit,
|
|
29711
|
+
resetMemoryCache: resetMemoryCache$1,
|
|
29634
29712
|
MAX_SNIPPETS,
|
|
29635
29713
|
MAX_TRIGGER_LENGTH,
|
|
29636
29714
|
MAX_EXPANSION_LENGTH,
|
|
@@ -29643,6 +29721,11 @@
|
|
|
29643
29721
|
const STORAGE_KEY$2 = "speechos_vocabulary";
|
|
29644
29722
|
const MAX_TERMS = 50;
|
|
29645
29723
|
const MAX_TERM_LENGTH = 50;
|
|
29724
|
+
/**
|
|
29725
|
+
* In-memory cache for vocabulary. When server sync is enabled, this is the
|
|
29726
|
+
* source of truth. localStorage is only used when server sync is disabled.
|
|
29727
|
+
*/
|
|
29728
|
+
let memoryCache$1 = null;
|
|
29646
29729
|
/**
|
|
29647
29730
|
* Generate a unique ID for vocabulary entries
|
|
29648
29731
|
*/
|
|
@@ -29650,15 +29733,18 @@
|
|
|
29650
29733
|
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
29651
29734
|
}
|
|
29652
29735
|
/**
|
|
29653
|
-
* Get all vocabulary terms from
|
|
29736
|
+
* Get all vocabulary terms. Prefers in-memory cache (from server sync),
|
|
29737
|
+
* then falls back to localStorage.
|
|
29654
29738
|
*/
|
|
29655
29739
|
function getVocabulary() {
|
|
29740
|
+
if (memoryCache$1 !== null) {
|
|
29741
|
+
return [...memoryCache$1].sort((a, b) => b.createdAt - a.createdAt);
|
|
29742
|
+
}
|
|
29656
29743
|
try {
|
|
29657
29744
|
const stored = localStorage.getItem(STORAGE_KEY$2);
|
|
29658
29745
|
if (!stored)
|
|
29659
29746
|
return [];
|
|
29660
29747
|
const entries = JSON.parse(stored);
|
|
29661
|
-
// Return newest first
|
|
29662
29748
|
return entries.sort((a, b) => b.createdAt - a.createdAt);
|
|
29663
29749
|
}
|
|
29664
29750
|
catch {
|
|
@@ -29666,14 +29752,27 @@
|
|
|
29666
29752
|
}
|
|
29667
29753
|
}
|
|
29668
29754
|
/**
|
|
29669
|
-
*
|
|
29755
|
+
* Set vocabulary directly (used by settings sync from server data).
|
|
29756
|
+
*/
|
|
29757
|
+
function setVocabulary(terms) {
|
|
29758
|
+
memoryCache$1 = terms.slice(0, MAX_TERMS);
|
|
29759
|
+
}
|
|
29760
|
+
/**
|
|
29761
|
+
* Reset memory cache (for testing only)
|
|
29762
|
+
*/
|
|
29763
|
+
function resetMemoryCache() {
|
|
29764
|
+
memoryCache$1 = null;
|
|
29765
|
+
}
|
|
29766
|
+
/**
|
|
29767
|
+
* Save vocabulary (updates memory cache and tries localStorage)
|
|
29670
29768
|
*/
|
|
29671
29769
|
function saveVocabulary(terms) {
|
|
29770
|
+
memoryCache$1 = terms;
|
|
29672
29771
|
try {
|
|
29673
29772
|
localStorage.setItem(STORAGE_KEY$2, JSON.stringify(terms));
|
|
29674
29773
|
}
|
|
29675
29774
|
catch {
|
|
29676
|
-
// localStorage full or unavailable -
|
|
29775
|
+
// localStorage full or unavailable - memory cache still updated
|
|
29677
29776
|
}
|
|
29678
29777
|
}
|
|
29679
29778
|
/**
|
|
@@ -29741,13 +29840,14 @@
|
|
|
29741
29840
|
* Clear all vocabulary
|
|
29742
29841
|
*/
|
|
29743
29842
|
function clearVocabulary() {
|
|
29843
|
+
memoryCache$1 = [];
|
|
29744
29844
|
try {
|
|
29745
29845
|
localStorage.removeItem(STORAGE_KEY$2);
|
|
29746
|
-
events.emit("settings:changed", { setting: "vocabulary" });
|
|
29747
29846
|
}
|
|
29748
29847
|
catch {
|
|
29749
29848
|
// Silently fail
|
|
29750
29849
|
}
|
|
29850
|
+
events.emit("settings:changed", { setting: "vocabulary" });
|
|
29751
29851
|
}
|
|
29752
29852
|
/**
|
|
29753
29853
|
* Get vocabulary count info
|
|
@@ -29763,11 +29863,13 @@
|
|
|
29763
29863
|
}
|
|
29764
29864
|
const vocabularyStore = {
|
|
29765
29865
|
getVocabulary,
|
|
29866
|
+
setVocabulary,
|
|
29766
29867
|
addTerm,
|
|
29767
29868
|
deleteTerm,
|
|
29768
29869
|
clearVocabulary,
|
|
29769
29870
|
getVocabularyCount,
|
|
29770
29871
|
isAtVocabularyLimit,
|
|
29872
|
+
resetMemoryCache,
|
|
29771
29873
|
MAX_TERMS,
|
|
29772
29874
|
MAX_TERM_LENGTH,
|
|
29773
29875
|
};
|
|
@@ -29865,6 +29967,462 @@
|
|
|
29865
29967
|
resetAudioSettings,
|
|
29866
29968
|
};
|
|
29867
29969
|
|
|
29970
|
+
/**
|
|
29971
|
+
* Transcript history store
|
|
29972
|
+
* Persists transcripts to localStorage for viewing in the settings modal
|
|
29973
|
+
*/
|
|
29974
|
+
const STORAGE_KEY = "speechos_transcripts";
|
|
29975
|
+
const MAX_ENTRIES = 50;
|
|
29976
|
+
/**
|
|
29977
|
+
* In-memory cache for transcripts. When server sync is enabled, this is the
|
|
29978
|
+
* source of truth. localStorage is only used when server sync is disabled.
|
|
29979
|
+
*/
|
|
29980
|
+
let memoryCache = null;
|
|
29981
|
+
/**
|
|
29982
|
+
* Generate a unique ID for transcript entries
|
|
29983
|
+
*/
|
|
29984
|
+
function generateId() {
|
|
29985
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
29986
|
+
}
|
|
29987
|
+
/**
|
|
29988
|
+
* Get all transcripts. Prefers in-memory cache (from server sync),
|
|
29989
|
+
* then falls back to localStorage.
|
|
29990
|
+
*/
|
|
29991
|
+
function getTranscripts() {
|
|
29992
|
+
// If we have in-memory data (from server sync), use it
|
|
29993
|
+
if (memoryCache !== null) {
|
|
29994
|
+
return [...memoryCache].sort((a, b) => b.timestamp - a.timestamp);
|
|
29995
|
+
}
|
|
29996
|
+
// Fall back to localStorage (when server sync is disabled)
|
|
29997
|
+
try {
|
|
29998
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
29999
|
+
if (!stored)
|
|
30000
|
+
return [];
|
|
30001
|
+
const entries = JSON.parse(stored);
|
|
30002
|
+
return entries.sort((a, b) => b.timestamp - a.timestamp);
|
|
30003
|
+
}
|
|
30004
|
+
catch {
|
|
30005
|
+
return [];
|
|
30006
|
+
}
|
|
30007
|
+
}
|
|
30008
|
+
/**
|
|
30009
|
+
* Set transcripts directly (used by settings sync from server data).
|
|
30010
|
+
* Server data is the source of truth - just update memory cache.
|
|
30011
|
+
*/
|
|
30012
|
+
function setTranscripts(entries) {
|
|
30013
|
+
memoryCache = entries.slice(0, MAX_ENTRIES);
|
|
30014
|
+
}
|
|
30015
|
+
/**
|
|
30016
|
+
* Save a new transcript entry
|
|
30017
|
+
*/
|
|
30018
|
+
function saveTranscript(text, action, originalTextOrOptions) {
|
|
30019
|
+
const entry = {
|
|
30020
|
+
id: generateId(),
|
|
30021
|
+
text,
|
|
30022
|
+
timestamp: Date.now(),
|
|
30023
|
+
action,
|
|
30024
|
+
};
|
|
30025
|
+
// Handle edit action with originalText string
|
|
30026
|
+
if (action === "edit" && typeof originalTextOrOptions === "string") {
|
|
30027
|
+
entry.originalText = originalTextOrOptions;
|
|
30028
|
+
}
|
|
30029
|
+
// Handle command action with options object
|
|
30030
|
+
if (action === "command" && typeof originalTextOrOptions === "object") {
|
|
30031
|
+
const options = originalTextOrOptions;
|
|
30032
|
+
if (options.inputText !== undefined)
|
|
30033
|
+
entry.inputText = options.inputText;
|
|
30034
|
+
if (options.commandResult !== undefined)
|
|
30035
|
+
entry.commandResult = options.commandResult;
|
|
30036
|
+
if (options.commandConfig !== undefined)
|
|
30037
|
+
entry.commandConfig = options.commandConfig;
|
|
30038
|
+
}
|
|
30039
|
+
const entries = getTranscripts();
|
|
30040
|
+
entries.unshift(entry);
|
|
30041
|
+
// Prune to max entries
|
|
30042
|
+
const pruned = entries.slice(0, MAX_ENTRIES);
|
|
30043
|
+
// Update memory cache (always)
|
|
30044
|
+
memoryCache = pruned;
|
|
30045
|
+
// Try to persist to localStorage (for when server sync is disabled)
|
|
30046
|
+
try {
|
|
30047
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(pruned));
|
|
30048
|
+
}
|
|
30049
|
+
catch {
|
|
30050
|
+
// Quota exceeded - memory cache is still updated
|
|
30051
|
+
}
|
|
30052
|
+
// Emit settings change event to trigger sync
|
|
30053
|
+
events.emit("settings:changed", { setting: "history" });
|
|
30054
|
+
return entry;
|
|
30055
|
+
}
|
|
30056
|
+
/**
|
|
30057
|
+
* Clear all transcript history
|
|
30058
|
+
*/
|
|
30059
|
+
function clearTranscripts() {
|
|
30060
|
+
memoryCache = [];
|
|
30061
|
+
try {
|
|
30062
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
30063
|
+
}
|
|
30064
|
+
catch {
|
|
30065
|
+
// Silently fail
|
|
30066
|
+
}
|
|
30067
|
+
events.emit("settings:changed", { setting: "history" });
|
|
30068
|
+
}
|
|
30069
|
+
/**
|
|
30070
|
+
* Delete a single transcript by ID
|
|
30071
|
+
*/
|
|
30072
|
+
function deleteTranscript(id) {
|
|
30073
|
+
const entries = getTranscripts().filter((e) => e.id !== id);
|
|
30074
|
+
memoryCache = entries;
|
|
30075
|
+
try {
|
|
30076
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));
|
|
30077
|
+
}
|
|
30078
|
+
catch {
|
|
30079
|
+
// Silently fail
|
|
30080
|
+
}
|
|
30081
|
+
events.emit("settings:changed", { setting: "history" });
|
|
30082
|
+
}
|
|
30083
|
+
const transcriptStore = {
|
|
30084
|
+
getTranscripts,
|
|
30085
|
+
setTranscripts,
|
|
30086
|
+
saveTranscript,
|
|
30087
|
+
clearTranscripts,
|
|
30088
|
+
deleteTranscript,
|
|
30089
|
+
};
|
|
30090
|
+
|
|
30091
|
+
/**
|
|
30092
|
+
* Settings sync manager
|
|
30093
|
+
* Syncs user settings (language, vocabulary, snippets, history) with the server
|
|
30094
|
+
*/
|
|
30095
|
+
// Sync debounce delay in milliseconds
|
|
30096
|
+
const SYNC_DEBOUNCE_MS = 2000;
|
|
30097
|
+
// Maximum retry attempts
|
|
30098
|
+
const MAX_RETRIES = 3;
|
|
30099
|
+
// Base retry delay in milliseconds (exponential backoff)
|
|
30100
|
+
const BASE_RETRY_DELAY_MS = 2000;
|
|
30101
|
+
/**
|
|
30102
|
+
* Settings sync manager singleton
|
|
30103
|
+
*/
|
|
30104
|
+
class SettingsSync {
|
|
30105
|
+
constructor() {
|
|
30106
|
+
this.syncTimer = null;
|
|
30107
|
+
this.isSyncing = false;
|
|
30108
|
+
this.retryCount = 0;
|
|
30109
|
+
this.isInitialized = false;
|
|
30110
|
+
this.unsubscribe = null;
|
|
30111
|
+
/** When true, sync is disabled due to CSP or network restrictions */
|
|
30112
|
+
this.syncDisabled = false;
|
|
30113
|
+
}
|
|
30114
|
+
/**
|
|
30115
|
+
* Make a fetch request using custom fetchHandler if configured, otherwise native fetch.
|
|
30116
|
+
* This allows the Chrome extension to route fetch traffic through the service worker
|
|
30117
|
+
* to bypass page CSP restrictions.
|
|
30118
|
+
*/
|
|
30119
|
+
async doFetch(url, options) {
|
|
30120
|
+
const config = getConfig();
|
|
30121
|
+
const customHandler = getFetchHandler();
|
|
30122
|
+
if (customHandler) {
|
|
30123
|
+
if (config.debug) {
|
|
30124
|
+
console.log("[SpeechOS] Using custom fetch handler (extension proxy)", options.method, url);
|
|
30125
|
+
}
|
|
30126
|
+
return customHandler(url, options);
|
|
30127
|
+
}
|
|
30128
|
+
if (config.debug) {
|
|
30129
|
+
console.log("[SpeechOS] Using native fetch", options.method, url);
|
|
30130
|
+
}
|
|
30131
|
+
// Use native fetch and wrap response to match FetchResponse interface
|
|
30132
|
+
const response = await fetch(url, options);
|
|
30133
|
+
return response;
|
|
30134
|
+
}
|
|
30135
|
+
/**
|
|
30136
|
+
* Initialize the settings sync manager
|
|
30137
|
+
* If a settingsToken is configured, loads settings from server
|
|
30138
|
+
*/
|
|
30139
|
+
async init() {
|
|
30140
|
+
const token = getSettingsToken();
|
|
30141
|
+
if (!token) {
|
|
30142
|
+
// No token configured, sync is disabled
|
|
30143
|
+
return;
|
|
30144
|
+
}
|
|
30145
|
+
if (this.isInitialized) {
|
|
30146
|
+
return;
|
|
30147
|
+
}
|
|
30148
|
+
this.isInitialized = true;
|
|
30149
|
+
// Subscribe to settings changes
|
|
30150
|
+
this.unsubscribe = events.on("settings:changed", () => {
|
|
30151
|
+
this.scheduleSyncToServer();
|
|
30152
|
+
});
|
|
30153
|
+
// Load settings from server
|
|
30154
|
+
await this.loadFromServer();
|
|
30155
|
+
}
|
|
30156
|
+
/**
|
|
30157
|
+
* Stop the sync manager and clean up
|
|
30158
|
+
*/
|
|
30159
|
+
destroy() {
|
|
30160
|
+
if (this.syncTimer) {
|
|
30161
|
+
clearTimeout(this.syncTimer);
|
|
30162
|
+
this.syncTimer = null;
|
|
30163
|
+
}
|
|
30164
|
+
if (this.unsubscribe) {
|
|
30165
|
+
this.unsubscribe();
|
|
30166
|
+
this.unsubscribe = null;
|
|
30167
|
+
}
|
|
30168
|
+
this.isInitialized = false;
|
|
30169
|
+
this.retryCount = 0;
|
|
30170
|
+
this.syncDisabled = false;
|
|
30171
|
+
}
|
|
30172
|
+
/**
|
|
30173
|
+
* Load settings from the server and merge with local
|
|
30174
|
+
*/
|
|
30175
|
+
async loadFromServer() {
|
|
30176
|
+
const token = getSettingsToken();
|
|
30177
|
+
if (!token) {
|
|
30178
|
+
return;
|
|
30179
|
+
}
|
|
30180
|
+
const config = getConfig();
|
|
30181
|
+
try {
|
|
30182
|
+
const response = await this.doFetch(`${config.host}/api/user-settings/`, {
|
|
30183
|
+
method: "GET",
|
|
30184
|
+
headers: {
|
|
30185
|
+
Authorization: `Bearer ${token}`,
|
|
30186
|
+
"Content-Type": "application/json",
|
|
30187
|
+
},
|
|
30188
|
+
});
|
|
30189
|
+
if (config.debug) {
|
|
30190
|
+
console.log("[SpeechOS] Settings fetch response:", response.status, response.ok ? "OK" : response.statusText);
|
|
30191
|
+
}
|
|
30192
|
+
if (response.status === 404) {
|
|
30193
|
+
// No settings on server yet (new user) - sync local settings to server
|
|
30194
|
+
if (config.debug) {
|
|
30195
|
+
console.log("[SpeechOS] No server settings found, syncing local to server");
|
|
30196
|
+
}
|
|
30197
|
+
await this.syncToServer();
|
|
30198
|
+
return;
|
|
30199
|
+
}
|
|
30200
|
+
if (response.status === 401 || response.status === 403) {
|
|
30201
|
+
// Token expired or invalid
|
|
30202
|
+
this.handleTokenExpired();
|
|
30203
|
+
return;
|
|
30204
|
+
}
|
|
30205
|
+
if (!response.ok) {
|
|
30206
|
+
throw new Error(`Server returned ${response.status}`);
|
|
30207
|
+
}
|
|
30208
|
+
const serverSettings = (await response.json());
|
|
30209
|
+
if (config.debug) {
|
|
30210
|
+
console.log("[SpeechOS] Settings received from server:", {
|
|
30211
|
+
language: serverSettings.language,
|
|
30212
|
+
vocabularyCount: serverSettings.vocabulary?.length ?? 0,
|
|
30213
|
+
snippetsCount: serverSettings.snippets?.length ?? 0,
|
|
30214
|
+
historyCount: serverSettings.history?.length ?? 0,
|
|
30215
|
+
lastSyncedAt: serverSettings.lastSyncedAt,
|
|
30216
|
+
});
|
|
30217
|
+
if (serverSettings.history?.length > 0) {
|
|
30218
|
+
console.log("[SpeechOS] History entries:", serverSettings.history);
|
|
30219
|
+
}
|
|
30220
|
+
}
|
|
30221
|
+
this.mergeSettings(serverSettings);
|
|
30222
|
+
events.emit("settings:loaded", undefined);
|
|
30223
|
+
if (config.debug) {
|
|
30224
|
+
console.log("[SpeechOS] Settings merged and loaded");
|
|
30225
|
+
}
|
|
30226
|
+
}
|
|
30227
|
+
catch (error) {
|
|
30228
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
30229
|
+
// Check if this is a CSP/network restriction - disable sync permanently for this session
|
|
30230
|
+
if (this.isNetworkRestrictionError(error)) {
|
|
30231
|
+
this.syncDisabled = true;
|
|
30232
|
+
if (config.debug) {
|
|
30233
|
+
console.log("[SpeechOS] Settings sync disabled (CSP/network restriction), using localStorage only");
|
|
30234
|
+
}
|
|
30235
|
+
}
|
|
30236
|
+
else if (config.debug) {
|
|
30237
|
+
console.warn("[SpeechOS] Failed to load settings from server:", errorMessage);
|
|
30238
|
+
}
|
|
30239
|
+
events.emit("settings:syncFailed", { error: errorMessage });
|
|
30240
|
+
// Continue with local settings on error
|
|
30241
|
+
}
|
|
30242
|
+
}
|
|
30243
|
+
/**
|
|
30244
|
+
* Merge server settings with local (server wins).
|
|
30245
|
+
* Uses store setters to update memory cache - localStorage is a fallback.
|
|
30246
|
+
*/
|
|
30247
|
+
mergeSettings(serverSettings) {
|
|
30248
|
+
// Language settings - server wins
|
|
30249
|
+
if (serverSettings.language) {
|
|
30250
|
+
setLanguageSettings(serverSettings.language);
|
|
30251
|
+
}
|
|
30252
|
+
// Vocabulary - server wins
|
|
30253
|
+
if (serverSettings.vocabulary) {
|
|
30254
|
+
setVocabulary(serverSettings.vocabulary);
|
|
30255
|
+
}
|
|
30256
|
+
// Snippets - server wins
|
|
30257
|
+
if (serverSettings.snippets) {
|
|
30258
|
+
setSnippets(serverSettings.snippets);
|
|
30259
|
+
}
|
|
30260
|
+
// History - server wins
|
|
30261
|
+
if (serverSettings.history) {
|
|
30262
|
+
setTranscripts(serverSettings.history);
|
|
30263
|
+
}
|
|
30264
|
+
}
|
|
30265
|
+
/**
|
|
30266
|
+
* Schedule a debounced sync to server
|
|
30267
|
+
*/
|
|
30268
|
+
scheduleSyncToServer() {
|
|
30269
|
+
const token = getSettingsToken();
|
|
30270
|
+
if (!token || this.syncDisabled) {
|
|
30271
|
+
return;
|
|
30272
|
+
}
|
|
30273
|
+
// Cancel any pending sync
|
|
30274
|
+
if (this.syncTimer) {
|
|
30275
|
+
clearTimeout(this.syncTimer);
|
|
30276
|
+
}
|
|
30277
|
+
// Schedule new sync
|
|
30278
|
+
this.syncTimer = setTimeout(() => {
|
|
30279
|
+
this.syncToServer();
|
|
30280
|
+
}, SYNC_DEBOUNCE_MS);
|
|
30281
|
+
}
|
|
30282
|
+
/**
|
|
30283
|
+
* Sync current settings to server
|
|
30284
|
+
*/
|
|
30285
|
+
async syncToServer() {
|
|
30286
|
+
const token = getSettingsToken();
|
|
30287
|
+
if (!token || this.isSyncing || this.syncDisabled) {
|
|
30288
|
+
return;
|
|
30289
|
+
}
|
|
30290
|
+
this.isSyncing = true;
|
|
30291
|
+
const config = getConfig();
|
|
30292
|
+
try {
|
|
30293
|
+
const languageSettings = getLanguageSettings();
|
|
30294
|
+
const vocabulary = getVocabulary();
|
|
30295
|
+
const snippets = getSnippets();
|
|
30296
|
+
const transcripts = getTranscripts();
|
|
30297
|
+
const payload = {
|
|
30298
|
+
language: {
|
|
30299
|
+
inputLanguageCode: languageSettings.inputLanguageCode,
|
|
30300
|
+
outputLanguageCode: languageSettings.outputLanguageCode,
|
|
30301
|
+
smartFormat: languageSettings.smartFormat,
|
|
30302
|
+
},
|
|
30303
|
+
vocabulary: vocabulary.map((v) => ({
|
|
30304
|
+
id: v.id,
|
|
30305
|
+
term: v.term,
|
|
30306
|
+
createdAt: v.createdAt,
|
|
30307
|
+
})),
|
|
30308
|
+
snippets: snippets.map((s) => ({
|
|
30309
|
+
id: s.id,
|
|
30310
|
+
trigger: s.trigger,
|
|
30311
|
+
expansion: s.expansion,
|
|
30312
|
+
createdAt: s.createdAt,
|
|
30313
|
+
})),
|
|
30314
|
+
// Sync history (excluding commandConfig to reduce payload size)
|
|
30315
|
+
history: transcripts.map((t) => ({
|
|
30316
|
+
id: t.id,
|
|
30317
|
+
text: t.text,
|
|
30318
|
+
timestamp: t.timestamp,
|
|
30319
|
+
action: t.action,
|
|
30320
|
+
...(t.originalText && { originalText: t.originalText }),
|
|
30321
|
+
...(t.inputText && { inputText: t.inputText }),
|
|
30322
|
+
...(t.commandResult !== undefined && { commandResult: t.commandResult }),
|
|
30323
|
+
})),
|
|
30324
|
+
};
|
|
30325
|
+
const response = await this.doFetch(`${config.host}/api/user-settings/`, {
|
|
30326
|
+
method: "PUT",
|
|
30327
|
+
headers: {
|
|
30328
|
+
Authorization: `Bearer ${token}`,
|
|
30329
|
+
"Content-Type": "application/json",
|
|
30330
|
+
},
|
|
30331
|
+
body: JSON.stringify(payload),
|
|
30332
|
+
});
|
|
30333
|
+
if (response.status === 401 || response.status === 403) {
|
|
30334
|
+
this.handleTokenExpired();
|
|
30335
|
+
return;
|
|
30336
|
+
}
|
|
30337
|
+
if (!response.ok) {
|
|
30338
|
+
throw new Error(`Server returned ${response.status}`);
|
|
30339
|
+
}
|
|
30340
|
+
// Reset retry count on success
|
|
30341
|
+
this.retryCount = 0;
|
|
30342
|
+
events.emit("settings:synced", undefined);
|
|
30343
|
+
if (config.debug) {
|
|
30344
|
+
console.log("[SpeechOS] Settings synced to server");
|
|
30345
|
+
}
|
|
30346
|
+
}
|
|
30347
|
+
catch (error) {
|
|
30348
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
30349
|
+
// Check if this is a CSP/network restriction - disable sync permanently for this session
|
|
30350
|
+
if (this.isNetworkRestrictionError(error)) {
|
|
30351
|
+
this.syncDisabled = true;
|
|
30352
|
+
if (config.debug) {
|
|
30353
|
+
console.log("[SpeechOS] Settings sync disabled (CSP/network restriction), using localStorage only");
|
|
30354
|
+
}
|
|
30355
|
+
events.emit("settings:syncFailed", { error: errorMessage });
|
|
30356
|
+
// Don't retry - CSP errors are permanent
|
|
30357
|
+
}
|
|
30358
|
+
else {
|
|
30359
|
+
if (config.debug) {
|
|
30360
|
+
console.warn("[SpeechOS] Failed to sync settings to server:", errorMessage);
|
|
30361
|
+
}
|
|
30362
|
+
events.emit("settings:syncFailed", { error: errorMessage });
|
|
30363
|
+
// Retry with exponential backoff (only for transient errors)
|
|
30364
|
+
this.scheduleRetry();
|
|
30365
|
+
}
|
|
30366
|
+
}
|
|
30367
|
+
finally {
|
|
30368
|
+
this.isSyncing = false;
|
|
30369
|
+
}
|
|
30370
|
+
}
|
|
30371
|
+
/**
|
|
30372
|
+
* Schedule a retry with exponential backoff
|
|
30373
|
+
*/
|
|
30374
|
+
scheduleRetry() {
|
|
30375
|
+
if (this.retryCount >= MAX_RETRIES) {
|
|
30376
|
+
const config = getConfig();
|
|
30377
|
+
if (config.debug) {
|
|
30378
|
+
console.warn("[SpeechOS] Max retries reached, giving up sync");
|
|
30379
|
+
}
|
|
30380
|
+
this.retryCount = 0;
|
|
30381
|
+
return;
|
|
30382
|
+
}
|
|
30383
|
+
this.retryCount++;
|
|
30384
|
+
const delay = BASE_RETRY_DELAY_MS * Math.pow(2, this.retryCount - 1);
|
|
30385
|
+
this.syncTimer = setTimeout(() => {
|
|
30386
|
+
this.syncToServer();
|
|
30387
|
+
}, delay);
|
|
30388
|
+
}
|
|
30389
|
+
/**
|
|
30390
|
+
* Check if an error is a CSP or network restriction error
|
|
30391
|
+
* These errors are permanent and shouldn't trigger retries
|
|
30392
|
+
*/
|
|
30393
|
+
isNetworkRestrictionError(error) {
|
|
30394
|
+
if (error instanceof TypeError) {
|
|
30395
|
+
const message = error.message.toLowerCase();
|
|
30396
|
+
// Common CSP/network error messages
|
|
30397
|
+
return (message.includes("failed to fetch") ||
|
|
30398
|
+
message.includes("network request failed") ||
|
|
30399
|
+
message.includes("content security policy") ||
|
|
30400
|
+
message.includes("csp") ||
|
|
30401
|
+
message.includes("blocked"));
|
|
30402
|
+
}
|
|
30403
|
+
return false;
|
|
30404
|
+
}
|
|
30405
|
+
/**
|
|
30406
|
+
* Handle token expiration
|
|
30407
|
+
*/
|
|
30408
|
+
handleTokenExpired() {
|
|
30409
|
+
clearSettingsToken();
|
|
30410
|
+
events.emit("settings:tokenExpired", undefined);
|
|
30411
|
+
const config = getConfig();
|
|
30412
|
+
if (config.debug) {
|
|
30413
|
+
console.warn("[SpeechOS] Settings token expired");
|
|
30414
|
+
}
|
|
30415
|
+
}
|
|
30416
|
+
/**
|
|
30417
|
+
* Check if sync is enabled (token is configured)
|
|
30418
|
+
*/
|
|
30419
|
+
isEnabled() {
|
|
30420
|
+
return !!getSettingsToken();
|
|
30421
|
+
}
|
|
30422
|
+
}
|
|
30423
|
+
// Singleton instance
|
|
30424
|
+
const settingsSync = new SettingsSync();
|
|
30425
|
+
|
|
29868
30426
|
/******************************************************************************
|
|
29869
30427
|
Copyright (c) Microsoft Corporation.
|
|
29870
30428
|
|
|
@@ -29948,6 +30506,9 @@
|
|
|
29948
30506
|
*/
|
|
29949
30507
|
const themeStyles = i$4 `
|
|
29950
30508
|
:host {
|
|
30509
|
+
/* Font stack - system fonts for consistent rendering across sites */
|
|
30510
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
30511
|
+
|
|
29951
30512
|
/* Color tokens */
|
|
29952
30513
|
--speechos-primary: #10B981;
|
|
29953
30514
|
--speechos-primary-hover: #059669;
|
|
@@ -30071,100 +30632,6 @@
|
|
|
30071
30632
|
}
|
|
30072
30633
|
`;
|
|
30073
30634
|
|
|
30074
|
-
/**
|
|
30075
|
-
* Transcript history store
|
|
30076
|
-
* Persists transcripts to localStorage for viewing in the settings modal
|
|
30077
|
-
*/
|
|
30078
|
-
const STORAGE_KEY = "speechos_transcripts";
|
|
30079
|
-
const MAX_ENTRIES = 50;
|
|
30080
|
-
/**
|
|
30081
|
-
* Generate a unique ID for transcript entries
|
|
30082
|
-
*/
|
|
30083
|
-
function generateId() {
|
|
30084
|
-
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
30085
|
-
}
|
|
30086
|
-
/**
|
|
30087
|
-
* Get all transcripts from localStorage
|
|
30088
|
-
*/
|
|
30089
|
-
function getTranscripts() {
|
|
30090
|
-
try {
|
|
30091
|
-
const stored = localStorage.getItem(STORAGE_KEY);
|
|
30092
|
-
if (!stored)
|
|
30093
|
-
return [];
|
|
30094
|
-
const entries = JSON.parse(stored);
|
|
30095
|
-
// Return newest first
|
|
30096
|
-
return entries.sort((a, b) => b.timestamp - a.timestamp);
|
|
30097
|
-
}
|
|
30098
|
-
catch {
|
|
30099
|
-
return [];
|
|
30100
|
-
}
|
|
30101
|
-
}
|
|
30102
|
-
/**
|
|
30103
|
-
* Save a new transcript entry
|
|
30104
|
-
*/
|
|
30105
|
-
function saveTranscript(text, action, originalTextOrOptions) {
|
|
30106
|
-
const entry = {
|
|
30107
|
-
id: generateId(),
|
|
30108
|
-
text,
|
|
30109
|
-
timestamp: Date.now(),
|
|
30110
|
-
action,
|
|
30111
|
-
};
|
|
30112
|
-
// Handle edit action with originalText string
|
|
30113
|
-
if (action === "edit" && typeof originalTextOrOptions === "string") {
|
|
30114
|
-
entry.originalText = originalTextOrOptions;
|
|
30115
|
-
}
|
|
30116
|
-
// Handle command action with options object
|
|
30117
|
-
if (action === "command" && typeof originalTextOrOptions === "object") {
|
|
30118
|
-
const options = originalTextOrOptions;
|
|
30119
|
-
if (options.inputText !== undefined)
|
|
30120
|
-
entry.inputText = options.inputText;
|
|
30121
|
-
if (options.commandResult !== undefined)
|
|
30122
|
-
entry.commandResult = options.commandResult;
|
|
30123
|
-
if (options.commandConfig !== undefined)
|
|
30124
|
-
entry.commandConfig = options.commandConfig;
|
|
30125
|
-
}
|
|
30126
|
-
const entries = getTranscripts();
|
|
30127
|
-
entries.unshift(entry);
|
|
30128
|
-
// Prune to max entries
|
|
30129
|
-
const pruned = entries.slice(0, MAX_ENTRIES);
|
|
30130
|
-
try {
|
|
30131
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(pruned));
|
|
30132
|
-
}
|
|
30133
|
-
catch {
|
|
30134
|
-
// localStorage full or unavailable - silently fail
|
|
30135
|
-
}
|
|
30136
|
-
return entry;
|
|
30137
|
-
}
|
|
30138
|
-
/**
|
|
30139
|
-
* Clear all transcript history
|
|
30140
|
-
*/
|
|
30141
|
-
function clearTranscripts() {
|
|
30142
|
-
try {
|
|
30143
|
-
localStorage.removeItem(STORAGE_KEY);
|
|
30144
|
-
}
|
|
30145
|
-
catch {
|
|
30146
|
-
// Silently fail
|
|
30147
|
-
}
|
|
30148
|
-
}
|
|
30149
|
-
/**
|
|
30150
|
-
* Delete a single transcript by ID
|
|
30151
|
-
*/
|
|
30152
|
-
function deleteTranscript(id) {
|
|
30153
|
-
const entries = getTranscripts().filter((e) => e.id !== id);
|
|
30154
|
-
try {
|
|
30155
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));
|
|
30156
|
-
}
|
|
30157
|
-
catch {
|
|
30158
|
-
// Silently fail
|
|
30159
|
-
}
|
|
30160
|
-
}
|
|
30161
|
-
const transcriptStore = {
|
|
30162
|
-
getTranscripts: getTranscripts,
|
|
30163
|
-
saveTranscript: saveTranscript,
|
|
30164
|
-
clearTranscripts: clearTranscripts,
|
|
30165
|
-
deleteTranscript: deleteTranscript,
|
|
30166
|
-
};
|
|
30167
|
-
|
|
30168
30635
|
function isNativeField(field) {
|
|
30169
30636
|
return field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement;
|
|
30170
30637
|
}
|
|
@@ -32162,6 +32629,8 @@
|
|
|
32162
32629
|
inset: 0;
|
|
32163
32630
|
pointer-events: none;
|
|
32164
32631
|
z-index: calc(var(--speechos-z-base) + 100);
|
|
32632
|
+
/* Ensure consistent font rendering across all sites */
|
|
32633
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
32165
32634
|
}
|
|
32166
32635
|
|
|
32167
32636
|
.modal-overlay {
|
|
@@ -32650,6 +33119,7 @@
|
|
|
32650
33119
|
constructor() {
|
|
32651
33120
|
super(...arguments);
|
|
32652
33121
|
this.transcripts = [];
|
|
33122
|
+
this.unsubscribeSettingsLoaded = null;
|
|
32653
33123
|
}
|
|
32654
33124
|
static { this.styles = [
|
|
32655
33125
|
themeStyles,
|
|
@@ -32786,11 +33256,36 @@
|
|
|
32786
33256
|
background: rgba(239, 68, 68, 0.18);
|
|
32787
33257
|
border-color: rgba(239, 68, 68, 0.25);
|
|
32788
33258
|
}
|
|
33259
|
+
|
|
33260
|
+
.command-matched {
|
|
33261
|
+
font-size: 12px;
|
|
33262
|
+
color: rgba(255, 255, 255, 0.5);
|
|
33263
|
+
margin-top: 6px;
|
|
33264
|
+
}
|
|
33265
|
+
|
|
33266
|
+
.command-matched code {
|
|
33267
|
+
background: rgba(245, 158, 11, 0.15);
|
|
33268
|
+
color: #fbbf24;
|
|
33269
|
+
padding: 2px 6px;
|
|
33270
|
+
border-radius: 4px;
|
|
33271
|
+
font-family: monospace;
|
|
33272
|
+
}
|
|
32789
33273
|
`,
|
|
32790
33274
|
]; }
|
|
32791
33275
|
connectedCallback() {
|
|
32792
33276
|
super.connectedCallback();
|
|
32793
33277
|
this.loadTranscripts();
|
|
33278
|
+
// Refresh when settings are loaded from the server (history may have changed)
|
|
33279
|
+
this.unsubscribeSettingsLoaded = events.on("settings:loaded", () => {
|
|
33280
|
+
this.loadTranscripts();
|
|
33281
|
+
});
|
|
33282
|
+
}
|
|
33283
|
+
disconnectedCallback() {
|
|
33284
|
+
super.disconnectedCallback();
|
|
33285
|
+
if (this.unsubscribeSettingsLoaded) {
|
|
33286
|
+
this.unsubscribeSettingsLoaded();
|
|
33287
|
+
this.unsubscribeSettingsLoaded = null;
|
|
33288
|
+
}
|
|
32794
33289
|
}
|
|
32795
33290
|
/** Reload transcripts from store */
|
|
32796
33291
|
refresh() {
|
|
@@ -32861,7 +33356,13 @@
|
|
|
32861
33356
|
renderCommandDetails(entry) {
|
|
32862
33357
|
// Show the transcript text (what the user said)
|
|
32863
33358
|
const displayText = entry.inputText || entry.text;
|
|
32864
|
-
|
|
33359
|
+
const commandName = entry.commandResult?.name;
|
|
33360
|
+
return b `
|
|
33361
|
+
<div class="transcript-text">${displayText}</div>
|
|
33362
|
+
${commandName
|
|
33363
|
+
? b `<div class="command-matched">Matched: <code>${commandName}</code></div>`
|
|
33364
|
+
: null}
|
|
33365
|
+
`;
|
|
32865
33366
|
}
|
|
32866
33367
|
getCopyText(entry) {
|
|
32867
33368
|
if (entry.action === "command") {
|
|
@@ -33411,6 +33912,7 @@
|
|
|
33411
33912
|
this.permissionGranted = false;
|
|
33412
33913
|
this.smartFormatEnabled = true;
|
|
33413
33914
|
this.savedIndicatorTimeout = null;
|
|
33915
|
+
this.unsubscribeSettingsLoaded = null;
|
|
33414
33916
|
}
|
|
33415
33917
|
static { this.styles = [
|
|
33416
33918
|
themeStyles,
|
|
@@ -33585,12 +34087,20 @@
|
|
|
33585
34087
|
connectedCallback() {
|
|
33586
34088
|
super.connectedCallback();
|
|
33587
34089
|
this.loadSettings();
|
|
34090
|
+
// Refresh when settings are loaded from the server
|
|
34091
|
+
this.unsubscribeSettingsLoaded = events.on("settings:loaded", () => {
|
|
34092
|
+
this.loadSettings();
|
|
34093
|
+
});
|
|
33588
34094
|
}
|
|
33589
34095
|
disconnectedCallback() {
|
|
33590
34096
|
super.disconnectedCallback();
|
|
33591
34097
|
if (this.savedIndicatorTimeout) {
|
|
33592
34098
|
clearTimeout(this.savedIndicatorTimeout);
|
|
33593
34099
|
}
|
|
34100
|
+
if (this.unsubscribeSettingsLoaded) {
|
|
34101
|
+
this.unsubscribeSettingsLoaded();
|
|
34102
|
+
this.unsubscribeSettingsLoaded = null;
|
|
34103
|
+
}
|
|
33594
34104
|
this.isTestingMic = false;
|
|
33595
34105
|
}
|
|
33596
34106
|
/** Called when tab becomes active */
|
|
@@ -33889,6 +34399,7 @@
|
|
|
33889
34399
|
this.trigger = "";
|
|
33890
34400
|
this.expansion = "";
|
|
33891
34401
|
this.error = "";
|
|
34402
|
+
this.unsubscribeSettingsLoaded = null;
|
|
33892
34403
|
}
|
|
33893
34404
|
static { this.styles = [
|
|
33894
34405
|
themeStyles,
|
|
@@ -33994,6 +34505,17 @@
|
|
|
33994
34505
|
connectedCallback() {
|
|
33995
34506
|
super.connectedCallback();
|
|
33996
34507
|
this.loadSnippets();
|
|
34508
|
+
// Refresh when settings are loaded from the server
|
|
34509
|
+
this.unsubscribeSettingsLoaded = events.on("settings:loaded", () => {
|
|
34510
|
+
this.loadSnippets();
|
|
34511
|
+
});
|
|
34512
|
+
}
|
|
34513
|
+
disconnectedCallback() {
|
|
34514
|
+
super.disconnectedCallback();
|
|
34515
|
+
if (this.unsubscribeSettingsLoaded) {
|
|
34516
|
+
this.unsubscribeSettingsLoaded();
|
|
34517
|
+
this.unsubscribeSettingsLoaded = null;
|
|
34518
|
+
}
|
|
33997
34519
|
}
|
|
33998
34520
|
/** Reload snippets from store */
|
|
33999
34521
|
refresh() {
|
|
@@ -34236,6 +34758,7 @@
|
|
|
34236
34758
|
this.showForm = false;
|
|
34237
34759
|
this.term = "";
|
|
34238
34760
|
this.error = "";
|
|
34761
|
+
this.unsubscribeSettingsLoaded = null;
|
|
34239
34762
|
}
|
|
34240
34763
|
static { this.styles = [
|
|
34241
34764
|
themeStyles,
|
|
@@ -34288,6 +34811,17 @@
|
|
|
34288
34811
|
connectedCallback() {
|
|
34289
34812
|
super.connectedCallback();
|
|
34290
34813
|
this.loadVocabulary();
|
|
34814
|
+
// Refresh when settings are loaded from the server
|
|
34815
|
+
this.unsubscribeSettingsLoaded = events.on("settings:loaded", () => {
|
|
34816
|
+
this.loadVocabulary();
|
|
34817
|
+
});
|
|
34818
|
+
}
|
|
34819
|
+
disconnectedCallback() {
|
|
34820
|
+
super.disconnectedCallback();
|
|
34821
|
+
if (this.unsubscribeSettingsLoaded) {
|
|
34822
|
+
this.unsubscribeSettingsLoaded();
|
|
34823
|
+
this.unsubscribeSettingsLoaded = null;
|
|
34824
|
+
}
|
|
34291
34825
|
}
|
|
34292
34826
|
/** Reload vocabulary from store */
|
|
34293
34827
|
refresh() {
|
|
@@ -35895,6 +36429,8 @@
|
|
|
35895
36429
|
const originalContent = this.getElementContent(target) || "";
|
|
35896
36430
|
if (tagName === "input" || tagName === "textarea") {
|
|
35897
36431
|
const inputEl = target;
|
|
36432
|
+
// Ensure DOM focus is on the input before inserting
|
|
36433
|
+
inputEl.focus();
|
|
35898
36434
|
// Restore cursor position before inserting
|
|
35899
36435
|
const start = this.dictationCursorStart ?? inputEl.value.length;
|
|
35900
36436
|
const end = this.dictationCursorEnd ?? inputEl.value.length;
|
|
@@ -36231,8 +36767,6 @@
|
|
|
36231
36767
|
// Note: command:complete event is already emitted by the backend
|
|
36232
36768
|
// when the command_result message is received, so we don't emit here
|
|
36233
36769
|
state.completeRecording();
|
|
36234
|
-
// Keep widget visible but collapsed (just mic button, no action bubbles)
|
|
36235
|
-
state.setState({ isExpanded: false });
|
|
36236
36770
|
// Show command feedback
|
|
36237
36771
|
this.showActionFeedback(result ? "command-success" : "command-none");
|
|
36238
36772
|
backend.disconnect().catch(() => { });
|
|
@@ -36390,6 +36924,8 @@
|
|
|
36390
36924
|
if (tagName === "input" || tagName === "textarea") {
|
|
36391
36925
|
const inputEl = target;
|
|
36392
36926
|
originalContent = inputEl.value;
|
|
36927
|
+
// Ensure DOM focus is on the input before editing
|
|
36928
|
+
inputEl.focus();
|
|
36393
36929
|
// Restore the original selection/cursor position
|
|
36394
36930
|
const selectionStart = this.editSelectionStart ?? 0;
|
|
36395
36931
|
const selectionEnd = this.editSelectionEnd ?? inputEl.value.length;
|
|
@@ -36557,6 +37093,30 @@
|
|
|
36557
37093
|
t$1("speechos-widget")
|
|
36558
37094
|
], SpeechOSWidget);
|
|
36559
37095
|
|
|
37096
|
+
/**
|
|
37097
|
+
* UI module exports
|
|
37098
|
+
* Lit-based Shadow DOM components
|
|
37099
|
+
*/
|
|
37100
|
+
// Patch customElements.define to silently ignore duplicate registrations for speechos-* elements.
|
|
37101
|
+
// This prevents errors when the extension loads on a page that already has SpeechOS.
|
|
37102
|
+
// The patch is scoped to only affect speechos-* tags to avoid unintended effects on host pages.
|
|
37103
|
+
const originalDefine = customElements.define.bind(customElements);
|
|
37104
|
+
customElements.define = (name, constructor, options) => {
|
|
37105
|
+
// Only intercept speechos-* elements
|
|
37106
|
+
if (name.startsWith("speechos-")) {
|
|
37107
|
+
if (customElements.get(name) === undefined) {
|
|
37108
|
+
originalDefine(name, constructor, options);
|
|
37109
|
+
}
|
|
37110
|
+
// Skip silently if already registered
|
|
37111
|
+
}
|
|
37112
|
+
else {
|
|
37113
|
+
// Pass through for non-speechos elements
|
|
37114
|
+
originalDefine(name, constructor, options);
|
|
37115
|
+
}
|
|
37116
|
+
};
|
|
37117
|
+
// Restore original customElements.define after our components are registered
|
|
37118
|
+
customElements.define = originalDefine;
|
|
37119
|
+
|
|
36560
37120
|
/**
|
|
36561
37121
|
* Main SpeechOS Client SDK class
|
|
36562
37122
|
* Composes core logic with UI components
|
|
@@ -36638,6 +37198,13 @@
|
|
|
36638
37198
|
state.show();
|
|
36639
37199
|
}
|
|
36640
37200
|
this.isInitialized = true;
|
|
37201
|
+
// Initialize settings sync if token is configured
|
|
37202
|
+
// This loads settings from server and subscribes to changes
|
|
37203
|
+
settingsSync.init().catch((error) => {
|
|
37204
|
+
if (finalConfig.debug) {
|
|
37205
|
+
console.warn("[SpeechOS] Settings sync initialization failed:", error);
|
|
37206
|
+
}
|
|
37207
|
+
});
|
|
36641
37208
|
// Log initialization in debug mode
|
|
36642
37209
|
if (finalConfig.debug) {
|
|
36643
37210
|
console.log("[SpeechOS] Initialized with config:", finalConfig);
|
|
@@ -36668,6 +37235,8 @@
|
|
|
36668
37235
|
resetClientConfig();
|
|
36669
37236
|
// Reset text input handler to default
|
|
36670
37237
|
resetTextInputHandler();
|
|
37238
|
+
// Stop settings sync
|
|
37239
|
+
settingsSync.destroy();
|
|
36671
37240
|
// Clear instance
|
|
36672
37241
|
this.instance = null;
|
|
36673
37242
|
this.isInitialized = false;
|
|
@@ -36891,6 +37460,7 @@
|
|
|
36891
37460
|
exports.setOutputLanguageCode = setOutputLanguageCode;
|
|
36892
37461
|
exports.setSmartFormatEnabled = setSmartFormatEnabled;
|
|
36893
37462
|
exports.setTextInputHandler = setTextInputHandler;
|
|
37463
|
+
exports.settingsSync = settingsSync;
|
|
36894
37464
|
exports.snippetsStore = snippetsStore;
|
|
36895
37465
|
exports.state = state;
|
|
36896
37466
|
exports.transcriptStore = transcriptStore;
|