@pie-players/pie-section-player-tools-tts-settings 0.3.25 → 0.3.27

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/README.md CHANGED
@@ -35,8 +35,8 @@ Without adapters, the panel uses the existing route contract:
35
35
 
36
36
  and applies settings via toolkit coordinator:
37
37
 
38
- - `getToolConfig("tts")`
39
- - `updateToolConfig("tts", ...)`
38
+ - `getToolConfig("textToSpeech")`
39
+ - `updateToolConfig("textToSpeech", ...)`
40
40
  - optional `ensureTTSReady(...)`
41
41
 
42
42
  ## Custom element API
@@ -73,7 +73,7 @@ Use adapters when your host app cannot or should not expose the default route co
73
73
  You can add provider tabs beyond Browser/Polly/Google through `customProviders`.
74
74
 
75
75
  - Keep provider `id` unique and avoid reserved ids: `browser`, `polly`, `google`.
76
- - The panel still owns persistence and `updateToolConfig("tts", ...)`.
76
+ - The panel still owns persistence and `updateToolConfig("textToSpeech", ...)`.
77
77
  - Provider apply returns normalized output: `{ config, message? }`.
78
78
 
79
79
  ### JS adapter mode
@@ -14,6 +14,14 @@
14
14
 
15
15
  <script lang="ts">
16
16
  import "@pie-players/pie-theme/components.css";
17
+ import {
18
+ DEFAULT_TTS_SPEED_OPTIONS,
19
+ formatTTSSpeedOptionsAsText,
20
+ normalizeTTSSpeedOptions,
21
+ parseTTSSpeedOptionsFromText,
22
+ resolveTTSRuntimeSettings,
23
+ type TTSLayoutMode,
24
+ } from "@pie-players/pie-assessment-toolkit";
17
25
  import { createEventDispatcher, onDestroy, onMount, untrack } from "svelte";
18
26
 
19
27
  type BuiltInBackendTab = "browser" | "polly" | "google";
@@ -164,6 +172,9 @@ type PreviewSpeechMark = { time: number; start: number; end: number; value?: str
164
172
  googleVoiceType?: string;
165
173
  googleGender?: string;
166
174
  providerOptions?: Record<string, unknown>;
175
+ layoutMode?: TTSLayoutMode;
176
+ /** Inline toolbar speed multipliers; `[]` hides speed buttons. */
177
+ speedOptions?: number[];
167
178
  [key: string]: unknown;
168
179
  };
169
180
 
@@ -186,6 +197,8 @@ type PreviewSpeechMark = { time: number; start: number; end: number; value?: str
186
197
  let browserVoice = $state("");
187
198
  let browserRate = $state(1);
188
199
  let browserPitch = $state(1);
200
+ let layoutMode = $state<TTSLayoutMode>("left-aligned");
201
+ let speedOptionsText = $state("");
189
202
 
190
203
  let pollyApiEndpoint = $state("");
191
204
  let pollyLanguage = $state("en-US");
@@ -291,6 +304,12 @@ type PreviewSpeechMark = { time: number; start: number; end: number; value?: str
291
304
  };
292
305
  const BUILT_IN_TABS: BuiltInBackendTab[] = ["browser", "polly", "google"];
293
306
  const PREVIEW_DEBUG_PREFIX = "[pie-tts-preview]";
307
+ const TTS_LAYOUT_MODES: readonly TTSLayoutMode[] = [
308
+ "reserved-row",
309
+ "expanding-row",
310
+ "floating-overlay",
311
+ "left-aligned",
312
+ ];
294
313
 
295
314
  function debugPreview(event: string, payload?: Record<string, unknown>): void {
296
315
  if (typeof console === "undefined") return;
@@ -301,6 +320,16 @@ function debugPreview(event: string, payload?: Record<string, unknown>): void {
301
320
  console.debug(`${PREVIEW_DEBUG_PREFIX} ${event}`);
302
321
  }
303
322
 
323
+ function normalizeLayoutMode(value: unknown): TTSLayoutMode {
324
+ return TTS_LAYOUT_MODES.includes(value as TTSLayoutMode)
325
+ ? (value as TTSLayoutMode)
326
+ : "left-aligned";
327
+ }
328
+
329
+ function resetInlineSpeedOptionsToDefaults(): void {
330
+ speedOptionsText = formatTTSSpeedOptionsAsText([...DEFAULT_TTS_SPEED_OPTIONS]);
331
+ }
332
+
304
333
  const normalizedCustomProviders = $derived.by(() => {
305
334
  const reserved = new Set<string>(BUILT_IN_TABS);
306
335
  const deduped: CustomProviderDescriptor[] = [];
@@ -479,7 +508,7 @@ function debugPreview(event: string, payload?: Record<string, unknown>): void {
479
508
  }
480
509
 
481
510
  function initializeFromCoordinator() {
482
- const existing = toolkitCoordinator?.getToolConfig?.("tts") || {};
511
+ const existing = toolkitCoordinator?.getToolConfig?.("textToSpeech") || {};
483
512
  const stored = readStoredSettings();
484
513
  const source = stored ? { ...existing, ...stored } : existing;
485
514
  const resolvedDefaultApiEndpoint = getDefaultApiEndpoint();
@@ -512,6 +541,22 @@ function debugPreview(event: string, payload?: Record<string, unknown>): void {
512
541
  ? "standard"
513
542
  : "neural";
514
543
  const sourceProviderOptions = (source?.providerOptions || {}) as Record<string, unknown>;
544
+ layoutMode = normalizeLayoutMode(source?.layoutMode);
545
+ const runtimeForSpeed = resolveTTSRuntimeSettings(
546
+ source && typeof source === "object" ? (source as Record<string, unknown>) : undefined,
547
+ );
548
+ if (runtimeForSpeed.speedOptions === undefined) {
549
+ speedOptionsText = formatTTSSpeedOptionsAsText([...DEFAULT_TTS_SPEED_OPTIONS]);
550
+ } else if (
551
+ Array.isArray(runtimeForSpeed.speedOptions) &&
552
+ runtimeForSpeed.speedOptions.length === 0
553
+ ) {
554
+ speedOptionsText = "";
555
+ } else {
556
+ speedOptionsText = formatTTSSpeedOptionsAsText(
557
+ normalizeTTSSpeedOptions(runtimeForSpeed.speedOptions),
558
+ );
559
+ }
515
560
  const defaultSampleRate = normalizePollySampleRate(
516
561
  Number(source?.sampleRate ?? sourceProviderOptions.sampleRate ?? 24000)
517
562
  );
@@ -570,6 +615,8 @@ function debugPreview(event: string, payload?: Record<string, unknown>): void {
570
615
  setPreviewTextForCurrentTab();
571
616
  }
572
617
 
618
+ const layoutModeReservesRow = $derived(layoutMode === "reserved-row");
619
+
573
620
  function sameRecordEntries<T>(left: Record<string, T>, right: Record<string, T>): boolean {
574
621
  const leftKeys = Object.keys(left);
575
622
  const rightKeys = Object.keys(right);
@@ -1422,6 +1469,7 @@ function normalizePreviewSpeechMarkOffsets(
1422
1469
 
1423
1470
  isApplying = true;
1424
1471
  try {
1472
+ const appliedSpeedOptions = parseTTSSpeedOptionsFromText(speedOptionsText);
1425
1473
  if (!isBuiltInTab(activeTab)) {
1426
1474
  const provider = getCustomProviderOrThrow(activeTab);
1427
1475
  let next: ProviderApplyResult | undefined;
@@ -1436,13 +1484,17 @@ function normalizePreviewSpeechMarkOffsets(
1436
1484
  `Custom provider '${provider.id}' did not return apply config.`
1437
1485
  );
1438
1486
  }
1439
- toolkitCoordinator.updateToolConfig("tts", {
1487
+ toolkitCoordinator.updateToolConfig("textToSpeech", {
1440
1488
  enabled: true,
1441
- ...next.config
1489
+ ...next.config,
1490
+ layoutMode,
1491
+ speedOptions: appliedSpeedOptions,
1442
1492
  });
1443
1493
  persistSettings({
1444
1494
  backend: provider.id,
1445
- ...(next.config || {})
1495
+ ...(next.config || {}),
1496
+ layoutMode,
1497
+ speedOptions: appliedSpeedOptions,
1446
1498
  });
1447
1499
  applyMessage = next.message || `Applied ${provider.label} TTS settings.`;
1448
1500
  } else if (activeTab === "browser") {
@@ -1453,9 +1505,11 @@ function normalizePreviewSpeechMarkOffsets(
1453
1505
  defaultVoice: resolveVoiceForBackend("browser"),
1454
1506
  rate: normalizeRate(browserRate),
1455
1507
  pitch: normalizePitch(browserPitch),
1456
- transportMode: "pie" as const
1508
+ transportMode: "pie" as const,
1509
+ layoutMode,
1510
+ speedOptions: appliedSpeedOptions,
1457
1511
  };
1458
- toolkitCoordinator.updateToolConfig("tts", {
1512
+ toolkitCoordinator.updateToolConfig("textToSpeech", {
1459
1513
  enabled: true,
1460
1514
  ...next
1461
1515
  });
@@ -1464,7 +1518,6 @@ function normalizePreviewSpeechMarkOffsets(
1464
1518
  const next = {
1465
1519
  backend: "polly" as const,
1466
1520
  serverProvider: "polly" as const,
1467
- provider: "polly" as const,
1468
1521
  apiEndpoint: normalizeApiEndpoint(pollyApiEndpoint, getDefaultApiEndpoint()),
1469
1522
  transportMode: "pie" as const,
1470
1523
  endpointMode: "synthesizePath" as const,
@@ -1481,9 +1534,11 @@ function normalizePreviewSpeechMarkOffsets(
1481
1534
  sampleRate: normalizePollySampleRate(pollySampleRate),
1482
1535
  format: pollyFormat,
1483
1536
  speechMarkTypes: getPollySpeechMarkTypes()
1484
- }
1537
+ },
1538
+ layoutMode,
1539
+ speedOptions: appliedSpeedOptions,
1485
1540
  };
1486
- toolkitCoordinator.updateToolConfig("tts", {
1541
+ toolkitCoordinator.updateToolConfig("textToSpeech", {
1487
1542
  enabled: true,
1488
1543
  ...next
1489
1544
  });
@@ -1492,7 +1547,6 @@ function normalizePreviewSpeechMarkOffsets(
1492
1547
  const next = {
1493
1548
  backend: "google" as const,
1494
1549
  serverProvider: "google" as const,
1495
- provider: "google" as const,
1496
1550
  apiEndpoint: normalizeApiEndpoint(googleApiEndpoint, getDefaultApiEndpoint()),
1497
1551
  transportMode: "pie" as const,
1498
1552
  endpointMode: "synthesizePath" as const,
@@ -1501,15 +1555,19 @@ function normalizePreviewSpeechMarkOffsets(
1501
1555
  rate: normalizeRate(googleRate),
1502
1556
  language: googleLanguage || undefined,
1503
1557
  googleVoiceType,
1504
- googleGender
1558
+ googleGender,
1559
+ layoutMode,
1560
+ speedOptions: appliedSpeedOptions,
1505
1561
  };
1506
- toolkitCoordinator.updateToolConfig("tts", {
1562
+ toolkitCoordinator.updateToolConfig("textToSpeech", {
1507
1563
  enabled: true,
1508
1564
  ...next
1509
1565
  });
1510
1566
  persistSettings(next);
1511
1567
  }
1512
- await toolkitCoordinator?.ensureTTSReady?.(toolkitCoordinator?.getToolConfig?.("tts"));
1568
+ await toolkitCoordinator?.ensureTTSReady?.(
1569
+ toolkitCoordinator?.getToolConfig?.("textToSpeech"),
1570
+ );
1513
1571
  if (isBuiltInTab(activeTab)) {
1514
1572
  applyMessage = `Applied ${activeTab} TTS settings.`;
1515
1573
  }
@@ -1602,6 +1660,48 @@ function normalizePreviewSpeechMarkOffsets(
1602
1660
  </button>
1603
1661
  </div>
1604
1662
 
1663
+ <div class="pie-tts-fieldset fieldset bg-base-200 border border-base-300 rounded-box">
1664
+ <div class="pie-tts-field">
1665
+ <label class="pie-tts-label" for="tts-layout-mode">Toolbar layout mode</label>
1666
+ <select
1667
+ id="tts-layout-mode"
1668
+ class="select select-sm select-bordered w-full"
1669
+ bind:value={layoutMode}
1670
+ >
1671
+ <option value="reserved-row">Reserved row</option>
1672
+ <option value="expanding-row">Expanding row</option>
1673
+ <option value="floating-overlay">Floating overlay</option>
1674
+ <option value="left-aligned">Left-aligned controls</option>
1675
+ </select>
1676
+ <div class="text-xs opacity-75">
1677
+ Item header row reservation: {layoutModeReservesRow ? "Enabled" : "Disabled"}
1678
+ </div>
1679
+ </div>
1680
+ <div class="pie-tts-field">
1681
+ <label class="pie-tts-label" for="tts-inline-speed-options">Inline speed buttons</label>
1682
+ <input
1683
+ id="tts-inline-speed-options"
1684
+ class="input input-sm input-bordered w-full"
1685
+ bind:value={speedOptionsText}
1686
+ placeholder="0.8, 1.25"
1687
+ autocomplete="off"
1688
+ />
1689
+ <div class="mt-1 flex flex-wrap items-center gap-2">
1690
+ <span class="text-xs opacity-75">
1691
+ Comma or semicolon-separated multipliers (1× is not shown as a button). Leave empty to
1692
+ hide speed buttons.
1693
+ </span>
1694
+ <button
1695
+ type="button"
1696
+ class="btn btn-xs btn-ghost"
1697
+ onclick={resetInlineSpeedOptionsToDefaults}
1698
+ >
1699
+ Reset to defaults
1700
+ </button>
1701
+ </div>
1702
+ </div>
1703
+ </div>
1704
+
1605
1705
  <div class="join pie-tts-tabs">
1606
1706
  {#each providerTabs as provider}
1607
1707
  <button
@@ -0,0 +1,33 @@
1
+ function o(e) {
2
+ "@babel/helpers - typeof";
3
+ return o = typeof Symbol == "function" && typeof Symbol.iterator == "symbol" ? function(t) {
4
+ return typeof t;
5
+ } : function(t) {
6
+ return t && typeof Symbol == "function" && t.constructor === Symbol && t !== Symbol.prototype ? "symbol" : typeof t;
7
+ }, o(e);
8
+ }
9
+ function i(e, t) {
10
+ if (o(e) != "object" || !e) return e;
11
+ var r = e[Symbol.toPrimitive];
12
+ if (r !== void 0) {
13
+ var n = r.call(e, t || "default");
14
+ if (o(n) != "object") return n;
15
+ throw new TypeError("@@toPrimitive must return a primitive value.");
16
+ }
17
+ return (t === "string" ? String : Number)(e);
18
+ }
19
+ function u(e) {
20
+ var t = i(e, "string");
21
+ return o(t) == "symbol" ? t : t + "";
22
+ }
23
+ function f(e, t, r) {
24
+ return (t = u(t)) in e ? Object.defineProperty(e, t, {
25
+ value: r,
26
+ enumerable: !0,
27
+ configurable: !0,
28
+ writable: !0
29
+ }) : e[t] = r, e;
30
+ }
31
+ export {
32
+ f as t
33
+ };