@foisit/angular-wrapper 2.4.655 → 2.5.0

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,332 @@
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
+ enableGestureActivation: this.config.enableGestureActivation,
45
+ });
46
+ // Let the overlay delegate command execution to our CommandHandler when
47
+ // a programmatic handler isn't registered on the overlay.
48
+ this.overlayManager.setExternalCommandExecutor(async (payload) => {
49
+ return this.commandHandler.executeCommand(payload);
50
+ });
51
+ // Register global callbacks for floating button when overlay exists
52
+ this.overlayManager.registerCallbacks(async (input) => {
53
+ if (typeof input === 'string') {
54
+ this.overlayManager.addMessage(input, 'user');
55
+ }
56
+ else if (input && typeof input === 'object') {
57
+ const label = this.extractUserLabel(input);
58
+ if (label) {
59
+ this.overlayManager.addMessage(label, 'user');
60
+ }
61
+ }
62
+ await this.handleCommand(input);
63
+ }, () => console.log('AssistantService: Overlay closed.'));
64
+ this.stateManager = new StateManager();
65
+ }
66
+ else {
67
+ // Server environment: keep browser-specific properties null
68
+ this.stateManager = undefined;
69
+ this.voiceProcessor = undefined;
70
+ this.textToSpeech = undefined;
71
+ this.gestureHandler = undefined;
72
+ this.overlayManager = undefined;
73
+ }
74
+ // Setup commands from config
75
+ this.config.commands.forEach((cmd) => this.commandHandler.addCommand(cmd));
76
+ // Setup fallback response
77
+ if (this.config.fallbackResponse) {
78
+ this.fallbackHandler.setFallbackMessage(this.config.fallbackResponse);
79
+ }
80
+ // Voice disabled for text-first pivot
81
+ // this.startListening();
82
+ // (moved into the browser-only block)
83
+ }
84
+ /** Start listening for activation and commands */
85
+ startListening() {
86
+ // No-op on server or when voice features are unavailable
87
+ if (typeof window === 'undefined' || !this.voiceProcessor) {
88
+ console.log('AssistantService: Voice is disabled or unavailable; startListening() is a no-op.');
89
+ return;
144
90
  }
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" };
91
+ /*
92
+ this.voiceProcessor.startListening(async (transcript: string) => {
93
+ if (this.processingLock) return;
94
+
95
+ const normalizedTranscript = transcript.toLowerCase().trim();
96
+
97
+ // Skip repeated or irrelevant inputs
98
+ if (
99
+ !normalizedTranscript ||
100
+ normalizedTranscript.length < 3 ||
101
+ normalizedTranscript === this.lastProcessedInput
102
+ ) {
103
+ console.log('AssistantService: Ignoring irrelevant input.');
104
+ return;
158
105
  }
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
- };
106
+
107
+ this.lastProcessedInput = normalizedTranscript;
108
+
109
+ if (!this.isActivated) {
110
+ await this.processActivation(normalizedTranscript);
111
+ return;
229
112
  }
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];
113
+
114
+ const isFallbackOrIntroMessage =
115
+ normalizedTranscript === this.config.fallbackResponse?.toLowerCase() ||
116
+ normalizedTranscript === this.config.introMessage?.toLowerCase() ||
117
+ normalizedTranscript === this.defaultIntroMessage.toLowerCase();
118
+
119
+ if (!isFallbackOrIntroMessage) {
120
+ await this.handleCommand(normalizedTranscript);
302
121
  } else {
303
- sanitized[p.name] = s;
122
+ console.log('AssistantService: Ignoring fallback or intro message.');
304
123
  }
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];
336
- }
337
- }
338
- }
124
+
125
+ // Throttle input processing to prevent rapid feedback
126
+ this.processingLock = true;
127
+ setTimeout(() => (this.processingLock = false), 1000);
128
+ });
129
+ */
339
130
  }
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;
131
+ /** Stop listening for voice input */
132
+ stopListening() {
133
+ if (typeof window === 'undefined' || !this.voiceProcessor) {
134
+ console.log('AssistantService: Voice unavailable; stopListening() is a no-op.');
135
+ this.isActivated = false;
444
136
  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
137
  }
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
- }
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;
763
- }
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
- }
138
+ this.voiceProcessor.stopListening();
139
+ this.isActivated = false;
1128
140
  }
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
- });
141
+ /** Reset activation state so the next activation flow can occur. */
142
+ reactivate() {
143
+ console.log('AssistantService: Reactivating assistant...');
144
+ this.isActivated = false;
145
+ try {
146
+ this.startListening();
1233
147
  }
1234
- if (field.defaultValue != null) {
1235
- select.value = String(field.defaultValue);
148
+ catch {
149
+ // no-op
1236
150
  }
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(",");
151
+ }
152
+ /** Process activation command */
153
+ async processActivation(transcript) {
154
+ const activationCmd = this.config.activationCommand?.toLowerCase();
155
+ // If no activation command is set, we can't activate via voice
156
+ if (!activationCmd)
157
+ return;
158
+ if (transcript === activationCmd) {
159
+ console.log('AssistantService: Activation matched.');
160
+ this.isActivated = true;
161
+ this.textToSpeech.speak(this.config.introMessage || this.defaultIntroMessage);
162
+ // this.stateManager.setState('listening'); // DISABLED - no gradient animation
1245
163
  }
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));
164
+ else {
165
+ console.log('AssistantService: Activation command not recognized.');
1253
166
  }
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";
167
+ }
168
+ /** Handle recognized commands */
169
+ async handleCommand(input) {
170
+ this.overlayManager.showLoading();
171
+ let response;
172
+ try {
173
+ response = await this.commandHandler.executeCommand(input);
174
+ }
175
+ finally {
176
+ this.overlayManager.hideLoading();
177
+ }
178
+ const originalText = typeof input === 'string' ? input : undefined;
179
+ this.processResponse(response, originalText);
180
+ }
181
+ /**
182
+ * Cleanup resources
183
+ */
184
+ destroy() {
185
+ this.voiceProcessor?.stopListening();
186
+ this.gestureHandler?.destroy();
187
+ this.overlayManager?.destroy();
188
+ }
189
+ /** Unified response processing */
190
+ processResponse(response, originalText) {
191
+ if (response.type === 'error' && originalText) {
192
+ this.fallbackHandler.handleFallback(originalText);
193
+ this.overlayManager.addMessage(this.fallbackHandler.getFallbackMessage(), 'system');
1279
194
  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
195
  }
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
- }
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";
196
+ if (response.type === 'form' && response.fields) {
197
+ this.overlayManager.addForm(response.message, response.fields, async (data) => {
198
+ this.overlayManager.showLoading();
199
+ let nextReponse;
200
+ try {
201
+ nextReponse = await this.commandHandler.executeCommand(data);
1386
202
  }
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";
203
+ finally {
204
+ this.overlayManager.hideLoading();
1394
205
  }
1395
- continue;
1396
- }
1397
- } catch (_c) {
206
+ this.processResponse(nextReponse);
207
+ });
208
+ return;
209
+ }
210
+ if ((response.type === 'ambiguous' || response.type === 'confirm') && response.options) {
211
+ if (response.message) {
212
+ this.overlayManager.addMessage(response.message, 'system');
1398
213
  }
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";
1408
- }
1409
- continue;
1410
- }
1411
- } catch (_d) {
214
+ this.overlayManager.addOptions(response.options);
215
+ return;
216
+ }
217
+ if (response.message) {
218
+ this.overlayManager.addMessage(response.message, 'system');
219
+ }
220
+ }
221
+ /** Expose programmatic command handler registration to host apps */
222
+ registerCommandHandler(commandId, handler) {
223
+ if (this.overlayManager)
224
+ this.overlayManager.registerCommandHandler(commandId, handler);
225
+ }
226
+ unregisterCommandHandler(commandId) {
227
+ if (this.overlayManager)
228
+ this.overlayManager.unregisterCommandHandler(commandId);
229
+ }
230
+ /** Programmatically run a registered command (proxies to OverlayManager) */
231
+ async runCommand(options) {
232
+ if (!this.overlayManager)
233
+ throw new Error('Overlay manager not available.');
234
+ const res = await this.overlayManager.runCommand(options);
235
+ // If the overlay delegated to the CommandHandler, it returns an
236
+ // InteractiveResponse object that we should process to render forms/options.
237
+ if (res && typeof res === 'object' && 'type' in res) {
238
+ // Let the existing response processing pipeline handle rendering.
239
+ this.processResponse(res);
240
+ }
241
+ return res;
242
+ }
243
+ /** Add a command dynamically (supports string or rich object) */
244
+ addCommand(commandOrObj, action) {
245
+ if (typeof commandOrObj === 'string') {
246
+ console.log(`AssistantService: Adding command "${commandOrObj}".`);
247
+ }
248
+ else {
249
+ console.log(`AssistantService: Adding rich command "${commandOrObj.command}".`);
250
+ }
251
+ this.commandHandler.addCommand(commandOrObj, action);
252
+ }
253
+ /** Remove a command dynamically */
254
+ removeCommand(command) {
255
+ console.log(`AssistantService: Removing command "${command}".`);
256
+ this.commandHandler.removeCommand(command);
257
+ }
258
+ /** Get all registered commands */
259
+ getCommands() {
260
+ return this.commandHandler.getCommands();
261
+ }
262
+ /** Toggle the assistant overlay */
263
+ toggle(onSubmit, onClose) {
264
+ console.log('AssistantService: Toggling overlay...');
265
+ this.overlayManager.toggle(async (input) => {
266
+ if (typeof input === 'string') {
267
+ this.overlayManager.addMessage(input, 'user');
1412
268
  }
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;
269
+ else if (input && typeof input === 'object') {
270
+ const label = this.extractUserLabel(input);
271
+ if (label) {
272
+ this.overlayManager.addMessage(label, 'user');
273
+ }
1427
274
  }
1428
- }
1429
- continue;
1430
- }
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;
275
+ if (onSubmit)
276
+ onSubmit(input);
277
+ await this.handleCommand(input);
278
+ }, () => {
279
+ console.log('AssistantService: Overlay closed.');
280
+ if (onClose)
281
+ onClose();
282
+ });
283
+ }
284
+ extractUserLabel(payload) {
285
+ const label = payload['label'];
286
+ if (typeof label === 'string' && label.trim()) {
287
+ return label.trim();
1442
288
  }
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;
289
+ const commandId = payload['commandId'];
290
+ if (typeof commandId === 'string' && commandId.trim()) {
291
+ return commandId.trim();
1451
292
  }
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;
293
+ return null;
1492
294
  }
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);
295
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantService, deps: [{ token: 'ASSISTANT_CONFIG' }], target: i0.ɵɵFactoryTarget.Injectable });
296
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantService, providedIn: 'root' });
297
+ }
298
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantService, decorators: [{
299
+ type: Injectable,
300
+ args: [{
301
+ providedIn: 'root',
302
+ }]
303
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
304
+ type: Inject,
305
+ args: ['ASSISTANT_CONFIG']
306
+ }] }] });
307
+
308
+ class AssistantModule {
309
+ static forRoot(config) {
310
+ return {
311
+ ngModule: AssistantModule,
312
+ providers: [
313
+ { provide: 'ASSISTANT_CONFIG', useValue: config },
314
+ AssistantService,
315
+ ],
1562
316
  };
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;
1623
- }
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
- }
317
+ }
318
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
319
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.0.7", ngImport: i0, type: AssistantModule });
320
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantModule });
321
+ }
322
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AssistantModule, decorators: [{
323
+ type: NgModule,
324
+ args: [{}]
325
+ }] });
1872
326
 
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
- };
327
+ /**
328
+ * Generated bundle index. Do not edit.
329
+ */
1899
330
 
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
- }
1946
- }
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;
1968
- }
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;
1976
- }
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 {
1987
- }
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;
2028
- }
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();
2037
- }
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");
2081
- }
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();
2096
- }
2097
- const commandId = payload["commandId"];
2098
- if (typeof commandId === "string" && commandId.trim()) {
2099
- return commandId.trim();
2100
- }
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
- };
331
+ export { AngularWrapperComponent, AssistantModule, AssistantService };
2138
332
  //# sourceMappingURL=foisit-angular-wrapper.mjs.map