@foisit/react-wrapper 2.4.2 → 2.4.5

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 CHANGED
@@ -1,10 +1,10 @@
1
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 = {};
2
+ import { createContext as F, useState as I, useEffect as $, useContext as N } from "react";
3
+ const O = {};
4
4
  function Q() {
5
- return /* @__PURE__ */ k("div", { className: W.container, children: /* @__PURE__ */ k("h1", { children: "Welcome to ReactWrapper!" }) });
5
+ return /* @__PURE__ */ k("div", { className: O.container, children: /* @__PURE__ */ k("h1", { children: "Welcome to ReactWrapper!" }) });
6
6
  }
7
- class M {
7
+ class T {
8
8
  constructor(t) {
9
9
  this.endpoint = t || "https://foisit-ninja.netlify.app/.netlify/functions/intent";
10
10
  }
@@ -12,11 +12,11 @@ class M {
12
12
  try {
13
13
  const s = {
14
14
  userInput: t,
15
- commands: e.map((r) => ({
16
- id: r.id,
17
- command: r.command,
18
- description: r.description,
19
- parameters: r.parameters
15
+ commands: e.map((o) => ({
16
+ id: o.id,
17
+ command: o.command,
18
+ description: o.description,
19
+ parameters: o.parameters
20
20
  // Send param schemas to AI
21
21
  })),
22
22
  context: i
@@ -35,13 +35,13 @@ class M {
35
35
  }
36
36
  }
37
37
  }
38
- class B {
38
+ class z {
39
39
  constructor(t = !0) {
40
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());
41
+ this.enableSmartIntent = t, this.enableSmartIntent && (this.openAIService = new T());
42
42
  return;
43
43
  }
44
- this.enableSmartIntent = t.enableSmartIntent ?? !0, this.enableSmartIntent && (this.openAIService = new M(t.intentEndpoint));
44
+ this.enableSmartIntent = t.enableSmartIntent ?? !0, this.enableSmartIntent && (this.openAIService = new T(t.intentEndpoint));
45
45
  }
46
46
  /** Add a new command (string or object) */
47
47
  addCommand(t, e) {
@@ -66,14 +66,14 @@ class B {
66
66
  async executeCommand(t) {
67
67
  if (typeof t == "object" && t !== null) {
68
68
  if (this.isStructured(t)) {
69
- const c = String(t.commandId), f = t.params ?? {}, a = this.getCommandById(c);
70
- if (!a) return { message: "That command is not available.", type: "error" };
71
- const p = this.sanitizeParamsForCommand(a, f), m = (a.parameters ?? []).filter((C) => C.required).filter((C) => p[C.name] == null || p[C.name] === "");
72
- return m.length > 0 ? (this.context = { commandId: this.getCommandIdentifier(a), params: p }, {
73
- message: `Please provide the required details for "${a.command}".`,
69
+ const c = String(t.commandId), f = t.params ?? {}, r = this.getCommandById(c);
70
+ if (!r) return { message: "That command is not available.", type: "error" };
71
+ const p = this.sanitizeParamsForCommand(r, f), m = (r.parameters ?? []).filter((C) => C.required).filter((C) => p[C.name] == null || p[C.name] === "");
72
+ return m.length > 0 ? (this.context = { commandId: this.getCommandIdentifier(r), params: p }, {
73
+ message: `Please provide the required details for "${r.command}".`,
74
74
  type: "form",
75
75
  fields: m
76
- }) : a.critical ? (this.pendingConfirmation = { commandId: this.getCommandIdentifier(a), params: p }, this.buildConfirmResponse(a)) : this.safeRunAction(a, p);
76
+ }) : r.critical ? (this.pendingConfirmation = { commandId: this.getCommandIdentifier(r), params: p }, this.buildConfirmResponse(r)) : this.safeRunAction(r, p);
77
77
  }
78
78
  if (!this.context)
79
79
  return { message: "Session expired or invalid context.", type: "error" };
@@ -82,28 +82,28 @@ class B {
82
82
  return this.context = null, { message: "Session expired or invalid context.", type: "error" };
83
83
  if (Array.isArray(t))
84
84
  return { message: "Invalid form payload.", type: "error" };
85
- const o = {
85
+ const a = {
86
86
  ...this.context.params,
87
87
  ...t
88
88
  };
89
89
  if (n.critical)
90
90
  return this.context = null, this.pendingConfirmation = {
91
91
  commandId: this.getCommandIdentifier(n),
92
- params: o
92
+ params: a
93
93
  }, this.buildConfirmResponse(n);
94
- const r = await this.safeRunAction(n, o);
95
- return this.context = null, this.normalizeResponse(r);
94
+ const o = await this.safeRunAction(n, a);
95
+ return this.context = null, this.normalizeResponse(o);
96
96
  }
97
97
  const e = t.trim().toLowerCase();
98
98
  if (this.pendingConfirmation) {
99
99
  const n = e;
100
100
  if (["yes", "y", "confirm", "ok", "okay"].includes(n)) {
101
- const { commandId: o, params: r } = this.pendingConfirmation;
101
+ const { commandId: a, params: o } = this.pendingConfirmation;
102
102
  this.pendingConfirmation = null;
103
- const c = this.getCommandById(o);
104
- return c ? this.safeRunAction(c, r) : { message: "That action is no longer available.", type: "error" };
103
+ const c = this.getCommandById(a);
104
+ return c ? this.safeRunAction(c, o) : { message: "That action is no longer available.", type: "error" };
105
105
  }
106
- return ["no", "n", "cancel", "stop"].includes(n) ? (this.pendingConfirmation = null, { message: "Cancelled.", type: "success" }) : {
106
+ return ["no", "n", "cancel", "stop"].includes(n) ? (this.pendingConfirmation = null, { message: "Cancelled.", type: "success" }) : {
107
107
  message: "Please confirm: Yes or No.",
108
108
  type: "confirm",
109
109
  options: [
@@ -114,22 +114,25 @@ class B {
114
114
  }
115
115
  const i = this.commands.get(e);
116
116
  if (i) {
117
- const n = i, o = (n.parameters ?? []).filter((r) => r.required);
118
- return o.length > 0 ? (this.context = { commandId: this.getCommandIdentifier(n), params: {} }, {
117
+ const n = i;
118
+ if (n.macro)
119
+ return this.safeRunAction(n, {});
120
+ const a = (n.parameters ?? []).filter((o) => o.required);
121
+ return a.length > 0 ? (this.context = { commandId: this.getCommandIdentifier(n), params: {} }, {
119
122
  message: `Please provide the required details for "${n.command}".`,
120
123
  type: "form",
121
- fields: o
124
+ fields: a
122
125
  }) : n.critical ? (this.pendingConfirmation = { commandId: this.getCommandIdentifier(n), params: {} }, this.buildConfirmResponse(n)) : this.safeRunAction(n, {});
123
126
  }
124
127
  const s = await this.tryDeterministicMatch(e);
125
128
  if (s) return s;
126
129
  if (this.enableSmartIntent && this.openAIService) {
127
- const n = await this.getCommandsForAI(), o = await this.openAIService.determineIntent(
130
+ const n = await this.getCommandsForAI(), a = await this.openAIService.determineIntent(
128
131
  e,
129
132
  n,
130
133
  this.context
131
134
  );
132
- return this.handleAIResult(o);
135
+ return this.handleAIResult(a);
133
136
  }
134
137
  return this.enableSmartIntent ? this.listAllCommands() : { message: "I'm not sure what you mean.", type: "error" };
135
138
  }
@@ -138,10 +141,12 @@ class B {
138
141
  const e = this.getCommandById(t.match);
139
142
  if (!e)
140
143
  return { message: "I'm not sure what you mean.", type: "error" };
141
- const i = t.params ?? {}, s = this.sanitizeParamsForCommand(e, i), n = e.allowAiParamExtraction === !1 ? {} : s, r = (e.parameters ?? []).filter((f) => f.required).filter((f) => n[f.name] == null || n[f.name] === "");
142
- if (t.incomplete || r.length > 0) {
143
- if (this.context = { commandId: this.getCommandIdentifier(e), params: n }, !(e.collectRequiredViaForm !== !1) && this.shouldAskSingleQuestion(r)) {
144
- const p = r.map((g) => g.name).join(" and ");
144
+ if (e.macro)
145
+ return this.safeRunAction(e, {});
146
+ const i = t.params ?? {}, s = this.sanitizeParamsForCommand(e, i), n = e.allowAiParamExtraction === !1 ? {} : s, o = (e.parameters ?? []).filter((f) => f.required).filter((f) => n[f.name] == null || n[f.name] === "");
147
+ if (t.incomplete || o.length > 0) {
148
+ if (this.context = { commandId: this.getCommandIdentifier(e), params: n }, !(e.collectRequiredViaForm !== !1) && this.shouldAskSingleQuestion(o)) {
149
+ const p = o.map((g) => g.name).join(" and ");
145
150
  return {
146
151
  message: t.message || `Please provide ${p}.`,
147
152
  type: "question"
@@ -150,7 +155,7 @@ class B {
150
155
  return {
151
156
  message: t.message || `Please fill in the missing details for "${e.command}".`,
152
157
  type: "form",
153
- fields: r
158
+ fields: o
154
159
  };
155
160
  }
156
161
  if (e.critical)
@@ -180,52 +185,52 @@ class B {
180
185
  delete i[s.name];
181
186
  continue;
182
187
  }
183
- const o = n.trim();
184
- if (!o) {
188
+ const a = n.trim();
189
+ if (!a) {
185
190
  delete i[s.name];
186
191
  continue;
187
192
  }
188
- i[s.name] = o;
193
+ i[s.name] = a;
189
194
  }
190
195
  if (s.type === "number") {
191
- const o = typeof n == "number" ? n : Number(n == null ? void 0 : n.toString().trim());
192
- if (Number.isNaN(o)) {
196
+ const a = typeof n == "number" ? n : Number(n == null ? void 0 : n.toString().trim());
197
+ if (Number.isNaN(a)) {
193
198
  delete i[s.name];
194
199
  continue;
195
200
  }
196
- if (typeof s.min == "number" && o < s.min) {
201
+ if (typeof s.min == "number" && a < s.min) {
197
202
  delete i[s.name];
198
203
  continue;
199
204
  }
200
- if (typeof s.max == "number" && o > s.max) {
205
+ if (typeof s.max == "number" && a > s.max) {
201
206
  delete i[s.name];
202
207
  continue;
203
208
  }
204
- i[s.name] = o;
209
+ i[s.name] = a;
205
210
  }
206
211
  if (s.type === "date") {
207
- const o = i[s.name];
208
- if (typeof o == "string") {
209
- const r = o.trim();
210
- this.isIsoDateString(r) ? i[s.name] = r : delete i[s.name];
212
+ const a = i[s.name];
213
+ if (typeof a == "string") {
214
+ const o = a.trim();
215
+ this.isIsoDateString(o) ? i[s.name] = o : delete i[s.name];
211
216
  } else
212
217
  delete i[s.name];
213
218
  }
214
219
  if (s.type === "select") {
215
- const o = typeof n == "string" ? n : n == null ? void 0 : n.toString();
216
- if (!o) {
220
+ const a = typeof n == "string" ? n : n == null ? void 0 : n.toString();
221
+ if (!a) {
217
222
  delete i[s.name];
218
223
  continue;
219
224
  }
220
- if (Array.isArray(s.options) && s.options.length > 0 && !s.options.some((c) => String(c.value) === String(o))) {
225
+ if (Array.isArray(s.options) && s.options.length > 0 && !s.options.some((c) => String(c.value) === String(a))) {
221
226
  delete i[s.name];
222
227
  continue;
223
228
  }
224
- i[s.name] = o;
229
+ i[s.name] = a;
225
230
  }
226
231
  if (s.type === "file") {
227
- const o = i[s.name], r = o && typeof o == "object" && typeof o.name == "string" && typeof o.size == "number", c = typeof o == "string" && /^data:[^;]+;base64,/.test(o);
228
- (s.delivery ?? "file") === "base64" ? !c && !r && delete i[s.name] : r || delete i[s.name];
232
+ const a = i[s.name], o = a && typeof a == "object" && typeof a.name == "string" && typeof a.size == "number", c = typeof a == "string" && /^data:[^;]+;base64,/.test(a);
233
+ (s.delivery ?? "file") === "base64" ? !c && !o && delete i[s.name] : o || delete i[s.name];
229
234
  }
230
235
  }
231
236
  return i;
@@ -242,7 +247,7 @@ class B {
242
247
  }
243
248
  buildConfirmResponse(t) {
244
249
  return {
245
- message: `⚠️ Are you sure you want to run "${t.command}"?`,
250
+ message: `Are you sure you want to run "${t.command}"?`,
246
251
  type: "confirm",
247
252
  options: [
248
253
  { label: "Yes", value: "yes" },
@@ -252,35 +257,35 @@ class B {
252
257
  }
253
258
  async tryDeterministicMatch(t) {
254
259
  const e = [];
255
- for (const r of this.commands.values()) {
260
+ for (const o of this.commands.values()) {
256
261
  let c = 0;
257
- const f = r.command.toLowerCase();
262
+ const f = o.command.toLowerCase();
258
263
  t.includes(f) && (c += 5);
259
- const a = r.keywords ?? [];
260
- for (const p of a) {
264
+ const r = o.keywords ?? [];
265
+ for (const p of r) {
261
266
  const g = p.toLowerCase().trim();
262
267
  g && (t === g ? c += 4 : t.includes(g) && (c += 3));
263
268
  }
264
- c > 0 && e.push({ cmd: r, score: c });
269
+ c > 0 && e.push({ cmd: o, score: c });
265
270
  }
266
271
  if (e.length === 0) return null;
267
- e.sort((r, c) => c.score - r.score);
268
- const i = e[0].score, s = e.filter((r) => r.score === i).slice(0, 3);
272
+ e.sort((o, c) => c.score - o.score);
273
+ const i = e[0].score, s = e.filter((o) => o.score === i).slice(0, 3);
269
274
  if (s.length > 1)
270
275
  return {
271
276
  message: "I think you mean one of these. Which one should I run?",
272
277
  type: "ambiguous",
273
- options: s.map((r) => ({
274
- label: r.cmd.command,
275
- value: r.cmd.command,
276
- commandId: r.cmd.id
278
+ options: s.map((o) => ({
279
+ label: o.cmd.command,
280
+ value: o.cmd.command,
281
+ commandId: o.cmd.id
277
282
  }))
278
283
  };
279
- const n = s[0].cmd, o = (n.parameters ?? []).filter((r) => r.required);
280
- return o.length > 0 ? (this.context = { commandId: this.getCommandIdentifier(n), params: {} }, {
284
+ const n = s[0].cmd, a = (n.parameters ?? []).filter((o) => o.required);
285
+ return a.length > 0 ? (this.context = { commandId: this.getCommandIdentifier(n), params: {} }, {
281
286
  message: `Please provide the required details for "${n.command}".`,
282
287
  type: "form",
283
- fields: o
288
+ fields: a
284
289
  }) : n.critical ? (this.pendingConfirmation = { commandId: this.getCommandIdentifier(n), params: {} }, this.buildConfirmResponse(n)) : this.safeRunAction(n, {});
285
290
  }
286
291
  async safeRunAction(t, e) {
@@ -301,14 +306,14 @@ class B {
301
306
  e.parameters && await Promise.all(
302
307
  e.parameters.map(async (i) => {
303
308
  if (i.type !== "select" || !i.getOptions || i.options && i.options.length) return;
304
- const s = `${e.id ?? e.command}:${i.name}`, n = this.selectOptionsCache.get(s), o = Date.now();
305
- if (n && o - n.ts < 6e4) {
309
+ const s = `${e.id ?? e.command}:${i.name}`, n = this.selectOptionsCache.get(s), a = Date.now();
310
+ if (n && a - n.ts < 6e4) {
306
311
  i.options = n.options;
307
312
  return;
308
313
  }
309
314
  try {
310
- const r = await i.getOptions();
311
- this.selectOptionsCache.set(s, { options: r, ts: o }), i.options = r;
315
+ const o = await i.getOptions();
316
+ this.selectOptionsCache.set(s, { options: o, ts: a }), i.options = o;
312
317
  } catch {
313
318
  }
314
319
  })
@@ -345,21 +350,21 @@ class B {
345
350
  return Array.from(this.commands.keys());
346
351
  }
347
352
  }
348
- class q {
353
+ class P {
349
354
  constructor() {
350
- this.synth = window.speechSynthesis;
355
+ this.synth = typeof window < "u" ? window.speechSynthesis : null;
351
356
  }
352
357
  speak(t, e) {
353
358
  if (!this.synth) {
354
- console.error("SpeechSynthesis API is not supported in this browser.");
359
+ console.error("SpeechSynthesis API is not supported in this environment.");
355
360
  return;
356
361
  }
357
362
  const i = new SpeechSynthesisUtterance(t);
358
- e && (i.pitch = e.pitch || 1, i.rate = e.rate || 1, i.volume = e.volume || 1), i.onstart = () => {
363
+ e && (i.pitch = e.pitch || 1, i.rate = e.rate || 1, i.volume = e.volume || 1), typeof window < "u" && (i.onstart = () => {
359
364
  window.dispatchEvent(new CustomEvent("foisit:tts-start"));
360
365
  }, i.onend = () => {
361
366
  console.log("Speech finished."), window.dispatchEvent(new CustomEvent("foisit:tts-end"));
362
- }, i.onerror = (s) => {
367
+ }), i.onerror = (s) => {
363
368
  console.error("Error during speech synthesis:", s.error);
364
369
  }, this.synth.speak(i);
365
370
  }
@@ -367,7 +372,7 @@ class q {
367
372
  this.synth && this.synth.cancel();
368
373
  }
369
374
  }
370
- class z {
375
+ class W {
371
376
  constructor() {
372
377
  this.fallbackMessage = "Sorry, I didn’t understand that.";
373
378
  }
@@ -375,17 +380,18 @@ class z {
375
380
  this.fallbackMessage = t;
376
381
  }
377
382
  handleFallback(t) {
378
- t && console.log(`Fallback triggered for: "${t}"`), console.log(this.fallbackMessage), new q().speak(this.fallbackMessage);
383
+ t && console.log(`Fallback triggered for: "${t}"`), console.log(this.fallbackMessage), new P().speak(this.fallbackMessage);
379
384
  }
380
385
  getFallbackMessage() {
381
386
  return this.fallbackMessage;
382
387
  }
383
388
  }
384
- const T = () => {
389
+ const H = () => {
390
+ if (typeof window > "u") return null;
385
391
  const d = window;
386
392
  return d.SpeechRecognition ?? d.webkitSpeechRecognition ?? null;
387
393
  };
388
- class O {
394
+ class B {
389
395
  constructor(t = "en-US", e = {}) {
390
396
  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 = () => {
391
397
  var s;
@@ -398,7 +404,7 @@ class O {
398
404
  }, this.onTTSEnd = () => {
399
405
  this.ttsSpeaking = !1, this.isListening && this.restartAllowed ? this.safeRestart() : this.emitStatus(this.isListening ? "listening" : "idle");
400
406
  };
401
- const i = T();
407
+ const i = H();
402
408
  if (i) {
403
409
  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 = () => {
404
410
  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");
@@ -407,16 +413,16 @@ class O {
407
413
  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);
408
414
  } else
409
415
  this.recognition = null, this.emitStatus("unsupported");
410
- window.addEventListener("foisit:tts-start", this.onTTSStart), window.addEventListener("foisit:tts-end", this.onTTSEnd), this.visibilityHandler = () => {
416
+ typeof window < "u" ? (window.addEventListener("foisit:tts-start", this.onTTSStart), window.addEventListener("foisit:tts-end", this.onTTSEnd), this.visibilityHandler = () => {
411
417
  var s;
412
- if (document.hidden) {
418
+ if (typeof document < "u" && document.hidden) {
413
419
  try {
414
420
  (s = this.recognition) == null || s.stop();
415
421
  } catch {
416
422
  }
417
423
  this.emitStatus(this.ttsSpeaking ? "speaking" : "idle");
418
424
  } else this.isListening && !this.ttsSpeaking && this.safeRestart();
419
- }, document.addEventListener("visibilitychange", this.visibilityHandler);
425
+ }, typeof document < "u" && document.addEventListener("visibilitychange", this.visibilityHandler)) : this.visibilityHandler = void 0;
420
426
  }
421
427
  // Debug logger helpers
422
428
  log(t) {
@@ -430,7 +436,7 @@ class O {
430
436
  }
431
437
  /** Check if SpeechRecognition is available */
432
438
  isSupported() {
433
- return T() !== null;
439
+ return H() !== null;
434
440
  }
435
441
  /** Allow consumers (wrappers) to observe status changes */
436
442
  onStatusChange(t) {
@@ -468,11 +474,11 @@ class O {
468
474
  var s, n;
469
475
  if (!this.resultCallback) return;
470
476
  const i = e.confidenceThreshold ?? 0.6;
471
- for (let o = t.resultIndex; o < t.results.length; o++) {
472
- const r = t.results[o], c = r && r[0], f = ((n = (s = c == null ? void 0 : c.transcript) == null ? void 0 : s.trim) == null ? void 0 : n.call(s)) || "", a = (c == null ? void 0 : c.confidence) ?? 0;
473
- if (f && !(!r.isFinal && e.interimResults === !1) && !(r.isFinal && a < i))
477
+ for (let a = t.resultIndex; a < t.results.length; a++) {
478
+ const o = t.results[a], c = o && o[0], f = ((n = (s = c == null ? void 0 : c.transcript) == null ? void 0 : s.trim) == null ? void 0 : n.call(s)) || "", r = (c == null ? void 0 : c.confidence) ?? 0;
479
+ if (f && !(!o.isFinal && e.interimResults === !1) && !(o.isFinal && r < i))
474
480
  try {
475
- this.hadResultThisSession = !0, this.resultCallback(f, !!r.isFinal);
481
+ this.hadResultThisSession = !0, this.resultCallback(f, !!o.isFinal);
476
482
  } catch {
477
483
  this.error("VoiceProcessor: result callback error");
478
484
  }
@@ -551,27 +557,29 @@ class O {
551
557
  }
552
558
  }
553
559
  }
554
- class U {
560
+ class R {
555
561
  constructor() {
556
- this.lastTap = 0;
562
+ this.lastTap = 0, this.tapCount = 0;
557
563
  }
558
564
  /**
559
- * Sets up double-click and double-tap listeners
560
- * @param onDoubleClickOrTap Callback to execute when a double-click or double-tap is detected
565
+ * Sets up triple-click and triple-tap listeners
566
+ * @param onTripleClickOrTap Callback to execute when a triple-click or triple-tap is detected
561
567
  */
562
- setupDoubleTapListener(t) {
563
- this.destroy(), this.dblClickListener = () => {
564
- t();
565
- }, document.addEventListener("dblclick", this.dblClickListener), this.touchEndListener = () => {
568
+ setupTripleTapListener(t) {
569
+ this.destroy(), this.clickListener = () => {
570
+ this.tapCount++, this.tapCount === 1 ? this.tapTimeout = window.setTimeout(() => {
571
+ this.tapCount = 0;
572
+ }, 500) : this.tapCount === 3 && (clearTimeout(this.tapTimeout), this.tapCount = 0, t());
573
+ }, document.addEventListener("click", this.clickListener), this.touchEndListener = () => {
566
574
  const e = (/* @__PURE__ */ new Date()).getTime(), i = e - this.lastTap;
567
- i < 300 && i > 0 && t(), this.lastTap = e;
575
+ i < 500 && i > 0 ? (this.tapCount++, this.tapCount === 3 && (this.tapCount = 0, t())) : this.tapCount = 1, this.lastTap = e;
568
576
  }, document.addEventListener("touchend", this.touchEndListener);
569
577
  }
570
578
  destroy() {
571
- this.dblClickListener && document.removeEventListener("dblclick", this.dblClickListener), this.touchEndListener && document.removeEventListener("touchend", this.touchEndListener), this.dblClickListener = void 0, this.touchEndListener = void 0;
579
+ this.dblClickListener && document.removeEventListener("dblclick", this.dblClickListener), this.touchEndListener && document.removeEventListener("touchend", this.touchEndListener), this.clickListener && document.removeEventListener("click", this.clickListener), this.tapTimeout && clearTimeout(this.tapTimeout), this.dblClickListener = void 0, this.touchEndListener = void 0, this.clickListener = void 0, this.tapTimeout = void 0, this.tapCount = 0;
572
580
  }
573
581
  }
574
- function D() {
582
+ function j() {
575
583
  if (document.querySelector("#assistant-styles")) {
576
584
  console.log("Styles already injected");
577
585
  return;
@@ -612,17 +620,20 @@ function D() {
612
620
  }
613
621
  `, document.head.appendChild(t), console.log("Gradient styles injected");
614
622
  }
615
- function $() {
623
+ function U() {
616
624
  if (document.querySelector("#gradient-indicator"))
617
625
  return;
618
626
  const d = document.createElement("div");
619
- d.id = "gradient-indicator", D(), d.classList.add("gradient-indicator"), document.body.appendChild(d), console.log("Gradient indicator added to the DOM");
627
+ d.id = "gradient-indicator", j(), d.classList.add("gradient-indicator"), document.body.appendChild(d), console.log("Gradient indicator added to the DOM");
620
628
  }
621
- function j() {
629
+ function D() {
622
630
  const d = document.querySelector("#gradient-indicator");
623
631
  d && (d.remove(), console.log("Gradient indicator removed from the DOM"));
624
632
  }
625
- class V {
633
+ function V() {
634
+ return typeof window < "u" && typeof document < "u";
635
+ }
636
+ class _ {
626
637
  constructor() {
627
638
  this.state = "idle", this.subscribers = [];
628
639
  }
@@ -630,7 +641,7 @@ class V {
630
641
  return this.state;
631
642
  }
632
643
  setState(t) {
633
- this.state = t, this.notifySubscribers(), console.log("State updated:", t), t === "listening" ? $() : j();
644
+ this.state = t, this.notifySubscribers(), console.log("State updated:", t), t === "listening" ? U() : D();
634
645
  }
635
646
  // eslint-disable-next-line no-unused-vars
636
647
  subscribe(t) {
@@ -642,7 +653,64 @@ class V {
642
653
  }
643
654
  class G {
644
655
  constructor(t) {
645
- this.container = null, this.chatWindow = null, this.messagesContainer = null, this.input = null, this.isOpen = !1, this.loadingEl = null, this.config = t, this.init();
656
+ this.container = null, this.chatWindow = null, this.messagesContainer = null, this.input = null, this.isOpen = !1, this.loadingEl = null, this.commandHandlers = /* @__PURE__ */ new Map(), this.active = V(), this.handleClickOutside = (e) => {
657
+ const i = e.target;
658
+ this.chatWindow && this.chatWindow.contains(i) || i.closest(".foisit-floating-btn") || this.toggle();
659
+ }, this.config = t, this.active && this.init();
660
+ }
661
+ /** Register a command handler that can be invoked programmatically via `runCommand` */
662
+ registerCommandHandler(t, e) {
663
+ !t || typeof e != "function" || this.commandHandlers.set(t, e);
664
+ }
665
+ /** Check whether a programmatic handler is registered locally */
666
+ hasCommandHandler(t) {
667
+ return this.commandHandlers.has(t);
668
+ }
669
+ /** Set an external executor (used to run commands registered via CommandHandler) */
670
+ setExternalCommandExecutor(t) {
671
+ this.externalCommandExecutor = t;
672
+ }
673
+ /** Unregister a previously registered command handler */
674
+ unregisterCommandHandler(t) {
675
+ t && this.commandHandlers.delete(t);
676
+ }
677
+ /**
678
+ * Run a registered command by id. Options:
679
+ * - `params`: passed to the handler
680
+ * - `openOverlay`: if true, open the overlay before running
681
+ * - `showInvocation`: if true, show the invocation as a user message
682
+ */
683
+ async runCommand(t) {
684
+ if (!t || !t.commandId) throw new Error("runCommand requires a commandId");
685
+ const { commandId: e, params: i, openOverlay: s = !0, showInvocation: n = !0 } = t;
686
+ s && !this.isOpen && this.toggle();
687
+ const a = this.commandHandlers.get(e);
688
+ if (a && n && this.messagesContainer && this.addMessage(`Command: ${e}`, "user"), a)
689
+ try {
690
+ this.showLoading();
691
+ const o = await a(i);
692
+ if (this.hideLoading(), typeof o == "string")
693
+ this.addMessage(o, "system");
694
+ else if (o && typeof o == "object")
695
+ try {
696
+ this.addMessage(JSON.stringify(o, null, 2), "system");
697
+ } catch {
698
+ this.addMessage(String(o), "system");
699
+ }
700
+ else o == null || this.addMessage(String(o), "system");
701
+ return o;
702
+ } catch (o) {
703
+ throw this.hideLoading(), this.addMessage(`Command "${e}" failed: ${String(o)}`, "system"), o;
704
+ }
705
+ if (this.externalCommandExecutor)
706
+ try {
707
+ this.showLoading();
708
+ const o = await this.externalCommandExecutor({ commandId: e, params: i });
709
+ return this.hideLoading(), o;
710
+ } catch (o) {
711
+ throw this.hideLoading(), this.addMessage(`Command "${e}" failed: ${String(o)}`, "system"), o;
712
+ }
713
+ this.addMessage(`No handler registered for command "${e}".`, "system");
646
714
  }
647
715
  init() {
648
716
  var e, i;
@@ -650,16 +718,16 @@ class G {
650
718
  this.injectOverlayStyles();
651
719
  const t = document.getElementById("foisit-overlay-container");
652
720
  if (t && t instanceof HTMLElement) {
653
- 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();
721
+ 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(), this.config.enableGestureActivation && (this.gestureHandler = new R(), this.gestureHandler.setupTripleTapListener(() => this.toggle()));
654
722
  return;
655
723
  }
656
- 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();
724
+ 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(), this.config.enableGestureActivation && (this.gestureHandler = new R(), this.gestureHandler.setupTripleTapListener(() => this.toggle()));
657
725
  }
658
726
  renderFloatingButton() {
659
- var s, n, o, r, c, f;
727
+ var s, n, a, o, c, f;
660
728
  const t = document.createElement("button");
661
729
  t.innerHTML = ((s = this.config.floatingButton) == null ? void 0 : s.customHtml) || "🎙️";
662
- const e = ((o = (n = this.config.floatingButton) == null ? void 0 : n.position) == null ? void 0 : o.bottom) || "20px", i = ((c = (r = this.config.floatingButton) == null ? void 0 : r.position) == null ? void 0 : c.right) || "20px";
730
+ const e = ((a = (n = this.config.floatingButton) == null ? void 0 : n.position) == null ? void 0 : a.bottom) || "20px", i = ((c = (o = this.config.floatingButton) == null ? void 0 : o.position) == null ? void 0 : c.right) || "20px";
663
731
  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);
664
732
  }
665
733
  renderChatWindow() {
@@ -673,31 +741,39 @@ class G {
673
741
  const i = document.createElement("button");
674
742
  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";
675
743
  const s = document.createElement("div");
676
- 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", (o) => {
677
- var r;
678
- if (o.key === "Enter" && ((r = this.input) != null && r.value.trim())) {
744
+ 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) => {
745
+ var o;
746
+ if (a.key === "Enter" && ((o = this.input) != null && o.value.trim())) {
679
747
  const c = this.input.value.trim();
680
748
  this.input.value = "", this.onSubmit && this.onSubmit(c);
681
749
  }
682
750
  }), 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);
683
751
  }
684
752
  registerCallbacks(t, e) {
685
- this.onSubmit = t, this.onClose = e;
753
+ this.active && (this.onSubmit = t, this.onClose = e);
686
754
  }
687
755
  toggle(t, e) {
688
- t && (this.onSubmit = t), e && (this.onClose = e), this.isOpen = !this.isOpen, this.chatWindow && (this.isOpen ? (this.chatWindow.style.display = "flex", requestAnimationFrame(() => {
756
+ this.active && (t && (this.onSubmit = t), e && (this.onClose = e), this.isOpen = !this.isOpen, this.chatWindow && (this.isOpen ? (this.container && (this.container.style.pointerEvents = "auto"), this.chatWindow.style.display = "flex", requestAnimationFrame(() => {
689
757
  this.chatWindow && (this.chatWindow.style.opacity = "1", this.chatWindow.style.transform = "translateY(0) scale(1)");
690
758
  }), setTimeout(() => {
691
759
  var i;
692
760
  return (i = this.input) == null ? void 0 : i.focus();
693
- }, 100)) : (this.chatWindow.style.opacity = "0", this.chatWindow.style.transform = "translateY(20px) scale(0.95)", setTimeout(() => {
761
+ }, 100), this.addClickOutsideListener()) : (this.chatWindow.style.opacity = "0", this.chatWindow.style.transform = "translateY(20px) scale(0.95)", setTimeout(() => {
694
762
  this.chatWindow && !this.isOpen && (this.chatWindow.style.display = "none");
695
- }, 200), this.onClose && this.onClose()));
763
+ }, 200), this.onClose && this.onClose(), this.removeClickOutsideListener(), this.container && (this.container.style.pointerEvents = "none"))));
764
+ }
765
+ addClickOutsideListener() {
766
+ this.container && (this.removeClickOutsideListener(), this.container.addEventListener("click", this.handleClickOutside));
767
+ }
768
+ removeClickOutsideListener() {
769
+ this.container && this.container.removeEventListener("click", this.handleClickOutside);
696
770
  }
697
771
  addMessage(t, e) {
698
772
  if (!this.messagesContainer) return;
699
773
  const i = document.createElement("div");
700
- i.textContent = t, i.className = e === "user" ? "foisit-bubble user" : "foisit-bubble system", this.messagesContainer.appendChild(i), this.scrollToBottom();
774
+ e === "system" ? i.innerHTML = this.renderMarkdown(t) : i.textContent = t, i.className = e === "user" ? "foisit-bubble user" : "foisit-bubble system";
775
+ const s = (t || "").length || 0, n = Math.max(120, 700 - Math.min(600, Math.floor(s * 6)));
776
+ i.style.opacity = "0", i.style.transform = "translateY(8px)", i.style.transition = "none", this.messagesContainer.appendChild(i), this.animateMessageEntrance(i, n), this.scrollToBottom();
701
777
  }
702
778
  addOptions(t) {
703
779
  if (!this.messagesContainer) return;
@@ -710,11 +786,11 @@ class G {
710
786
  this.onSubmit && this.onSubmit({ commandId: i.commandId });
711
787
  return;
712
788
  }
713
- const o = i && typeof i.value == "string" && i.value.trim() ? i.value : i.label;
714
- this.onSubmit && this.onSubmit(o);
789
+ const a = i && typeof i.value == "string" && i.value.trim() ? i.value : i.label;
790
+ this.onSubmit && this.onSubmit(a);
715
791
  };
716
- s.onclick = n, s.onkeydown = (o) => {
717
- (o.key === "Enter" || o.key === " ") && (o.preventDefault(), n());
792
+ s.onclick = n, s.onkeydown = (a) => {
793
+ (a.key === "Enter" || a.key === " ") && (a.preventDefault(), n());
718
794
  }, e.appendChild(s);
719
795
  }), this.messagesContainer.appendChild(e), this.scrollToBottom();
720
796
  }
@@ -723,57 +799,57 @@ class G {
723
799
  this.addMessage(t, "system");
724
800
  const s = document.createElement("form");
725
801
  s.className = "foisit-form";
726
- const n = [], o = (a, p) => {
802
+ const n = [], a = (r, p) => {
727
803
  const g = document.createElement("div");
728
- return g.className = "foisit-form-label", g.innerHTML = a + (p ? ' <span class="foisit-req-star">*</span>' : ""), g;
729
- }, r = () => {
730
- const a = document.createElement("div");
731
- return a.className = "foisit-form-error", a.style.display = "none", a;
804
+ return g.className = "foisit-form-label", g.innerHTML = r + (p ? ' <span class="foisit-req-star">*</span>' : ""), g;
805
+ }, o = () => {
806
+ const r = document.createElement("div");
807
+ return r.className = "foisit-form-error", r.style.display = "none", r;
732
808
  };
733
- (e ?? []).forEach((a) => {
809
+ (e ?? []).forEach((r) => {
734
810
  const p = document.createElement("div");
735
811
  p.className = "foisit-form-group";
736
- const g = a.description || a.name;
737
- p.appendChild(o(g, a.required));
812
+ const g = r.description || r.name;
813
+ p.appendChild(a(g, r.required));
738
814
  let m;
739
- if (a.type === "select") {
815
+ if (r.type === "select") {
740
816
  const l = document.createElement("select");
741
817
  l.className = "foisit-form-input";
742
- const b = document.createElement("option");
743
- b.value = "", b.textContent = "Select...", l.appendChild(b);
744
- const x = (h) => {
745
- (h ?? []).forEach((w) => {
746
- const y = document.createElement("option");
747
- y.value = String(w.value ?? w.label ?? ""), y.textContent = String(w.label ?? w.value ?? ""), l.appendChild(y);
818
+ const y = document.createElement("option");
819
+ y.value = "", y.textContent = "Select...", l.appendChild(y);
820
+ const w = (h) => {
821
+ (h ?? []).forEach((x) => {
822
+ const b = document.createElement("option");
823
+ b.value = String(x.value ?? x.label ?? ""), b.textContent = String(x.label ?? x.value ?? ""), l.appendChild(b);
748
824
  });
749
825
  };
750
- if (Array.isArray(a.options) && a.options.length)
751
- x(a.options);
752
- else if (typeof a.getOptions == "function") {
753
- const h = a.getOptions, w = document.createElement("option");
754
- w.value = "", w.textContent = "Loading...", l.appendChild(w), Promise.resolve().then(() => h()).then((y) => {
826
+ if (Array.isArray(r.options) && r.options.length)
827
+ w(r.options);
828
+ else if (typeof r.getOptions == "function") {
829
+ const h = r.getOptions, x = document.createElement("option");
830
+ x.value = "", x.textContent = "Loading...", l.appendChild(x), Promise.resolve().then(() => h()).then((b) => {
755
831
  for (; l.options.length > 1; ) l.remove(1);
756
- x(y);
832
+ w(b);
757
833
  }).catch(() => {
758
834
  for (; l.options.length > 1; ) l.remove(1);
759
- const y = document.createElement("option");
760
- y.value = "", y.textContent = "Error loading options", l.appendChild(y);
835
+ const b = document.createElement("option");
836
+ b.value = "", b.textContent = "Error loading options", l.appendChild(b);
761
837
  });
762
838
  }
763
- a.defaultValue != null && (l.value = String(a.defaultValue)), m = l;
764
- } else if (a.type === "file") {
765
- const l = a, b = document.createElement("input");
766
- b.className = "foisit-form-input", b.type = "file", l.accept && Array.isArray(l.accept) && (b.accept = l.accept.join(",")), l.multiple && (b.multiple = !0), l.capture && (l.capture === !0 ? b.setAttribute("capture", "") : b.setAttribute("capture", String(l.capture))), b.addEventListener("change", async () => {
767
- const x = Array.from(b.files || []), h = C;
768
- if (h.style.display = "none", h.textContent = "", x.length === 0) return;
769
- const w = l.maxFiles ?? (l.multiple ? 10 : 1);
770
- if (x.length > w) {
771
- h.textContent = `Please select at most ${w} file(s).`, h.style.display = "block";
839
+ r.defaultValue != null && (l.value = String(r.defaultValue)), m = l;
840
+ } else if (r.type === "file") {
841
+ const l = r, y = document.createElement("input");
842
+ 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 () => {
843
+ const w = Array.from(y.files || []), h = C;
844
+ if (h.style.display = "none", h.textContent = "", w.length === 0) return;
845
+ const x = l.maxFiles ?? (l.multiple ? 10 : 1);
846
+ if (w.length > x) {
847
+ h.textContent = `Please select at most ${x} file(s).`, h.style.display = "block";
772
848
  return;
773
849
  }
774
- const y = l.maxSizeBytes ?? 1 / 0, u = x.reduce((v, A) => v + A.size, 0);
775
- if (x.some((v) => v.size > y)) {
776
- h.textContent = `One or more files exceed the maximum size of ${Math.round(y / 1024)} KB.`, h.style.display = "block";
850
+ const b = l.maxSizeBytes ?? 1 / 0, u = w.reduce((v, E) => v + E.size, 0);
851
+ if (w.some((v) => v.size > b)) {
852
+ h.textContent = `One or more files exceed the maximum size of ${Math.round(b / 1024)} KB.`, h.style.display = "block";
777
853
  return;
778
854
  }
779
855
  const S = l.maxTotalBytes ?? 1 / 0;
@@ -783,29 +859,29 @@ class G {
783
859
  }
784
860
  if (l.accept && Array.isArray(l.accept)) {
785
861
  const v = l.accept;
786
- if (!x.every((E) => E.type ? v.some((L) => L.startsWith(".") ? E.name.toLowerCase().endsWith(L.toLowerCase()) : E.type === L || E.type.startsWith(L.split("/")[0] + "/")) : !0)) {
862
+ if (!w.every((L) => L.type ? v.some((A) => A.startsWith(".") ? L.name.toLowerCase().endsWith(A.toLowerCase()) : L.type === A || L.type.startsWith(A.split("/")[0] + "/")) : !0)) {
787
863
  h.textContent = "One or more files have an unsupported type.", h.style.display = "block";
788
864
  return;
789
865
  }
790
866
  }
791
- }), m = b;
867
+ }), m = y;
792
868
  } else {
793
869
  const l = document.createElement("input");
794
- l.className = "foisit-form-input", a.type === "string" && (l.placeholder = a.placeholder || "Type here..."), a.type === "number" ? (l.type = "number", typeof a.min == "number" && (l.min = String(a.min)), typeof a.max == "number" && (l.max = String(a.max)), typeof a.step == "number" && (l.step = String(a.step)), a.defaultValue != null && (l.value = String(a.defaultValue))) : a.type === "date" ? (l.type = "date", typeof a.min == "string" && (l.min = a.min), typeof a.max == "string" && (l.max = a.max), a.defaultValue != null && (l.value = String(a.defaultValue))) : (l.type = "text", a.defaultValue != null && (l.value = String(a.defaultValue))), m = l;
870
+ l.className = "foisit-form-input", r.type === "string" && (l.placeholder = r.placeholder || "Type here..."), r.type === "number" ? (l.type = "number", typeof r.min == "number" && (l.min = String(r.min)), typeof r.max == "number" && (l.max = String(r.max)), typeof r.step == "number" && (l.step = String(r.step)), r.defaultValue != null && (l.value = String(r.defaultValue))) : r.type === "date" ? (l.type = "date", typeof r.min == "string" && (l.min = r.min), typeof r.max == "string" && (l.max = r.max), r.defaultValue != null && (l.value = String(r.defaultValue))) : (l.type = "text", r.defaultValue != null && (l.value = String(r.defaultValue))), m = l;
795
871
  }
796
- const C = r();
872
+ const C = o();
797
873
  p.appendChild(m), p.appendChild(C), n.push({
798
- name: a.name,
799
- type: a.type,
874
+ name: r.name,
875
+ type: r.type,
800
876
  el: m,
801
- required: a.required
877
+ required: r.required
802
878
  }), s.appendChild(p);
803
879
  });
804
880
  const c = document.createElement("div");
805
881
  c.className = "foisit-form-actions";
806
882
  const f = document.createElement("button");
807
- f.type = "submit", f.textContent = "Submit", f.className = "foisit-option-chip", f.style.fontWeight = "600", c.appendChild(f), s.appendChild(c), s.onsubmit = async (a) => {
808
- a.preventDefault();
883
+ f.type = "submit", f.textContent = "Submit", f.className = "foisit-option-chip", f.style.fontWeight = "600", c.appendChild(f), s.appendChild(c), s.onsubmit = async (r) => {
884
+ r.preventDefault();
809
885
  const p = {};
810
886
  let g = !1;
811
887
  s.querySelectorAll(".foisit-form-error").forEach((m) => {
@@ -815,16 +891,16 @@ class G {
815
891
  });
816
892
  for (const m of n) {
817
893
  if (m.type === "file") {
818
- const x = m.el.parentElement, h = x == null ? void 0 : x.querySelector(".foisit-form-error"), w = m.el, y = Array.from(w.files || []);
819
- if (m.required && y.length === 0) {
820
- g = !0, w.classList.add("foisit-error-border"), h && (h.textContent = "This file is required", h.style.display = "block");
894
+ const w = m.el.parentElement, h = w == null ? void 0 : w.querySelector(".foisit-form-error"), x = m.el, b = Array.from(x.files || []);
895
+ if (m.required && b.length === 0) {
896
+ g = !0, x.classList.add("foisit-error-border"), h && (h.textContent = "This file is required", h.style.display = "block");
821
897
  continue;
822
898
  }
823
- if (y.length === 0) continue;
899
+ if (b.length === 0) continue;
824
900
  const u = (e ?? []).find((v) => v.name === m.name), S = (u == null ? void 0 : u.delivery) ?? "file";
825
901
  if (u != null && u.maxWidth || u != null && u.maxHeight)
826
902
  try {
827
- const v = await this.getImageDimensions(y[0]);
903
+ const v = await this.getImageDimensions(b[0]);
828
904
  if (u.maxWidth && v.width > u.maxWidth) {
829
905
  g = !0, h && (h.textContent = `Image width must be ≤ ${u.maxWidth}px`, h.style.display = "block");
830
906
  continue;
@@ -837,7 +913,7 @@ class G {
837
913
  }
838
914
  if (u != null && u.maxDurationSec)
839
915
  try {
840
- const v = await this.getMediaDuration(y[0]);
916
+ const v = await this.getMediaDuration(b[0]);
841
917
  if (v && v > u.maxDurationSec) {
842
918
  g = !0, h && (h.textContent = `Media duration must be ≤ ${u.maxDurationSec}s`, h.style.display = "block");
843
919
  continue;
@@ -845,10 +921,10 @@ class G {
845
921
  } catch {
846
922
  }
847
923
  if (S === "file")
848
- p[m.name] = u != null && u.multiple ? y : y[0];
924
+ p[m.name] = u != null && u.multiple ? b : b[0];
849
925
  else if (S === "base64")
850
926
  try {
851
- const v = await Promise.all(y.map((A) => this.readFileAsDataURL(A)));
927
+ const v = await Promise.all(b.map((E) => this.readFileAsDataURL(E)));
852
928
  p[m.name] = u != null && u.multiple ? v : v[0];
853
929
  } catch {
854
930
  g = !0, h && (h.textContent = "Failed to encode file(s) to base64.", h.style.display = "block");
@@ -856,15 +932,15 @@ class G {
856
932
  }
857
933
  continue;
858
934
  }
859
- const C = (m.el.value ?? "").toString().trim(), l = m.el.parentElement, b = l == null ? void 0 : l.querySelector(".foisit-form-error");
935
+ const C = (m.el.value ?? "").toString().trim(), l = m.el.parentElement, y = l == null ? void 0 : l.querySelector(".foisit-form-error");
860
936
  if (m.required && (C == null || C === "")) {
861
- g = !0, m.el.classList.add("foisit-error-border"), b && (b.textContent = "This field is required", b.style.display = "block");
937
+ g = !0, m.el.classList.add("foisit-error-border"), y && (y.textContent = "This field is required", y.style.display = "block");
862
938
  continue;
863
939
  }
864
940
  if (C !== "")
865
941
  if (m.type === "number") {
866
- const x = Number(C);
867
- Number.isNaN(x) || (p[m.name] = x);
942
+ const w = Number(C);
943
+ Number.isNaN(w) || (p[m.name] = w);
868
944
  } else
869
945
  p[m.name] = C;
870
946
  }
@@ -894,9 +970,54 @@ class G {
894
970
  scrollToBottom() {
895
971
  this.messagesContainer && (this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight);
896
972
  }
973
+ /** Subtle entrance animation for new messages */
974
+ animateMessageEntrance(t, e) {
975
+ if (!t) return;
976
+ t.style.transition = `opacity ${e}ms cubic-bezier(0.22, 0.9, 0.32, 1), transform ${Math.max(120, e)}ms cubic-bezier(0.22, 0.9, 0.32, 1)`, requestAnimationFrame(() => {
977
+ t.style.opacity = "1", t.style.transform = "translateY(0)";
978
+ });
979
+ const i = () => {
980
+ try {
981
+ t.style.transition = "";
982
+ } catch {
983
+ }
984
+ t.removeEventListener("transitionend", i);
985
+ };
986
+ t.addEventListener("transitionend", i);
987
+ }
988
+ /** Smoothly scroll messages container to bottom over duration (ms) */
989
+ animateScrollToBottom(t) {
990
+ if (!this.messagesContainer) return;
991
+ const e = this.messagesContainer, i = e.scrollTop, s = e.scrollHeight - e.clientHeight;
992
+ if (s <= i || t <= 0) {
993
+ e.scrollTop = s;
994
+ return;
995
+ }
996
+ const n = s - i, a = performance.now(), o = (c) => {
997
+ const f = Math.min(1, (c - a) / t), r = 1 - Math.pow(1 - f, 3);
998
+ e.scrollTop = Math.round(i + n * r), f < 1 && requestAnimationFrame(o);
999
+ };
1000
+ requestAnimationFrame(o);
1001
+ }
897
1002
  destroy() {
898
1003
  var t;
899
- (t = this.container) == null || t.remove(), this.container = null, this.chatWindow = null, this.messagesContainer = null, this.input = null, this.isOpen = !1;
1004
+ this.removeClickOutsideListener(), (t = this.container) == null || t.remove(), this.container = null, this.chatWindow = null, this.messagesContainer = null, this.input = null, this.isOpen = !1;
1005
+ }
1006
+ /** Escape HTML special characters to prevent XSS */
1007
+ escapeHtml(t) {
1008
+ const e = {
1009
+ "&": "&amp;",
1010
+ "<": "&lt;",
1011
+ ">": "&gt;",
1012
+ '"': "&quot;",
1013
+ "'": "&#039;"
1014
+ };
1015
+ return t.replace(/[&<>"']/g, (i) => e[i]);
1016
+ }
1017
+ /** Simple markdown renderer for AI responses */
1018
+ renderMarkdown(t) {
1019
+ let e = this.escapeHtml(t);
1020
+ return e = e.replace(/```(\w*)\n([\s\S]*?)```/g, (i, s, n) => `<pre class="foisit-code-block"><code${s ? ` class="language-${s}"` : ""}>${n.trim()}</code></pre>`), e = e.replace(/`([^`]+)`/g, '<code class="foisit-inline-code">$1</code>'), e = e.replace(/^###### (.+)$/gm, '<h6 class="foisit-md-h6">$1</h6>'), e = e.replace(/^##### (.+)$/gm, '<h5 class="foisit-md-h5">$1</h5>'), e = e.replace(/^#### (.+)$/gm, '<h4 class="foisit-md-h4">$1</h4>'), e = e.replace(/^### (.+)$/gm, '<h3 class="foisit-md-h3">$1</h3>'), e = e.replace(/^## (.+)$/gm, '<h2 class="foisit-md-h2">$1</h2>'), e = e.replace(/^# (.+)$/gm, '<h1 class="foisit-md-h1">$1</h1>'), e = e.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>"), e = e.replace(/__([^_]+)__/g, "<strong>$1</strong>"), e = e.replace(/\*([^*]+)\*/g, "<em>$1</em>"), e = e.replace(new RegExp("(?<!_)_([^_]+)_(?!_)", "g"), "<em>$1</em>"), e = e.replace(/~~([^~]+)~~/g, "<del>$1</del>"), e = e.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer" class="foisit-md-link">$1</a>'), e = e.replace(/^[\-\*] (.+)$/gm, '<li class="foisit-md-li">$1</li>'), e = e.replace(/(<li class="foisit-md-li">.*<\/li>\n?)+/g, (i) => `<ul class="foisit-md-ul">${i}</ul>`), e = e.replace(/^\d+\. (.+)$/gm, '<li class="foisit-md-li">$1</li>'), e = e.replace(new RegExp('(?<!<\\/ul>)(<li class="foisit-md-li">.*<\\/li>\\n?)+', "g"), (i) => i.includes("<ul") ? i : `<ol class="foisit-md-ol">${i}</ol>`), e = e.replace(/^&gt; (.+)$/gm, '<blockquote class="foisit-md-blockquote">$1</blockquote>'), e = e.replace(/^(---|___|\*\*\*)$/gm, '<hr class="foisit-md-hr">'), e = e.replace(/\n\n+/g, '</p><p class="foisit-md-p">'), e = e.replace(/\n/g, "<br>"), e.match(/^<(h[1-6]|ul|ol|pre|blockquote|hr|p)/) || (e = `<p class="foisit-md-p">${e}</p>`), e;
900
1021
  }
901
1022
  readFileAsDataURL(t) {
902
1023
  return new Promise((e, i) => {
@@ -924,16 +1045,16 @@ class G {
924
1045
  try {
925
1046
  const i = URL.createObjectURL(t), s = t.type.startsWith("audio") ? document.createElement("audio") : document.createElement("video");
926
1047
  let n = !1;
927
- const o = setTimeout(() => {
1048
+ const a = setTimeout(() => {
928
1049
  n || (n = !0, URL.revokeObjectURL(i), e(0));
929
1050
  }, 5e3);
930
1051
  s.preload = "metadata", s.onloadedmetadata = () => {
931
1052
  if (n) return;
932
- n = !0, clearTimeout(o);
1053
+ n = !0, clearTimeout(a);
933
1054
  const c = s.duration || 0;
934
1055
  URL.revokeObjectURL(i), e(c);
935
1056
  }, s.onerror = () => {
936
- n || (n = !0, clearTimeout(o), URL.revokeObjectURL(i), e(0));
1057
+ n || (n = !0, clearTimeout(a), URL.revokeObjectURL(i), e(0));
937
1058
  }, s.src = i;
938
1059
  } catch {
939
1060
  e(0);
@@ -1264,18 +1385,104 @@ class G {
1264
1385
  transition: transform 0.2s;
1265
1386
  }
1266
1387
  .foisit-floating-btn:hover { transform: scale(1.05); }
1388
+
1389
+ /* Markdown Styles */
1390
+ .foisit-bubble.system .foisit-md-p { margin: 0 0 0.5em 0; }
1391
+ .foisit-bubble.system .foisit-md-p:last-child { margin-bottom: 0; }
1392
+
1393
+ .foisit-bubble.system .foisit-md-h1,
1394
+ .foisit-bubble.system .foisit-md-h2,
1395
+ .foisit-bubble.system .foisit-md-h3,
1396
+ .foisit-bubble.system .foisit-md-h4,
1397
+ .foisit-bubble.system .foisit-md-h5,
1398
+ .foisit-bubble.system .foisit-md-h6 {
1399
+ margin: 0.8em 0 0.4em 0;
1400
+ font-weight: 600;
1401
+ line-height: 1.3;
1402
+ }
1403
+ .foisit-bubble.system .foisit-md-h1:first-child,
1404
+ .foisit-bubble.system .foisit-md-h2:first-child,
1405
+ .foisit-bubble.system .foisit-md-h3:first-child { margin-top: 0; }
1406
+
1407
+ .foisit-bubble.system .foisit-md-h1 { font-size: 1.4em; }
1408
+ .foisit-bubble.system .foisit-md-h2 { font-size: 1.25em; }
1409
+ .foisit-bubble.system .foisit-md-h3 { font-size: 1.1em; }
1410
+ .foisit-bubble.system .foisit-md-h4 { font-size: 1em; }
1411
+ .foisit-bubble.system .foisit-md-h5 { font-size: 0.95em; }
1412
+ .foisit-bubble.system .foisit-md-h6 { font-size: 0.9em; opacity: 0.85; }
1413
+
1414
+ .foisit-bubble.system .foisit-md-ul,
1415
+ .foisit-bubble.system .foisit-md-ol {
1416
+ margin: 0.5em 0;
1417
+ padding-left: 1.5em;
1418
+ }
1419
+ .foisit-bubble.system .foisit-md-li { margin: 0.25em 0; }
1420
+
1421
+ .foisit-bubble.system .foisit-code-block {
1422
+ background: rgba(0,0,0,0.15);
1423
+ border-radius: 6px;
1424
+ padding: 10px 12px;
1425
+ margin: 0.5em 0;
1426
+ overflow-x: auto;
1427
+ font-family: 'SF Mono', Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
1428
+ font-size: 0.85em;
1429
+ line-height: 1.4;
1430
+ }
1431
+ .foisit-bubble.system .foisit-code-block code {
1432
+ background: transparent;
1433
+ padding: 0;
1434
+ }
1435
+
1436
+ .foisit-bubble.system .foisit-inline-code {
1437
+ background: rgba(0,0,0,0.1);
1438
+ padding: 2px 6px;
1439
+ border-radius: 4px;
1440
+ font-family: 'SF Mono', Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
1441
+ font-size: 0.9em;
1442
+ }
1443
+
1444
+ .foisit-bubble.system .foisit-md-blockquote {
1445
+ border-left: 3px solid rgba(127,127,127,0.4);
1446
+ margin: 0.5em 0;
1447
+ padding-left: 12px;
1448
+ opacity: 0.9;
1449
+ font-style: italic;
1450
+ }
1451
+
1452
+ .foisit-bubble.system .foisit-md-link {
1453
+ color: inherit;
1454
+ text-decoration: underline;
1455
+ opacity: 0.9;
1456
+ }
1457
+ .foisit-bubble.system .foisit-md-link:hover { opacity: 1; }
1458
+
1459
+ .foisit-bubble.system .foisit-md-hr {
1460
+ border: none;
1461
+ border-top: 1px solid rgba(127,127,127,0.3);
1462
+ margin: 0.8em 0;
1463
+ }
1464
+
1465
+ .foisit-bubble.system strong { font-weight: 600; }
1466
+ .foisit-bubble.system em { font-style: italic; }
1467
+ .foisit-bubble.system del { text-decoration: line-through; opacity: 0.7; }
1468
+
1469
+ @media (prefers-color-scheme: dark) {
1470
+ .foisit-bubble.system .foisit-code-block { background: rgba(255,255,255,0.08); }
1471
+ .foisit-bubble.system .foisit-inline-code { background: rgba(255,255,255,0.1); }
1472
+ }
1267
1473
  `, document.head.appendChild(t);
1268
1474
  }
1269
1475
  }
1270
1476
  class Y {
1271
1477
  constructor(t) {
1272
- this.config = t, this.isActivated = !1, this.lastProcessedInput = "", this.processingLock = !1, this.defaultIntroMessage = "How can I help you?", this.commandHandler = new B({
1478
+ this.config = t, this.isActivated = !1, this.lastProcessedInput = "", this.processingLock = !1, this.defaultIntroMessage = "How can I help you?", this.commandHandler = new z({
1273
1479
  enableSmartIntent: this.config.enableSmartIntent !== !1,
1274
1480
  intentEndpoint: this.config.intentEndpoint
1275
- }), this.fallbackHandler = new z(), this.voiceProcessor = new O(), this.textToSpeech = new q(), this.stateManager = new V(), this.gestureHandler = new U(), this.overlayManager = new G({
1481
+ }), this.fallbackHandler = new W(), typeof window < "u" && typeof document < "u" ? (this.voiceProcessor = new B(), this.textToSpeech = new P(), this.stateManager = new _(), this.gestureHandler = new R(), this.overlayManager = new G({
1276
1482
  floatingButton: this.config.floatingButton,
1277
- inputPlaceholder: this.config.inputPlaceholder
1278
- }), 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(
1483
+ inputPlaceholder: this.config.inputPlaceholder,
1484
+ enableGestureActivation: this.config.enableGestureActivation
1485
+ }), this.overlayManager.setExternalCommandExecutor(async (e) => this.commandHandler.executeCommand(e)), this.config.commands.forEach((e) => this.commandHandler.addCommand(e)), this.config.fallbackResponse && this.fallbackHandler.setFallbackMessage(this.config.fallbackResponse), this.overlayManager.registerCallbacks(
1279
1486
  async (e) => {
1280
1487
  if (typeof e == "string") {
1281
1488
  this.overlayManager.addMessage(e, "user"), await this.handleCommand(e);
@@ -1289,15 +1496,22 @@ class Y {
1289
1496
  }
1290
1497
  },
1291
1498
  () => console.log("AssistantService: Overlay closed.")
1292
- );
1499
+ )) : (this.voiceProcessor = void 0, this.textToSpeech = void 0, this.stateManager = void 0, this.gestureHandler = void 0, this.overlayManager = void 0, this.config.commands.forEach((e) => this.commandHandler.addCommand(e)), this.config.fallbackResponse && this.fallbackHandler.setFallbackMessage(this.config.fallbackResponse));
1293
1500
  }
1294
1501
  /** Start listening for activation and commands */
1295
1502
  startListening() {
1296
- console.log("AssistantService: Voice is disabled; startListening() is a no-op.");
1503
+ if (typeof window > "u" || !this.voiceProcessor) {
1504
+ console.log("AssistantService: Voice is disabled or unavailable; startListening() is a no-op.");
1505
+ return;
1506
+ }
1297
1507
  }
1298
1508
  /** Stop listening */
1299
1509
  stopListening() {
1300
- console.log("AssistantService: Voice is disabled; stopListening() is a no-op."), this.isActivated = !1;
1510
+ if (typeof window > "u" || !this.voiceProcessor) {
1511
+ console.log("AssistantService: Voice unavailable; stopListening() is a no-op."), this.isActivated = !1;
1512
+ return;
1513
+ }
1514
+ this.voiceProcessor.stopListening(), this.isActivated = !1;
1301
1515
  }
1302
1516
  /**
1303
1517
  * Reset activation state so the next activation flow can occur.
@@ -1395,6 +1609,19 @@ class Y {
1395
1609
  removeCommand(t) {
1396
1610
  console.log(`AssistantService: Removing command "${t}".`), this.commandHandler.removeCommand(t);
1397
1611
  }
1612
+ /** Expose programmatic command handler registration to host apps */
1613
+ registerCommandHandler(t, e) {
1614
+ this.overlayManager && this.overlayManager.registerCommandHandler(t, e);
1615
+ }
1616
+ unregisterCommandHandler(t) {
1617
+ this.overlayManager && this.overlayManager.unregisterCommandHandler(t);
1618
+ }
1619
+ /** Programmatically run a registered command (proxies to OverlayManager) */
1620
+ async runCommand(t) {
1621
+ if (!this.overlayManager) throw new Error("Overlay manager not available.");
1622
+ const e = await this.overlayManager.runCommand(t);
1623
+ return e && typeof e == "object" && "type" in e && this.processResponse(e), e;
1624
+ }
1398
1625
  /** Get all registered commands */
1399
1626
  getCommands() {
1400
1627
  return this.commandHandler.getCommands();
@@ -1410,13 +1637,13 @@ class Y {
1410
1637
  if (i && typeof i == "object") {
1411
1638
  const s = i, n = s.label ?? s.commandId ?? "Selection";
1412
1639
  this.overlayManager.addMessage(String(n), "user"), this.overlayManager.showLoading();
1413
- let o;
1640
+ let a;
1414
1641
  try {
1415
- o = await this.commandHandler.executeCommand(s);
1642
+ a = await this.commandHandler.executeCommand(s);
1416
1643
  } finally {
1417
1644
  this.overlayManager.hideLoading();
1418
1645
  }
1419
- this.processResponse(o);
1646
+ this.processResponse(a);
1420
1647
  }
1421
1648
  },
1422
1649
  () => {
@@ -1425,26 +1652,26 @@ class Y {
1425
1652
  );
1426
1653
  }
1427
1654
  }
1428
- const H = F(null);
1429
- let I = null;
1430
- const J = ({ config: d, children: t }) => {
1431
- const [e, i] = R(null), [s, n] = R(!1);
1432
- return P(() => {
1433
- I ? console.warn(
1655
+ const q = F(null);
1656
+ let M = null;
1657
+ const Z = ({ config: d, children: t }) => {
1658
+ const [e, i] = I(null), [s, n] = I(!1);
1659
+ return $(() => {
1660
+ M ? console.warn(
1434
1661
  "Multiple AssistantProvider instances detected. Reusing global AssistantService."
1435
- ) : (console.log("Initializing global AssistantService..."), I = new Y(d));
1436
- const o = I;
1437
- return i(o), n(!0), () => {
1438
- var r;
1439
- console.log("Cleaning up AssistantService..."), (r = o.destroy) == null || r.call(o), I = null;
1662
+ ) : (console.log("Initializing global AssistantService..."), M = new Y(d));
1663
+ const a = M;
1664
+ return i(a), n(!0), () => {
1665
+ var o;
1666
+ console.log("Cleaning up AssistantService..."), (o = a.destroy) == null || o.call(a), M = null;
1440
1667
  };
1441
- }, [d]), !s || !e ? /* @__PURE__ */ k("div", { children: "Loading Assistant..." }) : /* @__PURE__ */ k(H.Provider, { value: e, children: t });
1668
+ }, [d]), !s || !e ? /* @__PURE__ */ k("div", { children: "Loading Assistant..." }) : /* @__PURE__ */ k(q.Provider, { value: e, children: t });
1442
1669
  }, K = () => {
1443
- const d = N(H);
1670
+ const d = N(q);
1444
1671
  if (console.log("assistant", d), !d)
1445
1672
  throw new Error("useAssistant must be used within an AssistantProvider");
1446
1673
  return d;
1447
- }, Z = ({
1674
+ }, tt = ({
1448
1675
  label: d = "Activate Assistant",
1449
1676
  onActivate: t
1450
1677
  }) => {
@@ -1452,9 +1679,9 @@ const J = ({ config: d, children: t }) => {
1452
1679
  return /* @__PURE__ */ k("button", { onClick: () => {
1453
1680
  t && t(), e.reactivate();
1454
1681
  }, className: "assistant-activator", children: d });
1455
- }, tt = (d) => {
1456
- const [t, e] = R(d.getState());
1457
- return P(() => {
1682
+ }, et = (d) => {
1683
+ const [t, e] = I(d.getState());
1684
+ return $(() => {
1458
1685
  const i = (s) => {
1459
1686
  e(s);
1460
1687
  };
@@ -1465,11 +1692,11 @@ const J = ({ config: d, children: t }) => {
1465
1692
  }, [d]), t;
1466
1693
  };
1467
1694
  export {
1468
- Z as AssistantActivator,
1469
- H as AssistantContext,
1470
- J as AssistantProvider,
1695
+ tt as AssistantActivator,
1696
+ q as AssistantContext,
1697
+ Z as AssistantProvider,
1471
1698
  Y as AssistantService,
1472
1699
  Q as ReactWrapper,
1473
1700
  K as useAssistant,
1474
- tt as useAssistantState
1701
+ et as useAssistantState
1475
1702
  };