@foisit/react-wrapper 2.4.4 → 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 = {};
4
- function J() {
5
- return /* @__PURE__ */ k("div", { className: W.container, children: /* @__PURE__ */ k("h1", { children: "Welcome to ReactWrapper!" }) });
2
+ import { createContext as F, useState as I, useEffect as $, useContext as N } from "react";
3
+ const O = {};
4
+ function Q() {
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,26 +82,26 @@ 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
106
  return ["no", "n", "cancel", "stop"].includes(n) ? (this.pendingConfirmation = null, { message: "Cancelled.", type: "success" }) : {
107
107
  message: "Please confirm: Yes or No.",
@@ -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;
@@ -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,7 +350,7 @@ class B {
345
350
  return Array.from(this.commands.keys());
346
351
  }
347
352
  }
348
- class H {
353
+ class P {
349
354
  constructor() {
350
355
  this.synth = typeof window < "u" ? window.speechSynthesis : null;
351
356
  }
@@ -367,7 +372,7 @@ class H {
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,18 +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 H().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 = () => {
385
390
  if (typeof window > "u") return null;
386
391
  const d = window;
387
392
  return d.SpeechRecognition ?? d.webkitSpeechRecognition ?? null;
388
393
  };
389
- class O {
394
+ class B {
390
395
  constructor(t = "en-US", e = {}) {
391
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 = () => {
392
397
  var s;
@@ -399,7 +404,7 @@ class O {
399
404
  }, this.onTTSEnd = () => {
400
405
  this.ttsSpeaking = !1, this.isListening && this.restartAllowed ? this.safeRestart() : this.emitStatus(this.isListening ? "listening" : "idle");
401
406
  };
402
- const i = T();
407
+ const i = H();
403
408
  if (i) {
404
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 = () => {
405
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");
@@ -431,7 +436,7 @@ class O {
431
436
  }
432
437
  /** Check if SpeechRecognition is available */
433
438
  isSupported() {
434
- return T() !== null;
439
+ return H() !== null;
435
440
  }
436
441
  /** Allow consumers (wrappers) to observe status changes */
437
442
  onStatusChange(t) {
@@ -469,11 +474,11 @@ class O {
469
474
  var s, n;
470
475
  if (!this.resultCallback) return;
471
476
  const i = e.confidenceThreshold ?? 0.6;
472
- for (let o = t.resultIndex; o < t.results.length; o++) {
473
- 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;
474
- 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))
475
480
  try {
476
- this.hadResultThisSession = !0, this.resultCallback(f, !!r.isFinal);
481
+ this.hadResultThisSession = !0, this.resultCallback(f, !!o.isFinal);
477
482
  } catch {
478
483
  this.error("VoiceProcessor: result callback error");
479
484
  }
@@ -552,27 +557,29 @@ class O {
552
557
  }
553
558
  }
554
559
  }
555
- class U {
560
+ class R {
556
561
  constructor() {
557
- this.lastTap = 0;
562
+ this.lastTap = 0, this.tapCount = 0;
558
563
  }
559
564
  /**
560
- * Sets up double-click and double-tap listeners
561
- * @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
562
567
  */
563
- setupDoubleTapListener(t) {
564
- this.destroy(), this.dblClickListener = () => {
565
- t();
566
- }, 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 = () => {
567
574
  const e = (/* @__PURE__ */ new Date()).getTime(), i = e - this.lastTap;
568
- 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;
569
576
  }, document.addEventListener("touchend", this.touchEndListener);
570
577
  }
571
578
  destroy() {
572
- 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;
573
580
  }
574
581
  }
575
- function D() {
582
+ function j() {
576
583
  if (document.querySelector("#assistant-styles")) {
577
584
  console.log("Styles already injected");
578
585
  return;
@@ -613,20 +620,20 @@ function D() {
613
620
  }
614
621
  `, document.head.appendChild(t), console.log("Gradient styles injected");
615
622
  }
616
- function $() {
623
+ function U() {
617
624
  if (document.querySelector("#gradient-indicator"))
618
625
  return;
619
626
  const d = document.createElement("div");
620
- 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");
621
628
  }
622
- function j() {
629
+ function D() {
623
630
  const d = document.querySelector("#gradient-indicator");
624
631
  d && (d.remove(), console.log("Gradient indicator removed from the DOM"));
625
632
  }
626
633
  function V() {
627
634
  return typeof window < "u" && typeof document < "u";
628
635
  }
629
- class G {
636
+ class _ {
630
637
  constructor() {
631
638
  this.state = "idle", this.subscribers = [];
632
639
  }
@@ -634,7 +641,7 @@ class G {
634
641
  return this.state;
635
642
  }
636
643
  setState(t) {
637
- 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();
638
645
  }
639
646
  // eslint-disable-next-line no-unused-vars
640
647
  subscribe(t) {
@@ -644,9 +651,66 @@ class G {
644
651
  this.subscribers.forEach((t) => t(this.state));
645
652
  }
646
653
  }
647
- class Y {
654
+ class G {
648
655
  constructor(t) {
649
- this.container = null, this.chatWindow = null, this.messagesContainer = null, this.input = null, this.isOpen = !1, this.loadingEl = null, this.active = V(), this.config = t, this.active && 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");
650
714
  }
651
715
  init() {
652
716
  var e, i;
@@ -654,16 +718,16 @@ class Y {
654
718
  this.injectOverlayStyles();
655
719
  const t = document.getElementById("foisit-overlay-container");
656
720
  if (t && t instanceof HTMLElement) {
657
- 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()));
658
722
  return;
659
723
  }
660
- 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()));
661
725
  }
662
726
  renderFloatingButton() {
663
- var s, n, o, r, c, f;
727
+ var s, n, a, o, c, f;
664
728
  const t = document.createElement("button");
665
729
  t.innerHTML = ((s = this.config.floatingButton) == null ? void 0 : s.customHtml) || "🎙️";
666
- 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";
667
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);
668
732
  }
669
733
  renderChatWindow() {
@@ -677,9 +741,9 @@ class Y {
677
741
  const i = document.createElement("button");
678
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";
679
743
  const s = document.createElement("div");
680
- 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) => {
681
- var r;
682
- 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())) {
683
747
  const c = this.input.value.trim();
684
748
  this.input.value = "", this.onSubmit && this.onSubmit(c);
685
749
  }
@@ -689,19 +753,27 @@ class Y {
689
753
  this.active && (this.onSubmit = t, this.onClose = e);
690
754
  }
691
755
  toggle(t, e) {
692
- this.active && (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(() => {
693
757
  this.chatWindow && (this.chatWindow.style.opacity = "1", this.chatWindow.style.transform = "translateY(0) scale(1)");
694
758
  }), setTimeout(() => {
695
759
  var i;
696
760
  return (i = this.input) == null ? void 0 : i.focus();
697
- }, 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(() => {
698
762
  this.chatWindow && !this.isOpen && (this.chatWindow.style.display = "none");
699
- }, 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);
700
770
  }
701
771
  addMessage(t, e) {
702
772
  if (!this.messagesContainer) return;
703
773
  const i = document.createElement("div");
704
- 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();
705
777
  }
706
778
  addOptions(t) {
707
779
  if (!this.messagesContainer) return;
@@ -714,11 +786,11 @@ class Y {
714
786
  this.onSubmit && this.onSubmit({ commandId: i.commandId });
715
787
  return;
716
788
  }
717
- const o = i && typeof i.value == "string" && i.value.trim() ? i.value : i.label;
718
- 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);
719
791
  };
720
- s.onclick = n, s.onkeydown = (o) => {
721
- (o.key === "Enter" || o.key === " ") && (o.preventDefault(), n());
792
+ s.onclick = n, s.onkeydown = (a) => {
793
+ (a.key === "Enter" || a.key === " ") && (a.preventDefault(), n());
722
794
  }, e.appendChild(s);
723
795
  }), this.messagesContainer.appendChild(e), this.scrollToBottom();
724
796
  }
@@ -727,57 +799,57 @@ class Y {
727
799
  this.addMessage(t, "system");
728
800
  const s = document.createElement("form");
729
801
  s.className = "foisit-form";
730
- const n = [], o = (a, p) => {
802
+ const n = [], a = (r, p) => {
731
803
  const g = document.createElement("div");
732
- return g.className = "foisit-form-label", g.innerHTML = a + (p ? ' <span class="foisit-req-star">*</span>' : ""), g;
733
- }, r = () => {
734
- const a = document.createElement("div");
735
- 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;
736
808
  };
737
- (e ?? []).forEach((a) => {
809
+ (e ?? []).forEach((r) => {
738
810
  const p = document.createElement("div");
739
811
  p.className = "foisit-form-group";
740
- const g = a.description || a.name;
741
- p.appendChild(o(g, a.required));
812
+ const g = r.description || r.name;
813
+ p.appendChild(a(g, r.required));
742
814
  let m;
743
- if (a.type === "select") {
815
+ if (r.type === "select") {
744
816
  const l = document.createElement("select");
745
817
  l.className = "foisit-form-input";
746
- const b = document.createElement("option");
747
- b.value = "", b.textContent = "Select...", l.appendChild(b);
818
+ const y = document.createElement("option");
819
+ y.value = "", y.textContent = "Select...", l.appendChild(y);
748
820
  const w = (h) => {
749
821
  (h ?? []).forEach((x) => {
750
- const y = document.createElement("option");
751
- y.value = String(x.value ?? x.label ?? ""), y.textContent = String(x.label ?? x.value ?? ""), l.appendChild(y);
822
+ const b = document.createElement("option");
823
+ b.value = String(x.value ?? x.label ?? ""), b.textContent = String(x.label ?? x.value ?? ""), l.appendChild(b);
752
824
  });
753
825
  };
754
- if (Array.isArray(a.options) && a.options.length)
755
- w(a.options);
756
- else if (typeof a.getOptions == "function") {
757
- const h = a.getOptions, x = document.createElement("option");
758
- x.value = "", x.textContent = "Loading...", l.appendChild(x), 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) => {
759
831
  for (; l.options.length > 1; ) l.remove(1);
760
- w(y);
832
+ w(b);
761
833
  }).catch(() => {
762
834
  for (; l.options.length > 1; ) l.remove(1);
763
- const y = document.createElement("option");
764
- 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);
765
837
  });
766
838
  }
767
- a.defaultValue != null && (l.value = String(a.defaultValue)), m = l;
768
- } else if (a.type === "file") {
769
- const l = a, b = document.createElement("input");
770
- 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 () => {
771
- const w = Array.from(b.files || []), h = C;
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;
772
844
  if (h.style.display = "none", h.textContent = "", w.length === 0) return;
773
845
  const x = l.maxFiles ?? (l.multiple ? 10 : 1);
774
846
  if (w.length > x) {
775
847
  h.textContent = `Please select at most ${x} file(s).`, h.style.display = "block";
776
848
  return;
777
849
  }
778
- const y = l.maxSizeBytes ?? 1 / 0, u = w.reduce((v, A) => v + A.size, 0);
779
- if (w.some((v) => v.size > y)) {
780
- 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";
781
853
  return;
782
854
  }
783
855
  const S = l.maxTotalBytes ?? 1 / 0;
@@ -787,29 +859,29 @@ class Y {
787
859
  }
788
860
  if (l.accept && Array.isArray(l.accept)) {
789
861
  const v = l.accept;
790
- if (!w.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)) {
791
863
  h.textContent = "One or more files have an unsupported type.", h.style.display = "block";
792
864
  return;
793
865
  }
794
866
  }
795
- }), m = b;
867
+ }), m = y;
796
868
  } else {
797
869
  const l = document.createElement("input");
798
- 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;
799
871
  }
800
- const C = r();
872
+ const C = o();
801
873
  p.appendChild(m), p.appendChild(C), n.push({
802
- name: a.name,
803
- type: a.type,
874
+ name: r.name,
875
+ type: r.type,
804
876
  el: m,
805
- required: a.required
877
+ required: r.required
806
878
  }), s.appendChild(p);
807
879
  });
808
880
  const c = document.createElement("div");
809
881
  c.className = "foisit-form-actions";
810
882
  const f = document.createElement("button");
811
- 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) => {
812
- 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();
813
885
  const p = {};
814
886
  let g = !1;
815
887
  s.querySelectorAll(".foisit-form-error").forEach((m) => {
@@ -819,16 +891,16 @@ class Y {
819
891
  });
820
892
  for (const m of n) {
821
893
  if (m.type === "file") {
822
- const w = m.el.parentElement, h = w == null ? void 0 : w.querySelector(".foisit-form-error"), x = m.el, y = Array.from(x.files || []);
823
- if (m.required && y.length === 0) {
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) {
824
896
  g = !0, x.classList.add("foisit-error-border"), h && (h.textContent = "This file is required", h.style.display = "block");
825
897
  continue;
826
898
  }
827
- if (y.length === 0) continue;
899
+ if (b.length === 0) continue;
828
900
  const u = (e ?? []).find((v) => v.name === m.name), S = (u == null ? void 0 : u.delivery) ?? "file";
829
901
  if (u != null && u.maxWidth || u != null && u.maxHeight)
830
902
  try {
831
- const v = await this.getImageDimensions(y[0]);
903
+ const v = await this.getImageDimensions(b[0]);
832
904
  if (u.maxWidth && v.width > u.maxWidth) {
833
905
  g = !0, h && (h.textContent = `Image width must be ≤ ${u.maxWidth}px`, h.style.display = "block");
834
906
  continue;
@@ -841,7 +913,7 @@ class Y {
841
913
  }
842
914
  if (u != null && u.maxDurationSec)
843
915
  try {
844
- const v = await this.getMediaDuration(y[0]);
916
+ const v = await this.getMediaDuration(b[0]);
845
917
  if (v && v > u.maxDurationSec) {
846
918
  g = !0, h && (h.textContent = `Media duration must be ≤ ${u.maxDurationSec}s`, h.style.display = "block");
847
919
  continue;
@@ -849,10 +921,10 @@ class Y {
849
921
  } catch {
850
922
  }
851
923
  if (S === "file")
852
- p[m.name] = u != null && u.multiple ? y : y[0];
924
+ p[m.name] = u != null && u.multiple ? b : b[0];
853
925
  else if (S === "base64")
854
926
  try {
855
- const v = await Promise.all(y.map((A) => this.readFileAsDataURL(A)));
927
+ const v = await Promise.all(b.map((E) => this.readFileAsDataURL(E)));
856
928
  p[m.name] = u != null && u.multiple ? v : v[0];
857
929
  } catch {
858
930
  g = !0, h && (h.textContent = "Failed to encode file(s) to base64.", h.style.display = "block");
@@ -860,9 +932,9 @@ class Y {
860
932
  }
861
933
  continue;
862
934
  }
863
- 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");
864
936
  if (m.required && (C == null || C === "")) {
865
- 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");
866
938
  continue;
867
939
  }
868
940
  if (C !== "")
@@ -898,9 +970,54 @@ class Y {
898
970
  scrollToBottom() {
899
971
  this.messagesContainer && (this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight);
900
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
+ }
901
1002
  destroy() {
902
1003
  var t;
903
- (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;
904
1021
  }
905
1022
  readFileAsDataURL(t) {
906
1023
  return new Promise((e, i) => {
@@ -928,16 +1045,16 @@ class Y {
928
1045
  try {
929
1046
  const i = URL.createObjectURL(t), s = t.type.startsWith("audio") ? document.createElement("audio") : document.createElement("video");
930
1047
  let n = !1;
931
- const o = setTimeout(() => {
1048
+ const a = setTimeout(() => {
932
1049
  n || (n = !0, URL.revokeObjectURL(i), e(0));
933
1050
  }, 5e3);
934
1051
  s.preload = "metadata", s.onloadedmetadata = () => {
935
1052
  if (n) return;
936
- n = !0, clearTimeout(o);
1053
+ n = !0, clearTimeout(a);
937
1054
  const c = s.duration || 0;
938
1055
  URL.revokeObjectURL(i), e(c);
939
1056
  }, s.onerror = () => {
940
- n || (n = !0, clearTimeout(o), URL.revokeObjectURL(i), e(0));
1057
+ n || (n = !0, clearTimeout(a), URL.revokeObjectURL(i), e(0));
941
1058
  }, s.src = i;
942
1059
  } catch {
943
1060
  e(0);
@@ -1268,18 +1385,104 @@ class Y {
1268
1385
  transition: transform 0.2s;
1269
1386
  }
1270
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
+ }
1271
1473
  `, document.head.appendChild(t);
1272
1474
  }
1273
1475
  }
1274
- class K {
1476
+ class Y {
1275
1477
  constructor(t) {
1276
- 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({
1277
1479
  enableSmartIntent: this.config.enableSmartIntent !== !1,
1278
1480
  intentEndpoint: this.config.intentEndpoint
1279
- }), this.fallbackHandler = new z(), typeof window < "u" && typeof document < "u" ? (this.voiceProcessor = new O(), this.textToSpeech = new H(), this.stateManager = new G(), this.gestureHandler = new U(), this.overlayManager = new Y({
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({
1280
1482
  floatingButton: this.config.floatingButton,
1281
- inputPlaceholder: this.config.inputPlaceholder
1282
- }), 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(
1283
1486
  async (e) => {
1284
1487
  if (typeof e == "string") {
1285
1488
  this.overlayManager.addMessage(e, "user"), await this.handleCommand(e);
@@ -1406,6 +1609,19 @@ class K {
1406
1609
  removeCommand(t) {
1407
1610
  console.log(`AssistantService: Removing command "${t}".`), this.commandHandler.removeCommand(t);
1408
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
+ }
1409
1625
  /** Get all registered commands */
1410
1626
  getCommands() {
1411
1627
  return this.commandHandler.getCommands();
@@ -1421,13 +1637,13 @@ class K {
1421
1637
  if (i && typeof i == "object") {
1422
1638
  const s = i, n = s.label ?? s.commandId ?? "Selection";
1423
1639
  this.overlayManager.addMessage(String(n), "user"), this.overlayManager.showLoading();
1424
- let o;
1640
+ let a;
1425
1641
  try {
1426
- o = await this.commandHandler.executeCommand(s);
1642
+ a = await this.commandHandler.executeCommand(s);
1427
1643
  } finally {
1428
1644
  this.overlayManager.hideLoading();
1429
1645
  }
1430
- this.processResponse(o);
1646
+ this.processResponse(a);
1431
1647
  }
1432
1648
  },
1433
1649
  () => {
@@ -1437,20 +1653,20 @@ class K {
1437
1653
  }
1438
1654
  }
1439
1655
  const q = F(null);
1440
- let I = null;
1656
+ let M = null;
1441
1657
  const Z = ({ config: d, children: t }) => {
1442
- const [e, i] = R(null), [s, n] = R(!1);
1443
- return P(() => {
1444
- I ? console.warn(
1658
+ const [e, i] = I(null), [s, n] = I(!1);
1659
+ return $(() => {
1660
+ M ? console.warn(
1445
1661
  "Multiple AssistantProvider instances detected. Reusing global AssistantService."
1446
- ) : (console.log("Initializing global AssistantService..."), I = new K(d));
1447
- const o = I;
1448
- return i(o), n(!0), () => {
1449
- var r;
1450
- 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;
1451
1667
  };
1452
1668
  }, [d]), !s || !e ? /* @__PURE__ */ k("div", { children: "Loading Assistant..." }) : /* @__PURE__ */ k(q.Provider, { value: e, children: t });
1453
- }, X = () => {
1669
+ }, K = () => {
1454
1670
  const d = N(q);
1455
1671
  if (console.log("assistant", d), !d)
1456
1672
  throw new Error("useAssistant must be used within an AssistantProvider");
@@ -1459,13 +1675,13 @@ const Z = ({ config: d, children: t }) => {
1459
1675
  label: d = "Activate Assistant",
1460
1676
  onActivate: t
1461
1677
  }) => {
1462
- const e = X();
1678
+ const e = K();
1463
1679
  return /* @__PURE__ */ k("button", { onClick: () => {
1464
1680
  t && t(), e.reactivate();
1465
1681
  }, className: "assistant-activator", children: d });
1466
1682
  }, et = (d) => {
1467
- const [t, e] = R(d.getState());
1468
- return P(() => {
1683
+ const [t, e] = I(d.getState());
1684
+ return $(() => {
1469
1685
  const i = (s) => {
1470
1686
  e(s);
1471
1687
  };
@@ -1479,8 +1695,8 @@ export {
1479
1695
  tt as AssistantActivator,
1480
1696
  q as AssistantContext,
1481
1697
  Z as AssistantProvider,
1482
- K as AssistantService,
1483
- J as ReactWrapper,
1484
- X as useAssistant,
1698
+ Y as AssistantService,
1699
+ Q as ReactWrapper,
1700
+ K as useAssistant,
1485
1701
  et as useAssistantState
1486
1702
  };