@foisit/angular-wrapper 2.4.65 → 2.4.66

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