@pie-players/pie-section-player 0.3.19 → 0.3.21

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.
@@ -0,0 +1,411 @@
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 = {
6
+ pie: 3e3,
7
+ 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);
15
+ return !Number.isFinite(r) || r <= 0.95 ? "slow" : r >= 1.5 ? "fast" : "medium";
16
+ }, I = (t) => {
17
+ const e = [];
18
+ let r = 0;
19
+ const i = t.split(`
20
+ `).map((o) => o.trim()).filter(Boolean);
21
+ 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({
24
+ time: d,
25
+ type: a,
26
+ start: y,
27
+ end: l,
28
+ value: n
29
+ });
30
+ } catch {
31
+ }
32
+ return e;
33
+ }, M = (t) => {
34
+ if (!Array.isArray(t)) return [];
35
+ const e = [];
36
+ let r = 0;
37
+ for (const i of t) {
38
+ 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({
41
+ time: s,
42
+ type: o,
43
+ start: c,
44
+ end: u,
45
+ value: a
46
+ });
47
+ }
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 = {
50
+ pie: {
51
+ id: "pie",
52
+ resolveSynthesisUrl: (t) => {
53
+ const e = w(t, "pie"), r = v(t.apiEndpoint);
54
+ return e === "rootPost" ? r : `${r}/synthesize`;
55
+ },
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;
58
+ return {
59
+ text: t,
60
+ provider: e.provider || "polly",
61
+ voice: e.voice,
62
+ language: e.language,
63
+ rate: e.rate,
64
+ engine: i,
65
+ sampleRate: o,
66
+ format: s,
67
+ speechMarkTypes: a,
68
+ includeSpeechMarks: !0
69
+ };
70
+ },
71
+ parseResponse: async (t) => {
72
+ const e = await t.json();
73
+ return {
74
+ audio: {
75
+ kind: "base64",
76
+ data: e.audio,
77
+ contentType: e.contentType
78
+ },
79
+ speechMarks: Array.isArray(e.speechMarks) ? e.speechMarks : []
80
+ };
81
+ }
82
+ },
83
+ custom: {
84
+ id: "custom",
85
+ resolveSynthesisUrl: (t) => {
86
+ const e = w(t, "custom"), r = v(t.apiEndpoint);
87
+ return e === "synthesizePath" ? `${r}/synthesize` : r;
88
+ },
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;
91
+ return {
92
+ text: t,
93
+ speedRate: R(e),
94
+ lang_id: i,
95
+ cache: o
96
+ };
97
+ },
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);
102
+ let a = [];
103
+ const d = M(o.speechMarks);
104
+ if (d.length > 0) a = d;
105
+ 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()));
111
+ }
112
+ return {
113
+ audio: {
114
+ kind: "url",
115
+ url: o.audioContent
116
+ },
117
+ speechMarks: a
118
+ };
119
+ }
120
+ }
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);
124
+ }
125
+ async emitTelemetry(t, e) {
126
+ try {
127
+ await this.telemetryReporter?.(t, e);
128
+ } catch (r) {
129
+ console.warn("[ServerTTSProvider] telemetry callback failed:", r);
130
+ }
131
+ }
132
+ async speak(t) {
133
+ this.stop(), this.intentionallyStopped = !1;
134
+ const e = ++this.synthesisRunId, r = new AbortController();
135
+ this.activeSynthesisController = r;
136
+ const { audioUrl: i, wordTimings: o } = await this.synthesizeSpeech(t, r.signal, e);
137
+ if (e !== this.synthesisRunId) {
138
+ URL.revokeObjectURL(i);
139
+ return;
140
+ }
141
+ const s = this.config.rate || 1;
142
+ return this.wordTimings = o.map((a) => ({
143
+ ...a,
144
+ time: a.time / s
145
+ })), new Promise((a, d) => {
146
+ const n = new Audio(i);
147
+ this.currentAudio = n, this.config.rate && (n.playbackRate = Math.max(0.25, Math.min(4, this.config.rate))), this.config.volume !== void 0 && (n.volume = Math.max(0, Math.min(1, this.config.volume))), n.onplay = () => {
148
+ this.pausedState = !1, this.onWordBoundary && this.wordTimings.length > 0 && this.startWordHighlighting();
149
+ }, n.onended = () => {
150
+ this.stopWordHighlighting(), URL.revokeObjectURL(i), this.currentAudio = null, this.wordTimings = [], a();
151
+ }, n.onerror = (c) => {
152
+ this.stopWordHighlighting(), URL.revokeObjectURL(i), this.currentAudio = null, this.wordTimings = [], this.intentionallyStopped ? a() : d(/* @__PURE__ */ new Error("Failed to play audio from server"));
153
+ }, n.onpause = () => {
154
+ this.stopWordHighlighting(), this.pausedState = !0;
155
+ }, n.play().catch(d);
156
+ });
157
+ }
158
+ async synthesizeSpeech(t, e, r) {
159
+ const i = Date.now();
160
+ await this.emitTelemetry("pie-tool-backend-call-start", {
161
+ toolId: "tts",
162
+ backend: this.config.provider || "server",
163
+ operation: "synthesize-speech"
164
+ });
165
+ const o = {
166
+ "Content-Type": "application/json",
167
+ ...this.config.headers
168
+ };
169
+ 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 () => {
171
+ try {
172
+ return await fetch(s, {
173
+ method: "POST",
174
+ headers: o,
175
+ body: JSON.stringify(a),
176
+ signal: e
177
+ });
178
+ } catch (l) {
179
+ throw await this.emitTelemetry("pie-tool-backend-call-error", {
180
+ toolId: "tts",
181
+ backend: this.config.provider || "server",
182
+ operation: "synthesize-speech",
183
+ duration: Date.now() - i,
184
+ errorType: "TTSBackendNetworkError",
185
+ message: l instanceof Error ? l.message : String(l)
186
+ }), l;
187
+ }
188
+ })();
189
+ if (!d.ok) {
190
+ const l = await d.json().catch(() => ({})), p = l.message || l.error?.message || `Server returned ${d.status}`;
191
+ throw await this.emitTelemetry("pie-tool-backend-call-error", {
192
+ toolId: "tts",
193
+ backend: this.config.provider || "server",
194
+ operation: "synthesize-speech",
195
+ duration: Date.now() - i,
196
+ statusCode: d.status,
197
+ errorType: "TTSBackendRequestError",
198
+ message: p
199
+ }), new Error(p);
200
+ }
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);
205
+ else {
206
+ const l = n.audio.url, p = Date.now();
207
+ await this.emitTelemetry("pie-tool-backend-call-start", {
208
+ toolId: "tts",
209
+ backend: this.config.provider || "server",
210
+ operation: "fetch-synthesized-audio-asset"
211
+ });
212
+ const f = {};
213
+ this.config.includeAuthOnAssetFetch && this.config.authToken && (f.Authorization = `Bearer ${this.config.authToken}`);
214
+ const m = await (async () => {
215
+ try {
216
+ return await fetch(l, {
217
+ headers: f,
218
+ signal: e
219
+ });
220
+ } catch (g) {
221
+ throw await this.emitTelemetry("pie-tool-backend-call-error", {
222
+ toolId: "tts",
223
+ backend: this.config.provider || "server",
224
+ operation: "fetch-synthesized-audio-asset",
225
+ duration: Date.now() - p,
226
+ errorType: "TTSAssetNetworkError",
227
+ message: g instanceof Error ? g.message : String(g)
228
+ }), g;
229
+ }
230
+ })();
231
+ if (!m.ok)
232
+ throw await this.emitTelemetry("pie-tool-backend-call-error", {
233
+ toolId: "tts",
234
+ backend: this.config.provider || "server",
235
+ operation: "fetch-synthesized-audio-asset",
236
+ duration: Date.now() - p,
237
+ statusCode: m.status,
238
+ errorType: "TTSAssetFetchError",
239
+ message: `Failed to download synthesized audio (${m.status})`
240
+ }), new Error(`Failed to download synthesized audio (${m.status})`);
241
+ c = await m.blob(), await this.emitTelemetry("pie-tool-backend-call-success", {
242
+ toolId: "tts",
243
+ backend: this.config.provider || "server",
244
+ operation: "fetch-synthesized-audio-asset",
245
+ duration: Date.now() - p
246
+ });
247
+ }
248
+ const u = URL.createObjectURL(c), y = this.parseSpeechMarks(n.speechMarks);
249
+ return await this.emitTelemetry("pie-tool-backend-call-success", {
250
+ toolId: "tts",
251
+ backend: this.config.provider || "server",
252
+ operation: "synthesize-speech",
253
+ duration: Date.now() - i
254
+ }), {
255
+ audioUrl: u,
256
+ wordTimings: y
257
+ };
258
+ }
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 });
263
+ }
264
+ parseSpeechMarks(t) {
265
+ return t.filter((e) => e.type === "word").map((e, r) => ({
266
+ time: e.time,
267
+ wordIndex: r,
268
+ charIndex: e.start,
269
+ length: e.end - e.start
270
+ }));
271
+ }
272
+ startWordHighlighting() {
273
+ if (this.stopWordHighlighting(), !this.currentAudio || !this.onWordBoundary || this.wordTimings.length === 0) {
274
+ console.log("[ServerTTSProvider] Cannot start highlighting:", {
275
+ hasAudio: !!this.currentAudio,
276
+ hasCallback: !!this.onWordBoundary,
277
+ wordTimingsCount: this.wordTimings.length
278
+ });
279
+ return;
280
+ }
281
+ 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;
283
+ this.highlightInterval = window.setInterval(() => {
284
+ if (!this.currentAudio) {
285
+ this.stopWordHighlighting();
286
+ return;
287
+ }
288
+ const e = this.currentAudio.currentTime * 1e3;
289
+ for (let r = 0; r < this.wordTimings.length; r++) {
290
+ 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;
293
+ break;
294
+ }
295
+ }
296
+ }, 50);
297
+ }
298
+ stopWordHighlighting() {
299
+ this.highlightInterval !== null && (clearInterval(this.highlightInterval), this.highlightInterval = null);
300
+ }
301
+ pause() {
302
+ this.currentAudio && !this.pausedState && (this.currentAudio.pause(), this.stopWordHighlighting(), this.pausedState = !0);
303
+ }
304
+ resume() {
305
+ this.currentAudio && this.pausedState && (this.currentAudio.play(), this.pausedState = !1, this.onWordBoundary && this.wordTimings.length > 0 && this.startWordHighlighting());
306
+ }
307
+ stop() {
308
+ this.synthesisRunId += 1, this.activeSynthesisController && (this.activeSynthesisController.abort(), this.activeSynthesisController = null), this.stopWordHighlighting(), this.currentAudio && (this.intentionallyStopped = !0, this.currentAudio.pause(), this.currentAudio.src && URL.revokeObjectURL(this.currentAudio.src), this.currentAudio.src = "", this.currentAudio = null), this.pausedState = !1, this.wordTimings = [];
309
+ }
310
+ isPlaying() {
311
+ return this.currentAudio !== null && !this.pausedState;
312
+ }
313
+ isPaused() {
314
+ return this.pausedState;
315
+ }
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);
318
+ }
319
+ }, B = class {
320
+ 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);
322
+ }
323
+ async emitTelemetry(t, e) {
324
+ try {
325
+ await this.telemetryReporter?.(t, e);
326
+ } catch (r) {
327
+ console.warn("[ServerTTSProvider] telemetry callback failed:", r);
328
+ }
329
+ }
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();
335
+ if (await this.emitTelemetry("pie-tool-backend-call-start", {
336
+ toolId: "tts",
337
+ backend: e.provider || "server",
338
+ operation: "validate-endpoint"
339
+ }), !await this.testAPIAvailability())
340
+ throw await this.emitTelemetry("pie-tool-backend-call-error", {
341
+ toolId: "tts",
342
+ backend: e.provider || "server",
343
+ operation: "validate-endpoint",
344
+ duration: Date.now() - r,
345
+ errorType: "TTSEndpointValidationError",
346
+ message: `Server TTS API not available at ${e.apiEndpoint}`
347
+ }), new Error(`Server TTS API not available at ${e.apiEndpoint}`);
348
+ await this.emitTelemetry("pie-tool-backend-call-success", {
349
+ toolId: "tts",
350
+ backend: e.provider || "server",
351
+ operation: "validate-endpoint",
352
+ duration: Date.now() - r
353
+ });
354
+ }
355
+ return new P(e, this.adapter);
356
+ }
357
+ async testAPIAvailability() {
358
+ if (!this.config || !this.adapter) return !1;
359
+ 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);
363
+ if (i === "none")
364
+ return clearTimeout(r), !0;
365
+ const o = i === "voices" ? k(this.config) : this.adapter.resolveSynthesisUrl(this.config), s = i === "voices" ? "GET" : "OPTIONS";
366
+ try {
367
+ const a = await fetch(o, {
368
+ method: s,
369
+ headers: t,
370
+ signal: e.signal
371
+ });
372
+ return clearTimeout(r), a.ok || a.status === 405;
373
+ } catch {
374
+ return clearTimeout(r), !1;
375
+ }
376
+ } catch {
377
+ return !1;
378
+ }
379
+ }
380
+ supportsFeature(t) {
381
+ switch (t) {
382
+ case "pause":
383
+ case "resume":
384
+ case "wordBoundary":
385
+ case "voiceSelection":
386
+ case "rateControl":
387
+ return !0;
388
+ case "pitchControl":
389
+ return !1;
390
+ default:
391
+ return !1;
392
+ }
393
+ }
394
+ getCapabilities() {
395
+ return {
396
+ supportsPause: !0,
397
+ supportsResume: !0,
398
+ supportsWordBoundary: !0,
399
+ supportsVoiceSelection: !0,
400
+ supportsRateControl: !0,
401
+ supportsPitchControl: !1,
402
+ maxTextLength: S[this.config ? T(this.config) : "pie"]
403
+ };
404
+ }
405
+ destroy() {
406
+ this.config = null, this.adapter = null, this.telemetryReporter = void 0;
407
+ }
408
+ };
409
+ export {
410
+ B as ServerTTSProvider
411
+ };
@@ -0,0 +1,201 @@
1
+ import { t as s } from "./defineProperty-CyepwRr5-eHUSgEtz.js";
2
+ var n = class {
3
+ constructor() {
4
+ s(this, "providerId", "desmos"), s(this, "providerName", "Desmos"), s(this, "supportedTypes", [
5
+ "basic",
6
+ "scientific",
7
+ "graphing"
8
+ ]), s(this, "version", "1.10"), s(this, "initialized", !1), s(this, "apiKey", void 0), s(this, "proxyEndpoint", void 0), s(this, "isDevelopment", !1), s(this, "onTelemetry", void 0);
9
+ }
10
+ async emitTelemetry(t, o) {
11
+ try {
12
+ await this.onTelemetry?.(t, o);
13
+ } catch (e) {
14
+ console.warn("[DesmosProvider] telemetry callback failed:", e);
15
+ }
16
+ }
17
+ getApiKey() {
18
+ return this.apiKey;
19
+ }
20
+ async loadDesmosScript() {
21
+ return new Promise((t, o) => {
22
+ const e = document.createElement("script");
23
+ e.src = this.apiKey ? `https://www.desmos.com/api/v1.10/calculator.js?apiKey=${this.apiKey}` : "https://www.desmos.com/api/v1.10/calculator.js", e.async = !0, e.onload = () => {
24
+ window.Desmos ? (console.log("[DesmosProvider] Desmos API loaded successfully"), t()) : o(/* @__PURE__ */ new Error("Desmos API loaded but window.Desmos is undefined"));
25
+ }, e.onerror = () => {
26
+ o(/* @__PURE__ */ new Error("Failed to load Desmos API from CDN"));
27
+ }, document.head.appendChild(e);
28
+ });
29
+ }
30
+ async initialize(t) {
31
+ if (!this.initialized) {
32
+ if (this.onTelemetry = t?.onTelemetry, typeof window > "u") throw new Error("Desmos calculators can only be initialized in the browser");
33
+ if (this.isDevelopment = process.env.NODE_ENV === "development" || typeof process > "u" || !process.env.NODE_ENV, t?.proxyEndpoint) {
34
+ this.proxyEndpoint = t.proxyEndpoint;
35
+ const o = Date.now();
36
+ await this.emitTelemetry("pie-tool-backend-call-start", {
37
+ toolId: "calculator",
38
+ backend: "desmos",
39
+ operation: "proxy-auth-fetch"
40
+ });
41
+ try {
42
+ const e = await fetch(t.proxyEndpoint);
43
+ if (!e.ok) throw new Error(`Proxy endpoint returned ${e.status}`);
44
+ this.apiKey = (await e.json()).apiKey, await this.emitTelemetry("pie-tool-backend-call-success", {
45
+ toolId: "calculator",
46
+ backend: "desmos",
47
+ operation: "proxy-auth-fetch",
48
+ duration: Date.now() - o
49
+ }), console.log("[DesmosProvider] Initialized with server-side proxy (SECURE)");
50
+ } catch (e) {
51
+ throw await this.emitTelemetry("pie-tool-backend-call-error", {
52
+ toolId: "calculator",
53
+ backend: "desmos",
54
+ operation: "proxy-auth-fetch",
55
+ duration: Date.now() - o,
56
+ errorType: "CalculatorProxyAuthError",
57
+ message: e instanceof Error ? e.message : String(e)
58
+ }), /* @__PURE__ */ new Error(`[DesmosProvider] Failed to fetch API key from proxy: ${e}`);
59
+ }
60
+ } else t?.apiKey ? (this.apiKey = t.apiKey, this.isDevelopment ? console.log("[DesmosProvider] Initialized with direct API key (DEVELOPMENT MODE)") : console.error(`⚠️ [DesmosProvider] SECURITY WARNING: API key exposed in client-side code!
61
+ This is insecure for production. Use proxyEndpoint instead.
62
+ See: https://pie-players.dev/docs/calculator-desmos#security`)) : console.warn(`[DesmosProvider] No API key or proxy endpoint provided.
63
+ Production usage requires authentication. Obtain API key from https://www.desmos.com/api
64
+ Recommended: Use proxyEndpoint for production, apiKey for development only.`);
65
+ if (!window.Desmos) {
66
+ console.log("[DesmosProvider] Loading Desmos API library...");
67
+ const o = Date.now();
68
+ await this.emitTelemetry("pie-tool-library-load-start", {
69
+ toolId: "calculator",
70
+ backend: "desmos",
71
+ operation: "desmos-script-load"
72
+ });
73
+ try {
74
+ await this.loadDesmosScript(), await this.emitTelemetry("pie-tool-library-load-success", {
75
+ toolId: "calculator",
76
+ backend: "desmos",
77
+ operation: "desmos-script-load",
78
+ duration: Date.now() - o
79
+ });
80
+ } catch (e) {
81
+ throw await this.emitTelemetry("pie-tool-library-load-error", {
82
+ toolId: "calculator",
83
+ backend: "desmos",
84
+ operation: "desmos-script-load",
85
+ duration: Date.now() - o,
86
+ errorType: "ToolLibraryLoadError",
87
+ message: e instanceof Error ? e.message : String(e)
88
+ }), e;
89
+ }
90
+ }
91
+ this.initialized = !0;
92
+ }
93
+ }
94
+ async createCalculator(t, o, e) {
95
+ if (this.initialized || await this.initialize(), !this.supportsType(t)) throw new Error(`Desmos does not support calculator type: ${t}`);
96
+ return new a(this, t, o, e, this.apiKey);
97
+ }
98
+ supportsType(t) {
99
+ return this.supportedTypes.includes(t);
100
+ }
101
+ destroy() {
102
+ this.initialized = !1, this.onTelemetry = void 0;
103
+ }
104
+ getCapabilities() {
105
+ return {
106
+ supportsHistory: !1,
107
+ supportsGraphing: !0,
108
+ supportsExpressions: !0,
109
+ canExport: !0,
110
+ maxPrecision: 15,
111
+ inputMethods: [
112
+ "keyboard",
113
+ "mouse",
114
+ "touch"
115
+ ]
116
+ };
117
+ }
118
+ }, a = class {
119
+ constructor(t, o, e, i, r) {
120
+ if (s(this, "provider", void 0), s(this, "type", void 0), s(this, "Desmos", void 0), s(this, "calculator", void 0), s(this, "container", void 0), this.provider = t, this.type = o, this.container = e, this.Desmos = window.Desmos, !this.Desmos) throw new Error("Desmos API not available");
121
+ this._initializeCalculator(i, r);
122
+ }
123
+ _initializeCalculator(t, o) {
124
+ const e = {
125
+ ...t?.desmos || {},
126
+ apiKey: o || t?.desmos?.apiKey
127
+ };
128
+ switch (t?.restrictedMode && Object.assign(e, {
129
+ expressionsTopbar: !1,
130
+ settingsMenu: !1,
131
+ zoomButtons: !1,
132
+ expressions: !1,
133
+ links: !1
134
+ }), this.type) {
135
+ case "graphing":
136
+ this.calculator = this.Desmos.GraphingCalculator(this.container, e);
137
+ break;
138
+ case "scientific":
139
+ this.calculator = this.Desmos.ScientificCalculator(this.container, e);
140
+ break;
141
+ case "basic":
142
+ this.calculator = this.Desmos.FourFunctionCalculator(this.container, e);
143
+ break;
144
+ default:
145
+ throw new Error(`Unsupported calculator type: ${this.type}`);
146
+ }
147
+ console.log(`[DesmosCalculator] Created ${this.type} calculator`);
148
+ }
149
+ getValue() {
150
+ if (this.type === "graphing" && this.calculator.getState) {
151
+ const t = this.calculator.getState();
152
+ return JSON.stringify(t);
153
+ }
154
+ return "";
155
+ }
156
+ setValue(t) {
157
+ if (this.type === "graphing" && this.calculator.setState) try {
158
+ const o = JSON.parse(t);
159
+ this.calculator.setState(o);
160
+ } catch (o) {
161
+ console.error("[DesmosCalculator] Failed to set state:", o);
162
+ }
163
+ }
164
+ clear() {
165
+ this.calculator.setBlank && this.calculator.setBlank();
166
+ }
167
+ async evaluate(t) {
168
+ return this.type === "graphing" ? new Promise((o) => {
169
+ const e = `eval_${Date.now()}`;
170
+ this.calculator.setExpression({
171
+ id: e,
172
+ latex: t
173
+ }), setTimeout(() => {
174
+ const i = this.calculator.HelperExpression({ latex: t }).numericValue || t;
175
+ this.calculator.removeExpression({ id: e }), o(String(i));
176
+ }, 100);
177
+ }) : t;
178
+ }
179
+ resize() {
180
+ this.calculator.resize && this.calculator.resize();
181
+ }
182
+ exportState() {
183
+ let t = {};
184
+ return this.type === "graphing" && this.calculator.getState && (t = this.calculator.getState()), {
185
+ type: this.type,
186
+ provider: "desmos",
187
+ value: this.getValue(),
188
+ providerState: t
189
+ };
190
+ }
191
+ importState(t) {
192
+ if (t.provider !== "desmos") throw new Error(`Cannot import state from provider: ${t.provider}`);
193
+ t.providerState && this.calculator.setState ? this.calculator.setState(t.providerState) : t.value && this.setValue(t.value);
194
+ }
195
+ destroy() {
196
+ this.calculator && this.calculator.destroy && this.calculator.destroy(), this.container.replaceChildren(), console.log("[DesmosCalculator] destroyed");
197
+ }
198
+ };
199
+ export {
200
+ n as DesmosCalculatorProvider
201
+ };