@foisit/react-wrapper 2.1.1 → 2.4.1

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/index.mjs ADDED
@@ -0,0 +1,1421 @@
1
+ import { jsx as k } from "react/jsx-runtime";
2
+ import { createContext as F, useState as R, useEffect as P, useContext as N } from "react";
3
+ const W = {};
4
+ function J() {
5
+ return /* @__PURE__ */ k("div", { className: W.container, children: /* @__PURE__ */ k("h1", { children: "Welcome to ReactWrapper!" }) });
6
+ }
7
+ class M {
8
+ constructor(t) {
9
+ this.endpoint = t || "https://foisit-ninja.netlify.app/.netlify/functions/intent";
10
+ }
11
+ async determineIntent(t, e, i) {
12
+ try {
13
+ const s = {
14
+ userInput: t,
15
+ commands: e.map((r) => ({
16
+ id: r.id,
17
+ command: r.command,
18
+ description: r.description,
19
+ parameters: r.parameters
20
+ // Send param schemas to AI
21
+ })),
22
+ context: i
23
+ }, n = await fetch(this.endpoint, {
24
+ method: "POST",
25
+ headers: {
26
+ "Content-Type": "application/json"
27
+ },
28
+ body: JSON.stringify(s)
29
+ });
30
+ if (!n.ok)
31
+ throw new Error(`Proxy API Error: ${n.statusText}`);
32
+ return await n.json();
33
+ } catch (s) {
34
+ return console.error("OpenAIService Error:", s), { type: "unknown" };
35
+ }
36
+ }
37
+ }
38
+ class B {
39
+ constructor(t = !0) {
40
+ if (this.commands = /* @__PURE__ */ new Map(), this.openAIService = null, this.context = null, this.pendingConfirmation = null, this.enableSmartIntent = !0, this.selectOptionsCache = /* @__PURE__ */ new Map(), typeof t == "boolean") {
41
+ this.enableSmartIntent = t, this.enableSmartIntent && (this.openAIService = new M());
42
+ return;
43
+ }
44
+ this.enableSmartIntent = t.enableSmartIntent ?? !0, this.enableSmartIntent && (this.openAIService = new M(t.intentEndpoint));
45
+ }
46
+ /** Add a new command (string or object) */
47
+ addCommand(t, e) {
48
+ let i;
49
+ if (typeof t == "string") {
50
+ if (!e)
51
+ throw new Error("Action required when adding command by string.");
52
+ i = {
53
+ id: t.toLowerCase().replace(/\s+/g, "_"),
54
+ command: t.toLowerCase(),
55
+ action: e
56
+ };
57
+ } else
58
+ i = { ...t }, i.id || (i.id = i.command.toLowerCase().replace(/\s+/g, "_"));
59
+ this.commands.set(i.command.toLowerCase(), i), i.id && (i.id, i.command);
60
+ }
61
+ /** Remove an existing command */
62
+ removeCommand(t) {
63
+ this.commands.delete(t.toLowerCase());
64
+ }
65
+ /** Execute a command by matching input */
66
+ async executeCommand(t) {
67
+ if (typeof t == "object" && t !== null) {
68
+ if (this.isStructured(t)) {
69
+ const c = String(t.commandId), f = t.params ?? {}, o = this.getCommandById(c);
70
+ if (!o) return { message: "That command is not available.", type: "error" };
71
+ const p = this.sanitizeParamsForCommand(o, f), h = (o.parameters ?? []).filter((C) => C.required).filter((C) => p[C.name] == null || p[C.name] === "");
72
+ return h.length > 0 ? (this.context = { commandId: this.getCommandIdentifier(o), params: p }, {
73
+ message: `Please provide the required details for "${o.command}".`,
74
+ type: "form",
75
+ fields: h
76
+ }) : o.critical ? (this.pendingConfirmation = { commandId: this.getCommandIdentifier(o), params: p }, this.buildConfirmResponse(o)) : this.safeRunAction(o, p);
77
+ }
78
+ if (!this.context)
79
+ return { message: "Session expired or invalid context.", type: "error" };
80
+ const n = this.getCommandById(this.context.commandId);
81
+ if (!n)
82
+ return this.context = null, { message: "Session expired or invalid context.", type: "error" };
83
+ if (Array.isArray(t))
84
+ return { message: "Invalid form payload.", type: "error" };
85
+ const a = {
86
+ ...this.context.params,
87
+ ...t
88
+ };
89
+ if (n.critical)
90
+ return this.context = null, this.pendingConfirmation = {
91
+ commandId: this.getCommandIdentifier(n),
92
+ params: a
93
+ }, this.buildConfirmResponse(n);
94
+ const r = await this.safeRunAction(n, a);
95
+ return this.context = null, this.normalizeResponse(r);
96
+ }
97
+ const e = t.trim().toLowerCase();
98
+ if (this.pendingConfirmation) {
99
+ const n = e;
100
+ if (["yes", "y", "confirm", "ok", "okay"].includes(n)) {
101
+ const { commandId: a, params: r } = this.pendingConfirmation;
102
+ this.pendingConfirmation = null;
103
+ const c = this.getCommandById(a);
104
+ return c ? this.safeRunAction(c, r) : { message: "That action is no longer available.", type: "error" };
105
+ }
106
+ return ["no", "n", "cancel", "stop"].includes(n) ? (this.pendingConfirmation = null, { message: "✅ Cancelled.", type: "success" }) : {
107
+ message: "Please confirm: Yes or No.",
108
+ type: "confirm",
109
+ options: [
110
+ { label: "Yes", value: "yes" },
111
+ { label: "No", value: "no" }
112
+ ]
113
+ };
114
+ }
115
+ const i = this.commands.get(e);
116
+ if (i) {
117
+ const n = i, a = (n.parameters ?? []).filter((r) => r.required);
118
+ return a.length > 0 ? (this.context = { commandId: this.getCommandIdentifier(n), params: {} }, {
119
+ message: `Please provide the required details for "${n.command}".`,
120
+ type: "form",
121
+ fields: a
122
+ }) : n.critical ? (this.pendingConfirmation = { commandId: this.getCommandIdentifier(n), params: {} }, this.buildConfirmResponse(n)) : this.safeRunAction(n, {});
123
+ }
124
+ const s = await this.tryDeterministicMatch(e);
125
+ if (s) return s;
126
+ if (this.enableSmartIntent && this.openAIService) {
127
+ const n = await this.getCommandsForAI(), a = await this.openAIService.determineIntent(
128
+ e,
129
+ n,
130
+ this.context
131
+ );
132
+ return this.handleAIResult(a);
133
+ }
134
+ return this.enableSmartIntent ? this.listAllCommands() : { message: "I'm not sure what you mean.", type: "error" };
135
+ }
136
+ async handleAIResult(t) {
137
+ if (t.type === "match" && t.match) {
138
+ const e = this.getCommandById(t.match);
139
+ if (!e)
140
+ return { message: "I'm not sure what you mean.", type: "error" };
141
+ const i = t.params ?? {}, s = this.sanitizeParamsForCommand(e, i), a = (e.parameters ?? []).filter((c) => c.required).filter((c) => s[c.name] == null || s[c.name] === "");
142
+ if (t.incomplete || a.length > 0) {
143
+ this.context = { commandId: this.getCommandIdentifier(e), params: s };
144
+ const c = a.some((f) => f.type === "select");
145
+ if (a.length <= 2 && !c) {
146
+ const f = a.map((o) => o.name).join(" and ");
147
+ return {
148
+ message: t.message || `Please provide ${f}.`,
149
+ type: "question"
150
+ };
151
+ }
152
+ return {
153
+ message: t.message || `Please fill in the missing details for "${e.command}".`,
154
+ type: "form",
155
+ fields: a
156
+ };
157
+ }
158
+ if (e.critical)
159
+ return this.pendingConfirmation = {
160
+ commandId: this.getCommandIdentifier(e),
161
+ params: s
162
+ }, this.buildConfirmResponse(e);
163
+ const r = await e.action(s);
164
+ return this.normalizeResponse(r);
165
+ }
166
+ return t.type === "ambiguous" && t.options && t.options.length ? {
167
+ message: t.message || "Did you mean one of these?",
168
+ type: "ambiguous",
169
+ options: t.options.map((e) => ({
170
+ label: e.label,
171
+ value: e.commandId ?? e.label,
172
+ commandId: e.commandId
173
+ }))
174
+ } : this.listAllCommands();
175
+ }
176
+ sanitizeParamsForCommand(t, e) {
177
+ const i = { ...e ?? {} };
178
+ for (const s of t.parameters ?? [])
179
+ if (s.type === "date") {
180
+ const n = i[s.name];
181
+ if (typeof n == "string") {
182
+ const a = n.trim();
183
+ this.isIsoDateString(a) || delete i[s.name];
184
+ }
185
+ }
186
+ return i;
187
+ }
188
+ isIsoDateString(t) {
189
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(t)) return !1;
190
+ const e = /* @__PURE__ */ new Date(`${t}T00:00:00Z`);
191
+ return !Number.isNaN(e.getTime());
192
+ }
193
+ buildConfirmResponse(t) {
194
+ return {
195
+ message: `⚠️ Are you sure you want to run "${t.command}"?`,
196
+ type: "confirm",
197
+ options: [
198
+ { label: "Yes", value: "yes" },
199
+ { label: "No", value: "no" }
200
+ ]
201
+ };
202
+ }
203
+ async tryDeterministicMatch(t) {
204
+ const e = [];
205
+ for (const r of this.commands.values()) {
206
+ let c = 0;
207
+ const f = r.command.toLowerCase();
208
+ t.includes(f) && (c += 5);
209
+ const o = r.keywords ?? [];
210
+ for (const p of o) {
211
+ const b = p.toLowerCase().trim();
212
+ b && (t === b ? c += 4 : t.includes(b) && (c += 3));
213
+ }
214
+ c > 0 && e.push({ cmd: r, score: c });
215
+ }
216
+ if (e.length === 0) return null;
217
+ e.sort((r, c) => c.score - r.score);
218
+ const i = e[0].score, s = e.filter((r) => r.score === i).slice(0, 3);
219
+ if (s.length > 1)
220
+ return {
221
+ message: "I think you mean one of these. Which one should I run?",
222
+ type: "ambiguous",
223
+ options: s.map((r) => ({
224
+ label: r.cmd.command,
225
+ value: r.cmd.command,
226
+ commandId: r.cmd.id
227
+ }))
228
+ };
229
+ const n = s[0].cmd, a = (n.parameters ?? []).filter((r) => r.required);
230
+ return a.length > 0 ? (this.context = { commandId: this.getCommandIdentifier(n), params: {} }, {
231
+ message: `Please provide the required details for "${n.command}".`,
232
+ type: "form",
233
+ fields: a
234
+ }) : n.critical ? (this.pendingConfirmation = { commandId: this.getCommandIdentifier(n), params: {} }, this.buildConfirmResponse(n)) : this.safeRunAction(n, {});
235
+ }
236
+ async safeRunAction(t, e) {
237
+ try {
238
+ const i = await t.action(e ?? {});
239
+ return this.normalizeResponse(i);
240
+ } catch {
241
+ return { message: "Something went wrong while running that command.", type: "error" };
242
+ }
243
+ }
244
+ async getCommandsForAI() {
245
+ const t = Array.from(this.commands.values()).map((e) => ({
246
+ ...e,
247
+ parameters: e.parameters ? e.parameters.map((i) => ({ ...i })) : void 0
248
+ }));
249
+ return await Promise.all(
250
+ t.map(async (e) => {
251
+ e.parameters && await Promise.all(
252
+ e.parameters.map(async (i) => {
253
+ if (i.type !== "select" || !i.getOptions || i.options && i.options.length) return;
254
+ const s = `${e.id ?? e.command}:${i.name}`, n = this.selectOptionsCache.get(s), a = Date.now();
255
+ if (n && a - n.ts < 6e4) {
256
+ i.options = n.options;
257
+ return;
258
+ }
259
+ try {
260
+ const r = await i.getOptions();
261
+ this.selectOptionsCache.set(s, { options: r, ts: a }), i.options = r;
262
+ } catch {
263
+ }
264
+ })
265
+ );
266
+ })
267
+ ), t;
268
+ }
269
+ getCommandById(t) {
270
+ for (const e of this.commands.values())
271
+ if (e.id === t) return e;
272
+ }
273
+ listAllCommands() {
274
+ return {
275
+ message: "Here are the available commands:",
276
+ type: "ambiguous",
277
+ options: Array.from(this.commands.values()).map((e) => ({
278
+ label: e.command,
279
+ value: e.id ?? e.command,
280
+ commandId: e.id ?? e.command
281
+ }))
282
+ };
283
+ }
284
+ normalizeResponse(t) {
285
+ return typeof t == "string" ? { message: t, type: "success" } : t && typeof t == "object" ? t : { message: "Done", type: "success" };
286
+ }
287
+ isStructured(t) {
288
+ return typeof t.commandId == "string";
289
+ }
290
+ getCommandIdentifier(t) {
291
+ return t.id || (t.id = t.command.toLowerCase().replace(/\s+/g, "_")), t.id;
292
+ }
293
+ /** List all registered commands */
294
+ getCommands() {
295
+ return Array.from(this.commands.keys());
296
+ }
297
+ }
298
+ class H {
299
+ constructor() {
300
+ this.synth = window.speechSynthesis;
301
+ }
302
+ speak(t, e) {
303
+ if (!this.synth) {
304
+ console.error("SpeechSynthesis API is not supported in this browser.");
305
+ return;
306
+ }
307
+ const i = new SpeechSynthesisUtterance(t);
308
+ e && (i.pitch = e.pitch || 1, i.rate = e.rate || 1, i.volume = e.volume || 1), i.onstart = () => {
309
+ window.dispatchEvent(new CustomEvent("foisit:tts-start"));
310
+ }, i.onend = () => {
311
+ console.log("Speech finished."), window.dispatchEvent(new CustomEvent("foisit:tts-end"));
312
+ }, i.onerror = (s) => {
313
+ console.error("Error during speech synthesis:", s.error);
314
+ }, this.synth.speak(i);
315
+ }
316
+ stopSpeaking() {
317
+ this.synth && this.synth.cancel();
318
+ }
319
+ }
320
+ class O {
321
+ constructor() {
322
+ this.fallbackMessage = "Sorry, I didn’t understand that.";
323
+ }
324
+ setFallbackMessage(t) {
325
+ this.fallbackMessage = t;
326
+ }
327
+ handleFallback(t) {
328
+ t && console.log(`Fallback triggered for: "${t}"`), console.log(this.fallbackMessage), new H().speak(this.fallbackMessage);
329
+ }
330
+ getFallbackMessage() {
331
+ return this.fallbackMessage;
332
+ }
333
+ }
334
+ const T = () => {
335
+ const d = window;
336
+ return d.SpeechRecognition ?? d.webkitSpeechRecognition ?? null;
337
+ };
338
+ class z {
339
+ constructor(t = "en-US", e = {}) {
340
+ this.recognition = null, this.isListening = !1, this.engineActive = !1, this.intentionallyStopped = !1, this.restartAllowed = !0, this.lastStart = 0, this.backoffMs = 250, this.destroyed = !1, this.resultCallback = null, this.ttsSpeaking = !1, this.debugEnabled = !0, this.restartTimer = null, this.prewarmed = !1, this.hadResultThisSession = !1, this.onTTSStart = () => {
341
+ var s;
342
+ this.ttsSpeaking = !0;
343
+ try {
344
+ (s = this.recognition) == null || s.stop();
345
+ } catch {
346
+ }
347
+ this.isListening && this.emitStatus("speaking");
348
+ }, this.onTTSEnd = () => {
349
+ this.ttsSpeaking = !1, this.isListening && this.restartAllowed ? this.safeRestart() : this.emitStatus(this.isListening ? "listening" : "idle");
350
+ };
351
+ const i = T();
352
+ if (i) {
353
+ this.recognition = new i(), this.recognition.lang = t, this.recognition.interimResults = e.interimResults ?? !0, this.recognition.continuous = e.continuous ?? !0, this.recognition.onresult = (n) => this.handleResult(n, e), this.recognition.onend = () => this.handleEnd(), this.recognition.onstart = () => {
354
+ this.log("recognition onstart"), this.engineActive = !0, this.hadResultThisSession = !1, this.restartTimer && (clearTimeout(this.restartTimer), this.restartTimer = null), this.backoffMs = 250, this.isListening && !this.ttsSpeaking && this.emitStatus("listening");
355
+ };
356
+ const s = this.recognition;
357
+ s.onaudiostart = () => this.log("onaudiostart"), s.onsoundstart = () => this.log("onsoundstart"), s.onspeechstart = () => this.log("onspeechstart"), s.onspeechend = () => this.log("onspeechend"), s.onsoundend = () => this.log("onsoundend"), s.onaudioend = () => this.log("onaudioend"), this.recognition.onerror = (n) => this.handleError(n);
358
+ } else
359
+ this.recognition = null, this.emitStatus("unsupported");
360
+ window.addEventListener("foisit:tts-start", this.onTTSStart), window.addEventListener("foisit:tts-end", this.onTTSEnd), this.visibilityHandler = () => {
361
+ var s;
362
+ if (document.hidden) {
363
+ try {
364
+ (s = this.recognition) == null || s.stop();
365
+ } catch {
366
+ }
367
+ this.emitStatus(this.ttsSpeaking ? "speaking" : "idle");
368
+ } else this.isListening && !this.ttsSpeaking && this.safeRestart();
369
+ }, document.addEventListener("visibilitychange", this.visibilityHandler);
370
+ }
371
+ // Debug logger helpers
372
+ log(t) {
373
+ this.debugEnabled && t && console.log("[VoiceProcessor]", t);
374
+ }
375
+ warn(t) {
376
+ this.debugEnabled && t && console.warn("[VoiceProcessor]", t);
377
+ }
378
+ error(t) {
379
+ this.debugEnabled && t && console.error("[VoiceProcessor]", t);
380
+ }
381
+ /** Check if SpeechRecognition is available */
382
+ isSupported() {
383
+ return T() !== null;
384
+ }
385
+ /** Allow consumers (wrappers) to observe status changes */
386
+ onStatusChange(t) {
387
+ this.statusCallback = t;
388
+ }
389
+ /** Start listening for speech input */
390
+ startListening(t) {
391
+ if (!this.isSupported() || !this.recognition) {
392
+ this.warn("VoiceProcessor: SpeechRecognition is not supported in this browser."), this.emitStatus("unsupported");
393
+ return;
394
+ }
395
+ if (this.isListening) {
396
+ this.warn("VoiceProcessor: Already listening."), this.resultCallback = t;
397
+ return;
398
+ }
399
+ this.resultCallback = t, this.intentionallyStopped = !1, this.restartAllowed = !0, this.isListening = !0, this.emitStatus("listening"), this.prewarmAudio().finally(() => {
400
+ this.safeRestart();
401
+ });
402
+ }
403
+ /** Stop listening for speech input */
404
+ stopListening() {
405
+ var t;
406
+ this.intentionallyStopped = !0, this.restartAllowed = !1, this.isListening = !1, this.emitStatus(this.ttsSpeaking ? "speaking" : "idle");
407
+ try {
408
+ (t = this.recognition) == null || t.stop();
409
+ } catch {
410
+ }
411
+ }
412
+ /** Clean up listeners */
413
+ destroy() {
414
+ this.destroyed = !0, this.stopListening(), this.resultCallback = null, window.removeEventListener("foisit:tts-start", this.onTTSStart), window.removeEventListener("foisit:tts-end", this.onTTSEnd), this.visibilityHandler && (document.removeEventListener("visibilitychange", this.visibilityHandler), this.visibilityHandler = void 0);
415
+ }
416
+ /** Handle recognized speech results */
417
+ handleResult(t, e) {
418
+ var s, n;
419
+ if (!this.resultCallback) return;
420
+ const i = e.confidenceThreshold ?? 0.6;
421
+ for (let a = t.resultIndex; a < t.results.length; a++) {
422
+ const r = t.results[a], c = r && r[0], f = ((n = (s = c == null ? void 0 : c.transcript) == null ? void 0 : s.trim) == null ? void 0 : n.call(s)) || "", o = (c == null ? void 0 : c.confidence) ?? 0;
423
+ if (f && !(!r.isFinal && e.interimResults === !1) && !(r.isFinal && o < i))
424
+ try {
425
+ this.hadResultThisSession = !0, this.resultCallback(f, !!r.isFinal);
426
+ } catch {
427
+ this.error("VoiceProcessor: result callback error");
428
+ }
429
+ }
430
+ }
431
+ /** Handle session end */
432
+ handleEnd() {
433
+ if (this.log("recognition onend"), this.engineActive = !1, this.destroyed || this.intentionallyStopped || !this.restartAllowed || this.ttsSpeaking) {
434
+ this.ttsSpeaking || (this.isListening = !1, this.emitStatus("idle"));
435
+ return;
436
+ }
437
+ this.isListening = !0, this.scheduleRestart();
438
+ }
439
+ /** Handle errors during speech recognition */
440
+ handleError(t) {
441
+ const e = t == null ? void 0 : t.error;
442
+ if (this.warn(`Error occurred: ${e ?? "unknown"}`), e && ["not-allowed", "service-not-allowed", "bad-grammar", "language-not-supported"].includes(e)) {
443
+ this.intentionallyStopped = !0, this.restartAllowed = !1, this.isListening = !1, this.emitStatus("error", { error: e });
444
+ return;
445
+ }
446
+ this.scheduleRestart();
447
+ }
448
+ safeRestart() {
449
+ if (!this.recognition) return;
450
+ if (this.engineActive) {
451
+ this.log("safeRestart: engine already active, skipping start");
452
+ return;
453
+ }
454
+ const t = Date.now();
455
+ if (t - this.lastStart < 300) {
456
+ setTimeout(() => this.safeRestart(), 300);
457
+ return;
458
+ }
459
+ this.lastStart = t;
460
+ try {
461
+ this.log("calling recognition.start()"), this.recognition.start(), this.backoffMs = 250, this.isListening && !this.ttsSpeaking && this.emitStatus("listening");
462
+ } catch {
463
+ this.error("recognition.start() threw; scheduling restart"), this.scheduleRestart();
464
+ }
465
+ }
466
+ scheduleRestart() {
467
+ if (this.destroyed || this.intentionallyStopped || !this.restartAllowed || this.ttsSpeaking) return;
468
+ if (this.engineActive) {
469
+ this.log("scheduleRestart: engine active, not scheduling");
470
+ return;
471
+ }
472
+ const t = Math.min(this.backoffMs, 2e3);
473
+ if (this.log(`scheduleRestart in ${t}ms`), this.restartTimer) {
474
+ this.log("scheduleRestart: restart already scheduled");
475
+ return;
476
+ }
477
+ this.restartTimer = setTimeout(() => {
478
+ this.restartTimer = null, !(this.destroyed || this.intentionallyStopped || !this.restartAllowed || this.ttsSpeaking) && this.safeRestart();
479
+ }, t), this.backoffMs = Math.min(this.backoffMs * 2, 2e3);
480
+ }
481
+ async prewarmAudio() {
482
+ if (!this.prewarmed)
483
+ try {
484
+ if (typeof navigator > "u" || !("mediaDevices" in navigator)) return;
485
+ const t = navigator.mediaDevices;
486
+ if (!(t != null && t.getUserMedia)) return;
487
+ this.log("prewarmAudio: requesting mic");
488
+ const e = await t.getUserMedia({ audio: !0 });
489
+ for (const i of e.getTracks()) i.stop();
490
+ this.prewarmed = !0, this.log("prewarmAudio: mic ready");
491
+ } catch {
492
+ this.warn("prewarmAudio: failed to get mic");
493
+ }
494
+ }
495
+ emitStatus(t, e) {
496
+ if (this.statusCallback)
497
+ try {
498
+ this.statusCallback(t, e);
499
+ } catch {
500
+ this.error("VoiceProcessor: status callback error");
501
+ }
502
+ }
503
+ }
504
+ class $ {
505
+ constructor() {
506
+ this.lastTap = 0;
507
+ }
508
+ /**
509
+ * Sets up double-click and double-tap listeners
510
+ * @param onDoubleClickOrTap Callback to execute when a double-click or double-tap is detected
511
+ */
512
+ setupDoubleTapListener(t) {
513
+ this.destroy(), this.dblClickListener = () => {
514
+ t();
515
+ }, document.addEventListener("dblclick", this.dblClickListener), this.touchEndListener = () => {
516
+ const e = (/* @__PURE__ */ new Date()).getTime(), i = e - this.lastTap;
517
+ i < 300 && i > 0 && t(), this.lastTap = e;
518
+ }, document.addEventListener("touchend", this.touchEndListener);
519
+ }
520
+ destroy() {
521
+ this.dblClickListener && document.removeEventListener("dblclick", this.dblClickListener), this.touchEndListener && document.removeEventListener("touchend", this.touchEndListener), this.dblClickListener = void 0, this.touchEndListener = void 0;
522
+ }
523
+ }
524
+ function D() {
525
+ if (document.querySelector("#assistant-styles")) {
526
+ console.log("Styles already injected");
527
+ return;
528
+ }
529
+ const t = document.createElement("style");
530
+ t.id = "assistant-styles", t.innerHTML = `
531
+ /* Rounded shape with gradient animation */
532
+ .gradient-indicator {
533
+ position: fixed;
534
+ top: 20px;
535
+ right: 20px;
536
+ width: 60px;
537
+ height: 60px;
538
+ border-radius: 50%;
539
+ background: linear-gradient(135deg, #ff6ec4, #7873f5, #5e8cff, #6ed0f6);
540
+ box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
541
+ animation: amoeba 5s infinite ease-in-out;
542
+ z-index: 9999; /* Ensure it's above all other elements */
543
+ }
544
+
545
+ /* Amoeba effect for the borders */
546
+ @keyframes amoeba {
547
+ 0% {
548
+ border-radius: 50%;
549
+ }
550
+ 25% {
551
+ border-radius: 40% 60% 60% 40%;
552
+ }
553
+ 50% {
554
+ border-radius: 60% 40% 40% 60%;
555
+ }
556
+ 75% {
557
+ border-radius: 40% 60% 60% 40%;
558
+ }
559
+ 100% {
560
+ border-radius: 50%;
561
+ }
562
+ }
563
+ `, document.head.appendChild(t), console.log("Gradient styles injected");
564
+ }
565
+ function j() {
566
+ if (document.querySelector("#gradient-indicator"))
567
+ return;
568
+ const d = document.createElement("div");
569
+ d.id = "gradient-indicator", D(), d.classList.add("gradient-indicator"), document.body.appendChild(d), console.log("Gradient indicator added to the DOM");
570
+ }
571
+ function U() {
572
+ const d = document.querySelector("#gradient-indicator");
573
+ d && (d.remove(), console.log("Gradient indicator removed from the DOM"));
574
+ }
575
+ class V {
576
+ constructor() {
577
+ this.state = "idle", this.subscribers = [];
578
+ }
579
+ getState() {
580
+ return this.state;
581
+ }
582
+ setState(t) {
583
+ this.state = t, this.notifySubscribers(), console.log("State updated:", t), t === "listening" ? j() : U();
584
+ }
585
+ // eslint-disable-next-line no-unused-vars
586
+ subscribe(t) {
587
+ this.subscribers.push(t);
588
+ }
589
+ notifySubscribers() {
590
+ this.subscribers.forEach((t) => t(this.state));
591
+ }
592
+ }
593
+ class G {
594
+ constructor(t) {
595
+ this.container = null, this.chatWindow = null, this.messagesContainer = null, this.input = null, this.isOpen = !1, this.loadingEl = null, this.config = t, this.init();
596
+ }
597
+ init() {
598
+ var e, i;
599
+ if (this.container) return;
600
+ this.injectOverlayStyles();
601
+ const t = document.getElementById("foisit-overlay-container");
602
+ if (t && t instanceof HTMLElement) {
603
+ this.container = t, this.chatWindow = t.querySelector(".foisit-chat"), this.messagesContainer = t.querySelector(".foisit-messages"), this.input = t.querySelector("input.foisit-input"), ((e = this.config.floatingButton) == null ? void 0 : e.visible) !== !1 && !t.querySelector(".foisit-floating-btn") && this.renderFloatingButton(), this.chatWindow || this.renderChatWindow();
604
+ return;
605
+ }
606
+ this.container = document.createElement("div"), this.container.id = "foisit-overlay-container", this.container.className = "foisit-overlay-container", document.body.appendChild(this.container), ((i = this.config.floatingButton) == null ? void 0 : i.visible) !== !1 && this.renderFloatingButton(), this.renderChatWindow();
607
+ }
608
+ renderFloatingButton() {
609
+ var s, n, a, r, c, f;
610
+ const t = document.createElement("button");
611
+ t.innerHTML = ((s = this.config.floatingButton) == null ? void 0 : s.customHtml) || "🎙️";
612
+ const e = ((a = (n = this.config.floatingButton) == null ? void 0 : n.position) == null ? void 0 : a.bottom) || "20px", i = ((c = (r = this.config.floatingButton) == null ? void 0 : r.position) == null ? void 0 : c.right) || "20px";
613
+ t.className = "foisit-floating-btn", t.style.bottom = e, t.style.right = i, t.onclick = () => this.toggle(), t.onmouseenter = () => t.style.transform = "scale(1.05)", t.onmouseleave = () => t.style.transform = "scale(1)", (f = this.container) == null || f.appendChild(t);
614
+ }
615
+ renderChatWindow() {
616
+ var n;
617
+ if (this.chatWindow) return;
618
+ this.chatWindow = document.createElement("div"), this.chatWindow.className = "foisit-chat";
619
+ const t = document.createElement("div");
620
+ t.className = "foisit-header";
621
+ const e = document.createElement("span");
622
+ e.className = "foisit-title", e.textContent = "Foisit";
623
+ const i = document.createElement("button");
624
+ i.type = "button", i.className = "foisit-close", i.setAttribute("aria-label", "Close"), i.innerHTML = "&times;", i.addEventListener("click", () => this.toggle()), t.appendChild(e), t.appendChild(i), this.messagesContainer = document.createElement("div"), this.messagesContainer.className = "foisit-messages";
625
+ const s = document.createElement("div");
626
+ s.className = "foisit-input-area", this.input = document.createElement("input"), this.input.placeholder = this.config.inputPlaceholder || "Type a command...", this.input.className = "foisit-input", this.input.addEventListener("keydown", (a) => {
627
+ var r;
628
+ if (a.key === "Enter" && ((r = this.input) != null && r.value.trim())) {
629
+ const c = this.input.value.trim();
630
+ this.input.value = "", this.onSubmit && this.onSubmit(c);
631
+ }
632
+ }), s.appendChild(this.input), this.chatWindow.appendChild(t), this.chatWindow.appendChild(this.messagesContainer), this.chatWindow.appendChild(s), (n = this.container) == null || n.appendChild(this.chatWindow);
633
+ }
634
+ registerCallbacks(t, e) {
635
+ this.onSubmit = t, this.onClose = e;
636
+ }
637
+ toggle(t, e) {
638
+ t && (this.onSubmit = t), e && (this.onClose = e), this.isOpen = !this.isOpen, this.chatWindow && (this.isOpen ? (this.chatWindow.style.display = "flex", requestAnimationFrame(() => {
639
+ this.chatWindow && (this.chatWindow.style.opacity = "1", this.chatWindow.style.transform = "translateY(0) scale(1)");
640
+ }), setTimeout(() => {
641
+ var i;
642
+ return (i = this.input) == null ? void 0 : i.focus();
643
+ }, 100)) : (this.chatWindow.style.opacity = "0", this.chatWindow.style.transform = "translateY(20px) scale(0.95)", setTimeout(() => {
644
+ this.chatWindow && !this.isOpen && (this.chatWindow.style.display = "none");
645
+ }, 200), this.onClose && this.onClose()));
646
+ }
647
+ addMessage(t, e) {
648
+ if (!this.messagesContainer) return;
649
+ const i = document.createElement("div");
650
+ i.textContent = t, i.className = e === "user" ? "foisit-bubble user" : "foisit-bubble system", this.messagesContainer.appendChild(i), this.scrollToBottom();
651
+ }
652
+ addOptions(t) {
653
+ if (!this.messagesContainer) return;
654
+ const e = document.createElement("div");
655
+ e.className = "foisit-options-container", t.forEach((i) => {
656
+ const s = document.createElement("button");
657
+ s.textContent = i.label, s.className = "foisit-option-chip", s.setAttribute("type", "button"), s.setAttribute("aria-label", i.label);
658
+ const n = () => {
659
+ if (i.commandId) {
660
+ this.onSubmit && this.onSubmit({ commandId: i.commandId });
661
+ return;
662
+ }
663
+ const a = i && typeof i.value == "string" && i.value.trim() ? i.value : i.label;
664
+ this.onSubmit && this.onSubmit(a);
665
+ };
666
+ s.onclick = n, s.onkeydown = (a) => {
667
+ (a.key === "Enter" || a.key === " ") && (a.preventDefault(), n());
668
+ }, e.appendChild(s);
669
+ }), this.messagesContainer.appendChild(e), this.scrollToBottom();
670
+ }
671
+ addForm(t, e, i) {
672
+ if (!this.messagesContainer) return;
673
+ this.addMessage(t, "system");
674
+ const s = document.createElement("form");
675
+ s.className = "foisit-form";
676
+ const n = [], a = (o, p) => {
677
+ const b = document.createElement("div");
678
+ return b.className = "foisit-form-label", b.innerHTML = o + (p ? ' <span class="foisit-req-star">*</span>' : ""), b;
679
+ }, r = () => {
680
+ const o = document.createElement("div");
681
+ return o.className = "foisit-form-error", o.style.display = "none", o;
682
+ };
683
+ (e ?? []).forEach((o) => {
684
+ const p = document.createElement("div");
685
+ p.className = "foisit-form-group";
686
+ const b = o.description || o.name;
687
+ p.appendChild(a(b, o.required));
688
+ let h;
689
+ if (o.type === "select") {
690
+ const l = document.createElement("select");
691
+ l.className = "foisit-form-input";
692
+ const y = document.createElement("option");
693
+ y.value = "", y.textContent = "Select...", l.appendChild(y);
694
+ const x = (m) => {
695
+ (m ?? []).forEach((w) => {
696
+ const g = document.createElement("option");
697
+ g.value = String(w.value ?? w.label ?? ""), g.textContent = String(w.label ?? w.value ?? ""), l.appendChild(g);
698
+ });
699
+ };
700
+ if (Array.isArray(o.options) && o.options.length)
701
+ x(o.options);
702
+ else if (typeof o.getOptions == "function") {
703
+ const m = o.getOptions, w = document.createElement("option");
704
+ w.value = "", w.textContent = "Loading...", l.appendChild(w), Promise.resolve().then(() => m()).then((g) => {
705
+ for (; l.options.length > 1; ) l.remove(1);
706
+ x(g);
707
+ }).catch(() => {
708
+ for (; l.options.length > 1; ) l.remove(1);
709
+ const g = document.createElement("option");
710
+ g.value = "", g.textContent = "Error loading options", l.appendChild(g);
711
+ });
712
+ }
713
+ o.defaultValue != null && (l.value = String(o.defaultValue)), h = l;
714
+ } else if (o.type === "file") {
715
+ const l = o, y = document.createElement("input");
716
+ y.className = "foisit-form-input", y.type = "file", l.accept && Array.isArray(l.accept) && (y.accept = l.accept.join(",")), l.multiple && (y.multiple = !0), l.capture && (l.capture === !0 ? y.setAttribute("capture", "") : y.setAttribute("capture", String(l.capture))), y.addEventListener("change", async () => {
717
+ const x = Array.from(y.files || []), m = C;
718
+ if (m.style.display = "none", m.textContent = "", x.length === 0) return;
719
+ const w = l.maxFiles ?? (l.multiple ? 10 : 1);
720
+ if (x.length > w) {
721
+ m.textContent = `Please select at most ${w} file(s).`, m.style.display = "block";
722
+ return;
723
+ }
724
+ const g = l.maxSizeBytes ?? 1 / 0, u = x.reduce((v, E) => v + E.size, 0);
725
+ if (x.some((v) => v.size > g)) {
726
+ m.textContent = `One or more files exceed the maximum size of ${Math.round(g / 1024)} KB.`, m.style.display = "block";
727
+ return;
728
+ }
729
+ const S = l.maxTotalBytes ?? 1 / 0;
730
+ if (u > S) {
731
+ m.textContent = `Total selected files exceed the maximum of ${Math.round(S / 1024)} KB.`, m.style.display = "block";
732
+ return;
733
+ }
734
+ if (l.accept && Array.isArray(l.accept)) {
735
+ const v = l.accept;
736
+ if (!x.every((A) => A.type ? v.some((L) => L.startsWith(".") ? A.name.toLowerCase().endsWith(L.toLowerCase()) : A.type === L || A.type.startsWith(L.split("/")[0] + "/")) : !0)) {
737
+ m.textContent = "One or more files have an unsupported type.", m.style.display = "block";
738
+ return;
739
+ }
740
+ }
741
+ }), h = y;
742
+ } else {
743
+ const l = document.createElement("input");
744
+ l.className = "foisit-form-input", o.type === "string" && (l.placeholder = o.placeholder || "Type here..."), o.type === "number" ? (l.type = "number", typeof o.min == "number" && (l.min = String(o.min)), typeof o.max == "number" && (l.max = String(o.max)), typeof o.step == "number" && (l.step = String(o.step)), o.defaultValue != null && (l.value = String(o.defaultValue))) : o.type === "date" ? (l.type = "date", typeof o.min == "string" && (l.min = o.min), typeof o.max == "string" && (l.max = o.max), o.defaultValue != null && (l.value = String(o.defaultValue))) : (l.type = "text", o.defaultValue != null && (l.value = String(o.defaultValue))), h = l;
745
+ }
746
+ const C = r();
747
+ p.appendChild(h), p.appendChild(C), n.push({
748
+ name: o.name,
749
+ type: o.type,
750
+ el: h,
751
+ required: o.required
752
+ }), s.appendChild(p);
753
+ });
754
+ const c = document.createElement("div");
755
+ c.className = "foisit-form-actions";
756
+ const f = document.createElement("button");
757
+ f.type = "submit", f.textContent = "Submit", f.className = "foisit-option-chip", f.style.fontWeight = "600", c.appendChild(f), s.appendChild(c), s.onsubmit = async (o) => {
758
+ o.preventDefault();
759
+ const p = {};
760
+ let b = !1;
761
+ s.querySelectorAll(".foisit-form-error").forEach((h) => {
762
+ h.style.display = "none", h.textContent = "";
763
+ }), s.querySelectorAll(".foisit-form-input").forEach((h) => {
764
+ h.classList.remove("foisit-error-border");
765
+ });
766
+ for (const h of n) {
767
+ if (h.type === "file") {
768
+ const x = h.el.parentElement, m = x == null ? void 0 : x.querySelector(".foisit-form-error"), w = h.el, g = Array.from(w.files || []);
769
+ if (h.required && g.length === 0) {
770
+ b = !0, w.classList.add("foisit-error-border"), m && (m.textContent = "This file is required", m.style.display = "block");
771
+ continue;
772
+ }
773
+ if (g.length === 0) continue;
774
+ const u = (e ?? []).find((v) => v.name === h.name), S = (u == null ? void 0 : u.delivery) ?? "file";
775
+ if (u != null && u.maxWidth || u != null && u.maxHeight)
776
+ try {
777
+ const v = await this.getImageDimensions(g[0]);
778
+ if (u.maxWidth && v.width > u.maxWidth) {
779
+ b = !0, m && (m.textContent = `Image width must be ≤ ${u.maxWidth}px`, m.style.display = "block");
780
+ continue;
781
+ }
782
+ if (u.maxHeight && v.height > u.maxHeight) {
783
+ b = !0, m && (m.textContent = `Image height must be ≤ ${u.maxHeight}px`, m.style.display = "block");
784
+ continue;
785
+ }
786
+ } catch {
787
+ }
788
+ if (u != null && u.maxDurationSec)
789
+ try {
790
+ const v = await this.getMediaDuration(g[0]);
791
+ if (v && v > u.maxDurationSec) {
792
+ b = !0, m && (m.textContent = `Media duration must be ≤ ${u.maxDurationSec}s`, m.style.display = "block");
793
+ continue;
794
+ }
795
+ } catch {
796
+ }
797
+ if (S === "file")
798
+ p[h.name] = u != null && u.multiple ? g : g[0];
799
+ else if (S === "base64")
800
+ try {
801
+ const v = await Promise.all(g.map((E) => this.readFileAsDataURL(E)));
802
+ p[h.name] = u != null && u.multiple ? v : v[0];
803
+ } catch {
804
+ b = !0, m && (m.textContent = "Failed to encode file(s) to base64.", m.style.display = "block");
805
+ continue;
806
+ }
807
+ continue;
808
+ }
809
+ const C = (h.el.value ?? "").toString().trim(), l = h.el.parentElement, y = l == null ? void 0 : l.querySelector(".foisit-form-error");
810
+ if (h.required && (C == null || C === "")) {
811
+ b = !0, h.el.classList.add("foisit-error-border"), y && (y.textContent = "This field is required", y.style.display = "block");
812
+ continue;
813
+ }
814
+ if (C !== "")
815
+ if (h.type === "number") {
816
+ const x = Number(C);
817
+ Number.isNaN(x) || (p[h.name] = x);
818
+ } else
819
+ p[h.name] = C;
820
+ }
821
+ if (b) {
822
+ s.classList.add("foisit-shake"), setTimeout(() => s.classList.remove("foisit-shake"), 400);
823
+ return;
824
+ }
825
+ f.disabled = !0, f.style.opacity = "0.6", n.forEach((h) => {
826
+ h.el.disabled = !0;
827
+ }), i(p);
828
+ }, this.messagesContainer.appendChild(s), this.scrollToBottom();
829
+ }
830
+ showLoading() {
831
+ if (this.messagesContainer && !this.loadingEl) {
832
+ this.loadingEl = document.createElement("div"), this.loadingEl.className = "foisit-loading-dots foisit-bubble system";
833
+ for (let t = 0; t < 3; t++) {
834
+ const e = document.createElement("div");
835
+ e.className = "foisit-dot", e.style.animation = `foisitPulse 1.4s infinite ease-in-out ${t * 0.2}s`, this.loadingEl.appendChild(e);
836
+ }
837
+ this.messagesContainer.appendChild(this.loadingEl), this.scrollToBottom();
838
+ }
839
+ }
840
+ hideLoading() {
841
+ var t;
842
+ (t = this.loadingEl) == null || t.remove(), this.loadingEl = null;
843
+ }
844
+ scrollToBottom() {
845
+ this.messagesContainer && (this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight);
846
+ }
847
+ destroy() {
848
+ var t;
849
+ (t = this.container) == null || t.remove(), this.container = null, this.chatWindow = null, this.messagesContainer = null, this.input = null, this.isOpen = !1;
850
+ }
851
+ readFileAsDataURL(t) {
852
+ return new Promise((e, i) => {
853
+ const s = new FileReader();
854
+ s.onerror = () => i(new Error("Failed to read file")), s.onload = () => e(String(s.result)), s.readAsDataURL(t);
855
+ });
856
+ }
857
+ getImageDimensions(t) {
858
+ return new Promise((e) => {
859
+ try {
860
+ const i = URL.createObjectURL(t), s = new Image();
861
+ s.onload = () => {
862
+ const n = { width: s.naturalWidth || s.width, height: s.naturalHeight || s.height };
863
+ URL.revokeObjectURL(i), e(n);
864
+ }, s.onerror = () => {
865
+ URL.revokeObjectURL(i), e({ width: 0, height: 0 });
866
+ }, s.src = i;
867
+ } catch {
868
+ e({ width: 0, height: 0 });
869
+ }
870
+ });
871
+ }
872
+ getMediaDuration(t) {
873
+ return new Promise((e) => {
874
+ try {
875
+ const i = URL.createObjectURL(t), s = t.type.startsWith("audio") ? document.createElement("audio") : document.createElement("video");
876
+ let n = !1;
877
+ const a = setTimeout(() => {
878
+ n || (n = !0, URL.revokeObjectURL(i), e(0));
879
+ }, 5e3);
880
+ s.preload = "metadata", s.onloadedmetadata = () => {
881
+ if (n) return;
882
+ n = !0, clearTimeout(a);
883
+ const c = s.duration || 0;
884
+ URL.revokeObjectURL(i), e(c);
885
+ }, s.onerror = () => {
886
+ n || (n = !0, clearTimeout(a), URL.revokeObjectURL(i), e(0));
887
+ }, s.src = i;
888
+ } catch {
889
+ e(0);
890
+ }
891
+ });
892
+ }
893
+ injectOverlayStyles() {
894
+ if (document.getElementById("foisit-overlay-styles")) return;
895
+ const t = document.createElement("style");
896
+ t.id = "foisit-overlay-styles", t.textContent = `
897
+ :root {
898
+ /* LIGHT MODE (Default) - Smoother gradient */
899
+ /* Changed: Softer, right-focused radial highlight to avoid a heavy white bottom */
900
+ --foisit-bg: radial-gradient(ellipse at 75% 30%, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.03));
901
+ --foisit-border: 1px solid rgba(255, 255, 255, 0.25);
902
+ --foisit-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
903
+ --foisit-text: #333;
904
+
905
+ /* Input */
906
+ --foisit-input-color: #333;
907
+ --foisit-input-placeholder: rgba(60, 60, 67, 0.6);
908
+
909
+ /* Bubbles */
910
+ --foisit-bubble-user-bg: rgba(0, 0, 0, 0.04);
911
+ --foisit-bubble-user-text: #333;
912
+
913
+ --foisit-bubble-sys-bg: rgba(255, 255, 255, 0.45);
914
+ --foisit-bubble-sys-text: #333;
915
+
916
+ /* Form Colors */
917
+ --foisit-req-star: #ef4444; /* Red asterisk */
918
+ --foisit-error-text: #dc2626;
919
+ --foisit-error-border: #fca5a5;
920
+ }
921
+
922
+ @media (prefers-color-scheme: dark) {
923
+ :root {
924
+ /* DARK MODE */
925
+ --foisit-bg: linear-gradient(135deg, rgba(40, 40, 40, 0.65), rgba(40, 40, 40, 0.25));
926
+ --foisit-border: 1px solid rgba(255, 255, 255, 0.1);
927
+ --foisit-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
928
+ --foisit-text: #fff;
929
+
930
+ /* Input */
931
+ --foisit-input-color: white;
932
+ --foisit-input-placeholder: rgba(235, 235, 245, 0.5);
933
+
934
+ /* Bubbles */
935
+ --foisit-bubble-user-bg: rgba(255, 255, 255, 0.1);
936
+ --foisit-bubble-user-text: white;
937
+
938
+ --foisit-bubble-sys-bg: rgba(255, 255, 255, 0.05);
939
+ --foisit-bubble-sys-text: rgba(255, 255, 255, 0.9);
940
+
941
+ /* Form Colors */
942
+ --foisit-req-star: #f87171;
943
+ --foisit-error-text: #fca5a5;
944
+ --foisit-error-border: #f87171;
945
+ }
946
+ }
947
+
948
+ @keyframes foisitPulse {
949
+ 0%, 100% { transform: scale(0.8); opacity: 0.5; }
950
+ 50% { transform: scale(1.2); opacity: 1; }
951
+ }
952
+
953
+ @keyframes foisitShake {
954
+ 0%, 100% { transform: translateX(0); }
955
+ 25% { transform: translateX(-4px); }
956
+ 75% { transform: translateX(4px); }
957
+ }
958
+ .foisit-shake { animation: foisitShake 0.4s ease-in-out; }
959
+
960
+ /* Container */
961
+ .foisit-overlay-container {
962
+ position: fixed;
963
+ inset: 0;
964
+ z-index: 2147483647;
965
+ pointer-events: none;
966
+ display: flex;
967
+ flex-direction: column;
968
+ justify-content: flex-end;
969
+ align-items: flex-end;
970
+ padding: 20px;
971
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
972
+ }
973
+
974
+ .foisit-overlay-container * {
975
+ box-sizing: border-box;
976
+ }
977
+
978
+ /* Chat Window - Dynamic Height */
979
+ .foisit-chat {
980
+ position: absolute;
981
+ top: 20px;
982
+ right: 20px;
983
+ width: min(420px, 92vw);
984
+
985
+ /* FIX: Auto height to prevent empty space */
986
+ height: auto;
987
+ min-height: 120px;
988
+ max-height: 80vh;
989
+
990
+ background: var(--foisit-bg);
991
+ border: var(--foisit-border);
992
+ box-shadow: var(--foisit-shadow);
993
+
994
+ backdrop-filter: blur(20px);
995
+ -webkit-backdrop-filter: blur(20px);
996
+
997
+ border-radius: 18px;
998
+ display: none;
999
+ flex-direction: column;
1000
+ overflow: hidden;
1001
+ pointer-events: auto;
1002
+ transform-origin: top right;
1003
+ transition: opacity 0.2s, transform 0.2s cubic-bezier(0.2, 0.9, 0.2, 1);
1004
+ }
1005
+
1006
+ .foisit-header {
1007
+ display: flex;
1008
+ align-items: center;
1009
+ justify-content: space-between;
1010
+ padding: 12px 16px;
1011
+ font-weight: 600;
1012
+ font-size: 14px;
1013
+ color: var(--foisit-text);
1014
+ border-bottom: 1px solid rgba(127,127,127,0.08); /* Subtle separator */
1015
+ }
1016
+
1017
+ .foisit-close {
1018
+ background: transparent;
1019
+ border: none;
1020
+ color: var(--foisit-text);
1021
+ opacity: 0.5;
1022
+ font-size: 24px;
1023
+ line-height: 1;
1024
+ cursor: pointer;
1025
+ padding: 0;
1026
+ width: 28px;
1027
+ height: 28px;
1028
+ display: flex;
1029
+ align-items: center;
1030
+ justify-content: center;
1031
+ transition: opacity 0.2s;
1032
+ }
1033
+ .foisit-close:hover { opacity: 1; }
1034
+
1035
+ /* Message Area */
1036
+ .foisit-messages {
1037
+ flex: 1;
1038
+ overflow-y: auto;
1039
+ padding: 16px;
1040
+ display: flex;
1041
+ flex-direction: column;
1042
+ gap: 10px;
1043
+ /* Ensure it doesn't get too tall initially */
1044
+ min-height: 60px;
1045
+ }
1046
+
1047
+ /* Make sure empty state isn't huge */
1048
+ .foisit-messages:empty {
1049
+ display: none;
1050
+ }
1051
+
1052
+ /* Only show messages container if it has children */
1053
+ .foisit-messages:not(:empty) {
1054
+ display: flex;
1055
+ }
1056
+
1057
+ /* Bubbles */
1058
+ .foisit-bubble {
1059
+ max-width: 90%;
1060
+ padding: 8px 14px;
1061
+ border-radius: 14px;
1062
+ font-size: 14px;
1063
+ line-height: 1.4;
1064
+ word-wrap: break-word;
1065
+ }
1066
+
1067
+ .foisit-bubble.user {
1068
+ align-self: flex-end;
1069
+ background: var(--foisit-bubble-user-bg);
1070
+ color: var(--foisit-bubble-user-text);
1071
+ border-bottom-right-radius: 4px;
1072
+ }
1073
+
1074
+ .foisit-bubble.system {
1075
+ align-self: flex-start;
1076
+ background: var(--foisit-bubble-sys-bg);
1077
+ color: var(--foisit-bubble-sys-text);
1078
+ border-bottom-left-radius: 4px;
1079
+ border: 1px solid rgba(255,255,255,0.1);
1080
+ }
1081
+
1082
+ /* Input Area */
1083
+ .foisit-input-area {
1084
+ padding: 0;
1085
+ width: 100%;
1086
+ border-top: 1px solid rgba(127,127,127,0.08);
1087
+ }
1088
+
1089
+ .foisit-input {
1090
+ width: 100%;
1091
+ background: transparent;
1092
+ border: none;
1093
+ font-size: 16px;
1094
+ color: var(--foisit-input-color);
1095
+ padding: 16px 20px;
1096
+ outline: none;
1097
+ text-align: left;
1098
+ }
1099
+
1100
+ .foisit-input::placeholder {
1101
+ color: var(--foisit-input-placeholder);
1102
+ font-weight: 400;
1103
+ }
1104
+
1105
+ /* Options & Buttons */
1106
+ .foisit-options-container {
1107
+ display: flex;
1108
+ flex-wrap: wrap;
1109
+ gap: 8px;
1110
+ margin-left: 2px;
1111
+ margin-top: 4px;
1112
+ }
1113
+
1114
+ .foisit-option-chip {
1115
+ padding: 6px 14px;
1116
+ background: var(--foisit-bubble-sys-bg);
1117
+ border: 1px solid rgba(127,127,127,0.1);
1118
+ border-radius: 20px;
1119
+ font-size: 13px;
1120
+ color: var(--foisit-text);
1121
+ cursor: pointer;
1122
+ transition: all 0.2s;
1123
+ font-weight: 500;
1124
+ }
1125
+ .foisit-option-chip:hover {
1126
+ background: rgba(127,127,127,0.15);
1127
+ }
1128
+
1129
+ /* Form Styling */
1130
+ .foisit-form {
1131
+ background: var(--foisit-bubble-sys-bg);
1132
+ padding: 16px;
1133
+ border-radius: 14px;
1134
+ display: flex;
1135
+ flex-direction: column;
1136
+ gap: 12px;
1137
+ width: 100%;
1138
+ border: 1px solid rgba(127,127,127,0.1);
1139
+ }
1140
+
1141
+ .foisit-form-label {
1142
+ font-size: 12px;
1143
+ font-weight: 600;
1144
+ color: var(--foisit-text);
1145
+ opacity: 0.9;
1146
+ margin-bottom: 2px;
1147
+ }
1148
+
1149
+ .foisit-req-star {
1150
+ color: var(--foisit-req-star);
1151
+ font-weight: bold;
1152
+ }
1153
+
1154
+ .foisit-form-input {
1155
+ width: 100%;
1156
+ padding: 10px;
1157
+ border-radius: 8px;
1158
+ border: 1px solid rgba(127,127,127,0.2);
1159
+ background: rgba(255,255,255,0.05); /* Very subtle fill */
1160
+ color: var(--foisit-text);
1161
+ font-size: 14px;
1162
+ outline: none;
1163
+ transition: border 0.2s;
1164
+ }
1165
+ .foisit-form-input:focus {
1166
+ border-color: var(--foisit-text);
1167
+ background: rgba(255,255,255,0.1);
1168
+ }
1169
+
1170
+ .foisit-error-border {
1171
+ border-color: var(--foisit-error-border) !important;
1172
+ }
1173
+
1174
+ .foisit-form-error {
1175
+ font-size: 11px;
1176
+ color: var(--foisit-error-text);
1177
+ margin-top: 4px;
1178
+ }
1179
+
1180
+ /* Loading */
1181
+ .foisit-loading-dots {
1182
+ display: inline-flex;
1183
+ gap: 4px;
1184
+ padding: 10px 14px;
1185
+ align-self: flex-start;
1186
+ }
1187
+ .foisit-dot {
1188
+ width: 6px;
1189
+ height: 6px;
1190
+ border-radius: 50%;
1191
+ background: var(--foisit-text);
1192
+ opacity: 0.4;
1193
+ }
1194
+
1195
+ /* Floating Button */
1196
+ .foisit-floating-btn {
1197
+ position: absolute;
1198
+ width: 56px;
1199
+ height: 56px;
1200
+ border-radius: 50%;
1201
+ border: 1px solid rgba(255,255,255,0.2);
1202
+ background: var(--foisit-bg);
1203
+ color: var(--foisit-text);
1204
+ backdrop-filter: blur(10px);
1205
+ -webkit-backdrop-filter: blur(10px);
1206
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
1207
+ cursor: pointer;
1208
+ pointer-events: auto;
1209
+ display: flex;
1210
+ align-items: center;
1211
+ justify-content: center;
1212
+ font-size: 24px;
1213
+ z-index: 100000;
1214
+ transition: transform 0.2s;
1215
+ }
1216
+ .foisit-floating-btn:hover { transform: scale(1.05); }
1217
+ `, document.head.appendChild(t);
1218
+ }
1219
+ }
1220
+ class Y {
1221
+ constructor(t) {
1222
+ this.config = t, this.isActivated = !1, this.lastProcessedInput = "", this.processingLock = !1, this.defaultIntroMessage = "How can I help you?", this.commandHandler = new B({
1223
+ enableSmartIntent: this.config.enableSmartIntent !== !1,
1224
+ intentEndpoint: this.config.intentEndpoint
1225
+ }), this.fallbackHandler = new O(), this.voiceProcessor = new z(), this.textToSpeech = new H(), this.stateManager = new V(), this.gestureHandler = new $(), this.overlayManager = new G({
1226
+ floatingButton: this.config.floatingButton,
1227
+ inputPlaceholder: this.config.inputPlaceholder
1228
+ }), this.config.commands.forEach((e) => this.commandHandler.addCommand(e)), this.config.fallbackResponse && this.fallbackHandler.setFallbackMessage(this.config.fallbackResponse), this.gestureHandler.setupDoubleTapListener(() => this.toggle()), this.overlayManager.registerCallbacks(
1229
+ async (e) => {
1230
+ if (typeof e == "string") {
1231
+ this.overlayManager.addMessage(e, "user"), await this.handleCommand(e);
1232
+ return;
1233
+ }
1234
+ if (e && typeof e == "object") {
1235
+ const i = e, s = i.label ?? i.commandId ?? "Selection";
1236
+ this.overlayManager.addMessage(String(s), "user"), this.overlayManager.showLoading();
1237
+ const n = await this.commandHandler.executeCommand(i);
1238
+ this.overlayManager.hideLoading(), this.processResponse(n);
1239
+ }
1240
+ },
1241
+ () => console.log("AssistantService: Overlay closed.")
1242
+ );
1243
+ }
1244
+ /** Start listening for activation and commands */
1245
+ startListening() {
1246
+ console.log("AssistantService: Voice is disabled; startListening() is a no-op.");
1247
+ }
1248
+ /** Stop listening */
1249
+ stopListening() {
1250
+ console.log("AssistantService: Voice is disabled; stopListening() is a no-op."), this.isActivated = !1;
1251
+ }
1252
+ /**
1253
+ * Reset activation state so the next activation flow can occur.
1254
+ * This is mainly used by the wrapper UI (e.g. AssistantActivator).
1255
+ */
1256
+ reactivate() {
1257
+ console.log("AssistantService: Reactivating assistant..."), this.isActivated = !1;
1258
+ try {
1259
+ this.startListening();
1260
+ } catch {
1261
+ }
1262
+ }
1263
+ /** Process activation command */
1264
+ async processActivation(t) {
1265
+ var i;
1266
+ const e = (i = this.config.activationCommand) == null ? void 0 : i.toLowerCase();
1267
+ e && (t === e ? (console.log("AssistantService: Activation matched."), this.isActivated = !0, this.textToSpeech.speak(
1268
+ this.config.introMessage || this.defaultIntroMessage
1269
+ )) : console.log("AssistantService: Activation command not recognized."));
1270
+ }
1271
+ /** Handle recognized commands */
1272
+ async handleCommand(t) {
1273
+ this.overlayManager.showLoading();
1274
+ let e;
1275
+ try {
1276
+ e = await this.commandHandler.executeCommand(t);
1277
+ } finally {
1278
+ this.overlayManager.hideLoading();
1279
+ }
1280
+ if (e.type === "form" && e.fields) {
1281
+ this.overlayManager.addForm(
1282
+ e.message,
1283
+ e.fields,
1284
+ async (i) => {
1285
+ this.overlayManager.showLoading();
1286
+ let s;
1287
+ try {
1288
+ s = await this.commandHandler.executeCommand(i);
1289
+ } finally {
1290
+ this.overlayManager.hideLoading();
1291
+ }
1292
+ this.processResponse(s);
1293
+ }
1294
+ );
1295
+ return;
1296
+ }
1297
+ if (e.type === "error") {
1298
+ this.fallbackHandler.handleFallback(t), this.overlayManager.addMessage(this.fallbackHandler.getFallbackMessage(), "system");
1299
+ return;
1300
+ }
1301
+ e.message ? this.overlayManager.addMessage(e.message, "system") : (e.type === "ambiguous" || e.type === "confirm") && e.options && this.overlayManager.addOptions(e.options);
1302
+ }
1303
+ /**
1304
+ * Cleanup resources
1305
+ */
1306
+ destroy() {
1307
+ this.voiceProcessor.stopListening(), this.gestureHandler.destroy(), this.overlayManager.destroy();
1308
+ }
1309
+ /** Unified response processing */
1310
+ processResponse(t) {
1311
+ if (t) {
1312
+ if (t.type === "form" && t.fields) {
1313
+ this.overlayManager.addForm(
1314
+ t.message,
1315
+ t.fields,
1316
+ async (e) => {
1317
+ this.overlayManager.showLoading();
1318
+ let i;
1319
+ try {
1320
+ i = await this.commandHandler.executeCommand(e);
1321
+ } finally {
1322
+ this.overlayManager.hideLoading();
1323
+ }
1324
+ this.processResponse(i);
1325
+ }
1326
+ );
1327
+ return;
1328
+ }
1329
+ if ((t.type === "ambiguous" || t.type === "confirm") && t.options) {
1330
+ t.message && this.overlayManager.addMessage(t.message, "system"), this.overlayManager.addOptions(t.options);
1331
+ return;
1332
+ }
1333
+ t.message && this.overlayManager.addMessage(t.message, "system");
1334
+ }
1335
+ }
1336
+ /** Add a command dynamically (supports string or rich object) */
1337
+ addCommand(t, e) {
1338
+ console.log(typeof t == "string" ? `AssistantService: Adding command "${t}".` : `AssistantService: Adding rich command "${t.command}".`), this.commandHandler.addCommand(t, e);
1339
+ }
1340
+ /** Remove a command dynamically */
1341
+ removeCommand(t) {
1342
+ console.log(`AssistantService: Removing command "${t}".`), this.commandHandler.removeCommand(t);
1343
+ }
1344
+ /** Get all registered commands */
1345
+ getCommands() {
1346
+ return this.commandHandler.getCommands();
1347
+ }
1348
+ /** Toggle the assistant overlay */
1349
+ toggle(t, e) {
1350
+ console.log("AssistantService: Toggling overlay..."), this.overlayManager.toggle(
1351
+ async (i) => {
1352
+ if (typeof i == "string") {
1353
+ this.overlayManager.addMessage(i, "user"), t && t(i), await this.handleCommand(i);
1354
+ return;
1355
+ }
1356
+ if (i && typeof i == "object") {
1357
+ const s = i, n = s.label ?? s.commandId ?? "Selection";
1358
+ this.overlayManager.addMessage(String(n), "user"), this.overlayManager.showLoading();
1359
+ let a;
1360
+ try {
1361
+ a = await this.commandHandler.executeCommand(s);
1362
+ } finally {
1363
+ this.overlayManager.hideLoading();
1364
+ }
1365
+ this.processResponse(a);
1366
+ }
1367
+ },
1368
+ () => {
1369
+ console.log("AssistantService: Overlay closed."), e && e();
1370
+ }
1371
+ );
1372
+ }
1373
+ }
1374
+ const q = F(null);
1375
+ let I = null;
1376
+ const Z = ({ config: d, children: t }) => {
1377
+ const [e, i] = R(null), [s, n] = R(!1);
1378
+ return P(() => {
1379
+ I ? console.warn(
1380
+ "Multiple AssistantProvider instances detected. Reusing global AssistantService."
1381
+ ) : (console.log("Initializing global AssistantService..."), I = new Y(d));
1382
+ const a = I;
1383
+ return i(a), n(!0), () => {
1384
+ var r;
1385
+ console.log("Cleaning up AssistantService..."), (r = a.destroy) == null || r.call(a), I = null;
1386
+ };
1387
+ }, [d]), !s || !e ? /* @__PURE__ */ k("div", { children: "Loading Assistant..." }) : /* @__PURE__ */ k(q.Provider, { value: e, children: t });
1388
+ }, K = () => {
1389
+ const d = N(q);
1390
+ if (console.log("assistant", d), !d)
1391
+ throw new Error("useAssistant must be used within an AssistantProvider");
1392
+ return d;
1393
+ }, Q = ({
1394
+ label: d = "Activate Assistant",
1395
+ onActivate: t
1396
+ }) => {
1397
+ const e = K();
1398
+ return /* @__PURE__ */ k("button", { onClick: () => {
1399
+ t && t(), e.reactivate();
1400
+ }, className: "assistant-activator", children: d });
1401
+ }, tt = (d) => {
1402
+ const [t, e] = R(d.getState());
1403
+ return P(() => {
1404
+ const i = (s) => {
1405
+ e(s);
1406
+ };
1407
+ return d.subscribe(i), () => {
1408
+ d.subscribe(() => {
1409
+ });
1410
+ };
1411
+ }, [d]), t;
1412
+ };
1413
+ export {
1414
+ Q as AssistantActivator,
1415
+ q as AssistantContext,
1416
+ Z as AssistantProvider,
1417
+ Y as AssistantService,
1418
+ J as ReactWrapper,
1419
+ K as useAssistant,
1420
+ tt as useAssistantState
1421
+ };