@pie-players/pie-tool-line-reader 0.3.28 → 0.3.30

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.
@@ -1,66 +1,101 @@
1
- import { t as h } from "./defineProperty-CyepwRr5.js";
2
- var b = (t) => {
3
- const e = (t.providerOptions && typeof t.providerOptions == "object" ? t.providerOptions : {}).__pieTelemetry;
4
- return typeof e == "function" ? e : void 0;
5
- }, S = {
1
+ import { t as l } from "./defineProperty-CyepwRr5.js";
2
+ function w(e, t) {
3
+ if (typeof e != "string" || e.length === 0) return null;
4
+ try {
5
+ const r = typeof t.apiEndpoint == "string" && /^https?:\/\//i.test(t.apiEndpoint) ? t.apiEndpoint : void 0, i = r ? new URL(e, r) : new URL(e);
6
+ return i.protocol !== "http:" && i.protocol !== "https:" ? null : i;
7
+ } catch {
8
+ return null;
9
+ }
10
+ }
11
+ function k(e, t) {
12
+ const r = Array.isArray(t.assetOrigins) ? t.assetOrigins.filter((i) => typeof i == "string" && i.length > 0).map((i) => {
13
+ try {
14
+ return new URL(i).origin;
15
+ } catch {
16
+ return i;
17
+ }
18
+ }) : [];
19
+ if (r.length === 0) {
20
+ if (typeof t.apiEndpoint == "string" && /^https?:\/\//i.test(t.apiEndpoint)) try {
21
+ return new URL(t.apiEndpoint).origin === e.origin;
22
+ } catch {
23
+ return !1;
24
+ }
25
+ return typeof window < "u" && typeof window.location?.origin == "string" && window.location.origin === e.origin;
26
+ }
27
+ return r.includes(e.origin);
28
+ }
29
+ function A(e) {
30
+ const t = {};
31
+ for (const [r, i] of Object.entries(e)) {
32
+ const o = r.toLowerCase();
33
+ o === "authorization" || o === "proxy-authorization" || o === "cookie" || o.startsWith("x-auth-") || (t[r] = i);
34
+ }
35
+ return t;
36
+ }
37
+ var R = (e) => {
38
+ const t = (e.providerOptions && typeof e.providerOptions == "object" ? e.providerOptions : {}).__pieTelemetry;
39
+ return typeof t == "function" ? t : void 0;
40
+ }, M = {
6
41
  pie: 3e3,
7
42
  custom: 3e3
8
- }, v = (t) => t.replace(/\/+$/, ""), k = (t) => {
9
- const e = v(t.apiEndpoint), r = (t.provider || "").toLowerCase();
10
- return r === "polly" || r === "google" ? `${e}/${r}/voices` : `${e}/voices`;
11
- }, T = (t) => t.transportMode === "custom" ? "custom" : t.transportMode === "pie" ? "pie" : t.provider === "custom" ? "custom" : "pie", w = (t, e) => t.endpointMode ? t.endpointMode : e === "custom" ? "rootPost" : "synthesizePath", A = (t, e) => t.endpointValidationMode ? t.endpointValidationMode : e === "custom" ? "none" : "voices", R = (t) => {
12
- const e = t.providerOptions || {};
13
- if (typeof e.speedRate == "string") return e.speedRate;
14
- const r = Number(t.rate ?? 1);
43
+ }, T = (e) => e.replace(/\/+$/, ""), E = (e) => {
44
+ const t = T(e.apiEndpoint), r = (e.provider || "").toLowerCase();
45
+ return r === "polly" || r === "google" ? `${t}/${r}/voices` : `${t}/voices`;
46
+ }, b = (e) => e.transportMode === "custom" ? "custom" : e.transportMode === "pie" ? "pie" : e.provider === "custom" ? "custom" : "pie", S = (e, t) => e.endpointMode ? e.endpointMode : t === "custom" ? "rootPost" : "synthesizePath", U = (e, t) => e.endpointValidationMode ? e.endpointValidationMode : t === "custom" ? "none" : "voices", O = (e) => {
47
+ const t = e.providerOptions || {};
48
+ if (typeof t.speedRate == "string") return t.speedRate;
49
+ const r = Number(e.rate ?? 1);
15
50
  return !Number.isFinite(r) || r <= 0.95 ? "slow" : r >= 1.5 ? "fast" : "medium";
16
- }, I = (t) => {
17
- const e = [];
51
+ }, P = (e) => {
52
+ const t = [];
18
53
  let r = 0;
19
- const i = t.split(`
54
+ const i = e.split(`
20
55
  `).map((o) => o.trim()).filter(Boolean);
21
56
  for (const o of i) try {
22
- const s = JSON.parse(o), a = typeof s.type == "string" ? s.type : "word", d = typeof s.time == "number" && Number.isFinite(s.time) ? s.time : 0, n = typeof s.value == "string" ? s.value : "", c = typeof s.start == "number" && Number.isFinite(s.start) ? s.start : null, u = typeof s.end == "number" && Number.isFinite(s.end) ? s.end : null, y = c ?? r, l = u ?? y + Math.max(1, n.length || String(s.value || "").length);
23
- r = Math.max(l + 1, r), e.push({
57
+ const s = JSON.parse(o), a = typeof s.type == "string" ? s.type : "word", d = typeof s.time == "number" && Number.isFinite(s.time) ? s.time : 0, n = typeof s.value == "string" ? s.value : "", h = typeof s.start == "number" && Number.isFinite(s.start) ? s.start : null, u = typeof s.end == "number" && Number.isFinite(s.end) ? s.end : null, p = h ?? r, c = u ?? p + Math.max(1, n.length || String(s.value || "").length);
58
+ r = Math.max(c + 1, r), t.push({
24
59
  time: d,
25
60
  type: a,
26
- start: y,
27
- end: l,
61
+ start: p,
62
+ end: c,
28
63
  value: n
29
64
  });
30
65
  } catch {
31
66
  }
32
- return e;
33
- }, M = (t) => {
34
- if (!Array.isArray(t)) return [];
35
- const e = [];
67
+ return t;
68
+ }, C = (e) => {
69
+ if (!Array.isArray(e)) return [];
70
+ const t = [];
36
71
  let r = 0;
37
- for (const i of t) {
72
+ for (const i of e) {
38
73
  if (!i || typeof i != "object") continue;
39
- const o = typeof i.type == "string" ? i.type : "word", s = typeof i.time == "number" && Number.isFinite(i.time) ? i.time : 0, a = typeof i.value == "string" ? i.value : "", d = typeof i.start == "number" && Number.isFinite(i.start) ? i.start : null, n = typeof i.end == "number" && Number.isFinite(i.end) ? i.end : null, c = d ?? r, u = n ?? c + Math.max(1, a.length || 1);
40
- r = Math.max(r, u + 1), e.push({
74
+ const o = typeof i.type == "string" ? i.type : "word", s = typeof i.time == "number" && Number.isFinite(i.time) ? i.time : 0, a = typeof i.value == "string" ? i.value : "", d = typeof i.start == "number" && Number.isFinite(i.start) ? i.start : null, n = typeof i.end == "number" && Number.isFinite(i.end) ? i.end : null, h = d ?? r, u = n ?? h + Math.max(1, a.length || 1);
75
+ r = Math.max(r, u + 1), t.push({
41
76
  time: s,
42
77
  type: o,
43
- start: c,
78
+ start: h,
44
79
  end: u,
45
80
  value: a
46
81
  });
47
82
  }
48
- return e.sort((i, o) => i.time !== o.time ? i.time - o.time : i.start !== o.start ? i.start - o.start : i.end - o.end);
49
- }, E = {
83
+ return t.sort((i, o) => i.time !== o.time ? i.time - o.time : i.start !== o.start ? i.start - o.start : i.end - o.end);
84
+ }, x = {
50
85
  pie: {
51
86
  id: "pie",
52
- resolveSynthesisUrl: (t) => {
53
- const e = w(t, "pie"), r = v(t.apiEndpoint);
54
- return e === "rootPost" ? r : `${r}/synthesize`;
87
+ resolveSynthesisUrl: (e) => {
88
+ const t = S(e, "pie"), r = T(e.apiEndpoint);
89
+ return t === "rootPost" ? r : `${r}/synthesize`;
55
90
  },
56
- buildRequestBody: (t, e) => {
57
- const r = e.providerOptions || {}, i = typeof e.engine == "string" ? e.engine : typeof r.engine == "string" ? r.engine : void 0, o = typeof r.sampleRate == "number" && Number.isFinite(r.sampleRate) ? r.sampleRate : void 0, s = r.format === "mp3" || r.format === "ogg" || r.format === "pcm" ? r.format : void 0, a = Array.isArray(r.speechMarkTypes) ? r.speechMarkTypes.filter((d) => d === "word" || d === "sentence" || d === "ssml") : void 0;
91
+ buildRequestBody: (e, t) => {
92
+ const r = t.providerOptions || {}, i = typeof t.engine == "string" ? t.engine : typeof r.engine == "string" ? r.engine : void 0, o = typeof r.sampleRate == "number" && Number.isFinite(r.sampleRate) ? r.sampleRate : void 0, s = r.format === "mp3" || r.format === "ogg" || r.format === "pcm" ? r.format : void 0, a = Array.isArray(r.speechMarkTypes) ? r.speechMarkTypes.filter((d) => d === "word" || d === "sentence" || d === "ssml") : void 0;
58
93
  return {
59
- text: t,
60
- provider: e.provider || "polly",
61
- voice: e.voice,
62
- language: e.language,
63
- rate: e.rate,
94
+ text: e,
95
+ provider: t.provider || "polly",
96
+ voice: t.voice,
97
+ language: t.language,
98
+ rate: t.rate,
64
99
  engine: i,
65
100
  sampleRate: o,
66
101
  format: s,
@@ -68,73 +103,79 @@ var b = (t) => {
68
103
  includeSpeechMarks: !0
69
104
  };
70
105
  },
71
- parseResponse: async (t) => {
72
- const e = await t.json();
106
+ parseResponse: async (e) => {
107
+ const t = await e.json();
73
108
  return {
74
109
  audio: {
75
110
  kind: "base64",
76
- data: e.audio,
77
- contentType: e.contentType
111
+ data: t.audio,
112
+ contentType: t.contentType
78
113
  },
79
- speechMarks: Array.isArray(e.speechMarks) ? e.speechMarks : []
114
+ speechMarks: Array.isArray(t.speechMarks) ? t.speechMarks : []
80
115
  };
81
116
  }
82
117
  },
83
118
  custom: {
84
119
  id: "custom",
85
- resolveSynthesisUrl: (t) => {
86
- const e = w(t, "custom"), r = v(t.apiEndpoint);
87
- return e === "synthesizePath" ? `${r}/synthesize` : r;
120
+ resolveSynthesisUrl: (e) => {
121
+ const t = S(e, "custom"), r = T(e.apiEndpoint);
122
+ return t === "synthesizePath" ? `${r}/synthesize` : r;
88
123
  },
89
- buildRequestBody: (t, e) => {
90
- const r = e.providerOptions || {}, i = typeof r.lang_id == "string" ? r.lang_id : e.language || "en-US", o = typeof r.cache == "boolean" ? r.cache : !0;
124
+ buildRequestBody: (e, t) => {
125
+ const r = t.providerOptions || {}, i = typeof r.lang_id == "string" ? r.lang_id : t.language || "en-US", o = typeof r.cache == "boolean" ? r.cache : !0;
91
126
  return {
92
- text: t,
93
- speedRate: R(e),
127
+ text: e,
128
+ speedRate: O(t),
94
129
  lang_id: i,
95
130
  cache: o
96
131
  };
97
132
  },
98
- parseResponse: async (t, e, r, i) => {
99
- const o = await t.json(), s = {};
100
- if (e.includeAuthOnAssetFetch)
101
- for (const [n, c] of Object.entries(r)) n.toLowerCase() === "authorization" && (s[n] = c);
133
+ parseResponse: async (e, t, r, i) => {
134
+ const o = await e.json(), s = {};
135
+ if (t.includeAuthOnAssetFetch)
136
+ for (const [h, u] of Object.entries(r)) h.toLowerCase() === "authorization" && (s[h] = u);
102
137
  let a = [];
103
- const d = M(o.speechMarks);
138
+ const d = C(o.speechMarks);
104
139
  if (d.length > 0) a = d;
105
140
  else if (typeof o.word == "string" && o.word.length > 0) {
106
- const n = await fetch(o.word, {
107
- headers: s,
108
- signal: i
109
- });
110
- n.ok && (a = I(await n.text()));
141
+ const h = w(o.word, t);
142
+ if (h !== null) {
143
+ const u = k(h, t) ? s : A(s), p = await fetch(h.toString(), {
144
+ headers: u,
145
+ signal: i,
146
+ redirect: "error"
147
+ });
148
+ p.ok && (a = P(await p.text()));
149
+ }
111
150
  }
151
+ const n = w(o.audioContent, t);
152
+ if (n === null) throw new Error("TTS server returned an invalid audio URL");
112
153
  return {
113
154
  audio: {
114
155
  kind: "url",
115
- url: o.audioContent
156
+ url: n.toString()
116
157
  },
117
158
  speechMarks: a
118
159
  };
119
160
  }
120
161
  }
121
- }, P = class {
122
- constructor(t, e) {
123
- h(this, "config", void 0), h(this, "adapter", void 0), h(this, "currentAudio", null), h(this, "pausedState", !1), h(this, "wordTimings", []), h(this, "highlightInterval", null), h(this, "intentionallyStopped", !1), h(this, "activeSynthesisController", null), h(this, "synthesisRunId", 0), h(this, "telemetryReporter", void 0), h(this, "onWordBoundary", void 0), this.config = t, this.adapter = e, this.telemetryReporter = b(t);
162
+ }, z = class {
163
+ constructor(e, t) {
164
+ l(this, "config", void 0), l(this, "adapter", void 0), l(this, "currentAudio", null), l(this, "pausedState", !1), l(this, "wordTimings", []), l(this, "highlightInterval", null), l(this, "intentionallyStopped", !1), l(this, "activeSynthesisController", null), l(this, "synthesisRunId", 0), l(this, "telemetryReporter", void 0), l(this, "onWordBoundary", void 0), this.config = e, this.adapter = t, this.telemetryReporter = R(e);
124
165
  }
125
- async emitTelemetry(t, e) {
166
+ async emitTelemetry(e, t) {
126
167
  try {
127
- await this.telemetryReporter?.(t, e);
168
+ await this.telemetryReporter?.(e, t);
128
169
  } catch (r) {
129
170
  console.warn("[ServerTTSProvider] telemetry callback failed:", r);
130
171
  }
131
172
  }
132
- async speak(t) {
173
+ async speak(e) {
133
174
  this.stop(), this.intentionallyStopped = !1;
134
- const e = ++this.synthesisRunId, r = new AbortController();
175
+ const t = ++this.synthesisRunId, r = new AbortController();
135
176
  this.activeSynthesisController = r;
136
- const { audioUrl: i, wordTimings: o } = await this.synthesizeSpeech(t, r.signal, e);
137
- if (e !== this.synthesisRunId) {
177
+ const { audioUrl: i, wordTimings: o } = await this.synthesizeSpeech(e, r.signal, t);
178
+ if (t !== this.synthesisRunId) {
138
179
  URL.revokeObjectURL(i);
139
180
  return;
140
181
  }
@@ -148,14 +189,14 @@ var b = (t) => {
148
189
  this.pausedState = !1, this.onWordBoundary && this.wordTimings.length > 0 && this.startWordHighlighting();
149
190
  }, n.onended = () => {
150
191
  this.stopWordHighlighting(), URL.revokeObjectURL(i), this.currentAudio = null, this.wordTimings = [], a();
151
- }, n.onerror = (c) => {
192
+ }, n.onerror = (h) => {
152
193
  this.stopWordHighlighting(), URL.revokeObjectURL(i), this.currentAudio = null, this.wordTimings = [], this.intentionallyStopped ? a() : d(/* @__PURE__ */ new Error("Failed to play audio from server"));
153
194
  }, n.onpause = () => {
154
195
  this.stopWordHighlighting(), this.pausedState = !0;
155
196
  }, n.play().catch(d);
156
197
  });
157
198
  }
158
- async synthesizeSpeech(t, e, r) {
199
+ async synthesizeSpeech(e, t, r) {
159
200
  const i = Date.now();
160
201
  await this.emitTelemetry("pie-tool-backend-call-start", {
161
202
  toolId: "tts",
@@ -167,27 +208,27 @@ var b = (t) => {
167
208
  ...this.config.headers
168
209
  };
169
210
  this.config.authToken && (o.Authorization = `Bearer ${this.config.authToken}`);
170
- const s = this.adapter.resolveSynthesisUrl(this.config), a = this.adapter.buildRequestBody(t, this.config), d = await (async () => {
211
+ const s = this.adapter.resolveSynthesisUrl(this.config), a = this.adapter.buildRequestBody(e, this.config), d = await (async () => {
171
212
  try {
172
213
  return await fetch(s, {
173
214
  method: "POST",
174
215
  headers: o,
175
216
  body: JSON.stringify(a),
176
- signal: e
217
+ signal: t
177
218
  });
178
- } catch (l) {
219
+ } catch (c) {
179
220
  throw await this.emitTelemetry("pie-tool-backend-call-error", {
180
221
  toolId: "tts",
181
222
  backend: this.config.provider || "server",
182
223
  operation: "synthesize-speech",
183
224
  duration: Date.now() - i,
184
225
  errorType: "TTSBackendNetworkError",
185
- message: l instanceof Error ? l.message : String(l)
186
- }), l;
226
+ message: c instanceof Error ? c.message : String(c)
227
+ }), c;
187
228
  }
188
229
  })();
189
230
  if (!d.ok) {
190
- const l = await d.json().catch(() => ({})), p = l.message || l.error?.message || `Server returned ${d.status}`;
231
+ const c = await d.json().catch(() => ({})), y = c.message || c.error?.message || `Server returned ${d.status}`;
191
232
  throw await this.emitTelemetry("pie-tool-backend-call-error", {
192
233
  toolId: "tts",
193
234
  backend: this.config.provider || "server",
@@ -195,15 +236,15 @@ var b = (t) => {
195
236
  duration: Date.now() - i,
196
237
  statusCode: d.status,
197
238
  errorType: "TTSBackendRequestError",
198
- message: p
199
- }), new Error(p);
239
+ message: y
240
+ }), new Error(y);
200
241
  }
201
- const n = await this.adapter.parseResponse(d, this.config, o, e);
202
- if (r !== this.synthesisRunId || e.aborted) throw new Error("Synthesis superseded by a newer request");
203
- let c;
204
- if (n.audio.kind === "base64") c = this.base64ToBlob(n.audio.data, n.audio.contentType);
242
+ const n = await this.adapter.parseResponse(d, this.config, o, t);
243
+ if (r !== this.synthesisRunId || t.aborted) throw new Error("Synthesis superseded by a newer request");
244
+ let h;
245
+ if (n.audio.kind === "base64") h = this.base64ToBlob(n.audio.data, n.audio.contentType);
205
246
  else {
206
- const l = n.audio.url, p = Date.now();
247
+ const c = n.audio.url, y = Date.now();
207
248
  await this.emitTelemetry("pie-tool-backend-call-start", {
208
249
  toolId: "tts",
209
250
  backend: this.config.provider || "server",
@@ -211,18 +252,29 @@ var b = (t) => {
211
252
  });
212
253
  const f = {};
213
254
  this.config.includeAuthOnAssetFetch && this.config.authToken && (f.Authorization = `Bearer ${this.config.authToken}`);
214
- const m = await (async () => {
255
+ const v = w(c, this.config);
256
+ if (v === null)
257
+ throw await this.emitTelemetry("pie-tool-backend-call-error", {
258
+ toolId: "tts",
259
+ backend: this.config.provider || "server",
260
+ operation: "fetch-synthesized-audio-asset",
261
+ duration: Date.now() - y,
262
+ errorType: "TTSAssetInvalidUrl",
263
+ message: "TTS asset URL rejected (non-http(s) or malformed)"
264
+ }), new Error("TTS asset URL rejected (non-http(s) or malformed)");
265
+ const I = k(v, this.config) ? f : A(f), m = await (async () => {
215
266
  try {
216
- return await fetch(l, {
217
- headers: f,
218
- signal: e
267
+ return await fetch(v.toString(), {
268
+ headers: I,
269
+ signal: t,
270
+ redirect: "error"
219
271
  });
220
272
  } catch (g) {
221
273
  throw await this.emitTelemetry("pie-tool-backend-call-error", {
222
274
  toolId: "tts",
223
275
  backend: this.config.provider || "server",
224
276
  operation: "fetch-synthesized-audio-asset",
225
- duration: Date.now() - p,
277
+ duration: Date.now() - y,
226
278
  errorType: "TTSAssetNetworkError",
227
279
  message: g instanceof Error ? g.message : String(g)
228
280
  }), g;
@@ -233,19 +285,19 @@ var b = (t) => {
233
285
  toolId: "tts",
234
286
  backend: this.config.provider || "server",
235
287
  operation: "fetch-synthesized-audio-asset",
236
- duration: Date.now() - p,
288
+ duration: Date.now() - y,
237
289
  statusCode: m.status,
238
290
  errorType: "TTSAssetFetchError",
239
291
  message: `Failed to download synthesized audio (${m.status})`
240
292
  }), new Error(`Failed to download synthesized audio (${m.status})`);
241
- c = await m.blob(), await this.emitTelemetry("pie-tool-backend-call-success", {
293
+ h = await m.blob(), await this.emitTelemetry("pie-tool-backend-call-success", {
242
294
  toolId: "tts",
243
295
  backend: this.config.provider || "server",
244
296
  operation: "fetch-synthesized-audio-asset",
245
- duration: Date.now() - p
297
+ duration: Date.now() - y
246
298
  });
247
299
  }
248
- const u = URL.createObjectURL(c), y = this.parseSpeechMarks(n.speechMarks);
300
+ const u = URL.createObjectURL(h), p = this.parseSpeechMarks(n.speechMarks);
249
301
  return await this.emitTelemetry("pie-tool-backend-call-success", {
250
302
  toolId: "tts",
251
303
  backend: this.config.provider || "server",
@@ -253,20 +305,21 @@ var b = (t) => {
253
305
  duration: Date.now() - i
254
306
  }), {
255
307
  audioUrl: u,
256
- wordTimings: y
308
+ wordTimings: p
257
309
  };
258
310
  }
259
- base64ToBlob(t, e) {
260
- const r = atob(t), i = new Array(r.length);
261
- for (let o = 0; o < r.length; o++) i[o] = r.charCodeAt(o);
262
- return new Blob([new Uint8Array(i)], { type: e });
311
+ base64ToBlob(e, t) {
312
+ const r = atob(e), i = new Array(r.length);
313
+ for (let s = 0; s < r.length; s++) i[s] = r.charCodeAt(s);
314
+ const o = new Uint8Array(i);
315
+ return new Blob([o], { type: t });
263
316
  }
264
- parseSpeechMarks(t) {
265
- return t.filter((e) => e.type === "word").map((e, r) => ({
266
- time: e.time,
317
+ parseSpeechMarks(e) {
318
+ return e.filter((t) => t.type === "word").map((t, r) => ({
319
+ time: t.time,
267
320
  wordIndex: r,
268
- charIndex: e.start,
269
- length: e.end - e.start
321
+ charIndex: t.start,
322
+ length: t.end - t.start
270
323
  }));
271
324
  }
272
325
  startWordHighlighting() {
@@ -279,17 +332,17 @@ var b = (t) => {
279
332
  return;
280
333
  }
281
334
  console.log("[ServerTTSProvider] Starting word highlighting with", this.wordTimings.length, "word timings"), console.log("[ServerTTSProvider] Playback rate:", this.currentAudio.playbackRate), console.log("[ServerTTSProvider] First 3 timings:", this.wordTimings.slice(0, 3));
282
- let t = -1;
335
+ let e = -1;
283
336
  this.highlightInterval = window.setInterval(() => {
284
337
  if (!this.currentAudio) {
285
338
  this.stopWordHighlighting();
286
339
  return;
287
340
  }
288
- const e = this.currentAudio.currentTime * 1e3;
341
+ const t = this.currentAudio.currentTime * 1e3;
289
342
  for (let r = 0; r < this.wordTimings.length; r++) {
290
343
  const i = this.wordTimings[r];
291
- if (e >= i.time && r > t) {
292
- this.onWordBoundary && (console.log("[ServerTTSProvider] Highlighting word at charIndex:", i.charIndex, "length:", i.length, "time:", i.time, "currentTime:", e), this.onWordBoundary("", i.charIndex, i.length)), t = r;
344
+ if (t >= i.time && r > e) {
345
+ this.onWordBoundary && (console.log("[ServerTTSProvider] Highlighting word at charIndex:", i.charIndex, "length:", i.length, "time:", i.time, "currentTime:", t), this.onWordBoundary("", i.charIndex, i.length)), e = r;
293
346
  break;
294
347
  }
295
348
  }
@@ -313,61 +366,63 @@ var b = (t) => {
313
366
  isPaused() {
314
367
  return this.pausedState;
315
368
  }
316
- updateSettings(t) {
317
- t.rate !== void 0 && (this.config.rate = t.rate, this.currentAudio && (this.currentAudio.playbackRate = Math.max(0.25, Math.min(4, t.rate)))), t.pitch !== void 0 && (this.config.pitch = t.pitch), t.voice !== void 0 && (this.config.voice = t.voice);
369
+ updateSettings(e) {
370
+ e.rate !== void 0 && (this.config.rate = e.rate, this.currentAudio && (this.currentAudio.playbackRate = Math.max(0.25, Math.min(4, e.rate)))), e.pitch !== void 0 && (this.config.pitch = e.pitch), e.voice !== void 0 && (this.config.voice = e.voice);
318
371
  }
319
- }, B = class {
372
+ }, L = class {
320
373
  constructor() {
321
- h(this, "providerId", "server-tts"), h(this, "providerName", "Server TTS"), h(this, "version", "1.0.0"), h(this, "config", null), h(this, "adapter", null), h(this, "telemetryReporter", void 0);
374
+ l(this, "providerId", "server-tts"), l(this, "providerName", "Server TTS"), l(this, "version", "1.0.0"), l(this, "config", null), l(this, "adapter", null), l(this, "telemetryReporter", void 0);
322
375
  }
323
- async emitTelemetry(t, e) {
376
+ async emitTelemetry(e, t) {
324
377
  try {
325
- await this.telemetryReporter?.(t, e);
378
+ await this.telemetryReporter?.(e, t);
326
379
  } catch (r) {
327
380
  console.warn("[ServerTTSProvider] telemetry callback failed:", r);
328
381
  }
329
382
  }
330
- async initialize(t) {
331
- const e = t;
332
- if (!e.apiEndpoint) throw new Error("apiEndpoint is required for ServerTTSProvider");
333
- if (this.config = e, this.telemetryReporter = b(e), this.adapter = E[T(e)], e.validateEndpoint) {
334
- const r = Date.now();
383
+ async initialize(e) {
384
+ const t = e;
385
+ if (!t.apiEndpoint) throw new Error("apiEndpoint is required for ServerTTSProvider");
386
+ this.config = t, this.telemetryReporter = R(t);
387
+ const r = b(t);
388
+ if (this.adapter = x[r], t.validateEndpoint) {
389
+ const i = Date.now();
335
390
  if (await this.emitTelemetry("pie-tool-backend-call-start", {
336
391
  toolId: "tts",
337
- backend: e.provider || "server",
392
+ backend: t.provider || "server",
338
393
  operation: "validate-endpoint"
339
394
  }), !await this.testAPIAvailability())
340
395
  throw await this.emitTelemetry("pie-tool-backend-call-error", {
341
396
  toolId: "tts",
342
- backend: e.provider || "server",
397
+ backend: t.provider || "server",
343
398
  operation: "validate-endpoint",
344
- duration: Date.now() - r,
399
+ duration: Date.now() - i,
345
400
  errorType: "TTSEndpointValidationError",
346
- message: `Server TTS API not available at ${e.apiEndpoint}`
347
- }), new Error(`Server TTS API not available at ${e.apiEndpoint}`);
401
+ message: `Server TTS API not available at ${t.apiEndpoint}`
402
+ }), new Error(`Server TTS API not available at ${t.apiEndpoint}`);
348
403
  await this.emitTelemetry("pie-tool-backend-call-success", {
349
404
  toolId: "tts",
350
- backend: e.provider || "server",
405
+ backend: t.provider || "server",
351
406
  operation: "validate-endpoint",
352
- duration: Date.now() - r
407
+ duration: Date.now() - i
353
408
  });
354
409
  }
355
- return new P(e, this.adapter);
410
+ return new z(t, this.adapter);
356
411
  }
357
412
  async testAPIAvailability() {
358
413
  if (!this.config || !this.adapter) return !1;
359
414
  try {
360
- const t = { ...this.config.headers };
361
- this.config.authToken && (t.Authorization = `Bearer ${this.config.authToken}`);
362
- const e = new AbortController(), r = setTimeout(() => e.abort(), 5e3), i = A(this.config, this.adapter.id);
415
+ const e = { ...this.config.headers };
416
+ this.config.authToken && (e.Authorization = `Bearer ${this.config.authToken}`);
417
+ const t = new AbortController(), r = setTimeout(() => t.abort(), 5e3), i = U(this.config, this.adapter.id);
363
418
  if (i === "none")
364
419
  return clearTimeout(r), !0;
365
- const o = i === "voices" ? k(this.config) : this.adapter.resolveSynthesisUrl(this.config), s = i === "voices" ? "GET" : "OPTIONS";
420
+ const o = i === "voices" ? E(this.config) : this.adapter.resolveSynthesisUrl(this.config), s = i === "voices" ? "GET" : "OPTIONS";
366
421
  try {
367
422
  const a = await fetch(o, {
368
423
  method: s,
369
- headers: t,
370
- signal: e.signal
424
+ headers: e,
425
+ signal: t.signal
371
426
  });
372
427
  return clearTimeout(r), a.ok || a.status === 405;
373
428
  } catch {
@@ -377,8 +432,8 @@ var b = (t) => {
377
432
  return !1;
378
433
  }
379
434
  }
380
- supportsFeature(t) {
381
- switch (t) {
435
+ supportsFeature(e) {
436
+ switch (e) {
382
437
  case "pause":
383
438
  case "resume":
384
439
  case "wordBoundary":
@@ -399,7 +454,7 @@ var b = (t) => {
399
454
  supportsVoiceSelection: !0,
400
455
  supportsRateControl: !0,
401
456
  supportsPitchControl: !1,
402
- maxTextLength: S[this.config ? T(this.config) : "pie"]
457
+ maxTextLength: M[this.config ? b(this.config) : "pie"]
403
458
  };
404
459
  }
405
460
  destroy() {
@@ -407,5 +462,5 @@ var b = (t) => {
407
462
  }
408
463
  };
409
464
  export {
410
- B as ServerTTSProvider
465
+ L as ServerTTSProvider
411
466
  };
@@ -41,7 +41,8 @@ var n = class {
41
41
  try {
42
42
  const t = await fetch(e.proxyEndpoint);
43
43
  if (!t.ok) throw new Error(`Proxy endpoint returned ${t.status}`);
44
- this.apiKey = (await t.json()).apiKey, await this.emitTelemetry("pie-tool-backend-call-success", {
44
+ const r = await t.json();
45
+ this.apiKey = r.apiKey, await this.emitTelemetry("pie-tool-backend-call-success", {
45
46
  toolId: "calculator",
46
47
  backend: "desmos",
47
48
  operation: "proxy-auth-fetch",
@@ -179,6 +180,17 @@ Recommended: Use proxyEndpoint for production, apiKey for development only.`);
179
180
  resize() {
180
181
  this.calculator.resize && this.calculator.resize();
181
182
  }
183
+ focus() {
184
+ try {
185
+ if (this.type === "graphing" && typeof this.calculator?.focusFirstExpression == "function") {
186
+ this.calculator.focusFirstExpression();
187
+ return;
188
+ }
189
+ this.container.querySelector('.dcg-mq-editable-field[tabindex="0"], textarea, [contenteditable="true"], [tabindex]:not([tabindex="-1"])')?.focus();
190
+ } catch (e) {
191
+ console.warn("[DesmosCalculator] focus() failed:", e);
192
+ }
193
+ }
182
194
  exportState() {
183
195
  let e = {};
184
196
  return this.type === "graphing" && this.calculator.getState && (e = this.calculator.getState()), {