@foisit/angular-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.
@@ -1,288 +1,2102 @@
1
- import * as i0 from '@angular/core';
2
- import { Component, Inject, Injectable, NgModule } from '@angular/core';
3
- import { CommonModule } from '@angular/common';
4
- import { CommandHandler, FallbackHandler, VoiceProcessor, TextToSpeech, StateManager, GestureHandler, OverlayManager } from '@foisit/core';
5
-
6
- class AngularWrapperComponent {
7
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AngularWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.0.7", type: AngularWrapperComponent, isStandalone: true, selector: "lib-angular-wrapper", ngImport: i0, template: "<p>AngularWrapper works!</p>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
9
- }
10
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AngularWrapperComponent, decorators: [{
11
- type: Component,
12
- args: [{ selector: 'lib-angular-wrapper', imports: [CommonModule], template: "<p>AngularWrapper works!</p>\n" }]
13
- }] });
14
-
15
- class AssistantService {
16
- config;
17
- commandHandler;
18
- fallbackHandler;
19
- voiceProcessor;
20
- textToSpeech;
21
- stateManager;
22
- lastProcessedInput = '';
23
- processingLock = false;
24
- isActivated = false; // Tracks activation status
25
- gestureHandler;
26
- overlayManager;
27
- defaultIntroMessage = 'How can I help you?';
28
- constructor(config) {
29
- this.config = config;
30
- // Pass enableSmartIntent (default true) and potential apiKey (if we add it to config later)
31
- this.commandHandler = new CommandHandler({
32
- enableSmartIntent: this.config.enableSmartIntent !== false,
33
- intentEndpoint: this.config.intentEndpoint,
34
- });
35
- this.fallbackHandler = new FallbackHandler();
36
- this.voiceProcessor = new VoiceProcessor();
37
- this.textToSpeech = new TextToSpeech();
38
- this.stateManager = new StateManager();
39
- this.gestureHandler = new GestureHandler();
40
- this.overlayManager = new OverlayManager({
41
- floatingButton: this.config.floatingButton,
42
- inputPlaceholder: this.config.inputPlaceholder,
1
+ // dist/libs/angular-wrapper/fesm2022/foisit-angular-wrapper.mjs
2
+ import * as i0 from "@angular/core";
3
+ import { Component, Inject, Injectable, NgModule } from "@angular/core";
4
+ import { CommonModule } from "@angular/common";
5
+
6
+ // dist/libs/core/src/lib/command/command-handler.js
7
+ import { __awaiter as __awaiter2 } from "tslib";
8
+
9
+ // dist/libs/core/src/lib/ai/openai.service.js
10
+ import { __awaiter } from "tslib";
11
+ var OpenAIService = class {
12
+ constructor(endpoint) {
13
+ this.endpoint = endpoint || "https://foisit-ninja.netlify.app/.netlify/functions/intent";
14
+ }
15
+ determineIntent(userInput, availableCommands, context) {
16
+ return __awaiter(this, void 0, void 0, function* () {
17
+ try {
18
+ const payload = {
19
+ userInput,
20
+ commands: availableCommands.map((cmd) => ({
21
+ id: cmd.id,
22
+ command: cmd.command,
23
+ description: cmd.description,
24
+ parameters: cmd.parameters
25
+ // Send param schemas to AI
26
+ })),
27
+ context
28
+ };
29
+ const response = yield fetch(this.endpoint, {
30
+ method: "POST",
31
+ headers: {
32
+ "Content-Type": "application/json"
33
+ },
34
+ body: JSON.stringify(payload)
43
35
  });
44
- // Setup commands from config
45
- this.config.commands.forEach((cmd) => this.commandHandler.addCommand(cmd));
46
- // Setup fallback response
47
- if (this.config.fallbackResponse) {
48
- this.fallbackHandler.setFallbackMessage(this.config.fallbackResponse);
49
- }
50
- // Voice disabled for text-first pivot
51
- // this.startListening();
52
- // Setup double-tap/double-click listener
53
- this.gestureHandler.setupDoubleTapListener(() => this.toggle());
54
- // Register global callbacks for floating button
55
- this.overlayManager.registerCallbacks(async (input) => {
56
- if (typeof input === 'string') {
57
- this.overlayManager.addMessage(input, 'user');
58
- }
59
- else if (input && typeof input === 'object') {
60
- const label = this.extractUserLabel(input);
61
- if (label) {
62
- this.overlayManager.addMessage(label, 'user');
63
- }
64
- }
65
- await this.handleCommand(input);
66
- }, () => console.log('AssistantService: Overlay closed.'));
36
+ if (!response.ok) {
37
+ throw new Error(`Proxy API Error: ${response.statusText}`);
38
+ }
39
+ const result = yield response.json();
40
+ return result;
41
+ } catch (error) {
42
+ console.error("OpenAIService Error:", error);
43
+ return { type: "unknown" };
44
+ }
45
+ });
46
+ }
47
+ };
48
+
49
+ // dist/libs/core/src/lib/command/command-handler.js
50
+ var CommandHandler = class {
51
+ constructor(arg = true) {
52
+ var _a;
53
+ this.commands = /* @__PURE__ */ new Map();
54
+ this.openAIService = null;
55
+ this.context = null;
56
+ this.pendingConfirmation = null;
57
+ this.enableSmartIntent = true;
58
+ this.selectOptionsCache = /* @__PURE__ */ new Map();
59
+ if (typeof arg === "boolean") {
60
+ this.enableSmartIntent = arg;
61
+ if (this.enableSmartIntent) {
62
+ this.openAIService = new OpenAIService();
63
+ }
64
+ return;
67
65
  }
68
- /** Start listening for activation and commands */
69
- startListening() {
70
- // Voice is currently disabled (text-only mode)
71
- console.log('AssistantService: Voice is disabled; startListening() is a no-op.');
72
- return;
73
- /*
74
- this.voiceProcessor.startListening(async (transcript: string) => {
75
- if (this.processingLock) return;
76
-
77
- const normalizedTranscript = transcript.toLowerCase().trim();
78
-
79
- // Skip repeated or irrelevant inputs
80
- if (
81
- !normalizedTranscript ||
82
- normalizedTranscript.length < 3 ||
83
- normalizedTranscript === this.lastProcessedInput
84
- ) {
85
- console.log('AssistantService: Ignoring irrelevant input.');
86
- return;
66
+ this.enableSmartIntent = (_a = arg.enableSmartIntent) !== null && _a !== void 0 ? _a : true;
67
+ if (this.enableSmartIntent) {
68
+ this.openAIService = new OpenAIService(arg.intentEndpoint);
69
+ }
70
+ }
71
+ /** Add a new command (string or object) */
72
+ addCommand(commandOrObj, action) {
73
+ let cmd;
74
+ if (typeof commandOrObj === "string") {
75
+ if (!action) {
76
+ throw new Error("Action required when adding command by string.");
77
+ }
78
+ cmd = {
79
+ id: commandOrObj.toLowerCase().replace(/\s+/g, "_"),
80
+ command: commandOrObj.toLowerCase(),
81
+ action
82
+ };
83
+ } else {
84
+ cmd = Object.assign({}, commandOrObj);
85
+ if (!cmd.id) {
86
+ cmd.id = cmd.command.toLowerCase().replace(/\s+/g, "_");
87
+ }
88
+ }
89
+ this.commands.set(cmd.command.toLowerCase(), cmd);
90
+ if (cmd.id && cmd.id !== cmd.command) {
91
+ }
92
+ }
93
+ /** Remove an existing command */
94
+ removeCommand(command) {
95
+ this.commands.delete(command.toLowerCase());
96
+ }
97
+ /** Execute a command by matching input */
98
+ executeCommand(input) {
99
+ return __awaiter2(this, void 0, void 0, function* () {
100
+ var _a, _b, _c;
101
+ if (typeof input === "object" && input !== null) {
102
+ if (this.isStructured(input)) {
103
+ const cmdId = String(input.commandId);
104
+ const rawParams = (_a = input.params) !== null && _a !== void 0 ? _a : {};
105
+ const cmd2 = this.getCommandById(cmdId);
106
+ if (!cmd2)
107
+ return { message: "That command is not available.", type: "error" };
108
+ const params = this.sanitizeParamsForCommand(cmd2, rawParams);
109
+ const requiredParams = ((_b = cmd2.parameters) !== null && _b !== void 0 ? _b : []).filter((p) => p.required);
110
+ const missing = requiredParams.filter((p) => params[p.name] == null || params[p.name] === "");
111
+ if (missing.length > 0) {
112
+ this.context = { commandId: this.getCommandIdentifier(cmd2), params };
113
+ return {
114
+ message: `Please provide the required details for "${cmd2.command}".`,
115
+ type: "form",
116
+ fields: missing
117
+ };
87
118
  }
88
-
89
- this.lastProcessedInput = normalizedTranscript;
90
-
91
- if (!this.isActivated) {
92
- await this.processActivation(normalizedTranscript);
93
- return;
119
+ if (cmd2.critical) {
120
+ this.pendingConfirmation = { commandId: this.getCommandIdentifier(cmd2), params };
121
+ return this.buildConfirmResponse(cmd2);
94
122
  }
95
-
96
- const isFallbackOrIntroMessage =
97
- normalizedTranscript === this.config.fallbackResponse?.toLowerCase() ||
98
- normalizedTranscript === this.config.introMessage?.toLowerCase() ||
99
- normalizedTranscript === this.defaultIntroMessage.toLowerCase();
100
-
101
- if (!isFallbackOrIntroMessage) {
102
- await this.handleCommand(normalizedTranscript);
123
+ return this.safeRunAction(cmd2, params);
124
+ }
125
+ if (!this.context) {
126
+ return { message: "Session expired or invalid context.", type: "error" };
127
+ }
128
+ const cmd = this.getCommandById(this.context.commandId);
129
+ if (!cmd) {
130
+ this.context = null;
131
+ return { message: "Session expired or invalid context.", type: "error" };
132
+ }
133
+ if (Array.isArray(input)) {
134
+ return { message: "Invalid form payload.", type: "error" };
135
+ }
136
+ const mergedParams = Object.assign(Object.assign({}, this.context.params), input);
137
+ if (cmd.critical) {
138
+ this.context = null;
139
+ this.pendingConfirmation = {
140
+ commandId: this.getCommandIdentifier(cmd),
141
+ params: mergedParams
142
+ };
143
+ return this.buildConfirmResponse(cmd);
144
+ }
145
+ const result = yield this.safeRunAction(cmd, mergedParams);
146
+ this.context = null;
147
+ return this.normalizeResponse(result);
148
+ }
149
+ const trimmedInput = input.trim().toLowerCase();
150
+ if (this.pendingConfirmation) {
151
+ const normalized = trimmedInput;
152
+ if (["yes", "y", "confirm", "ok", "okay"].includes(normalized)) {
153
+ const { commandId, params } = this.pendingConfirmation;
154
+ this.pendingConfirmation = null;
155
+ const cmd = this.getCommandById(commandId);
156
+ if (!cmd) {
157
+ return { message: "That action is no longer available.", type: "error" };
158
+ }
159
+ return this.safeRunAction(cmd, params);
160
+ }
161
+ if (["no", "n", "cancel", "stop"].includes(normalized)) {
162
+ this.pendingConfirmation = null;
163
+ return { message: "Cancelled.", type: "success" };
164
+ }
165
+ return {
166
+ message: "Please confirm: Yes or No.",
167
+ type: "confirm",
168
+ options: [
169
+ { label: "Yes", value: "yes" },
170
+ { label: "No", value: "no" }
171
+ ]
172
+ };
173
+ }
174
+ const exactCommand = this.commands.get(trimmedInput);
175
+ if (exactCommand) {
176
+ const cmd = exactCommand;
177
+ const requiredParams = ((_c = cmd.parameters) !== null && _c !== void 0 ? _c : []).filter((p) => p.required);
178
+ if (requiredParams.length > 0) {
179
+ this.context = { commandId: this.getCommandIdentifier(cmd), params: {} };
180
+ return {
181
+ message: `Please provide the required details for "${cmd.command}".`,
182
+ type: "form",
183
+ fields: requiredParams
184
+ };
185
+ }
186
+ if (cmd.critical) {
187
+ this.pendingConfirmation = { commandId: this.getCommandIdentifier(cmd), params: {} };
188
+ return this.buildConfirmResponse(cmd);
189
+ }
190
+ return this.safeRunAction(cmd, {});
191
+ }
192
+ const deterministic = yield this.tryDeterministicMatch(trimmedInput);
193
+ if (deterministic)
194
+ return deterministic;
195
+ if (this.enableSmartIntent && this.openAIService) {
196
+ const availableCommands = yield this.getCommandsForAI();
197
+ const aiResult = yield this.openAIService.determineIntent(trimmedInput, availableCommands, this.context);
198
+ return this.handleAIResult(aiResult);
199
+ }
200
+ if (!this.enableSmartIntent) {
201
+ return { message: "I'm not sure what you mean.", type: "error" };
202
+ }
203
+ return this.listAllCommands();
204
+ });
205
+ }
206
+ handleAIResult(result) {
207
+ return __awaiter2(this, void 0, void 0, function* () {
208
+ var _a, _b;
209
+ if (result.type === "match" && result.match) {
210
+ const cmd = this.getCommandById(result.match);
211
+ if (!cmd) {
212
+ return { message: "I'm not sure what you mean.", type: "error" };
213
+ }
214
+ const rawParams = (_a = result.params) !== null && _a !== void 0 ? _a : {};
215
+ const sanitizedParams = this.sanitizeParamsForCommand(cmd, rawParams);
216
+ const params = cmd.allowAiParamExtraction === false ? {} : sanitizedParams;
217
+ const requiredParams = ((_b = cmd.parameters) !== null && _b !== void 0 ? _b : []).filter((p) => p.required);
218
+ const missingRequired = requiredParams.filter((p) => params[p.name] == null || params[p.name] === "");
219
+ if (result.incomplete || missingRequired.length > 0) {
220
+ this.context = { commandId: this.getCommandIdentifier(cmd), params };
221
+ const mustUseForm = cmd.collectRequiredViaForm !== false;
222
+ const askSingle = !mustUseForm && this.shouldAskSingleQuestion(missingRequired);
223
+ if (askSingle) {
224
+ const names = missingRequired.map((p) => p.name).join(" and ");
225
+ return {
226
+ message: result.message || `Please provide ${names}.`,
227
+ type: "question"
228
+ };
229
+ }
230
+ return {
231
+ message: result.message || `Please fill in the missing details for "${cmd.command}".`,
232
+ type: "form",
233
+ fields: missingRequired
234
+ };
235
+ }
236
+ if (cmd.critical) {
237
+ this.pendingConfirmation = {
238
+ commandId: this.getCommandIdentifier(cmd),
239
+ params
240
+ };
241
+ return this.buildConfirmResponse(cmd);
242
+ }
243
+ const actionResult = yield cmd.action(params);
244
+ return this.normalizeResponse(actionResult);
245
+ }
246
+ if (result.type === "ambiguous" && result.options && result.options.length) {
247
+ return {
248
+ message: result.message || "Did you mean one of these?",
249
+ type: "ambiguous",
250
+ options: result.options.map((o) => {
251
+ var _a2;
252
+ return {
253
+ label: o.label,
254
+ value: (_a2 = o.commandId) !== null && _a2 !== void 0 ? _a2 : o.label,
255
+ commandId: o.commandId
256
+ };
257
+ })
258
+ };
259
+ }
260
+ return this.listAllCommands();
261
+ });
262
+ }
263
+ sanitizeParamsForCommand(cmd, params) {
264
+ var _a, _b;
265
+ const sanitized = Object.assign({}, params !== null && params !== void 0 ? params : {});
266
+ for (const p of (_a = cmd.parameters) !== null && _a !== void 0 ? _a : []) {
267
+ const value = sanitized[p.name];
268
+ if (p.type === "string") {
269
+ if (typeof value !== "string") {
270
+ delete sanitized[p.name];
271
+ continue;
272
+ }
273
+ const trimmed = value.trim();
274
+ if (!trimmed) {
275
+ delete sanitized[p.name];
276
+ continue;
277
+ }
278
+ sanitized[p.name] = trimmed;
279
+ }
280
+ if (p.type === "number") {
281
+ const numeric = typeof value === "number" ? value : Number(value === null || value === void 0 ? void 0 : value.toString().trim());
282
+ if (Number.isNaN(numeric)) {
283
+ delete sanitized[p.name];
284
+ continue;
285
+ }
286
+ if (typeof p.min === "number" && numeric < p.min) {
287
+ delete sanitized[p.name];
288
+ continue;
289
+ }
290
+ if (typeof p.max === "number" && numeric > p.max) {
291
+ delete sanitized[p.name];
292
+ continue;
293
+ }
294
+ sanitized[p.name] = numeric;
295
+ }
296
+ if (p.type === "date") {
297
+ const v = sanitized[p.name];
298
+ if (typeof v === "string") {
299
+ const s = v.trim();
300
+ if (!this.isIsoDateString(s)) {
301
+ delete sanitized[p.name];
103
302
  } else {
104
- console.log('AssistantService: Ignoring fallback or intro message.');
303
+ sanitized[p.name] = s;
105
304
  }
106
-
107
- // Throttle input processing to prevent rapid feedback
108
- this.processingLock = true;
109
- setTimeout(() => (this.processingLock = false), 1000);
110
- });
111
- */
112
- }
113
- /** Stop listening for voice input */
114
- stopListening() {
115
- // Voice is currently disabled (text-only mode)
116
- console.log('AssistantService: Voice is disabled; stopListening() is a no-op.');
117
- this.isActivated = false;
118
- }
119
- /** Reset activation state so the next activation flow can occur. */
120
- reactivate() {
121
- console.log('AssistantService: Reactivating assistant...');
122
- this.isActivated = false;
123
- try {
124
- this.startListening();
305
+ } else {
306
+ delete sanitized[p.name];
307
+ }
308
+ }
309
+ if (p.type === "select") {
310
+ const v = typeof value === "string" ? value : value === null || value === void 0 ? void 0 : value.toString();
311
+ if (!v) {
312
+ delete sanitized[p.name];
313
+ continue;
125
314
  }
126
- catch {
127
- // no-op
315
+ if (Array.isArray(p.options) && p.options.length > 0) {
316
+ const allowed = p.options.some((opt) => String(opt.value) === String(v));
317
+ if (!allowed) {
318
+ delete sanitized[p.name];
319
+ continue;
320
+ }
321
+ }
322
+ sanitized[p.name] = v;
323
+ }
324
+ if (p.type === "file") {
325
+ const val = sanitized[p.name];
326
+ const isFileLike = val && typeof val === "object" && typeof val.name === "string" && typeof val.size === "number";
327
+ const isDataUrl = typeof val === "string" && /^data:[^;]+;base64,/.test(val);
328
+ const delivery = (_b = p.delivery) !== null && _b !== void 0 ? _b : "file";
329
+ if (delivery === "base64") {
330
+ if (!isDataUrl && !isFileLike) {
331
+ delete sanitized[p.name];
332
+ }
333
+ } else {
334
+ if (!isFileLike) {
335
+ delete sanitized[p.name];
336
+ }
128
337
  }
338
+ }
129
339
  }
130
- /** Process activation command */
131
- async processActivation(transcript) {
132
- const activationCmd = this.config.activationCommand?.toLowerCase();
133
- // If no activation command is set, we can't activate via voice
134
- if (!activationCmd)
340
+ return sanitized;
341
+ }
342
+ isIsoDateString(value) {
343
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value))
344
+ return false;
345
+ const dt = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
346
+ return !Number.isNaN(dt.getTime());
347
+ }
348
+ shouldAskSingleQuestion(missing) {
349
+ if (missing.length !== 1)
350
+ return false;
351
+ const t = missing[0].type;
352
+ return t === "string" || t === "number" || t === "date";
353
+ }
354
+ buildConfirmResponse(cmd) {
355
+ return {
356
+ message: `Are you sure you want to run "${cmd.command}"?`,
357
+ type: "confirm",
358
+ options: [
359
+ { label: "Yes", value: "yes" },
360
+ { label: "No", value: "no" }
361
+ ]
362
+ };
363
+ }
364
+ tryDeterministicMatch(input) {
365
+ return __awaiter2(this, void 0, void 0, function* () {
366
+ var _a, _b;
367
+ const candidates = [];
368
+ for (const cmd2 of this.commands.values()) {
369
+ let score = 0;
370
+ const commandPhrase = cmd2.command.toLowerCase();
371
+ if (input.includes(commandPhrase))
372
+ score += 5;
373
+ const keywords = (_a = cmd2.keywords) !== null && _a !== void 0 ? _a : [];
374
+ for (const kw of keywords) {
375
+ const k = kw.toLowerCase().trim();
376
+ if (!k)
377
+ continue;
378
+ if (input === k)
379
+ score += 4;
380
+ else if (input.includes(k))
381
+ score += 3;
382
+ }
383
+ if (score > 0)
384
+ candidates.push({ cmd: cmd2, score });
385
+ }
386
+ if (candidates.length === 0)
387
+ return null;
388
+ candidates.sort((a, b) => b.score - a.score);
389
+ const topScore = candidates[0].score;
390
+ const top = candidates.filter((c) => c.score === topScore).slice(0, 3);
391
+ if (top.length > 1) {
392
+ return {
393
+ message: "I think you mean one of these. Which one should I run?",
394
+ type: "ambiguous",
395
+ options: top.map((c) => ({
396
+ label: c.cmd.command,
397
+ value: c.cmd.command,
398
+ commandId: c.cmd.id
399
+ }))
400
+ };
401
+ }
402
+ const cmd = top[0].cmd;
403
+ const requiredParams = ((_b = cmd.parameters) !== null && _b !== void 0 ? _b : []).filter((p) => p.required);
404
+ if (requiredParams.length > 0) {
405
+ this.context = { commandId: this.getCommandIdentifier(cmd), params: {} };
406
+ return {
407
+ message: `Please provide the required details for "${cmd.command}".`,
408
+ type: "form",
409
+ fields: requiredParams
410
+ };
411
+ }
412
+ if (cmd.critical) {
413
+ this.pendingConfirmation = { commandId: this.getCommandIdentifier(cmd), params: {} };
414
+ return this.buildConfirmResponse(cmd);
415
+ }
416
+ return this.safeRunAction(cmd, {});
417
+ });
418
+ }
419
+ safeRunAction(cmd, params) {
420
+ return __awaiter2(this, void 0, void 0, function* () {
421
+ try {
422
+ const result = yield cmd.action(params !== null && params !== void 0 ? params : {});
423
+ return this.normalizeResponse(result);
424
+ } catch (_a) {
425
+ return { message: "Something went wrong while running that command.", type: "error" };
426
+ }
427
+ });
428
+ }
429
+ getCommandsForAI() {
430
+ return __awaiter2(this, void 0, void 0, function* () {
431
+ const commands = Array.from(this.commands.values()).map((cmd) => Object.assign(Object.assign({}, cmd), { parameters: cmd.parameters ? cmd.parameters.map((param) => Object.assign({}, param)) : void 0 }));
432
+ yield Promise.all(commands.map((cmd) => __awaiter2(this, void 0, void 0, function* () {
433
+ if (!cmd.parameters)
434
+ return;
435
+ yield Promise.all(cmd.parameters.map((p) => __awaiter2(this, void 0, void 0, function* () {
436
+ var _a;
437
+ if (p.type !== "select" || !p.getOptions || p.options && p.options.length)
438
+ return;
439
+ const cacheKey = `${(_a = cmd.id) !== null && _a !== void 0 ? _a : cmd.command}:${p.name}`;
440
+ const cached = this.selectOptionsCache.get(cacheKey);
441
+ const now = Date.now();
442
+ if (cached && now - cached.ts < 6e4) {
443
+ p.options = cached.options;
135
444
  return;
136
- if (transcript === activationCmd) {
137
- console.log('AssistantService: Activation matched.');
138
- this.isActivated = true;
139
- this.textToSpeech.speak(this.config.introMessage || this.defaultIntroMessage);
140
- // this.stateManager.setState('listening'); // DISABLED - no gradient animation
445
+ }
446
+ try {
447
+ const opts = yield p.getOptions();
448
+ this.selectOptionsCache.set(cacheKey, { options: opts, ts: now });
449
+ p.options = opts;
450
+ } catch (_b) {
451
+ }
452
+ })));
453
+ })));
454
+ return commands;
455
+ });
456
+ }
457
+ getCommandById(id) {
458
+ for (const cmd of this.commands.values()) {
459
+ if (cmd.id === id)
460
+ return cmd;
461
+ }
462
+ return void 0;
463
+ }
464
+ listAllCommands() {
465
+ const options = Array.from(this.commands.values()).map((cmd) => {
466
+ var _a, _b;
467
+ return {
468
+ label: cmd.command,
469
+ value: (_a = cmd.id) !== null && _a !== void 0 ? _a : cmd.command,
470
+ commandId: (_b = cmd.id) !== null && _b !== void 0 ? _b : cmd.command
471
+ };
472
+ });
473
+ return {
474
+ message: "Here are the available commands:",
475
+ type: "ambiguous",
476
+ options
477
+ };
478
+ }
479
+ normalizeResponse(result) {
480
+ if (typeof result === "string") {
481
+ return { message: result, type: "success" };
482
+ }
483
+ if (result && typeof result === "object") {
484
+ return result;
485
+ }
486
+ return { message: "Done", type: "success" };
487
+ }
488
+ isStructured(input) {
489
+ return typeof input["commandId"] === "string";
490
+ }
491
+ getCommandIdentifier(cmd) {
492
+ if (!cmd.id) {
493
+ cmd.id = cmd.command.toLowerCase().replace(/\s+/g, "_");
494
+ }
495
+ return cmd.id;
496
+ }
497
+ /** List all registered commands */
498
+ getCommands() {
499
+ return Array.from(this.commands.keys());
500
+ }
501
+ };
502
+
503
+ // dist/libs/core/src/lib/speech/text-to-speech.js
504
+ var TextToSpeech = class {
505
+ constructor() {
506
+ this.synth = window.speechSynthesis;
507
+ }
508
+ speak(text, options) {
509
+ if (!this.synth) {
510
+ console.error("SpeechSynthesis API is not supported in this browser.");
511
+ return;
512
+ }
513
+ const utterance = new SpeechSynthesisUtterance(text);
514
+ if (options) {
515
+ utterance.pitch = options.pitch || 1;
516
+ utterance.rate = options.rate || 1;
517
+ utterance.volume = options.volume || 1;
518
+ }
519
+ utterance.onstart = () => {
520
+ window.dispatchEvent(new CustomEvent("foisit:tts-start"));
521
+ };
522
+ utterance.onend = () => {
523
+ console.log("Speech finished.");
524
+ window.dispatchEvent(new CustomEvent("foisit:tts-end"));
525
+ };
526
+ utterance.onerror = (event) => {
527
+ console.error("Error during speech synthesis:", event.error);
528
+ };
529
+ this.synth.speak(utterance);
530
+ }
531
+ stopSpeaking() {
532
+ if (this.synth) {
533
+ this.synth.cancel();
534
+ }
535
+ }
536
+ };
537
+
538
+ // dist/libs/core/src/lib/fallback/fallback-handler.js
539
+ var FallbackHandler = class {
540
+ constructor() {
541
+ this.fallbackMessage = "Sorry, I didn\u2019t understand that.";
542
+ }
543
+ setFallbackMessage(message) {
544
+ this.fallbackMessage = message;
545
+ }
546
+ handleFallback(transcript) {
547
+ if (transcript)
548
+ console.log(`Fallback triggered for: "${transcript}"`);
549
+ console.log(this.fallbackMessage);
550
+ new TextToSpeech().speak(this.fallbackMessage);
551
+ }
552
+ getFallbackMessage() {
553
+ return this.fallbackMessage;
554
+ }
555
+ };
556
+
557
+ // dist/libs/core/src/lib/speech/voice-processor.js
558
+ import { __awaiter as __awaiter3 } from "tslib";
559
+ var getRecognitionCtor = () => {
560
+ var _a, _b;
561
+ const w = window;
562
+ return (_b = (_a = w.SpeechRecognition) !== null && _a !== void 0 ? _a : w.webkitSpeechRecognition) !== null && _b !== void 0 ? _b : null;
563
+ };
564
+ var VoiceProcessor = class {
565
+ // Debug logger helpers
566
+ log(message) {
567
+ if (this.debugEnabled && message) {
568
+ console.log("[VoiceProcessor]", message);
569
+ }
570
+ }
571
+ warn(message) {
572
+ if (this.debugEnabled && message) {
573
+ console.warn("[VoiceProcessor]", message);
574
+ }
575
+ }
576
+ error(message) {
577
+ if (this.debugEnabled && message) {
578
+ console.error("[VoiceProcessor]", message);
579
+ }
580
+ }
581
+ constructor(language = "en-US", options = {}) {
582
+ var _a, _b;
583
+ this.recognition = null;
584
+ this.isListening = false;
585
+ this.engineActive = false;
586
+ this.intentionallyStopped = false;
587
+ this.restartAllowed = true;
588
+ this.lastStart = 0;
589
+ this.backoffMs = 250;
590
+ this.destroyed = false;
591
+ this.resultCallback = null;
592
+ this.ttsSpeaking = false;
593
+ this.debugEnabled = true;
594
+ this.restartTimer = null;
595
+ this.prewarmed = false;
596
+ this.hadResultThisSession = false;
597
+ this.onTTSStart = () => {
598
+ var _a2;
599
+ this.ttsSpeaking = true;
600
+ try {
601
+ (_a2 = this.recognition) === null || _a2 === void 0 ? void 0 : _a2.stop();
602
+ } catch (_b2) {
603
+ }
604
+ if (this.isListening) {
605
+ this.emitStatus("speaking");
606
+ }
607
+ };
608
+ this.onTTSEnd = () => {
609
+ this.ttsSpeaking = false;
610
+ if (this.isListening && this.restartAllowed) {
611
+ this.safeRestart();
612
+ } else {
613
+ this.emitStatus(this.isListening ? "listening" : "idle");
614
+ }
615
+ };
616
+ const Ctor = getRecognitionCtor();
617
+ if (Ctor) {
618
+ this.recognition = new Ctor();
619
+ this.recognition.lang = language;
620
+ this.recognition.interimResults = (_a = options.interimResults) !== null && _a !== void 0 ? _a : true;
621
+ this.recognition.continuous = (_b = options.continuous) !== null && _b !== void 0 ? _b : true;
622
+ this.recognition.onresult = (event) => this.handleResult(event, options);
623
+ this.recognition.onend = () => this.handleEnd();
624
+ this.recognition.onstart = () => {
625
+ this.log("recognition onstart");
626
+ this.engineActive = true;
627
+ this.hadResultThisSession = false;
628
+ if (this.restartTimer) {
629
+ clearTimeout(this.restartTimer);
630
+ this.restartTimer = null;
141
631
  }
142
- else {
143
- console.log('AssistantService: Activation command not recognized.');
632
+ this.backoffMs = 250;
633
+ if (this.isListening && !this.ttsSpeaking) {
634
+ this.emitStatus("listening");
144
635
  }
636
+ };
637
+ const vrec = this.recognition;
638
+ vrec.onaudiostart = () => this.log("onaudiostart");
639
+ vrec.onsoundstart = () => this.log("onsoundstart");
640
+ vrec.onspeechstart = () => this.log("onspeechstart");
641
+ vrec.onspeechend = () => this.log("onspeechend");
642
+ vrec.onsoundend = () => this.log("onsoundend");
643
+ vrec.onaudioend = () => this.log("onaudioend");
644
+ this.recognition.onerror = (event) => this.handleError(event);
645
+ } else {
646
+ this.recognition = null;
647
+ this.emitStatus("unsupported");
145
648
  }
146
- /** Handle recognized commands */
147
- async handleCommand(input) {
148
- this.overlayManager.showLoading();
149
- let response;
649
+ window.addEventListener("foisit:tts-start", this.onTTSStart);
650
+ window.addEventListener("foisit:tts-end", this.onTTSEnd);
651
+ this.visibilityHandler = () => {
652
+ var _a2;
653
+ if (document.hidden) {
150
654
  try {
151
- response = await this.commandHandler.executeCommand(input);
152
- }
153
- finally {
154
- this.overlayManager.hideLoading();
155
- }
156
- const originalText = typeof input === 'string' ? input : undefined;
157
- this.processResponse(response, originalText);
158
- }
159
- /**
160
- * Cleanup resources
161
- */
162
- destroy() {
163
- this.voiceProcessor.stopListening();
164
- this.gestureHandler.destroy();
165
- this.overlayManager.destroy();
166
- }
167
- /** Unified response processing */
168
- processResponse(response, originalText) {
169
- if (response.type === 'error' && originalText) {
170
- this.fallbackHandler.handleFallback(originalText);
171
- this.overlayManager.addMessage(this.fallbackHandler.getFallbackMessage(), 'system');
655
+ (_a2 = this.recognition) === null || _a2 === void 0 ? void 0 : _a2.stop();
656
+ } catch (_b2) {
657
+ }
658
+ this.emitStatus(this.ttsSpeaking ? "speaking" : "idle");
659
+ } else if (this.isListening && !this.ttsSpeaking) {
660
+ this.safeRestart();
661
+ }
662
+ };
663
+ document.addEventListener("visibilitychange", this.visibilityHandler);
664
+ }
665
+ /** Check if SpeechRecognition is available */
666
+ isSupported() {
667
+ return getRecognitionCtor() !== null;
668
+ }
669
+ /** Allow consumers (wrappers) to observe status changes */
670
+ onStatusChange(callback) {
671
+ this.statusCallback = callback;
672
+ }
673
+ /** Start listening for speech input */
674
+ startListening(callback) {
675
+ if (!this.isSupported() || !this.recognition) {
676
+ this.warn("VoiceProcessor: SpeechRecognition is not supported in this browser.");
677
+ this.emitStatus("unsupported");
678
+ return;
679
+ }
680
+ if (this.isListening) {
681
+ this.warn("VoiceProcessor: Already listening.");
682
+ this.resultCallback = callback;
683
+ return;
684
+ }
685
+ this.resultCallback = callback;
686
+ this.intentionallyStopped = false;
687
+ this.restartAllowed = true;
688
+ this.isListening = true;
689
+ this.emitStatus("listening");
690
+ this.prewarmAudio().finally(() => {
691
+ this.safeRestart();
692
+ });
693
+ }
694
+ /** Stop listening for speech input */
695
+ stopListening() {
696
+ var _a;
697
+ this.intentionallyStopped = true;
698
+ this.restartAllowed = false;
699
+ this.isListening = false;
700
+ this.emitStatus(this.ttsSpeaking ? "speaking" : "idle");
701
+ try {
702
+ (_a = this.recognition) === null || _a === void 0 ? void 0 : _a.stop();
703
+ } catch (_b) {
704
+ }
705
+ }
706
+ /** Clean up listeners */
707
+ destroy() {
708
+ this.destroyed = true;
709
+ this.stopListening();
710
+ this.resultCallback = null;
711
+ window.removeEventListener("foisit:tts-start", this.onTTSStart);
712
+ window.removeEventListener("foisit:tts-end", this.onTTSEnd);
713
+ if (this.visibilityHandler) {
714
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
715
+ this.visibilityHandler = void 0;
716
+ }
717
+ }
718
+ /** Handle recognized speech results */
719
+ handleResult(event, options) {
720
+ var _a, _b, _c, _d;
721
+ if (!this.resultCallback)
722
+ return;
723
+ const threshold = (_a = options.confidenceThreshold) !== null && _a !== void 0 ? _a : 0.6;
724
+ for (let i = event.resultIndex; i < event.results.length; i++) {
725
+ const res = event.results[i];
726
+ const alt = res && res[0];
727
+ const transcript = ((_c = (_b = alt === null || alt === void 0 ? void 0 : alt.transcript) === null || _b === void 0 ? void 0 : _b.trim) === null || _c === void 0 ? void 0 : _c.call(_b)) || "";
728
+ const confidence = (_d = alt === null || alt === void 0 ? void 0 : alt.confidence) !== null && _d !== void 0 ? _d : 0;
729
+ if (!transcript)
730
+ continue;
731
+ if (!res.isFinal && options.interimResults === false)
732
+ continue;
733
+ if (res.isFinal && confidence < threshold)
734
+ continue;
735
+ try {
736
+ this.hadResultThisSession = true;
737
+ this.resultCallback(transcript, !!res.isFinal);
738
+ } catch (_e) {
739
+ this.error("VoiceProcessor: result callback error");
740
+ }
741
+ }
742
+ }
743
+ /** Handle session end */
744
+ handleEnd() {
745
+ this.log("recognition onend");
746
+ this.engineActive = false;
747
+ if (this.destroyed || this.intentionallyStopped || !this.restartAllowed || this.ttsSpeaking) {
748
+ if (!this.ttsSpeaking) {
749
+ this.isListening = false;
750
+ this.emitStatus("idle");
751
+ }
752
+ return;
753
+ }
754
+ this.isListening = true;
755
+ this.scheduleRestart();
756
+ }
757
+ /** Handle errors during speech recognition */
758
+ handleError(event) {
759
+ const err = event === null || event === void 0 ? void 0 : event.error;
760
+ this.warn(`Error occurred: ${err !== null && err !== void 0 ? err : "unknown"}`);
761
+ const fatal = ["not-allowed", "service-not-allowed", "bad-grammar", "language-not-supported"];
762
+ if (err && fatal.includes(err)) {
763
+ this.intentionallyStopped = true;
764
+ this.restartAllowed = false;
765
+ this.isListening = false;
766
+ this.emitStatus("error", { error: err });
767
+ return;
768
+ }
769
+ this.scheduleRestart();
770
+ }
771
+ safeRestart() {
772
+ if (!this.recognition)
773
+ return;
774
+ if (this.engineActive) {
775
+ this.log("safeRestart: engine already active, skipping start");
776
+ return;
777
+ }
778
+ const now = Date.now();
779
+ if (now - this.lastStart < 300) {
780
+ setTimeout(() => this.safeRestart(), 300);
781
+ return;
782
+ }
783
+ this.lastStart = now;
784
+ try {
785
+ this.log("calling recognition.start()");
786
+ this.recognition.start();
787
+ this.backoffMs = 250;
788
+ if (this.isListening && !this.ttsSpeaking) {
789
+ this.emitStatus("listening");
790
+ }
791
+ } catch (_a) {
792
+ this.error("recognition.start() threw; scheduling restart");
793
+ this.scheduleRestart();
794
+ }
795
+ }
796
+ scheduleRestart() {
797
+ if (this.destroyed || this.intentionallyStopped || !this.restartAllowed || this.ttsSpeaking)
798
+ return;
799
+ if (this.engineActive) {
800
+ this.log("scheduleRestart: engine active, not scheduling");
801
+ return;
802
+ }
803
+ const delay = Math.min(this.backoffMs, 2e3);
804
+ this.log(`scheduleRestart in ${delay}ms`);
805
+ if (this.restartTimer) {
806
+ this.log("scheduleRestart: restart already scheduled");
807
+ return;
808
+ }
809
+ this.restartTimer = setTimeout(() => {
810
+ this.restartTimer = null;
811
+ if (this.destroyed || this.intentionallyStopped || !this.restartAllowed || this.ttsSpeaking)
812
+ return;
813
+ this.safeRestart();
814
+ }, delay);
815
+ this.backoffMs = Math.min(this.backoffMs * 2, 2e3);
816
+ }
817
+ prewarmAudio() {
818
+ return __awaiter3(this, void 0, void 0, function* () {
819
+ if (this.prewarmed)
820
+ return;
821
+ try {
822
+ if (typeof navigator === "undefined" || !("mediaDevices" in navigator))
823
+ return;
824
+ const md = navigator.mediaDevices;
825
+ if (!(md === null || md === void 0 ? void 0 : md.getUserMedia))
826
+ return;
827
+ this.log("prewarmAudio: requesting mic");
828
+ const stream = yield md.getUserMedia({ audio: true });
829
+ for (const track of stream.getTracks())
830
+ track.stop();
831
+ this.prewarmed = true;
832
+ this.log("prewarmAudio: mic ready");
833
+ } catch (_a) {
834
+ this.warn("prewarmAudio: failed to get mic");
835
+ }
836
+ });
837
+ }
838
+ emitStatus(status, details) {
839
+ if (!this.statusCallback)
840
+ return;
841
+ try {
842
+ this.statusCallback(status, details);
843
+ } catch (_a) {
844
+ this.error("VoiceProcessor: status callback error");
845
+ }
846
+ }
847
+ };
848
+
849
+ // dist/libs/core/src/lib/utils/dom-interactions.js
850
+ var GestureHandler = class {
851
+ constructor() {
852
+ this.lastTap = 0;
853
+ }
854
+ /**
855
+ * Sets up double-click and double-tap listeners
856
+ * @param onDoubleClickOrTap Callback to execute when a double-click or double-tap is detected
857
+ */
858
+ setupDoubleTapListener(onDoubleClickOrTap) {
859
+ this.destroy();
860
+ this.dblClickListener = () => {
861
+ onDoubleClickOrTap();
862
+ };
863
+ document.addEventListener("dblclick", this.dblClickListener);
864
+ this.touchEndListener = () => {
865
+ const currentTime = (/* @__PURE__ */ new Date()).getTime();
866
+ const tapInterval = currentTime - this.lastTap;
867
+ if (tapInterval < 300 && tapInterval > 0) {
868
+ onDoubleClickOrTap();
869
+ }
870
+ this.lastTap = currentTime;
871
+ };
872
+ document.addEventListener("touchend", this.touchEndListener);
873
+ }
874
+ destroy() {
875
+ if (this.dblClickListener) {
876
+ document.removeEventListener("dblclick", this.dblClickListener);
877
+ }
878
+ if (this.touchEndListener) {
879
+ document.removeEventListener("touchend", this.touchEndListener);
880
+ }
881
+ this.dblClickListener = void 0;
882
+ this.touchEndListener = void 0;
883
+ }
884
+ };
885
+ function injectStyles() {
886
+ const existingStyle = document.querySelector("#assistant-styles");
887
+ if (existingStyle) {
888
+ console.log("Styles already injected");
889
+ return;
890
+ }
891
+ const style = document.createElement("style");
892
+ style.id = "assistant-styles";
893
+ style.innerHTML = `
894
+ /* Rounded shape with gradient animation */
895
+ .gradient-indicator {
896
+ position: fixed;
897
+ top: 20px;
898
+ right: 20px;
899
+ width: 60px;
900
+ height: 60px;
901
+ border-radius: 50%;
902
+ background: linear-gradient(135deg, #ff6ec4, #7873f5, #5e8cff, #6ed0f6);
903
+ box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
904
+ animation: amoeba 5s infinite ease-in-out;
905
+ z-index: 9999; /* Ensure it's above all other elements */
906
+ }
907
+
908
+ /* Amoeba effect for the borders */
909
+ @keyframes amoeba {
910
+ 0% {
911
+ border-radius: 50%;
912
+ }
913
+ 25% {
914
+ border-radius: 40% 60% 60% 40%;
915
+ }
916
+ 50% {
917
+ border-radius: 60% 40% 40% 60%;
918
+ }
919
+ 75% {
920
+ border-radius: 40% 60% 60% 40%;
921
+ }
922
+ 100% {
923
+ border-radius: 50%;
924
+ }
925
+ }
926
+ `;
927
+ document.head.appendChild(style);
928
+ console.log("Gradient styles injected");
929
+ }
930
+ function addGradientAnimation() {
931
+ if (document.querySelector("#gradient-indicator")) {
932
+ return;
933
+ }
934
+ const gradientDiv = document.createElement("div");
935
+ gradientDiv.id = "gradient-indicator";
936
+ injectStyles();
937
+ gradientDiv.classList.add("gradient-indicator");
938
+ document.body.appendChild(gradientDiv);
939
+ console.log("Gradient indicator added to the DOM");
940
+ }
941
+ function removeGradientAnimation() {
942
+ const gradientDiv = document.querySelector("#gradient-indicator");
943
+ if (gradientDiv) {
944
+ gradientDiv.remove();
945
+ console.log("Gradient indicator removed from the DOM");
946
+ }
947
+ }
948
+
949
+ // dist/libs/core/src/lib/state-manager.js
950
+ var StateManager = class {
951
+ constructor() {
952
+ this.state = "idle";
953
+ this.subscribers = [];
954
+ }
955
+ getState() {
956
+ return this.state;
957
+ }
958
+ setState(state) {
959
+ this.state = state;
960
+ this.notifySubscribers();
961
+ console.log("State updated:", state);
962
+ if (state === "listening") {
963
+ addGradientAnimation();
964
+ } else {
965
+ removeGradientAnimation();
966
+ }
967
+ }
968
+ // eslint-disable-next-line no-unused-vars
969
+ subscribe(callback) {
970
+ this.subscribers.push(callback);
971
+ }
972
+ notifySubscribers() {
973
+ this.subscribers.forEach((callback) => callback(this.state));
974
+ }
975
+ };
976
+
977
+ // dist/libs/core/src/lib/ui/overlay-manager.js
978
+ import { __awaiter as __awaiter4 } from "tslib";
979
+ var OverlayManager = class {
980
+ constructor(config) {
981
+ this.container = null;
982
+ this.chatWindow = null;
983
+ this.messagesContainer = null;
984
+ this.input = null;
985
+ this.isOpen = false;
986
+ this.loadingEl = null;
987
+ this.config = config;
988
+ this.init();
989
+ }
990
+ init() {
991
+ var _a, _b;
992
+ if (this.container)
993
+ return;
994
+ this.injectOverlayStyles();
995
+ const existing = document.getElementById("foisit-overlay-container");
996
+ if (existing && existing instanceof HTMLElement) {
997
+ this.container = existing;
998
+ this.chatWindow = existing.querySelector(".foisit-chat");
999
+ this.messagesContainer = existing.querySelector(".foisit-messages");
1000
+ this.input = existing.querySelector("input.foisit-input");
1001
+ if (((_a = this.config.floatingButton) === null || _a === void 0 ? void 0 : _a.visible) !== false && !existing.querySelector(".foisit-floating-btn")) {
1002
+ this.renderFloatingButton();
1003
+ }
1004
+ if (!this.chatWindow) {
1005
+ this.renderChatWindow();
1006
+ }
1007
+ return;
1008
+ }
1009
+ this.container = document.createElement("div");
1010
+ this.container.id = "foisit-overlay-container";
1011
+ this.container.className = "foisit-overlay-container";
1012
+ document.body.appendChild(this.container);
1013
+ if (((_b = this.config.floatingButton) === null || _b === void 0 ? void 0 : _b.visible) !== false) {
1014
+ this.renderFloatingButton();
1015
+ }
1016
+ this.renderChatWindow();
1017
+ }
1018
+ renderFloatingButton() {
1019
+ var _a, _b, _c, _d, _e, _f;
1020
+ const btn = document.createElement("button");
1021
+ btn.innerHTML = ((_a = this.config.floatingButton) === null || _a === void 0 ? void 0 : _a.customHtml) || "\u{1F399}\uFE0F";
1022
+ const bottom = ((_c = (_b = this.config.floatingButton) === null || _b === void 0 ? void 0 : _b.position) === null || _c === void 0 ? void 0 : _c.bottom) || "20px";
1023
+ const right = ((_e = (_d = this.config.floatingButton) === null || _d === void 0 ? void 0 : _d.position) === null || _e === void 0 ? void 0 : _e.right) || "20px";
1024
+ btn.className = "foisit-floating-btn";
1025
+ btn.style.bottom = bottom;
1026
+ btn.style.right = right;
1027
+ btn.onclick = () => this.toggle();
1028
+ btn.onmouseenter = () => btn.style.transform = "scale(1.05)";
1029
+ btn.onmouseleave = () => btn.style.transform = "scale(1)";
1030
+ (_f = this.container) === null || _f === void 0 ? void 0 : _f.appendChild(btn);
1031
+ }
1032
+ renderChatWindow() {
1033
+ var _a;
1034
+ if (this.chatWindow)
1035
+ return;
1036
+ this.chatWindow = document.createElement("div");
1037
+ this.chatWindow.className = "foisit-chat";
1038
+ const header = document.createElement("div");
1039
+ header.className = "foisit-header";
1040
+ const title = document.createElement("span");
1041
+ title.className = "foisit-title";
1042
+ title.textContent = "Foisit";
1043
+ const closeButton = document.createElement("button");
1044
+ closeButton.type = "button";
1045
+ closeButton.className = "foisit-close";
1046
+ closeButton.setAttribute("aria-label", "Close");
1047
+ closeButton.innerHTML = "&times;";
1048
+ closeButton.addEventListener("click", () => this.toggle());
1049
+ header.appendChild(title);
1050
+ header.appendChild(closeButton);
1051
+ this.messagesContainer = document.createElement("div");
1052
+ this.messagesContainer.className = "foisit-messages";
1053
+ const inputArea = document.createElement("div");
1054
+ inputArea.className = "foisit-input-area";
1055
+ this.input = document.createElement("input");
1056
+ this.input.placeholder = this.config.inputPlaceholder || "Type a command...";
1057
+ this.input.className = "foisit-input";
1058
+ this.input.addEventListener("keydown", (e) => {
1059
+ var _a2;
1060
+ if (e.key === "Enter" && ((_a2 = this.input) === null || _a2 === void 0 ? void 0 : _a2.value.trim())) {
1061
+ const text = this.input.value.trim();
1062
+ this.input.value = "";
1063
+ if (this.onSubmit)
1064
+ this.onSubmit(text);
1065
+ }
1066
+ });
1067
+ inputArea.appendChild(this.input);
1068
+ this.chatWindow.appendChild(header);
1069
+ this.chatWindow.appendChild(this.messagesContainer);
1070
+ this.chatWindow.appendChild(inputArea);
1071
+ (_a = this.container) === null || _a === void 0 ? void 0 : _a.appendChild(this.chatWindow);
1072
+ }
1073
+ registerCallbacks(onSubmit, onClose) {
1074
+ this.onSubmit = onSubmit;
1075
+ this.onClose = onClose;
1076
+ }
1077
+ toggle(onSubmit, onClose) {
1078
+ if (onSubmit)
1079
+ this.onSubmit = onSubmit;
1080
+ if (onClose)
1081
+ this.onClose = onClose;
1082
+ this.isOpen = !this.isOpen;
1083
+ if (this.chatWindow) {
1084
+ if (this.isOpen) {
1085
+ this.chatWindow.style.display = "flex";
1086
+ requestAnimationFrame(() => {
1087
+ if (this.chatWindow) {
1088
+ this.chatWindow.style.opacity = "1";
1089
+ this.chatWindow.style.transform = "translateY(0) scale(1)";
1090
+ }
1091
+ });
1092
+ setTimeout(() => {
1093
+ var _a;
1094
+ return (_a = this.input) === null || _a === void 0 ? void 0 : _a.focus();
1095
+ }, 100);
1096
+ } else {
1097
+ this.chatWindow.style.opacity = "0";
1098
+ this.chatWindow.style.transform = "translateY(20px) scale(0.95)";
1099
+ setTimeout(() => {
1100
+ if (this.chatWindow && !this.isOpen) {
1101
+ this.chatWindow.style.display = "none";
1102
+ }
1103
+ }, 200);
1104
+ if (this.onClose)
1105
+ this.onClose();
1106
+ }
1107
+ }
1108
+ }
1109
+ addMessage(text, type) {
1110
+ if (!this.messagesContainer)
1111
+ return;
1112
+ const msg = document.createElement("div");
1113
+ msg.textContent = text;
1114
+ msg.className = type === "user" ? "foisit-bubble user" : "foisit-bubble system";
1115
+ this.messagesContainer.appendChild(msg);
1116
+ this.scrollToBottom();
1117
+ }
1118
+ addOptions(options) {
1119
+ if (!this.messagesContainer)
1120
+ return;
1121
+ const container = document.createElement("div");
1122
+ container.className = "foisit-options-container";
1123
+ options.forEach((opt) => {
1124
+ const btn = document.createElement("button");
1125
+ btn.textContent = opt.label;
1126
+ btn.className = "foisit-option-chip";
1127
+ btn.setAttribute("type", "button");
1128
+ btn.setAttribute("aria-label", opt.label);
1129
+ const clickPayload = () => {
1130
+ if (opt.commandId) {
1131
+ if (this.onSubmit)
1132
+ this.onSubmit({ commandId: opt.commandId });
1133
+ return;
1134
+ }
1135
+ const value = opt && typeof opt.value === "string" && opt.value.trim() ? opt.value : opt.label;
1136
+ if (this.onSubmit)
1137
+ this.onSubmit(value);
1138
+ };
1139
+ btn.onclick = clickPayload;
1140
+ btn.onkeydown = (e) => {
1141
+ if (e.key === "Enter" || e.key === " ") {
1142
+ e.preventDefault();
1143
+ clickPayload();
1144
+ }
1145
+ };
1146
+ container.appendChild(btn);
1147
+ });
1148
+ this.messagesContainer.appendChild(container);
1149
+ this.scrollToBottom();
1150
+ }
1151
+ addForm(message, fields, onSubmit) {
1152
+ if (!this.messagesContainer)
1153
+ return;
1154
+ this.addMessage(message, "system");
1155
+ const form = document.createElement("form");
1156
+ form.className = "foisit-form";
1157
+ const controls = [];
1158
+ const createLabel = (text, required) => {
1159
+ const label = document.createElement("div");
1160
+ label.className = "foisit-form-label";
1161
+ label.innerHTML = text + (required ? ' <span class="foisit-req-star">*</span>' : "");
1162
+ return label;
1163
+ };
1164
+ const createError = () => {
1165
+ const error = document.createElement("div");
1166
+ error.className = "foisit-form-error";
1167
+ error.style.display = "none";
1168
+ return error;
1169
+ };
1170
+ (fields !== null && fields !== void 0 ? fields : []).forEach((field) => {
1171
+ const wrapper = document.createElement("div");
1172
+ wrapper.className = "foisit-form-group";
1173
+ const labelText = field.description || field.name;
1174
+ wrapper.appendChild(createLabel(labelText, field.required));
1175
+ let inputEl;
1176
+ if (field.type === "select") {
1177
+ const select = document.createElement("select");
1178
+ select.className = "foisit-form-input";
1179
+ const placeholderOpt = document.createElement("option");
1180
+ placeholderOpt.value = "";
1181
+ placeholderOpt.textContent = "Select...";
1182
+ select.appendChild(placeholderOpt);
1183
+ const populate = (options) => {
1184
+ (options !== null && options !== void 0 ? options : []).forEach((opt) => {
1185
+ var _a, _b, _c, _d;
1186
+ const o = document.createElement("option");
1187
+ o.value = String((_b = (_a = opt.value) !== null && _a !== void 0 ? _a : opt.label) !== null && _b !== void 0 ? _b : "");
1188
+ o.textContent = String((_d = (_c = opt.label) !== null && _c !== void 0 ? _c : opt.value) !== null && _d !== void 0 ? _d : "");
1189
+ select.appendChild(o);
1190
+ });
1191
+ };
1192
+ if (Array.isArray(field.options) && field.options.length) {
1193
+ populate(field.options);
1194
+ } else if (typeof field.getOptions === "function") {
1195
+ const getOptions = field.getOptions;
1196
+ const loadingOpt = document.createElement("option");
1197
+ loadingOpt.value = "";
1198
+ loadingOpt.textContent = "Loading...";
1199
+ select.appendChild(loadingOpt);
1200
+ Promise.resolve().then(() => getOptions()).then((opts) => {
1201
+ while (select.options.length > 1)
1202
+ select.remove(1);
1203
+ populate(opts);
1204
+ }).catch(() => {
1205
+ while (select.options.length > 1)
1206
+ select.remove(1);
1207
+ const errOpt = document.createElement("option");
1208
+ errOpt.value = "";
1209
+ errOpt.textContent = "Error loading options";
1210
+ select.appendChild(errOpt);
1211
+ });
1212
+ }
1213
+ if (field.defaultValue != null) {
1214
+ select.value = String(field.defaultValue);
1215
+ }
1216
+ inputEl = select;
1217
+ } else if (field.type === "file") {
1218
+ const ffield = field;
1219
+ const input = document.createElement("input");
1220
+ input.className = "foisit-form-input";
1221
+ input.type = "file";
1222
+ if (ffield.accept && Array.isArray(ffield.accept)) {
1223
+ input.accept = ffield.accept.join(",");
1224
+ }
1225
+ if (ffield.multiple)
1226
+ input.multiple = true;
1227
+ if (ffield.capture) {
1228
+ if (ffield.capture === true)
1229
+ input.setAttribute("capture", "");
1230
+ else
1231
+ input.setAttribute("capture", String(ffield.capture));
1232
+ }
1233
+ input.addEventListener("change", () => __awaiter4(this, void 0, void 0, function* () {
1234
+ var _a, _b, _c;
1235
+ const files = Array.from(input.files || []);
1236
+ const errEl = errorEl;
1237
+ errEl.style.display = "none";
1238
+ errEl.textContent = "";
1239
+ if (files.length === 0)
1240
+ return;
1241
+ const maxFiles = (_a = ffield.maxFiles) !== null && _a !== void 0 ? _a : ffield.multiple ? 10 : 1;
1242
+ if (files.length > maxFiles) {
1243
+ errEl.textContent = `Please select at most ${maxFiles} file(s).`;
1244
+ errEl.style.display = "block";
172
1245
  return;
1246
+ }
1247
+ const maxSize = (_b = ffield.maxSizeBytes) !== null && _b !== void 0 ? _b : Infinity;
1248
+ const total = files.reduce((s, f) => s + f.size, 0);
1249
+ if (files.some((f) => f.size > maxSize)) {
1250
+ errEl.textContent = `One or more files exceed the maximum size of ${Math.round(maxSize / 1024)} KB.`;
1251
+ errEl.style.display = "block";
1252
+ return;
1253
+ }
1254
+ const maxTotal = (_c = ffield.maxTotalBytes) !== null && _c !== void 0 ? _c : Infinity;
1255
+ if (total > maxTotal) {
1256
+ errEl.textContent = `Total selected files exceed the maximum of ${Math.round(maxTotal / 1024)} KB.`;
1257
+ errEl.style.display = "block";
1258
+ return;
1259
+ }
1260
+ if (ffield.accept && Array.isArray(ffield.accept)) {
1261
+ const accepts = ffield.accept;
1262
+ const ok = files.every((file) => {
1263
+ if (!file.type)
1264
+ return true;
1265
+ return accepts.some((a) => a.startsWith(".") ? file.name.toLowerCase().endsWith(a.toLowerCase()) : file.type === a || file.type.startsWith(a.split("/")[0] + "/"));
1266
+ });
1267
+ if (!ok) {
1268
+ errEl.textContent = "One or more files have an unsupported type.";
1269
+ errEl.style.display = "block";
1270
+ return;
1271
+ }
1272
+ }
1273
+ }));
1274
+ inputEl = input;
1275
+ } else {
1276
+ const input = document.createElement("input");
1277
+ input.className = "foisit-form-input";
1278
+ if (field.type === "string") {
1279
+ input.placeholder = field.placeholder || "Type here...";
1280
+ }
1281
+ if (field.type === "number") {
1282
+ input.type = "number";
1283
+ if (typeof field.min === "number")
1284
+ input.min = String(field.min);
1285
+ if (typeof field.max === "number")
1286
+ input.max = String(field.max);
1287
+ if (typeof field.step === "number")
1288
+ input.step = String(field.step);
1289
+ if (field.defaultValue != null)
1290
+ input.value = String(field.defaultValue);
1291
+ } else if (field.type === "date") {
1292
+ input.type = "date";
1293
+ if (typeof field.min === "string")
1294
+ input.min = field.min;
1295
+ if (typeof field.max === "string")
1296
+ input.max = field.max;
1297
+ if (field.defaultValue != null)
1298
+ input.value = String(field.defaultValue);
1299
+ } else {
1300
+ input.type = "text";
1301
+ if (field.defaultValue != null)
1302
+ input.value = String(field.defaultValue);
173
1303
  }
174
- if (response.type === 'form' && response.fields) {
175
- this.overlayManager.addForm(response.message, response.fields, async (data) => {
176
- this.overlayManager.showLoading();
177
- let nextReponse;
178
- try {
179
- nextReponse = await this.commandHandler.executeCommand(data);
1304
+ inputEl = input;
1305
+ }
1306
+ const errorEl = createError();
1307
+ wrapper.appendChild(inputEl);
1308
+ wrapper.appendChild(errorEl);
1309
+ controls.push({
1310
+ name: field.name,
1311
+ type: field.type,
1312
+ el: inputEl,
1313
+ required: field.required
1314
+ });
1315
+ form.appendChild(wrapper);
1316
+ });
1317
+ const actions = document.createElement("div");
1318
+ actions.className = "foisit-form-actions";
1319
+ const submitBtn = document.createElement("button");
1320
+ submitBtn.type = "submit";
1321
+ submitBtn.textContent = "Submit";
1322
+ submitBtn.className = "foisit-option-chip";
1323
+ submitBtn.style.fontWeight = "600";
1324
+ actions.appendChild(submitBtn);
1325
+ form.appendChild(actions);
1326
+ form.onsubmit = (e) => __awaiter4(this, void 0, void 0, function* () {
1327
+ var _a, _b;
1328
+ e.preventDefault();
1329
+ const data = {};
1330
+ let hasError = false;
1331
+ form.querySelectorAll(".foisit-form-error").forEach((el) => {
1332
+ el.style.display = "none";
1333
+ el.textContent = "";
1334
+ });
1335
+ form.querySelectorAll(".foisit-form-input").forEach((el) => {
1336
+ el.classList.remove("foisit-error-border");
1337
+ });
1338
+ for (const c of controls) {
1339
+ if (c.type === "file") {
1340
+ const fileWrapper = c.el.parentElement;
1341
+ const fileErrorEl = fileWrapper === null || fileWrapper === void 0 ? void 0 : fileWrapper.querySelector(".foisit-form-error");
1342
+ const input = c.el;
1343
+ const files = Array.from(input.files || []);
1344
+ if (c.required && files.length === 0) {
1345
+ hasError = true;
1346
+ input.classList.add("foisit-error-border");
1347
+ if (fileErrorEl) {
1348
+ fileErrorEl.textContent = "This file is required";
1349
+ fileErrorEl.style.display = "block";
1350
+ }
1351
+ continue;
1352
+ }
1353
+ if (files.length === 0)
1354
+ continue;
1355
+ const fieldDef = (fields !== null && fields !== void 0 ? fields : []).find((f) => f.name === c.name);
1356
+ const delivery = (_a = fieldDef === null || fieldDef === void 0 ? void 0 : fieldDef.delivery) !== null && _a !== void 0 ? _a : "file";
1357
+ if ((fieldDef === null || fieldDef === void 0 ? void 0 : fieldDef.maxWidth) || (fieldDef === null || fieldDef === void 0 ? void 0 : fieldDef.maxHeight)) {
1358
+ try {
1359
+ const dims = yield this.getImageDimensions(files[0]);
1360
+ if (fieldDef.maxWidth && dims.width > fieldDef.maxWidth) {
1361
+ hasError = true;
1362
+ if (fileErrorEl) {
1363
+ fileErrorEl.textContent = `Image width must be \u2264 ${fieldDef.maxWidth}px`;
1364
+ fileErrorEl.style.display = "block";
180
1365
  }
181
- finally {
182
- this.overlayManager.hideLoading();
1366
+ continue;
1367
+ }
1368
+ if (fieldDef.maxHeight && dims.height > fieldDef.maxHeight) {
1369
+ hasError = true;
1370
+ if (fileErrorEl) {
1371
+ fileErrorEl.textContent = `Image height must be \u2264 ${fieldDef.maxHeight}px`;
1372
+ fileErrorEl.style.display = "block";
183
1373
  }
184
- this.processResponse(nextReponse);
185
- });
186
- return;
187
- }
188
- if ((response.type === 'ambiguous' || response.type === 'confirm') && response.options) {
189
- if (response.message) {
190
- this.overlayManager.addMessage(response.message, 'system');
1374
+ continue;
1375
+ }
1376
+ } catch (_c) {
191
1377
  }
192
- this.overlayManager.addOptions(response.options);
193
- return;
1378
+ }
1379
+ if (fieldDef === null || fieldDef === void 0 ? void 0 : fieldDef.maxDurationSec) {
1380
+ try {
1381
+ const dur = yield this.getMediaDuration(files[0]);
1382
+ if (dur && dur > fieldDef.maxDurationSec) {
1383
+ hasError = true;
1384
+ if (fileErrorEl) {
1385
+ fileErrorEl.textContent = `Media duration must be \u2264 ${fieldDef.maxDurationSec}s`;
1386
+ fileErrorEl.style.display = "block";
1387
+ }
1388
+ continue;
1389
+ }
1390
+ } catch (_d) {
1391
+ }
1392
+ }
1393
+ if (delivery === "file") {
1394
+ data[c.name] = (fieldDef === null || fieldDef === void 0 ? void 0 : fieldDef.multiple) ? files : files[0];
1395
+ } else if (delivery === "base64") {
1396
+ try {
1397
+ const encoded = yield Promise.all(files.map((file) => this.readFileAsDataURL(file)));
1398
+ data[c.name] = (fieldDef === null || fieldDef === void 0 ? void 0 : fieldDef.multiple) ? encoded : encoded[0];
1399
+ } catch (_e) {
1400
+ hasError = true;
1401
+ if (fileErrorEl) {
1402
+ fileErrorEl.textContent = "Failed to encode file(s) to base64.";
1403
+ fileErrorEl.style.display = "block";
1404
+ }
1405
+ continue;
1406
+ }
1407
+ }
1408
+ continue;
194
1409
  }
195
- if (response.message) {
196
- this.overlayManager.addMessage(response.message, 'system');
1410
+ const val = ((_b = c.el.value) !== null && _b !== void 0 ? _b : "").toString().trim();
1411
+ const valueWrapper = c.el.parentElement;
1412
+ const fieldErrorEl = valueWrapper === null || valueWrapper === void 0 ? void 0 : valueWrapper.querySelector(".foisit-form-error");
1413
+ if (c.required && (val == null || val === "")) {
1414
+ hasError = true;
1415
+ c.el.classList.add("foisit-error-border");
1416
+ if (fieldErrorEl) {
1417
+ fieldErrorEl.textContent = "This field is required";
1418
+ fieldErrorEl.style.display = "block";
1419
+ }
1420
+ continue;
197
1421
  }
1422
+ if (val === "")
1423
+ continue;
1424
+ if (c.type === "number") {
1425
+ const n = Number(val);
1426
+ if (!Number.isNaN(n))
1427
+ data[c.name] = n;
1428
+ } else {
1429
+ data[c.name] = val;
1430
+ }
1431
+ }
1432
+ if (hasError) {
1433
+ form.classList.add("foisit-shake");
1434
+ setTimeout(() => form.classList.remove("foisit-shake"), 400);
1435
+ return;
1436
+ }
1437
+ submitBtn.disabled = true;
1438
+ submitBtn.style.opacity = "0.6";
1439
+ controls.forEach((c) => {
1440
+ c.el.disabled = true;
1441
+ });
1442
+ onSubmit(data);
1443
+ });
1444
+ this.messagesContainer.appendChild(form);
1445
+ this.scrollToBottom();
1446
+ }
1447
+ showLoading() {
1448
+ if (!this.messagesContainer)
1449
+ return;
1450
+ if (this.loadingEl)
1451
+ return;
1452
+ this.loadingEl = document.createElement("div");
1453
+ this.loadingEl.className = "foisit-loading-dots foisit-bubble system";
1454
+ for (let i = 0; i < 3; i++) {
1455
+ const dot = document.createElement("div");
1456
+ dot.className = "foisit-dot";
1457
+ dot.style.animation = `foisitPulse 1.4s infinite ease-in-out ${i * 0.2}s`;
1458
+ this.loadingEl.appendChild(dot);
1459
+ }
1460
+ this.messagesContainer.appendChild(this.loadingEl);
1461
+ this.scrollToBottom();
1462
+ }
1463
+ hideLoading() {
1464
+ var _a;
1465
+ (_a = this.loadingEl) === null || _a === void 0 ? void 0 : _a.remove();
1466
+ this.loadingEl = null;
1467
+ }
1468
+ scrollToBottom() {
1469
+ if (this.messagesContainer) {
1470
+ this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
198
1471
  }
199
- /** Add a command dynamically (supports string or rich object) */
200
- addCommand(commandOrObj, action) {
201
- if (typeof commandOrObj === 'string') {
202
- console.log(`AssistantService: Adding command "${commandOrObj}".`);
1472
+ }
1473
+ destroy() {
1474
+ var _a;
1475
+ (_a = this.container) === null || _a === void 0 ? void 0 : _a.remove();
1476
+ this.container = null;
1477
+ this.chatWindow = null;
1478
+ this.messagesContainer = null;
1479
+ this.input = null;
1480
+ this.isOpen = false;
1481
+ }
1482
+ readFileAsDataURL(file) {
1483
+ return new Promise((resolve, reject) => {
1484
+ const fr = new FileReader();
1485
+ fr.onerror = () => reject(new Error("Failed to read file"));
1486
+ fr.onload = () => resolve(String(fr.result));
1487
+ fr.readAsDataURL(file);
1488
+ });
1489
+ }
1490
+ getImageDimensions(file) {
1491
+ return new Promise((resolve) => {
1492
+ try {
1493
+ const url = URL.createObjectURL(file);
1494
+ const img = new Image();
1495
+ img.onload = () => {
1496
+ const dims = { width: img.naturalWidth || img.width, height: img.naturalHeight || img.height };
1497
+ URL.revokeObjectURL(url);
1498
+ resolve(dims);
1499
+ };
1500
+ img.onerror = () => {
1501
+ URL.revokeObjectURL(url);
1502
+ resolve({ width: 0, height: 0 });
1503
+ };
1504
+ img.src = url;
1505
+ } catch (_a) {
1506
+ resolve({ width: 0, height: 0 });
1507
+ }
1508
+ });
1509
+ }
1510
+ getMediaDuration(file) {
1511
+ return new Promise((resolve) => {
1512
+ try {
1513
+ const url = URL.createObjectURL(file);
1514
+ const el = file.type.startsWith("audio") ? document.createElement("audio") : document.createElement("video");
1515
+ let settled = false;
1516
+ const timeout = setTimeout(() => {
1517
+ if (!settled) {
1518
+ settled = true;
1519
+ URL.revokeObjectURL(url);
1520
+ resolve(0);
1521
+ }
1522
+ }, 5e3);
1523
+ el.preload = "metadata";
1524
+ el.onloadedmetadata = () => {
1525
+ if (settled)
1526
+ return;
1527
+ settled = true;
1528
+ clearTimeout(timeout);
1529
+ const mediaEl = el;
1530
+ const d = mediaEl.duration || 0;
1531
+ URL.revokeObjectURL(url);
1532
+ resolve(d);
1533
+ };
1534
+ el.onerror = () => {
1535
+ if (settled)
1536
+ return;
1537
+ settled = true;
1538
+ clearTimeout(timeout);
1539
+ URL.revokeObjectURL(url);
1540
+ resolve(0);
1541
+ };
1542
+ el.src = url;
1543
+ } catch (_a) {
1544
+ resolve(0);
1545
+ }
1546
+ });
1547
+ }
1548
+ injectOverlayStyles() {
1549
+ if (document.getElementById("foisit-overlay-styles"))
1550
+ return;
1551
+ const style = document.createElement("style");
1552
+ style.id = "foisit-overlay-styles";
1553
+ style.textContent = `
1554
+ :root {
1555
+ /* LIGHT MODE (Default) - Smoother gradient */
1556
+ /* Changed: Softer, right-focused radial highlight to avoid a heavy white bottom */
1557
+ --foisit-bg: radial-gradient(ellipse at 75% 30%, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.03));
1558
+ --foisit-border: 1px solid rgba(255, 255, 255, 0.25);
1559
+ --foisit-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
1560
+ --foisit-text: #333;
1561
+
1562
+ /* Input */
1563
+ --foisit-input-color: #333;
1564
+ --foisit-input-placeholder: rgba(60, 60, 67, 0.6);
1565
+
1566
+ /* Bubbles */
1567
+ --foisit-bubble-user-bg: rgba(0, 0, 0, 0.04);
1568
+ --foisit-bubble-user-text: #333;
1569
+
1570
+ --foisit-bubble-sys-bg: rgba(255, 255, 255, 0.45);
1571
+ --foisit-bubble-sys-text: #333;
1572
+
1573
+ /* Form Colors */
1574
+ --foisit-req-star: #ef4444; /* Red asterisk */
1575
+ --foisit-error-text: #dc2626;
1576
+ --foisit-error-border: #fca5a5;
1577
+ }
1578
+
1579
+ @media (prefers-color-scheme: dark) {
1580
+ :root {
1581
+ /* DARK MODE */
1582
+ --foisit-bg: linear-gradient(135deg, rgba(40, 40, 40, 0.65), rgba(40, 40, 40, 0.25));
1583
+ --foisit-border: 1px solid rgba(255, 255, 255, 0.1);
1584
+ --foisit-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
1585
+ --foisit-text: #fff;
1586
+
1587
+ /* Input */
1588
+ --foisit-input-color: white;
1589
+ --foisit-input-placeholder: rgba(235, 235, 245, 0.5);
1590
+
1591
+ /* Bubbles */
1592
+ --foisit-bubble-user-bg: rgba(255, 255, 255, 0.1);
1593
+ --foisit-bubble-user-text: white;
1594
+
1595
+ --foisit-bubble-sys-bg: rgba(255, 255, 255, 0.05);
1596
+ --foisit-bubble-sys-text: rgba(255, 255, 255, 0.9);
1597
+
1598
+ /* Form Colors */
1599
+ --foisit-req-star: #f87171;
1600
+ --foisit-error-text: #fca5a5;
1601
+ --foisit-error-border: #f87171;
203
1602
  }
204
- else {
205
- console.log(`AssistantService: Adding rich command "${commandOrObj.command}".`);
1603
+ }
1604
+
1605
+ @keyframes foisitPulse {
1606
+ 0%, 100% { transform: scale(0.8); opacity: 0.5; }
1607
+ 50% { transform: scale(1.2); opacity: 1; }
1608
+ }
1609
+
1610
+ @keyframes foisitShake {
1611
+ 0%, 100% { transform: translateX(0); }
1612
+ 25% { transform: translateX(-4px); }
1613
+ 75% { transform: translateX(4px); }
1614
+ }
1615
+ .foisit-shake { animation: foisitShake 0.4s ease-in-out; }
1616
+
1617
+ /* Container */
1618
+ .foisit-overlay-container {
1619
+ position: fixed;
1620
+ inset: 0;
1621
+ z-index: 2147483647;
1622
+ pointer-events: none;
1623
+ display: flex;
1624
+ flex-direction: column;
1625
+ justify-content: flex-end;
1626
+ align-items: flex-end;
1627
+ padding: 20px;
1628
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
1629
+ }
1630
+
1631
+ .foisit-overlay-container * {
1632
+ box-sizing: border-box;
1633
+ }
1634
+
1635
+ /* Chat Window - Dynamic Height */
1636
+ .foisit-chat {
1637
+ position: absolute;
1638
+ top: 20px;
1639
+ right: 20px;
1640
+ width: min(420px, 92vw);
1641
+
1642
+ /* FIX: Auto height to prevent empty space */
1643
+ height: auto;
1644
+ min-height: 120px;
1645
+ max-height: 80vh;
1646
+
1647
+ background: var(--foisit-bg);
1648
+ border: var(--foisit-border);
1649
+ box-shadow: var(--foisit-shadow);
1650
+
1651
+ backdrop-filter: blur(20px);
1652
+ -webkit-backdrop-filter: blur(20px);
1653
+
1654
+ border-radius: 18px;
1655
+ display: none;
1656
+ flex-direction: column;
1657
+ overflow: hidden;
1658
+ pointer-events: auto;
1659
+ transform-origin: top right;
1660
+ transition: opacity 0.2s, transform 0.2s cubic-bezier(0.2, 0.9, 0.2, 1);
1661
+ }
1662
+
1663
+ .foisit-header {
1664
+ display: flex;
1665
+ align-items: center;
1666
+ justify-content: space-between;
1667
+ padding: 12px 16px;
1668
+ font-weight: 600;
1669
+ font-size: 14px;
1670
+ color: var(--foisit-text);
1671
+ border-bottom: 1px solid rgba(127,127,127,0.08); /* Subtle separator */
1672
+ }
1673
+
1674
+ .foisit-close {
1675
+ background: transparent;
1676
+ border: none;
1677
+ color: var(--foisit-text);
1678
+ opacity: 0.5;
1679
+ font-size: 24px;
1680
+ line-height: 1;
1681
+ cursor: pointer;
1682
+ padding: 0;
1683
+ width: 28px;
1684
+ height: 28px;
1685
+ display: flex;
1686
+ align-items: center;
1687
+ justify-content: center;
1688
+ transition: opacity 0.2s;
1689
+ }
1690
+ .foisit-close:hover { opacity: 1; }
1691
+
1692
+ /* Message Area */
1693
+ .foisit-messages {
1694
+ flex: 1;
1695
+ overflow-y: auto;
1696
+ padding: 16px;
1697
+ display: flex;
1698
+ flex-direction: column;
1699
+ gap: 10px;
1700
+ /* Ensure it doesn't get too tall initially */
1701
+ min-height: 60px;
1702
+ }
1703
+
1704
+ /* Make sure empty state isn't huge */
1705
+ .foisit-messages:empty {
1706
+ display: none;
1707
+ }
1708
+
1709
+ /* Only show messages container if it has children */
1710
+ .foisit-messages:not(:empty) {
1711
+ display: flex;
1712
+ }
1713
+
1714
+ /* Bubbles */
1715
+ .foisit-bubble {
1716
+ max-width: 90%;
1717
+ padding: 8px 14px;
1718
+ border-radius: 14px;
1719
+ font-size: 14px;
1720
+ line-height: 1.4;
1721
+ word-wrap: break-word;
1722
+ }
1723
+
1724
+ .foisit-bubble.user {
1725
+ align-self: flex-end;
1726
+ background: var(--foisit-bubble-user-bg);
1727
+ color: var(--foisit-bubble-user-text);
1728
+ border-bottom-right-radius: 4px;
1729
+ }
1730
+
1731
+ .foisit-bubble.system {
1732
+ align-self: flex-start;
1733
+ background: var(--foisit-bubble-sys-bg);
1734
+ color: var(--foisit-bubble-sys-text);
1735
+ border-bottom-left-radius: 4px;
1736
+ border: 1px solid rgba(255,255,255,0.1);
1737
+ }
1738
+
1739
+ /* Input Area */
1740
+ .foisit-input-area {
1741
+ padding: 0;
1742
+ width: 100%;
1743
+ border-top: 1px solid rgba(127,127,127,0.08);
1744
+ }
1745
+
1746
+ .foisit-input {
1747
+ width: 100%;
1748
+ background: transparent;
1749
+ border: none;
1750
+ font-size: 16px;
1751
+ color: var(--foisit-input-color);
1752
+ padding: 16px 20px;
1753
+ outline: none;
1754
+ text-align: left;
1755
+ }
1756
+
1757
+ .foisit-input::placeholder {
1758
+ color: var(--foisit-input-placeholder);
1759
+ font-weight: 400;
1760
+ }
1761
+
1762
+ /* Options & Buttons */
1763
+ .foisit-options-container {
1764
+ display: flex;
1765
+ flex-wrap: wrap;
1766
+ gap: 8px;
1767
+ margin-left: 2px;
1768
+ margin-top: 4px;
1769
+ }
1770
+
1771
+ .foisit-option-chip {
1772
+ padding: 6px 14px;
1773
+ background: var(--foisit-bubble-sys-bg);
1774
+ border: 1px solid rgba(127,127,127,0.1);
1775
+ border-radius: 20px;
1776
+ font-size: 13px;
1777
+ color: var(--foisit-text);
1778
+ cursor: pointer;
1779
+ transition: all 0.2s;
1780
+ font-weight: 500;
1781
+ }
1782
+ .foisit-option-chip:hover {
1783
+ background: rgba(127,127,127,0.15);
1784
+ }
1785
+
1786
+ /* Form Styling */
1787
+ .foisit-form {
1788
+ background: var(--foisit-bubble-sys-bg);
1789
+ padding: 16px;
1790
+ border-radius: 14px;
1791
+ display: flex;
1792
+ flex-direction: column;
1793
+ gap: 12px;
1794
+ width: 100%;
1795
+ border: 1px solid rgba(127,127,127,0.1);
1796
+ }
1797
+
1798
+ .foisit-form-label {
1799
+ font-size: 12px;
1800
+ font-weight: 600;
1801
+ color: var(--foisit-text);
1802
+ opacity: 0.9;
1803
+ margin-bottom: 2px;
1804
+ }
1805
+
1806
+ .foisit-req-star {
1807
+ color: var(--foisit-req-star);
1808
+ font-weight: bold;
1809
+ }
1810
+
1811
+ .foisit-form-input {
1812
+ width: 100%;
1813
+ padding: 10px;
1814
+ border-radius: 8px;
1815
+ border: 1px solid rgba(127,127,127,0.2);
1816
+ background: rgba(255,255,255,0.05); /* Very subtle fill */
1817
+ color: var(--foisit-text);
1818
+ font-size: 14px;
1819
+ outline: none;
1820
+ transition: border 0.2s;
1821
+ }
1822
+ .foisit-form-input:focus {
1823
+ border-color: var(--foisit-text);
1824
+ background: rgba(255,255,255,0.1);
1825
+ }
1826
+
1827
+ .foisit-error-border {
1828
+ border-color: var(--foisit-error-border) !important;
1829
+ }
1830
+
1831
+ .foisit-form-error {
1832
+ font-size: 11px;
1833
+ color: var(--foisit-error-text);
1834
+ margin-top: 4px;
1835
+ }
1836
+
1837
+ /* Loading */
1838
+ .foisit-loading-dots {
1839
+ display: inline-flex;
1840
+ gap: 4px;
1841
+ padding: 10px 14px;
1842
+ align-self: flex-start;
1843
+ }
1844
+ .foisit-dot {
1845
+ width: 6px;
1846
+ height: 6px;
1847
+ border-radius: 50%;
1848
+ background: var(--foisit-text);
1849
+ opacity: 0.4;
1850
+ }
1851
+
1852
+ /* Floating Button */
1853
+ .foisit-floating-btn {
1854
+ position: absolute;
1855
+ width: 56px;
1856
+ height: 56px;
1857
+ border-radius: 50%;
1858
+ border: 1px solid rgba(255,255,255,0.2);
1859
+ background: var(--foisit-bg);
1860
+ color: var(--foisit-text);
1861
+ backdrop-filter: blur(10px);
1862
+ -webkit-backdrop-filter: blur(10px);
1863
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
1864
+ cursor: pointer;
1865
+ pointer-events: auto;
1866
+ display: flex;
1867
+ align-items: center;
1868
+ justify-content: center;
1869
+ font-size: 24px;
1870
+ z-index: 100000;
1871
+ transition: transform 0.2s;
1872
+ }
1873
+ .foisit-floating-btn:hover { transform: scale(1.05); }
1874
+ `;
1875
+ document.head.appendChild(style);
1876
+ }
1877
+ };
1878
+
1879
+ // dist/libs/angular-wrapper/fesm2022/foisit-angular-wrapper.mjs
1880
+ var AngularWrapperComponent = class _AngularWrapperComponent {
1881
+ static \u0275fac = i0.\u0275\u0275ngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: _AngularWrapperComponent, deps: [], target: i0.\u0275\u0275FactoryTarget.Component });
1882
+ static \u0275cmp = i0.\u0275\u0275ngDeclareComponent({ minVersion: "14.0.0", version: "19.0.7", type: _AngularWrapperComponent, isStandalone: true, selector: "lib-angular-wrapper", ngImport: i0, template: "<p>AngularWrapper works!</p>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
1883
+ };
1884
+ i0.\u0275\u0275ngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AngularWrapperComponent, decorators: [{
1885
+ type: Component,
1886
+ args: [{ selector: "lib-angular-wrapper", imports: [CommonModule], template: "<p>AngularWrapper works!</p>\n" }]
1887
+ }] });
1888
+ var AssistantService = class _AssistantService {
1889
+ config;
1890
+ commandHandler;
1891
+ fallbackHandler;
1892
+ voiceProcessor;
1893
+ textToSpeech;
1894
+ stateManager;
1895
+ lastProcessedInput = "";
1896
+ processingLock = false;
1897
+ isActivated = false;
1898
+ // Tracks activation status
1899
+ gestureHandler;
1900
+ overlayManager;
1901
+ defaultIntroMessage = "How can I help you?";
1902
+ constructor(config) {
1903
+ this.config = config;
1904
+ this.commandHandler = new CommandHandler({
1905
+ enableSmartIntent: this.config.enableSmartIntent !== false,
1906
+ intentEndpoint: this.config.intentEndpoint
1907
+ });
1908
+ this.fallbackHandler = new FallbackHandler();
1909
+ this.voiceProcessor = new VoiceProcessor();
1910
+ this.textToSpeech = new TextToSpeech();
1911
+ this.stateManager = new StateManager();
1912
+ this.gestureHandler = new GestureHandler();
1913
+ this.overlayManager = new OverlayManager({
1914
+ floatingButton: this.config.floatingButton,
1915
+ inputPlaceholder: this.config.inputPlaceholder
1916
+ });
1917
+ this.config.commands.forEach((cmd) => this.commandHandler.addCommand(cmd));
1918
+ if (this.config.fallbackResponse) {
1919
+ this.fallbackHandler.setFallbackMessage(this.config.fallbackResponse);
1920
+ }
1921
+ this.gestureHandler.setupDoubleTapListener(() => this.toggle());
1922
+ this.overlayManager.registerCallbacks(async (input) => {
1923
+ if (typeof input === "string") {
1924
+ this.overlayManager.addMessage(input, "user");
1925
+ } else if (input && typeof input === "object") {
1926
+ const label = this.extractUserLabel(input);
1927
+ if (label) {
1928
+ this.overlayManager.addMessage(label, "user");
206
1929
  }
207
- this.commandHandler.addCommand(commandOrObj, action);
1930
+ }
1931
+ await this.handleCommand(input);
1932
+ }, () => console.log("AssistantService: Overlay closed."));
1933
+ }
1934
+ /** Start listening for activation and commands */
1935
+ startListening() {
1936
+ console.log("AssistantService: Voice is disabled; startListening() is a no-op.");
1937
+ return;
1938
+ }
1939
+ /** Stop listening for voice input */
1940
+ stopListening() {
1941
+ console.log("AssistantService: Voice is disabled; stopListening() is a no-op.");
1942
+ this.isActivated = false;
1943
+ }
1944
+ /** Reset activation state so the next activation flow can occur. */
1945
+ reactivate() {
1946
+ console.log("AssistantService: Reactivating assistant...");
1947
+ this.isActivated = false;
1948
+ try {
1949
+ this.startListening();
1950
+ } catch {
208
1951
  }
209
- /** Remove a command dynamically */
210
- removeCommand(command) {
211
- console.log(`AssistantService: Removing command "${command}".`);
212
- this.commandHandler.removeCommand(command);
1952
+ }
1953
+ /** Process activation command */
1954
+ async processActivation(transcript) {
1955
+ const activationCmd = this.config.activationCommand?.toLowerCase();
1956
+ if (!activationCmd)
1957
+ return;
1958
+ if (transcript === activationCmd) {
1959
+ console.log("AssistantService: Activation matched.");
1960
+ this.isActivated = true;
1961
+ this.textToSpeech.speak(this.config.introMessage || this.defaultIntroMessage);
1962
+ } else {
1963
+ console.log("AssistantService: Activation command not recognized.");
213
1964
  }
214
- /** Get all registered commands */
215
- getCommands() {
216
- return this.commandHandler.getCommands();
1965
+ }
1966
+ /** Handle recognized commands */
1967
+ async handleCommand(input) {
1968
+ this.overlayManager.showLoading();
1969
+ let response;
1970
+ try {
1971
+ response = await this.commandHandler.executeCommand(input);
1972
+ } finally {
1973
+ this.overlayManager.hideLoading();
217
1974
  }
218
- /** Toggle the assistant overlay */
219
- toggle(onSubmit, onClose) {
220
- console.log('AssistantService: Toggling overlay...');
221
- this.overlayManager.toggle(async (input) => {
222
- if (typeof input === 'string') {
223
- this.overlayManager.addMessage(input, 'user');
224
- }
225
- else if (input && typeof input === 'object') {
226
- const label = this.extractUserLabel(input);
227
- if (label) {
228
- this.overlayManager.addMessage(label, 'user');
229
- }
230
- }
231
- if (onSubmit)
232
- onSubmit(input);
233
- await this.handleCommand(input);
234
- }, () => {
235
- console.log('AssistantService: Overlay closed.');
236
- if (onClose)
237
- onClose();
238
- });
1975
+ const originalText = typeof input === "string" ? input : void 0;
1976
+ this.processResponse(response, originalText);
1977
+ }
1978
+ /**
1979
+ * Cleanup resources
1980
+ */
1981
+ destroy() {
1982
+ this.voiceProcessor.stopListening();
1983
+ this.gestureHandler.destroy();
1984
+ this.overlayManager.destroy();
1985
+ }
1986
+ /** Unified response processing */
1987
+ processResponse(response, originalText) {
1988
+ if (response.type === "error" && originalText) {
1989
+ this.fallbackHandler.handleFallback(originalText);
1990
+ this.overlayManager.addMessage(this.fallbackHandler.getFallbackMessage(), "system");
1991
+ return;
239
1992
  }
240
- extractUserLabel(payload) {
241
- const label = payload['label'];
242
- if (typeof label === 'string' && label.trim()) {
243
- return label.trim();
1993
+ if (response.type === "form" && response.fields) {
1994
+ this.overlayManager.addForm(response.message, response.fields, async (data) => {
1995
+ this.overlayManager.showLoading();
1996
+ let nextReponse;
1997
+ try {
1998
+ nextReponse = await this.commandHandler.executeCommand(data);
1999
+ } finally {
2000
+ this.overlayManager.hideLoading();
244
2001
  }
245
- const commandId = payload['commandId'];
246
- if (typeof commandId === 'string' && commandId.trim()) {
247
- return commandId.trim();
2002
+ this.processResponse(nextReponse);
2003
+ });
2004
+ return;
2005
+ }
2006
+ if ((response.type === "ambiguous" || response.type === "confirm") && response.options) {
2007
+ if (response.message) {
2008
+ this.overlayManager.addMessage(response.message, "system");
2009
+ }
2010
+ this.overlayManager.addOptions(response.options);
2011
+ return;
2012
+ }
2013
+ if (response.message) {
2014
+ this.overlayManager.addMessage(response.message, "system");
2015
+ }
2016
+ }
2017
+ /** Add a command dynamically (supports string or rich object) */
2018
+ addCommand(commandOrObj, action) {
2019
+ if (typeof commandOrObj === "string") {
2020
+ console.log(`AssistantService: Adding command "${commandOrObj}".`);
2021
+ } else {
2022
+ console.log(`AssistantService: Adding rich command "${commandOrObj.command}".`);
2023
+ }
2024
+ this.commandHandler.addCommand(commandOrObj, action);
2025
+ }
2026
+ /** Remove a command dynamically */
2027
+ removeCommand(command) {
2028
+ console.log(`AssistantService: Removing command "${command}".`);
2029
+ this.commandHandler.removeCommand(command);
2030
+ }
2031
+ /** Get all registered commands */
2032
+ getCommands() {
2033
+ return this.commandHandler.getCommands();
2034
+ }
2035
+ /** Toggle the assistant overlay */
2036
+ toggle(onSubmit, onClose) {
2037
+ console.log("AssistantService: Toggling overlay...");
2038
+ this.overlayManager.toggle(async (input) => {
2039
+ if (typeof input === "string") {
2040
+ this.overlayManager.addMessage(input, "user");
2041
+ } else if (input && typeof input === "object") {
2042
+ const label = this.extractUserLabel(input);
2043
+ if (label) {
2044
+ this.overlayManager.addMessage(label, "user");
248
2045
  }
249
- return null;
2046
+ }
2047
+ if (onSubmit)
2048
+ onSubmit(input);
2049
+ await this.handleCommand(input);
2050
+ }, () => {
2051
+ console.log("AssistantService: Overlay closed.");
2052
+ if (onClose)
2053
+ onClose();
2054
+ });
2055
+ }
2056
+ extractUserLabel(payload) {
2057
+ const label = payload["label"];
2058
+ if (typeof label === "string" && label.trim()) {
2059
+ return label.trim();
250
2060
  }
251
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantService, deps: [{ token: 'ASSISTANT_CONFIG' }], target: i0.ɵɵFactoryTarget.Injectable });
252
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantService, providedIn: 'root' });
253
- }
254
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantService, decorators: [{
255
- type: Injectable,
256
- args: [{
257
- providedIn: 'root',
258
- }]
259
- }], ctorParameters: () => [{ type: undefined, decorators: [{
260
- type: Inject,
261
- args: ['ASSISTANT_CONFIG']
262
- }] }] });
263
-
264
- class AssistantModule {
265
- static forRoot(config) {
266
- return {
267
- ngModule: AssistantModule,
268
- providers: [
269
- { provide: 'ASSISTANT_CONFIG', useValue: config },
270
- AssistantService,
271
- ],
272
- };
2061
+ const commandId = payload["commandId"];
2062
+ if (typeof commandId === "string" && commandId.trim()) {
2063
+ return commandId.trim();
273
2064
  }
274
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
275
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.0.7", ngImport: i0, type: AssistantModule });
276
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantModule });
277
- }
278
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantModule, decorators: [{
279
- type: NgModule,
280
- args: [{}]
281
- }] });
282
-
283
- /**
284
- * Generated bundle index. Do not edit.
285
- */
286
-
287
- export { AngularWrapperComponent, AssistantModule, AssistantService };
2065
+ return null;
2066
+ }
2067
+ static \u0275fac = i0.\u0275\u0275ngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: _AssistantService, deps: [{ token: "ASSISTANT_CONFIG" }], target: i0.\u0275\u0275FactoryTarget.Injectable });
2068
+ static \u0275prov = i0.\u0275\u0275ngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: _AssistantService, providedIn: "root" });
2069
+ };
2070
+ i0.\u0275\u0275ngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantService, decorators: [{
2071
+ type: Injectable,
2072
+ args: [{
2073
+ providedIn: "root"
2074
+ }]
2075
+ }], ctorParameters: () => [{ type: void 0, decorators: [{
2076
+ type: Inject,
2077
+ args: ["ASSISTANT_CONFIG"]
2078
+ }] }] });
2079
+ var AssistantModule = class _AssistantModule {
2080
+ static forRoot(config) {
2081
+ return {
2082
+ ngModule: _AssistantModule,
2083
+ providers: [
2084
+ { provide: "ASSISTANT_CONFIG", useValue: config },
2085
+ AssistantService
2086
+ ]
2087
+ };
2088
+ }
2089
+ static \u0275fac = i0.\u0275\u0275ngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: _AssistantModule, deps: [], target: i0.\u0275\u0275FactoryTarget.NgModule });
2090
+ static \u0275mod = i0.\u0275\u0275ngDeclareNgModule({ minVersion: "14.0.0", version: "19.0.7", ngImport: i0, type: _AssistantModule });
2091
+ static \u0275inj = i0.\u0275\u0275ngDeclareInjector({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: _AssistantModule });
2092
+ };
2093
+ i0.\u0275\u0275ngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantModule, decorators: [{
2094
+ type: NgModule,
2095
+ args: [{}]
2096
+ }] });
2097
+ export {
2098
+ AngularWrapperComponent,
2099
+ AssistantModule,
2100
+ AssistantService
2101
+ };
288
2102
  //# sourceMappingURL=foisit-angular-wrapper.mjs.map