@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.iife.js
CHANGED
|
@@ -26911,7 +26911,8 @@
|
|
|
26911
26911
|
userId: "",
|
|
26912
26912
|
host: DEFAULT_HOST,
|
|
26913
26913
|
debug: false,
|
|
26914
|
-
webSocketFactory: void 0
|
|
26914
|
+
webSocketFactory: void 0,
|
|
26915
|
+
settingsToken: void 0
|
|
26915
26916
|
};
|
|
26916
26917
|
/**
|
|
26917
26918
|
* Validates and merges user config with defaults
|
|
@@ -26919,13 +26920,14 @@
|
|
|
26919
26920
|
* @returns Validated and merged configuration
|
|
26920
26921
|
*/
|
|
26921
26922
|
function validateConfig(userConfig) {
|
|
26922
|
-
if (!userConfig.apiKey) throw new Error("SpeechOS requires an apiKey. Get one from your team dashboard at /a
|
|
26923
|
+
if (!userConfig.apiKey) throw new Error("SpeechOS requires an apiKey. Get one from your team dashboard at /a/.");
|
|
26923
26924
|
return {
|
|
26924
26925
|
apiKey: userConfig.apiKey,
|
|
26925
26926
|
userId: userConfig.userId ?? defaultConfig.userId,
|
|
26926
26927
|
host: userConfig.host ?? defaultConfig.host,
|
|
26927
26928
|
debug: userConfig.debug ?? defaultConfig.debug,
|
|
26928
|
-
webSocketFactory: userConfig.webSocketFactory ?? defaultConfig.webSocketFactory
|
|
26929
|
+
webSocketFactory: userConfig.webSocketFactory ?? defaultConfig.webSocketFactory,
|
|
26930
|
+
settingsToken: userConfig.settingsToken ?? defaultConfig.settingsToken
|
|
26929
26931
|
};
|
|
26930
26932
|
}
|
|
26931
26933
|
/**
|
|
@@ -26962,6 +26964,22 @@
|
|
|
26962
26964
|
};
|
|
26963
26965
|
}
|
|
26964
26966
|
/**
|
|
26967
|
+
* Get the settings token from the current configuration
|
|
26968
|
+
* @returns The settings token or undefined if not configured
|
|
26969
|
+
*/
|
|
26970
|
+
function getSettingsToken() {
|
|
26971
|
+
return currentConfig.settingsToken;
|
|
26972
|
+
}
|
|
26973
|
+
/**
|
|
26974
|
+
* Clear the settings token (e.g., when it expires)
|
|
26975
|
+
*/
|
|
26976
|
+
function clearSettingsToken() {
|
|
26977
|
+
currentConfig = {
|
|
26978
|
+
...currentConfig,
|
|
26979
|
+
settingsToken: void 0
|
|
26980
|
+
};
|
|
26981
|
+
}
|
|
26982
|
+
/**
|
|
26965
26983
|
* LocalStorage key for anonymous ID persistence
|
|
26966
26984
|
*/
|
|
26967
26985
|
const ANONYMOUS_ID_KEY = "speechos_anonymous_id";
|
|
@@ -28778,6 +28796,7 @@
|
|
|
28778
28796
|
commands: [],
|
|
28779
28797
|
zIndex: 999999,
|
|
28780
28798
|
alwaysVisible: false,
|
|
28799
|
+
useExternalSettings: false,
|
|
28781
28800
|
};
|
|
28782
28801
|
/**
|
|
28783
28802
|
* Current client configuration singleton
|
|
@@ -28793,6 +28812,7 @@
|
|
|
28793
28812
|
commands: config.commands ?? defaultClientConfig.commands,
|
|
28794
28813
|
zIndex: config.zIndex ?? defaultClientConfig.zIndex,
|
|
28795
28814
|
alwaysVisible: config.alwaysVisible ?? defaultClientConfig.alwaysVisible,
|
|
28815
|
+
useExternalSettings: config.useExternalSettings ?? defaultClientConfig.useExternalSettings,
|
|
28796
28816
|
};
|
|
28797
28817
|
// Validate zIndex
|
|
28798
28818
|
if (typeof resolved.zIndex !== "number" || resolved.zIndex < 0) {
|
|
@@ -28844,6 +28864,12 @@
|
|
|
28844
28864
|
function isAlwaysVisible() {
|
|
28845
28865
|
return currentClientConfig.alwaysVisible;
|
|
28846
28866
|
}
|
|
28867
|
+
/**
|
|
28868
|
+
* Check if external settings page should be used
|
|
28869
|
+
*/
|
|
28870
|
+
function useExternalSettings() {
|
|
28871
|
+
return currentClientConfig.useExternalSettings;
|
|
28872
|
+
}
|
|
28847
28873
|
|
|
28848
28874
|
/**
|
|
28849
28875
|
* Form field focus detection for SpeechOS Client SDK
|
|
@@ -29247,15 +29273,23 @@
|
|
|
29247
29273
|
* Persists input language preferences to localStorage
|
|
29248
29274
|
*/
|
|
29249
29275
|
const STORAGE_KEY$4 = "speechos_language_settings";
|
|
29276
|
+
/**
|
|
29277
|
+
* In-memory cache for language settings. When server sync is enabled, this is the
|
|
29278
|
+
* source of truth. localStorage is only used when server sync is disabled.
|
|
29279
|
+
*/
|
|
29280
|
+
let memoryCache$3 = null;
|
|
29250
29281
|
/**
|
|
29251
29282
|
* Supported input languages for speech recognition
|
|
29252
29283
|
* Each language has a name, primary code, and available variants
|
|
29253
29284
|
* Sorted alphabetically by name for dropdown display
|
|
29254
29285
|
*/
|
|
29255
29286
|
const SUPPORTED_LANGUAGES = [
|
|
29287
|
+
{ name: "Belarusian", code: "be", variants: ["be"] },
|
|
29288
|
+
{ name: "Bengali", code: "bn", variants: ["bn"] },
|
|
29289
|
+
{ name: "Bosnian", code: "bs", variants: ["bs"] },
|
|
29256
29290
|
{ name: "Bulgarian", code: "bg", variants: ["bg"] },
|
|
29257
29291
|
{ name: "Catalan", code: "ca", variants: ["ca"] },
|
|
29258
|
-
{ name: "
|
|
29292
|
+
{ name: "Croatian", code: "hr", variants: ["hr"] },
|
|
29259
29293
|
{ name: "Czech", code: "cs", variants: ["cs"] },
|
|
29260
29294
|
{ name: "Danish", code: "da", variants: ["da", "da-DK"] },
|
|
29261
29295
|
{ name: "Dutch", code: "nl", variants: ["nl"] },
|
|
@@ -29276,18 +29310,26 @@
|
|
|
29276
29310
|
{ name: "Indonesian", code: "id", variants: ["id"] },
|
|
29277
29311
|
{ name: "Italian", code: "it", variants: ["it"] },
|
|
29278
29312
|
{ name: "Japanese", code: "ja", variants: ["ja"] },
|
|
29313
|
+
{ name: "Kannada", code: "kn", variants: ["kn"] },
|
|
29279
29314
|
{ name: "Korean", code: "ko", variants: ["ko", "ko-KR"] },
|
|
29280
29315
|
{ name: "Latvian", code: "lv", variants: ["lv"] },
|
|
29281
29316
|
{ name: "Lithuanian", code: "lt", variants: ["lt"] },
|
|
29317
|
+
{ name: "Macedonian", code: "mk", variants: ["mk"] },
|
|
29282
29318
|
{ name: "Malay", code: "ms", variants: ["ms"] },
|
|
29319
|
+
{ name: "Marathi", code: "mr", variants: ["mr"] },
|
|
29283
29320
|
{ name: "Norwegian", code: "no", variants: ["no"] },
|
|
29284
29321
|
{ name: "Polish", code: "pl", variants: ["pl"] },
|
|
29285
29322
|
{ name: "Portuguese", code: "pt", variants: ["pt", "pt-BR", "pt-PT"] },
|
|
29286
29323
|
{ name: "Romanian", code: "ro", variants: ["ro"] },
|
|
29287
29324
|
{ name: "Russian", code: "ru", variants: ["ru"] },
|
|
29325
|
+
{ name: "Serbian", code: "sr", variants: ["sr"] },
|
|
29288
29326
|
{ name: "Slovak", code: "sk", variants: ["sk"] },
|
|
29327
|
+
{ name: "Slovenian", code: "sl", variants: ["sl"] },
|
|
29289
29328
|
{ name: "Spanish", code: "es", variants: ["es", "es-419"] },
|
|
29290
29329
|
{ name: "Swedish", code: "sv", variants: ["sv", "sv-SE"] },
|
|
29330
|
+
{ name: "Tagalog", code: "tl", variants: ["tl"] },
|
|
29331
|
+
{ name: "Tamil", code: "ta", variants: ["ta"] },
|
|
29332
|
+
{ name: "Telugu", code: "te", variants: ["te"] },
|
|
29291
29333
|
{ name: "Turkish", code: "tr", variants: ["tr"] },
|
|
29292
29334
|
{ name: "Ukrainian", code: "uk", variants: ["uk"] },
|
|
29293
29335
|
{ name: "Vietnamese", code: "vi", variants: ["vi"] },
|
|
@@ -29298,9 +29340,13 @@
|
|
|
29298
29340
|
smartFormat: true,
|
|
29299
29341
|
};
|
|
29300
29342
|
/**
|
|
29301
|
-
* Get current language settings from
|
|
29343
|
+
* Get current language settings. Prefers in-memory cache (from server sync),
|
|
29344
|
+
* then falls back to localStorage.
|
|
29302
29345
|
*/
|
|
29303
29346
|
function getLanguageSettings() {
|
|
29347
|
+
if (memoryCache$3 !== null) {
|
|
29348
|
+
return { ...memoryCache$3 };
|
|
29349
|
+
}
|
|
29304
29350
|
try {
|
|
29305
29351
|
const stored = localStorage.getItem(STORAGE_KEY$4);
|
|
29306
29352
|
if (!stored)
|
|
@@ -29312,14 +29358,27 @@
|
|
|
29312
29358
|
}
|
|
29313
29359
|
}
|
|
29314
29360
|
/**
|
|
29315
|
-
*
|
|
29361
|
+
* Set language settings directly (used by settings sync from server data).
|
|
29362
|
+
*/
|
|
29363
|
+
function setLanguageSettings(settings) {
|
|
29364
|
+
memoryCache$3 = { ...defaultSettings$1, ...settings };
|
|
29365
|
+
}
|
|
29366
|
+
/**
|
|
29367
|
+
* Reset memory cache (for testing only)
|
|
29368
|
+
*/
|
|
29369
|
+
function resetMemoryCache$2() {
|
|
29370
|
+
memoryCache$3 = null;
|
|
29371
|
+
}
|
|
29372
|
+
/**
|
|
29373
|
+
* Save language settings (updates memory cache and tries localStorage)
|
|
29316
29374
|
*/
|
|
29317
29375
|
function saveLanguageSettings(settings) {
|
|
29376
|
+
memoryCache$3 = settings;
|
|
29318
29377
|
try {
|
|
29319
29378
|
localStorage.setItem(STORAGE_KEY$4, JSON.stringify(settings));
|
|
29320
29379
|
}
|
|
29321
29380
|
catch {
|
|
29322
|
-
// localStorage full or unavailable -
|
|
29381
|
+
// localStorage full or unavailable - memory cache still updated
|
|
29323
29382
|
}
|
|
29324
29383
|
}
|
|
29325
29384
|
/**
|
|
@@ -29423,6 +29482,7 @@
|
|
|
29423
29482
|
* Reset language settings to defaults
|
|
29424
29483
|
*/
|
|
29425
29484
|
function resetLanguageSettings() {
|
|
29485
|
+
memoryCache$3 = null;
|
|
29426
29486
|
try {
|
|
29427
29487
|
localStorage.removeItem(STORAGE_KEY$4);
|
|
29428
29488
|
}
|
|
@@ -29432,6 +29492,7 @@
|
|
|
29432
29492
|
}
|
|
29433
29493
|
const languageSettings = {
|
|
29434
29494
|
getLanguageSettings,
|
|
29495
|
+
setLanguageSettings,
|
|
29435
29496
|
getInputLanguageCode,
|
|
29436
29497
|
setInputLanguageCode,
|
|
29437
29498
|
getOutputLanguageCode,
|
|
@@ -29442,6 +29503,7 @@
|
|
|
29442
29503
|
getSmartFormatEnabled,
|
|
29443
29504
|
setSmartFormatEnabled,
|
|
29444
29505
|
resetLanguageSettings,
|
|
29506
|
+
resetMemoryCache: resetMemoryCache$2,
|
|
29445
29507
|
SUPPORTED_LANGUAGES,
|
|
29446
29508
|
// Legacy aliases
|
|
29447
29509
|
getLanguageCode,
|
|
@@ -29457,6 +29519,11 @@
|
|
|
29457
29519
|
const MAX_SNIPPETS = 25;
|
|
29458
29520
|
const MAX_TRIGGER_LENGTH = 30;
|
|
29459
29521
|
const MAX_EXPANSION_LENGTH = 300;
|
|
29522
|
+
/**
|
|
29523
|
+
* In-memory cache for snippets. When server sync is enabled, this is the
|
|
29524
|
+
* source of truth. localStorage is only used when server sync is disabled.
|
|
29525
|
+
*/
|
|
29526
|
+
let memoryCache$2 = null;
|
|
29460
29527
|
/**
|
|
29461
29528
|
* Generate a unique ID for snippet entries
|
|
29462
29529
|
*/
|
|
@@ -29464,15 +29531,18 @@
|
|
|
29464
29531
|
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
29465
29532
|
}
|
|
29466
29533
|
/**
|
|
29467
|
-
* Get all snippets from
|
|
29534
|
+
* Get all snippets. Prefers in-memory cache (from server sync),
|
|
29535
|
+
* then falls back to localStorage.
|
|
29468
29536
|
*/
|
|
29469
29537
|
function getSnippets() {
|
|
29538
|
+
if (memoryCache$2 !== null) {
|
|
29539
|
+
return [...memoryCache$2].sort((a, b) => b.createdAt - a.createdAt);
|
|
29540
|
+
}
|
|
29470
29541
|
try {
|
|
29471
29542
|
const stored = localStorage.getItem(STORAGE_KEY$3);
|
|
29472
29543
|
if (!stored)
|
|
29473
29544
|
return [];
|
|
29474
29545
|
const entries = JSON.parse(stored);
|
|
29475
|
-
// Return newest first
|
|
29476
29546
|
return entries.sort((a, b) => b.createdAt - a.createdAt);
|
|
29477
29547
|
}
|
|
29478
29548
|
catch {
|
|
@@ -29480,14 +29550,27 @@
|
|
|
29480
29550
|
}
|
|
29481
29551
|
}
|
|
29482
29552
|
/**
|
|
29483
|
-
*
|
|
29553
|
+
* Set snippets directly (used by settings sync from server data).
|
|
29554
|
+
*/
|
|
29555
|
+
function setSnippets(snippets) {
|
|
29556
|
+
memoryCache$2 = snippets.slice(0, MAX_SNIPPETS);
|
|
29557
|
+
}
|
|
29558
|
+
/**
|
|
29559
|
+
* Reset memory cache (for testing only)
|
|
29560
|
+
*/
|
|
29561
|
+
function resetMemoryCache$1() {
|
|
29562
|
+
memoryCache$2 = null;
|
|
29563
|
+
}
|
|
29564
|
+
/**
|
|
29565
|
+
* Save snippets (updates memory cache and tries localStorage)
|
|
29484
29566
|
*/
|
|
29485
29567
|
function saveSnippets(snippets) {
|
|
29568
|
+
memoryCache$2 = snippets;
|
|
29486
29569
|
try {
|
|
29487
29570
|
localStorage.setItem(STORAGE_KEY$3, JSON.stringify(snippets));
|
|
29488
29571
|
}
|
|
29489
29572
|
catch {
|
|
29490
|
-
// localStorage full or unavailable -
|
|
29573
|
+
// localStorage full or unavailable - memory cache still updated
|
|
29491
29574
|
}
|
|
29492
29575
|
}
|
|
29493
29576
|
/**
|
|
@@ -29605,13 +29688,14 @@
|
|
|
29605
29688
|
* Clear all snippets
|
|
29606
29689
|
*/
|
|
29607
29690
|
function clearSnippets() {
|
|
29691
|
+
memoryCache$2 = [];
|
|
29608
29692
|
try {
|
|
29609
29693
|
localStorage.removeItem(STORAGE_KEY$3);
|
|
29610
|
-
events.emit("settings:changed", { setting: "snippets" });
|
|
29611
29694
|
}
|
|
29612
29695
|
catch {
|
|
29613
29696
|
// Silently fail
|
|
29614
29697
|
}
|
|
29698
|
+
events.emit("settings:changed", { setting: "snippets" });
|
|
29615
29699
|
}
|
|
29616
29700
|
/**
|
|
29617
29701
|
* Get snippet count info
|
|
@@ -29627,12 +29711,14 @@
|
|
|
29627
29711
|
}
|
|
29628
29712
|
const snippetsStore = {
|
|
29629
29713
|
getSnippets,
|
|
29714
|
+
setSnippets,
|
|
29630
29715
|
addSnippet,
|
|
29631
29716
|
updateSnippet,
|
|
29632
29717
|
deleteSnippet,
|
|
29633
29718
|
clearSnippets,
|
|
29634
29719
|
getSnippetCount,
|
|
29635
29720
|
isAtSnippetLimit,
|
|
29721
|
+
resetMemoryCache: resetMemoryCache$1,
|
|
29636
29722
|
MAX_SNIPPETS,
|
|
29637
29723
|
MAX_TRIGGER_LENGTH,
|
|
29638
29724
|
MAX_EXPANSION_LENGTH,
|
|
@@ -29645,6 +29731,11 @@
|
|
|
29645
29731
|
const STORAGE_KEY$2 = "speechos_vocabulary";
|
|
29646
29732
|
const MAX_TERMS = 50;
|
|
29647
29733
|
const MAX_TERM_LENGTH = 50;
|
|
29734
|
+
/**
|
|
29735
|
+
* In-memory cache for vocabulary. When server sync is enabled, this is the
|
|
29736
|
+
* source of truth. localStorage is only used when server sync is disabled.
|
|
29737
|
+
*/
|
|
29738
|
+
let memoryCache$1 = null;
|
|
29648
29739
|
/**
|
|
29649
29740
|
* Generate a unique ID for vocabulary entries
|
|
29650
29741
|
*/
|
|
@@ -29652,15 +29743,18 @@
|
|
|
29652
29743
|
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
29653
29744
|
}
|
|
29654
29745
|
/**
|
|
29655
|
-
* Get all vocabulary terms from
|
|
29746
|
+
* Get all vocabulary terms. Prefers in-memory cache (from server sync),
|
|
29747
|
+
* then falls back to localStorage.
|
|
29656
29748
|
*/
|
|
29657
29749
|
function getVocabulary() {
|
|
29750
|
+
if (memoryCache$1 !== null) {
|
|
29751
|
+
return [...memoryCache$1].sort((a, b) => b.createdAt - a.createdAt);
|
|
29752
|
+
}
|
|
29658
29753
|
try {
|
|
29659
29754
|
const stored = localStorage.getItem(STORAGE_KEY$2);
|
|
29660
29755
|
if (!stored)
|
|
29661
29756
|
return [];
|
|
29662
29757
|
const entries = JSON.parse(stored);
|
|
29663
|
-
// Return newest first
|
|
29664
29758
|
return entries.sort((a, b) => b.createdAt - a.createdAt);
|
|
29665
29759
|
}
|
|
29666
29760
|
catch {
|
|
@@ -29668,14 +29762,27 @@
|
|
|
29668
29762
|
}
|
|
29669
29763
|
}
|
|
29670
29764
|
/**
|
|
29671
|
-
*
|
|
29765
|
+
* Set vocabulary directly (used by settings sync from server data).
|
|
29766
|
+
*/
|
|
29767
|
+
function setVocabulary(terms) {
|
|
29768
|
+
memoryCache$1 = terms.slice(0, MAX_TERMS);
|
|
29769
|
+
}
|
|
29770
|
+
/**
|
|
29771
|
+
* Reset memory cache (for testing only)
|
|
29772
|
+
*/
|
|
29773
|
+
function resetMemoryCache() {
|
|
29774
|
+
memoryCache$1 = null;
|
|
29775
|
+
}
|
|
29776
|
+
/**
|
|
29777
|
+
* Save vocabulary (updates memory cache and tries localStorage)
|
|
29672
29778
|
*/
|
|
29673
29779
|
function saveVocabulary(terms) {
|
|
29780
|
+
memoryCache$1 = terms;
|
|
29674
29781
|
try {
|
|
29675
29782
|
localStorage.setItem(STORAGE_KEY$2, JSON.stringify(terms));
|
|
29676
29783
|
}
|
|
29677
29784
|
catch {
|
|
29678
|
-
// localStorage full or unavailable -
|
|
29785
|
+
// localStorage full or unavailable - memory cache still updated
|
|
29679
29786
|
}
|
|
29680
29787
|
}
|
|
29681
29788
|
/**
|
|
@@ -29743,13 +29850,14 @@
|
|
|
29743
29850
|
* Clear all vocabulary
|
|
29744
29851
|
*/
|
|
29745
29852
|
function clearVocabulary() {
|
|
29853
|
+
memoryCache$1 = [];
|
|
29746
29854
|
try {
|
|
29747
29855
|
localStorage.removeItem(STORAGE_KEY$2);
|
|
29748
|
-
events.emit("settings:changed", { setting: "vocabulary" });
|
|
29749
29856
|
}
|
|
29750
29857
|
catch {
|
|
29751
29858
|
// Silently fail
|
|
29752
29859
|
}
|
|
29860
|
+
events.emit("settings:changed", { setting: "vocabulary" });
|
|
29753
29861
|
}
|
|
29754
29862
|
/**
|
|
29755
29863
|
* Get vocabulary count info
|
|
@@ -29765,11 +29873,13 @@
|
|
|
29765
29873
|
}
|
|
29766
29874
|
const vocabularyStore = {
|
|
29767
29875
|
getVocabulary,
|
|
29876
|
+
setVocabulary,
|
|
29768
29877
|
addTerm,
|
|
29769
29878
|
deleteTerm,
|
|
29770
29879
|
clearVocabulary,
|
|
29771
29880
|
getVocabularyCount,
|
|
29772
29881
|
isAtVocabularyLimit,
|
|
29882
|
+
resetMemoryCache,
|
|
29773
29883
|
MAX_TERMS,
|
|
29774
29884
|
MAX_TERM_LENGTH,
|
|
29775
29885
|
};
|
|
@@ -29867,6 +29977,451 @@
|
|
|
29867
29977
|
resetAudioSettings,
|
|
29868
29978
|
};
|
|
29869
29979
|
|
|
29980
|
+
/**
|
|
29981
|
+
* Transcript history store
|
|
29982
|
+
* Persists transcripts to localStorage for viewing in the settings modal
|
|
29983
|
+
*/
|
|
29984
|
+
const STORAGE_KEY = "speechos_transcripts";
|
|
29985
|
+
const MAX_ENTRIES = 50;
|
|
29986
|
+
/**
|
|
29987
|
+
* In-memory cache for transcripts. When server sync is enabled, this is the
|
|
29988
|
+
* source of truth. localStorage is only used when server sync is disabled.
|
|
29989
|
+
*/
|
|
29990
|
+
let memoryCache = null;
|
|
29991
|
+
/**
|
|
29992
|
+
* Generate a unique ID for transcript entries
|
|
29993
|
+
*/
|
|
29994
|
+
function generateId() {
|
|
29995
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
29996
|
+
}
|
|
29997
|
+
/**
|
|
29998
|
+
* Get all transcripts. Prefers in-memory cache (from server sync),
|
|
29999
|
+
* then falls back to localStorage.
|
|
30000
|
+
*/
|
|
30001
|
+
function getTranscripts() {
|
|
30002
|
+
// If we have in-memory data (from server sync), use it
|
|
30003
|
+
if (memoryCache !== null) {
|
|
30004
|
+
return [...memoryCache].sort((a, b) => b.timestamp - a.timestamp);
|
|
30005
|
+
}
|
|
30006
|
+
// Fall back to localStorage (when server sync is disabled)
|
|
30007
|
+
try {
|
|
30008
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
30009
|
+
if (!stored)
|
|
30010
|
+
return [];
|
|
30011
|
+
const entries = JSON.parse(stored);
|
|
30012
|
+
return entries.sort((a, b) => b.timestamp - a.timestamp);
|
|
30013
|
+
}
|
|
30014
|
+
catch {
|
|
30015
|
+
return [];
|
|
30016
|
+
}
|
|
30017
|
+
}
|
|
30018
|
+
/**
|
|
30019
|
+
* Set transcripts directly (used by settings sync from server data).
|
|
30020
|
+
* Server data is the source of truth - just update memory cache.
|
|
30021
|
+
*/
|
|
30022
|
+
function setTranscripts(entries) {
|
|
30023
|
+
memoryCache = entries.slice(0, MAX_ENTRIES);
|
|
30024
|
+
}
|
|
30025
|
+
/**
|
|
30026
|
+
* Save a new transcript entry
|
|
30027
|
+
*/
|
|
30028
|
+
function saveTranscript(text, action, originalTextOrOptions) {
|
|
30029
|
+
const entry = {
|
|
30030
|
+
id: generateId(),
|
|
30031
|
+
text,
|
|
30032
|
+
timestamp: Date.now(),
|
|
30033
|
+
action,
|
|
30034
|
+
};
|
|
30035
|
+
// Handle edit action with originalText string
|
|
30036
|
+
if (action === "edit" && typeof originalTextOrOptions === "string") {
|
|
30037
|
+
entry.originalText = originalTextOrOptions;
|
|
30038
|
+
}
|
|
30039
|
+
// Handle command action with options object
|
|
30040
|
+
if (action === "command" && typeof originalTextOrOptions === "object") {
|
|
30041
|
+
const options = originalTextOrOptions;
|
|
30042
|
+
if (options.inputText !== undefined)
|
|
30043
|
+
entry.inputText = options.inputText;
|
|
30044
|
+
if (options.commandResult !== undefined)
|
|
30045
|
+
entry.commandResult = options.commandResult;
|
|
30046
|
+
if (options.commandConfig !== undefined)
|
|
30047
|
+
entry.commandConfig = options.commandConfig;
|
|
30048
|
+
}
|
|
30049
|
+
const entries = getTranscripts();
|
|
30050
|
+
entries.unshift(entry);
|
|
30051
|
+
// Prune to max entries
|
|
30052
|
+
const pruned = entries.slice(0, MAX_ENTRIES);
|
|
30053
|
+
// Update memory cache (always)
|
|
30054
|
+
memoryCache = pruned;
|
|
30055
|
+
// Try to persist to localStorage (for when server sync is disabled)
|
|
30056
|
+
try {
|
|
30057
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(pruned));
|
|
30058
|
+
}
|
|
30059
|
+
catch {
|
|
30060
|
+
// Quota exceeded - memory cache is still updated
|
|
30061
|
+
}
|
|
30062
|
+
// Emit settings change event to trigger sync
|
|
30063
|
+
events.emit("settings:changed", { setting: "history" });
|
|
30064
|
+
return entry;
|
|
30065
|
+
}
|
|
30066
|
+
/**
|
|
30067
|
+
* Clear all transcript history
|
|
30068
|
+
*/
|
|
30069
|
+
function clearTranscripts() {
|
|
30070
|
+
memoryCache = [];
|
|
30071
|
+
try {
|
|
30072
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
30073
|
+
}
|
|
30074
|
+
catch {
|
|
30075
|
+
// Silently fail
|
|
30076
|
+
}
|
|
30077
|
+
events.emit("settings:changed", { setting: "history" });
|
|
30078
|
+
}
|
|
30079
|
+
/**
|
|
30080
|
+
* Delete a single transcript by ID
|
|
30081
|
+
*/
|
|
30082
|
+
function deleteTranscript(id) {
|
|
30083
|
+
const entries = getTranscripts().filter((e) => e.id !== id);
|
|
30084
|
+
memoryCache = entries;
|
|
30085
|
+
try {
|
|
30086
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));
|
|
30087
|
+
}
|
|
30088
|
+
catch {
|
|
30089
|
+
// Silently fail
|
|
30090
|
+
}
|
|
30091
|
+
events.emit("settings:changed", { setting: "history" });
|
|
30092
|
+
}
|
|
30093
|
+
const transcriptStore = {
|
|
30094
|
+
getTranscripts,
|
|
30095
|
+
setTranscripts,
|
|
30096
|
+
saveTranscript,
|
|
30097
|
+
clearTranscripts,
|
|
30098
|
+
deleteTranscript,
|
|
30099
|
+
};
|
|
30100
|
+
|
|
30101
|
+
/**
|
|
30102
|
+
* Settings sync manager
|
|
30103
|
+
* Syncs user settings (language, vocabulary, snippets, history) with the server
|
|
30104
|
+
*/
|
|
30105
|
+
// Sync debounce delay in milliseconds
|
|
30106
|
+
const SYNC_DEBOUNCE_MS = 2000;
|
|
30107
|
+
// Maximum retry attempts
|
|
30108
|
+
const MAX_RETRIES = 3;
|
|
30109
|
+
// Base retry delay in milliseconds (exponential backoff)
|
|
30110
|
+
const BASE_RETRY_DELAY_MS = 2000;
|
|
30111
|
+
/**
|
|
30112
|
+
* Settings sync manager singleton
|
|
30113
|
+
*/
|
|
30114
|
+
class SettingsSync {
|
|
30115
|
+
constructor() {
|
|
30116
|
+
this.syncTimer = null;
|
|
30117
|
+
this.isSyncing = false;
|
|
30118
|
+
this.retryCount = 0;
|
|
30119
|
+
this.isInitialized = false;
|
|
30120
|
+
this.unsubscribe = null;
|
|
30121
|
+
/** When true, sync is disabled due to CSP or network restrictions */
|
|
30122
|
+
this.syncDisabled = false;
|
|
30123
|
+
}
|
|
30124
|
+
/**
|
|
30125
|
+
* Make a fetch request using native fetch.
|
|
30126
|
+
*/
|
|
30127
|
+
async doFetch(url, options) {
|
|
30128
|
+
const config = getConfig();
|
|
30129
|
+
if (config.debug) {
|
|
30130
|
+
console.log("[SpeechOS] Using native fetch", options.method, url);
|
|
30131
|
+
}
|
|
30132
|
+
return fetch(url, options);
|
|
30133
|
+
}
|
|
30134
|
+
/**
|
|
30135
|
+
* Initialize the settings sync manager
|
|
30136
|
+
* If a settingsToken is configured, loads settings from server
|
|
30137
|
+
*/
|
|
30138
|
+
async init() {
|
|
30139
|
+
const token = getSettingsToken();
|
|
30140
|
+
if (!token) {
|
|
30141
|
+
// No token configured, sync is disabled
|
|
30142
|
+
return;
|
|
30143
|
+
}
|
|
30144
|
+
if (this.isInitialized) {
|
|
30145
|
+
return;
|
|
30146
|
+
}
|
|
30147
|
+
this.isInitialized = true;
|
|
30148
|
+
// Subscribe to settings changes
|
|
30149
|
+
this.unsubscribe = events.on("settings:changed", () => {
|
|
30150
|
+
this.scheduleSyncToServer();
|
|
30151
|
+
});
|
|
30152
|
+
// Load settings from server
|
|
30153
|
+
await this.loadFromServer();
|
|
30154
|
+
}
|
|
30155
|
+
/**
|
|
30156
|
+
* Stop the sync manager and clean up
|
|
30157
|
+
*/
|
|
30158
|
+
destroy() {
|
|
30159
|
+
if (this.syncTimer) {
|
|
30160
|
+
clearTimeout(this.syncTimer);
|
|
30161
|
+
this.syncTimer = null;
|
|
30162
|
+
}
|
|
30163
|
+
if (this.unsubscribe) {
|
|
30164
|
+
this.unsubscribe();
|
|
30165
|
+
this.unsubscribe = null;
|
|
30166
|
+
}
|
|
30167
|
+
this.isInitialized = false;
|
|
30168
|
+
this.retryCount = 0;
|
|
30169
|
+
this.syncDisabled = false;
|
|
30170
|
+
}
|
|
30171
|
+
/**
|
|
30172
|
+
* Load settings from the server and merge with local
|
|
30173
|
+
*/
|
|
30174
|
+
async loadFromServer() {
|
|
30175
|
+
const token = getSettingsToken();
|
|
30176
|
+
if (!token) {
|
|
30177
|
+
return;
|
|
30178
|
+
}
|
|
30179
|
+
const config = getConfig();
|
|
30180
|
+
try {
|
|
30181
|
+
const response = await this.doFetch(`${config.host}/api/user-settings/`, {
|
|
30182
|
+
method: "GET",
|
|
30183
|
+
headers: {
|
|
30184
|
+
Authorization: `Bearer ${token}`,
|
|
30185
|
+
"Content-Type": "application/json",
|
|
30186
|
+
},
|
|
30187
|
+
});
|
|
30188
|
+
if (config.debug) {
|
|
30189
|
+
console.log("[SpeechOS] Settings fetch response:", response.status, response.ok ? "OK" : response.statusText);
|
|
30190
|
+
}
|
|
30191
|
+
if (response.status === 404) {
|
|
30192
|
+
// No settings on server yet (new user) - sync local settings to server
|
|
30193
|
+
if (config.debug) {
|
|
30194
|
+
console.log("[SpeechOS] No server settings found, syncing local to server");
|
|
30195
|
+
}
|
|
30196
|
+
await this.syncToServer();
|
|
30197
|
+
return;
|
|
30198
|
+
}
|
|
30199
|
+
if (response.status === 401 || response.status === 403) {
|
|
30200
|
+
// Token expired or invalid
|
|
30201
|
+
this.handleTokenExpired();
|
|
30202
|
+
return;
|
|
30203
|
+
}
|
|
30204
|
+
if (!response.ok) {
|
|
30205
|
+
throw new Error(`Server returned ${response.status}`);
|
|
30206
|
+
}
|
|
30207
|
+
const serverSettings = (await response.json());
|
|
30208
|
+
if (config.debug) {
|
|
30209
|
+
console.log("[SpeechOS] Settings received from server:", {
|
|
30210
|
+
language: serverSettings.language,
|
|
30211
|
+
vocabularyCount: serverSettings.vocabulary?.length ?? 0,
|
|
30212
|
+
snippetsCount: serverSettings.snippets?.length ?? 0,
|
|
30213
|
+
historyCount: serverSettings.history?.length ?? 0,
|
|
30214
|
+
lastSyncedAt: serverSettings.lastSyncedAt,
|
|
30215
|
+
});
|
|
30216
|
+
if (serverSettings.history?.length > 0) {
|
|
30217
|
+
console.log("[SpeechOS] History entries:", serverSettings.history);
|
|
30218
|
+
}
|
|
30219
|
+
}
|
|
30220
|
+
this.mergeSettings(serverSettings);
|
|
30221
|
+
events.emit("settings:loaded", undefined);
|
|
30222
|
+
if (config.debug) {
|
|
30223
|
+
console.log("[SpeechOS] Settings merged and loaded");
|
|
30224
|
+
}
|
|
30225
|
+
}
|
|
30226
|
+
catch (error) {
|
|
30227
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
30228
|
+
// Check if this is a CSP/network restriction - disable sync permanently for this session
|
|
30229
|
+
if (this.isNetworkRestrictionError(error)) {
|
|
30230
|
+
this.syncDisabled = true;
|
|
30231
|
+
if (config.debug) {
|
|
30232
|
+
console.log("[SpeechOS] Settings sync disabled (CSP/network restriction), using localStorage only");
|
|
30233
|
+
}
|
|
30234
|
+
}
|
|
30235
|
+
else if (config.debug) {
|
|
30236
|
+
console.warn("[SpeechOS] Failed to load settings from server:", errorMessage);
|
|
30237
|
+
}
|
|
30238
|
+
events.emit("settings:syncFailed", { error: errorMessage });
|
|
30239
|
+
// Continue with local settings on error
|
|
30240
|
+
}
|
|
30241
|
+
}
|
|
30242
|
+
/**
|
|
30243
|
+
* Merge server settings with local (server wins).
|
|
30244
|
+
* Uses store setters to update memory cache - localStorage is a fallback.
|
|
30245
|
+
*/
|
|
30246
|
+
mergeSettings(serverSettings) {
|
|
30247
|
+
// Language settings - server wins
|
|
30248
|
+
if (serverSettings.language) {
|
|
30249
|
+
setLanguageSettings(serverSettings.language);
|
|
30250
|
+
}
|
|
30251
|
+
// Vocabulary - server wins
|
|
30252
|
+
if (serverSettings.vocabulary) {
|
|
30253
|
+
setVocabulary(serverSettings.vocabulary);
|
|
30254
|
+
}
|
|
30255
|
+
// Snippets - server wins
|
|
30256
|
+
if (serverSettings.snippets) {
|
|
30257
|
+
setSnippets(serverSettings.snippets);
|
|
30258
|
+
}
|
|
30259
|
+
// History - server wins
|
|
30260
|
+
if (serverSettings.history) {
|
|
30261
|
+
setTranscripts(serverSettings.history);
|
|
30262
|
+
}
|
|
30263
|
+
}
|
|
30264
|
+
/**
|
|
30265
|
+
* Schedule a debounced sync to server
|
|
30266
|
+
*/
|
|
30267
|
+
scheduleSyncToServer() {
|
|
30268
|
+
const token = getSettingsToken();
|
|
30269
|
+
if (!token || this.syncDisabled) {
|
|
30270
|
+
return;
|
|
30271
|
+
}
|
|
30272
|
+
// Cancel any pending sync
|
|
30273
|
+
if (this.syncTimer) {
|
|
30274
|
+
clearTimeout(this.syncTimer);
|
|
30275
|
+
}
|
|
30276
|
+
// Schedule new sync
|
|
30277
|
+
this.syncTimer = setTimeout(() => {
|
|
30278
|
+
this.syncToServer();
|
|
30279
|
+
}, SYNC_DEBOUNCE_MS);
|
|
30280
|
+
}
|
|
30281
|
+
/**
|
|
30282
|
+
* Sync current settings to server
|
|
30283
|
+
*/
|
|
30284
|
+
async syncToServer() {
|
|
30285
|
+
const token = getSettingsToken();
|
|
30286
|
+
if (!token || this.isSyncing || this.syncDisabled) {
|
|
30287
|
+
return;
|
|
30288
|
+
}
|
|
30289
|
+
this.isSyncing = true;
|
|
30290
|
+
const config = getConfig();
|
|
30291
|
+
try {
|
|
30292
|
+
const languageSettings = getLanguageSettings();
|
|
30293
|
+
const vocabulary = getVocabulary();
|
|
30294
|
+
const snippets = getSnippets();
|
|
30295
|
+
const transcripts = getTranscripts();
|
|
30296
|
+
const payload = {
|
|
30297
|
+
language: {
|
|
30298
|
+
inputLanguageCode: languageSettings.inputLanguageCode,
|
|
30299
|
+
outputLanguageCode: languageSettings.outputLanguageCode,
|
|
30300
|
+
smartFormat: languageSettings.smartFormat,
|
|
30301
|
+
},
|
|
30302
|
+
vocabulary: vocabulary.map((v) => ({
|
|
30303
|
+
id: v.id,
|
|
30304
|
+
term: v.term,
|
|
30305
|
+
createdAt: v.createdAt,
|
|
30306
|
+
})),
|
|
30307
|
+
snippets: snippets.map((s) => ({
|
|
30308
|
+
id: s.id,
|
|
30309
|
+
trigger: s.trigger,
|
|
30310
|
+
expansion: s.expansion,
|
|
30311
|
+
createdAt: s.createdAt,
|
|
30312
|
+
})),
|
|
30313
|
+
// Sync history (excluding commandConfig to reduce payload size)
|
|
30314
|
+
history: transcripts.map((t) => ({
|
|
30315
|
+
id: t.id,
|
|
30316
|
+
text: t.text,
|
|
30317
|
+
timestamp: t.timestamp,
|
|
30318
|
+
action: t.action,
|
|
30319
|
+
...(t.originalText && { originalText: t.originalText }),
|
|
30320
|
+
...(t.inputText && { inputText: t.inputText }),
|
|
30321
|
+
...(t.commandResult !== undefined && { commandResult: t.commandResult }),
|
|
30322
|
+
})),
|
|
30323
|
+
};
|
|
30324
|
+
const response = await this.doFetch(`${config.host}/api/user-settings/`, {
|
|
30325
|
+
method: "PUT",
|
|
30326
|
+
headers: {
|
|
30327
|
+
Authorization: `Bearer ${token}`,
|
|
30328
|
+
"Content-Type": "application/json",
|
|
30329
|
+
},
|
|
30330
|
+
body: JSON.stringify(payload),
|
|
30331
|
+
});
|
|
30332
|
+
if (response.status === 401 || response.status === 403) {
|
|
30333
|
+
this.handleTokenExpired();
|
|
30334
|
+
return;
|
|
30335
|
+
}
|
|
30336
|
+
if (!response.ok) {
|
|
30337
|
+
throw new Error(`Server returned ${response.status}`);
|
|
30338
|
+
}
|
|
30339
|
+
// Reset retry count on success
|
|
30340
|
+
this.retryCount = 0;
|
|
30341
|
+
events.emit("settings:synced", undefined);
|
|
30342
|
+
if (config.debug) {
|
|
30343
|
+
console.log("[SpeechOS] Settings synced to server");
|
|
30344
|
+
}
|
|
30345
|
+
}
|
|
30346
|
+
catch (error) {
|
|
30347
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
30348
|
+
// Check if this is a CSP/network restriction - disable sync permanently for this session
|
|
30349
|
+
if (this.isNetworkRestrictionError(error)) {
|
|
30350
|
+
this.syncDisabled = true;
|
|
30351
|
+
if (config.debug) {
|
|
30352
|
+
console.log("[SpeechOS] Settings sync disabled (CSP/network restriction), using localStorage only");
|
|
30353
|
+
}
|
|
30354
|
+
events.emit("settings:syncFailed", { error: errorMessage });
|
|
30355
|
+
// Don't retry - CSP errors are permanent
|
|
30356
|
+
}
|
|
30357
|
+
else {
|
|
30358
|
+
if (config.debug) {
|
|
30359
|
+
console.warn("[SpeechOS] Failed to sync settings to server:", errorMessage);
|
|
30360
|
+
}
|
|
30361
|
+
events.emit("settings:syncFailed", { error: errorMessage });
|
|
30362
|
+
// Retry with exponential backoff (only for transient errors)
|
|
30363
|
+
this.scheduleRetry();
|
|
30364
|
+
}
|
|
30365
|
+
}
|
|
30366
|
+
finally {
|
|
30367
|
+
this.isSyncing = false;
|
|
30368
|
+
}
|
|
30369
|
+
}
|
|
30370
|
+
/**
|
|
30371
|
+
* Schedule a retry with exponential backoff
|
|
30372
|
+
*/
|
|
30373
|
+
scheduleRetry() {
|
|
30374
|
+
if (this.retryCount >= MAX_RETRIES) {
|
|
30375
|
+
const config = getConfig();
|
|
30376
|
+
if (config.debug) {
|
|
30377
|
+
console.warn("[SpeechOS] Max retries reached, giving up sync");
|
|
30378
|
+
}
|
|
30379
|
+
this.retryCount = 0;
|
|
30380
|
+
return;
|
|
30381
|
+
}
|
|
30382
|
+
this.retryCount++;
|
|
30383
|
+
const delay = BASE_RETRY_DELAY_MS * Math.pow(2, this.retryCount - 1);
|
|
30384
|
+
this.syncTimer = setTimeout(() => {
|
|
30385
|
+
this.syncToServer();
|
|
30386
|
+
}, delay);
|
|
30387
|
+
}
|
|
30388
|
+
/**
|
|
30389
|
+
* Check if an error is a CSP or network restriction error
|
|
30390
|
+
* These errors are permanent and shouldn't trigger retries
|
|
30391
|
+
*/
|
|
30392
|
+
isNetworkRestrictionError(error) {
|
|
30393
|
+
if (error instanceof TypeError) {
|
|
30394
|
+
const message = error.message.toLowerCase();
|
|
30395
|
+
// Common CSP/network error messages
|
|
30396
|
+
return (message.includes("failed to fetch") ||
|
|
30397
|
+
message.includes("network request failed") ||
|
|
30398
|
+
message.includes("content security policy") ||
|
|
30399
|
+
message.includes("csp") ||
|
|
30400
|
+
message.includes("blocked"));
|
|
30401
|
+
}
|
|
30402
|
+
return false;
|
|
30403
|
+
}
|
|
30404
|
+
/**
|
|
30405
|
+
* Handle token expiration
|
|
30406
|
+
*/
|
|
30407
|
+
handleTokenExpired() {
|
|
30408
|
+
clearSettingsToken();
|
|
30409
|
+
events.emit("settings:tokenExpired", undefined);
|
|
30410
|
+
const config = getConfig();
|
|
30411
|
+
if (config.debug) {
|
|
30412
|
+
console.warn("[SpeechOS] Settings token expired");
|
|
30413
|
+
}
|
|
30414
|
+
}
|
|
30415
|
+
/**
|
|
30416
|
+
* Check if sync is enabled (token is configured)
|
|
30417
|
+
*/
|
|
30418
|
+
isEnabled() {
|
|
30419
|
+
return !!getSettingsToken();
|
|
30420
|
+
}
|
|
30421
|
+
}
|
|
30422
|
+
// Singleton instance
|
|
30423
|
+
const settingsSync = new SettingsSync();
|
|
30424
|
+
|
|
29870
30425
|
/******************************************************************************
|
|
29871
30426
|
Copyright (c) Microsoft Corporation.
|
|
29872
30427
|
|
|
@@ -29950,6 +30505,9 @@
|
|
|
29950
30505
|
*/
|
|
29951
30506
|
const themeStyles = i$4 `
|
|
29952
30507
|
:host {
|
|
30508
|
+
/* Font stack - system fonts for consistent rendering across sites */
|
|
30509
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
30510
|
+
|
|
29953
30511
|
/* Color tokens */
|
|
29954
30512
|
--speechos-primary: #10B981;
|
|
29955
30513
|
--speechos-primary-hover: #059669;
|
|
@@ -30073,100 +30631,6 @@
|
|
|
30073
30631
|
}
|
|
30074
30632
|
`;
|
|
30075
30633
|
|
|
30076
|
-
/**
|
|
30077
|
-
* Transcript history store
|
|
30078
|
-
* Persists transcripts to localStorage for viewing in the settings modal
|
|
30079
|
-
*/
|
|
30080
|
-
const STORAGE_KEY = "speechos_transcripts";
|
|
30081
|
-
const MAX_ENTRIES = 50;
|
|
30082
|
-
/**
|
|
30083
|
-
* Generate a unique ID for transcript entries
|
|
30084
|
-
*/
|
|
30085
|
-
function generateId() {
|
|
30086
|
-
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
30087
|
-
}
|
|
30088
|
-
/**
|
|
30089
|
-
* Get all transcripts from localStorage
|
|
30090
|
-
*/
|
|
30091
|
-
function getTranscripts() {
|
|
30092
|
-
try {
|
|
30093
|
-
const stored = localStorage.getItem(STORAGE_KEY);
|
|
30094
|
-
if (!stored)
|
|
30095
|
-
return [];
|
|
30096
|
-
const entries = JSON.parse(stored);
|
|
30097
|
-
// Return newest first
|
|
30098
|
-
return entries.sort((a, b) => b.timestamp - a.timestamp);
|
|
30099
|
-
}
|
|
30100
|
-
catch {
|
|
30101
|
-
return [];
|
|
30102
|
-
}
|
|
30103
|
-
}
|
|
30104
|
-
/**
|
|
30105
|
-
* Save a new transcript entry
|
|
30106
|
-
*/
|
|
30107
|
-
function saveTranscript(text, action, originalTextOrOptions) {
|
|
30108
|
-
const entry = {
|
|
30109
|
-
id: generateId(),
|
|
30110
|
-
text,
|
|
30111
|
-
timestamp: Date.now(),
|
|
30112
|
-
action,
|
|
30113
|
-
};
|
|
30114
|
-
// Handle edit action with originalText string
|
|
30115
|
-
if (action === "edit" && typeof originalTextOrOptions === "string") {
|
|
30116
|
-
entry.originalText = originalTextOrOptions;
|
|
30117
|
-
}
|
|
30118
|
-
// Handle command action with options object
|
|
30119
|
-
if (action === "command" && typeof originalTextOrOptions === "object") {
|
|
30120
|
-
const options = originalTextOrOptions;
|
|
30121
|
-
if (options.inputText !== undefined)
|
|
30122
|
-
entry.inputText = options.inputText;
|
|
30123
|
-
if (options.commandResult !== undefined)
|
|
30124
|
-
entry.commandResult = options.commandResult;
|
|
30125
|
-
if (options.commandConfig !== undefined)
|
|
30126
|
-
entry.commandConfig = options.commandConfig;
|
|
30127
|
-
}
|
|
30128
|
-
const entries = getTranscripts();
|
|
30129
|
-
entries.unshift(entry);
|
|
30130
|
-
// Prune to max entries
|
|
30131
|
-
const pruned = entries.slice(0, MAX_ENTRIES);
|
|
30132
|
-
try {
|
|
30133
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(pruned));
|
|
30134
|
-
}
|
|
30135
|
-
catch {
|
|
30136
|
-
// localStorage full or unavailable - silently fail
|
|
30137
|
-
}
|
|
30138
|
-
return entry;
|
|
30139
|
-
}
|
|
30140
|
-
/**
|
|
30141
|
-
* Clear all transcript history
|
|
30142
|
-
*/
|
|
30143
|
-
function clearTranscripts() {
|
|
30144
|
-
try {
|
|
30145
|
-
localStorage.removeItem(STORAGE_KEY);
|
|
30146
|
-
}
|
|
30147
|
-
catch {
|
|
30148
|
-
// Silently fail
|
|
30149
|
-
}
|
|
30150
|
-
}
|
|
30151
|
-
/**
|
|
30152
|
-
* Delete a single transcript by ID
|
|
30153
|
-
*/
|
|
30154
|
-
function deleteTranscript(id) {
|
|
30155
|
-
const entries = getTranscripts().filter((e) => e.id !== id);
|
|
30156
|
-
try {
|
|
30157
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));
|
|
30158
|
-
}
|
|
30159
|
-
catch {
|
|
30160
|
-
// Silently fail
|
|
30161
|
-
}
|
|
30162
|
-
}
|
|
30163
|
-
const transcriptStore = {
|
|
30164
|
-
getTranscripts: getTranscripts,
|
|
30165
|
-
saveTranscript: saveTranscript,
|
|
30166
|
-
clearTranscripts: clearTranscripts,
|
|
30167
|
-
deleteTranscript: deleteTranscript,
|
|
30168
|
-
};
|
|
30169
|
-
|
|
30170
30634
|
function isNativeField(field) {
|
|
30171
30635
|
return field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement;
|
|
30172
30636
|
}
|
|
@@ -32164,6 +32628,8 @@
|
|
|
32164
32628
|
inset: 0;
|
|
32165
32629
|
pointer-events: none;
|
|
32166
32630
|
z-index: calc(var(--speechos-z-base) + 100);
|
|
32631
|
+
/* Ensure consistent font rendering across all sites */
|
|
32632
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
32167
32633
|
}
|
|
32168
32634
|
|
|
32169
32635
|
.modal-overlay {
|
|
@@ -32652,6 +33118,7 @@
|
|
|
32652
33118
|
constructor() {
|
|
32653
33119
|
super(...arguments);
|
|
32654
33120
|
this.transcripts = [];
|
|
33121
|
+
this.unsubscribeSettingsLoaded = null;
|
|
32655
33122
|
}
|
|
32656
33123
|
static { this.styles = [
|
|
32657
33124
|
themeStyles,
|
|
@@ -32788,11 +33255,36 @@
|
|
|
32788
33255
|
background: rgba(239, 68, 68, 0.18);
|
|
32789
33256
|
border-color: rgba(239, 68, 68, 0.25);
|
|
32790
33257
|
}
|
|
33258
|
+
|
|
33259
|
+
.command-matched {
|
|
33260
|
+
font-size: 12px;
|
|
33261
|
+
color: rgba(255, 255, 255, 0.5);
|
|
33262
|
+
margin-top: 6px;
|
|
33263
|
+
}
|
|
33264
|
+
|
|
33265
|
+
.command-matched code {
|
|
33266
|
+
background: rgba(245, 158, 11, 0.15);
|
|
33267
|
+
color: #fbbf24;
|
|
33268
|
+
padding: 2px 6px;
|
|
33269
|
+
border-radius: 4px;
|
|
33270
|
+
font-family: monospace;
|
|
33271
|
+
}
|
|
32791
33272
|
`,
|
|
32792
33273
|
]; }
|
|
32793
33274
|
connectedCallback() {
|
|
32794
33275
|
super.connectedCallback();
|
|
32795
33276
|
this.loadTranscripts();
|
|
33277
|
+
// Refresh when settings are loaded from the server (history may have changed)
|
|
33278
|
+
this.unsubscribeSettingsLoaded = events.on("settings:loaded", () => {
|
|
33279
|
+
this.loadTranscripts();
|
|
33280
|
+
});
|
|
33281
|
+
}
|
|
33282
|
+
disconnectedCallback() {
|
|
33283
|
+
super.disconnectedCallback();
|
|
33284
|
+
if (this.unsubscribeSettingsLoaded) {
|
|
33285
|
+
this.unsubscribeSettingsLoaded();
|
|
33286
|
+
this.unsubscribeSettingsLoaded = null;
|
|
33287
|
+
}
|
|
32796
33288
|
}
|
|
32797
33289
|
/** Reload transcripts from store */
|
|
32798
33290
|
refresh() {
|
|
@@ -32863,7 +33355,13 @@
|
|
|
32863
33355
|
renderCommandDetails(entry) {
|
|
32864
33356
|
// Show the transcript text (what the user said)
|
|
32865
33357
|
const displayText = entry.inputText || entry.text;
|
|
32866
|
-
|
|
33358
|
+
const commandName = entry.commandResult?.name;
|
|
33359
|
+
return b `
|
|
33360
|
+
<div class="transcript-text">${displayText}</div>
|
|
33361
|
+
${commandName
|
|
33362
|
+
? b `<div class="command-matched">Matched: <code>${commandName}</code></div>`
|
|
33363
|
+
: null}
|
|
33364
|
+
`;
|
|
32867
33365
|
}
|
|
32868
33366
|
getCopyText(entry) {
|
|
32869
33367
|
if (entry.action === "command") {
|
|
@@ -33413,6 +33911,7 @@
|
|
|
33413
33911
|
this.permissionGranted = false;
|
|
33414
33912
|
this.smartFormatEnabled = true;
|
|
33415
33913
|
this.savedIndicatorTimeout = null;
|
|
33914
|
+
this.unsubscribeSettingsLoaded = null;
|
|
33416
33915
|
}
|
|
33417
33916
|
static { this.styles = [
|
|
33418
33917
|
themeStyles,
|
|
@@ -33506,6 +34005,16 @@
|
|
|
33506
34005
|
padding: 8px;
|
|
33507
34006
|
}
|
|
33508
34007
|
|
|
34008
|
+
.settings-select:disabled {
|
|
34009
|
+
opacity: 0.4;
|
|
34010
|
+
cursor: not-allowed;
|
|
34011
|
+
}
|
|
34012
|
+
|
|
34013
|
+
.settings-select:disabled:hover {
|
|
34014
|
+
border-color: rgba(255, 255, 255, 0.08);
|
|
34015
|
+
background: rgba(0, 0, 0, 0.3);
|
|
34016
|
+
}
|
|
34017
|
+
|
|
33509
34018
|
.settings-select-arrow {
|
|
33510
34019
|
position: absolute;
|
|
33511
34020
|
right: 14px;
|
|
@@ -33587,12 +34096,20 @@
|
|
|
33587
34096
|
connectedCallback() {
|
|
33588
34097
|
super.connectedCallback();
|
|
33589
34098
|
this.loadSettings();
|
|
34099
|
+
// Refresh when settings are loaded from the server
|
|
34100
|
+
this.unsubscribeSettingsLoaded = events.on("settings:loaded", () => {
|
|
34101
|
+
this.loadSettings();
|
|
34102
|
+
});
|
|
33590
34103
|
}
|
|
33591
34104
|
disconnectedCallback() {
|
|
33592
34105
|
super.disconnectedCallback();
|
|
33593
34106
|
if (this.savedIndicatorTimeout) {
|
|
33594
34107
|
clearTimeout(this.savedIndicatorTimeout);
|
|
33595
34108
|
}
|
|
34109
|
+
if (this.unsubscribeSettingsLoaded) {
|
|
34110
|
+
this.unsubscribeSettingsLoaded();
|
|
34111
|
+
this.unsubscribeSettingsLoaded = null;
|
|
34112
|
+
}
|
|
33596
34113
|
this.isTestingMic = false;
|
|
33597
34114
|
}
|
|
33598
34115
|
/** Called when tab becomes active */
|
|
@@ -33679,13 +34196,14 @@
|
|
|
33679
34196
|
setSmartFormatEnabled(this.smartFormatEnabled);
|
|
33680
34197
|
this.showSaved();
|
|
33681
34198
|
}
|
|
33682
|
-
renderLanguageSelector(selectedCode, onChange) {
|
|
34199
|
+
renderLanguageSelector(selectedCode, onChange, disabled = false) {
|
|
33683
34200
|
return b `
|
|
33684
34201
|
<div class="settings-select-wrapper">
|
|
33685
34202
|
<select
|
|
33686
34203
|
class="settings-select"
|
|
33687
34204
|
.value="${selectedCode}"
|
|
33688
34205
|
@change="${onChange}"
|
|
34206
|
+
?disabled="${disabled}"
|
|
33689
34207
|
>
|
|
33690
34208
|
${SUPPORTED_LANGUAGES.map((lang) => b `
|
|
33691
34209
|
<option
|
|
@@ -33803,7 +34321,7 @@
|
|
|
33803
34321
|
<div class="settings-section-description">
|
|
33804
34322
|
AI automatically removes filler words, adds punctuation, and polishes
|
|
33805
34323
|
your text. Disable for raw transcription output. Note: disabling also
|
|
33806
|
-
turns off text snippets.
|
|
34324
|
+
turns off text snippets and output language translation.
|
|
33807
34325
|
</div>
|
|
33808
34326
|
<div class="settings-toggle-row">
|
|
33809
34327
|
<span class="settings-toggle-label">Enable AI formatting</span>
|
|
@@ -33844,9 +34362,13 @@
|
|
|
33844
34362
|
</div>
|
|
33845
34363
|
<div class="settings-section-description">
|
|
33846
34364
|
The language for your transcribed text. Usually the same as input, but
|
|
33847
|
-
can differ for translation.
|
|
34365
|
+
can differ for translation.${!this.smartFormatEnabled
|
|
34366
|
+
? " Requires Smart Format to be enabled."
|
|
34367
|
+
: ""}
|
|
33848
34368
|
</div>
|
|
33849
|
-
${this.renderLanguageSelector(this.
|
|
34369
|
+
${this.renderLanguageSelector(this.smartFormatEnabled
|
|
34370
|
+
? this.selectedOutputLanguageCode
|
|
34371
|
+
: this.selectedInputLanguageCode, this.handleOutputLanguageChange.bind(this), !this.smartFormatEnabled)}
|
|
33850
34372
|
</div>
|
|
33851
34373
|
`;
|
|
33852
34374
|
}
|
|
@@ -33891,6 +34413,7 @@
|
|
|
33891
34413
|
this.trigger = "";
|
|
33892
34414
|
this.expansion = "";
|
|
33893
34415
|
this.error = "";
|
|
34416
|
+
this.unsubscribeSettingsLoaded = null;
|
|
33894
34417
|
}
|
|
33895
34418
|
static { this.styles = [
|
|
33896
34419
|
themeStyles,
|
|
@@ -33996,6 +34519,17 @@
|
|
|
33996
34519
|
connectedCallback() {
|
|
33997
34520
|
super.connectedCallback();
|
|
33998
34521
|
this.loadSnippets();
|
|
34522
|
+
// Refresh when settings are loaded from the server
|
|
34523
|
+
this.unsubscribeSettingsLoaded = events.on("settings:loaded", () => {
|
|
34524
|
+
this.loadSnippets();
|
|
34525
|
+
});
|
|
34526
|
+
}
|
|
34527
|
+
disconnectedCallback() {
|
|
34528
|
+
super.disconnectedCallback();
|
|
34529
|
+
if (this.unsubscribeSettingsLoaded) {
|
|
34530
|
+
this.unsubscribeSettingsLoaded();
|
|
34531
|
+
this.unsubscribeSettingsLoaded = null;
|
|
34532
|
+
}
|
|
33999
34533
|
}
|
|
34000
34534
|
/** Reload snippets from store */
|
|
34001
34535
|
refresh() {
|
|
@@ -34238,6 +34772,7 @@
|
|
|
34238
34772
|
this.showForm = false;
|
|
34239
34773
|
this.term = "";
|
|
34240
34774
|
this.error = "";
|
|
34775
|
+
this.unsubscribeSettingsLoaded = null;
|
|
34241
34776
|
}
|
|
34242
34777
|
static { this.styles = [
|
|
34243
34778
|
themeStyles,
|
|
@@ -34290,6 +34825,17 @@
|
|
|
34290
34825
|
connectedCallback() {
|
|
34291
34826
|
super.connectedCallback();
|
|
34292
34827
|
this.loadVocabulary();
|
|
34828
|
+
// Refresh when settings are loaded from the server
|
|
34829
|
+
this.unsubscribeSettingsLoaded = events.on("settings:loaded", () => {
|
|
34830
|
+
this.loadVocabulary();
|
|
34831
|
+
});
|
|
34832
|
+
}
|
|
34833
|
+
disconnectedCallback() {
|
|
34834
|
+
super.disconnectedCallback();
|
|
34835
|
+
if (this.unsubscribeSettingsLoaded) {
|
|
34836
|
+
this.unsubscribeSettingsLoaded();
|
|
34837
|
+
this.unsubscribeSettingsLoaded = null;
|
|
34838
|
+
}
|
|
34293
34839
|
}
|
|
34294
34840
|
/** Reload vocabulary from store */
|
|
34295
34841
|
refresh() {
|
|
@@ -35808,7 +36354,14 @@
|
|
|
35808
36354
|
state.hide();
|
|
35809
36355
|
}
|
|
35810
36356
|
handleSettingsClick() {
|
|
35811
|
-
|
|
36357
|
+
if (useExternalSettings()) {
|
|
36358
|
+
const host = getConfig().host;
|
|
36359
|
+
const fullUrl = `${host}/a/extension-settings`;
|
|
36360
|
+
window.open(fullUrl, '_blank', 'noopener,noreferrer');
|
|
36361
|
+
}
|
|
36362
|
+
else {
|
|
36363
|
+
this.settingsOpen = true;
|
|
36364
|
+
}
|
|
35812
36365
|
}
|
|
35813
36366
|
handleDragStart(e) {
|
|
35814
36367
|
if (e.button !== 0)
|
|
@@ -36349,10 +36902,17 @@
|
|
|
36349
36902
|
this.editSelectionStart = null;
|
|
36350
36903
|
this.editSelectionEnd = null;
|
|
36351
36904
|
this.editSelectedText = "";
|
|
36352
|
-
// Open settings modal
|
|
36353
|
-
|
|
36905
|
+
// Open settings - either external URL or modal
|
|
36906
|
+
if (useExternalSettings()) {
|
|
36907
|
+
const host = getConfig().host;
|
|
36908
|
+
const fullUrl = `${host}/a/extension-settings`;
|
|
36909
|
+
window.open(fullUrl, '_blank', 'noopener,noreferrer');
|
|
36910
|
+
}
|
|
36911
|
+
else {
|
|
36912
|
+
this.settingsOpen = true;
|
|
36913
|
+
}
|
|
36354
36914
|
if (getConfig().debug) {
|
|
36355
|
-
console.log("[SpeechOS] Settings
|
|
36915
|
+
console.log("[SpeechOS] Settings opened from no-audio warning", { useExternalSettings: useExternalSettings() });
|
|
36356
36916
|
}
|
|
36357
36917
|
await disconnectPromise;
|
|
36358
36918
|
}
|
|
@@ -36561,6 +37121,30 @@
|
|
|
36561
37121
|
t$1("speechos-widget")
|
|
36562
37122
|
], SpeechOSWidget);
|
|
36563
37123
|
|
|
37124
|
+
/**
|
|
37125
|
+
* UI module exports
|
|
37126
|
+
* Lit-based Shadow DOM components
|
|
37127
|
+
*/
|
|
37128
|
+
// Patch customElements.define to silently ignore duplicate registrations for speechos-* elements.
|
|
37129
|
+
// This prevents errors when the extension loads on a page that already has SpeechOS.
|
|
37130
|
+
// The patch is scoped to only affect speechos-* tags to avoid unintended effects on host pages.
|
|
37131
|
+
const originalDefine = customElements.define.bind(customElements);
|
|
37132
|
+
customElements.define = (name, constructor, options) => {
|
|
37133
|
+
// Only intercept speechos-* elements
|
|
37134
|
+
if (name.startsWith("speechos-")) {
|
|
37135
|
+
if (customElements.get(name) === undefined) {
|
|
37136
|
+
originalDefine(name, constructor, options);
|
|
37137
|
+
}
|
|
37138
|
+
// Skip silently if already registered
|
|
37139
|
+
}
|
|
37140
|
+
else {
|
|
37141
|
+
// Pass through for non-speechos elements
|
|
37142
|
+
originalDefine(name, constructor, options);
|
|
37143
|
+
}
|
|
37144
|
+
};
|
|
37145
|
+
// Restore original customElements.define after our components are registered
|
|
37146
|
+
customElements.define = originalDefine;
|
|
37147
|
+
|
|
36564
37148
|
/**
|
|
36565
37149
|
* Main SpeechOS Client SDK class
|
|
36566
37150
|
* Composes core logic with UI components
|
|
@@ -36642,6 +37226,13 @@
|
|
|
36642
37226
|
state.show();
|
|
36643
37227
|
}
|
|
36644
37228
|
this.isInitialized = true;
|
|
37229
|
+
// Initialize settings sync if token is configured
|
|
37230
|
+
// This loads settings from server and subscribes to changes
|
|
37231
|
+
settingsSync.init().catch((error) => {
|
|
37232
|
+
if (finalConfig.debug) {
|
|
37233
|
+
console.warn("[SpeechOS] Settings sync initialization failed:", error);
|
|
37234
|
+
}
|
|
37235
|
+
});
|
|
36645
37236
|
// Log initialization in debug mode
|
|
36646
37237
|
if (finalConfig.debug) {
|
|
36647
37238
|
console.log("[SpeechOS] Initialized with config:", finalConfig);
|
|
@@ -36672,6 +37263,8 @@
|
|
|
36672
37263
|
resetClientConfig();
|
|
36673
37264
|
// Reset text input handler to default
|
|
36674
37265
|
resetTextInputHandler();
|
|
37266
|
+
// Stop settings sync
|
|
37267
|
+
settingsSync.destroy();
|
|
36675
37268
|
// Clear instance
|
|
36676
37269
|
this.instance = null;
|
|
36677
37270
|
this.isInitialized = false;
|
|
@@ -36895,10 +37488,12 @@
|
|
|
36895
37488
|
exports.setOutputLanguageCode = setOutputLanguageCode;
|
|
36896
37489
|
exports.setSmartFormatEnabled = setSmartFormatEnabled;
|
|
36897
37490
|
exports.setTextInputHandler = setTextInputHandler;
|
|
37491
|
+
exports.settingsSync = settingsSync;
|
|
36898
37492
|
exports.snippetsStore = snippetsStore;
|
|
36899
37493
|
exports.state = state;
|
|
36900
37494
|
exports.transcriptStore = transcriptStore;
|
|
36901
37495
|
exports.updateSnippet = updateSnippet;
|
|
37496
|
+
exports.useExternalSettings = useExternalSettings;
|
|
36902
37497
|
exports.vocabularyStore = vocabularyStore;
|
|
36903
37498
|
|
|
36904
37499
|
Object.defineProperty(exports, '__esModule', { value: true });
|