@foisit/react-wrapper 2.4.5 → 3.0.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.
- package/README.md +45 -1
- package/index.d.ts +1 -0
- package/index.js +73 -44
- package/index.mjs +232 -123
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Transform your React app into an intelligent, voice-ready platform. Foisit provi
|
|
|
33
33
|
- **Programmatic UI Triggers** - Direct command execution via `runCommand()`
|
|
34
34
|
- **Rich Markdown Rendering** - Enhanced response formatting with headings, code, and links
|
|
35
35
|
- **Advanced File Validations** - Comprehensive client-side file validation with size, type, and dimension checks
|
|
36
|
-
- **Premium UI** -
|
|
36
|
+
- **Premium UI** - Glass or solid theme with dark/light mode support
|
|
37
37
|
- **Zero Backend Required** - Secure proxy architecture keeps API keys server-side
|
|
38
38
|
- **React Native** - Uses Hooks, Context API, and modern React patterns
|
|
39
39
|
- **Type-Safe** - Full TypeScript support with comprehensive types
|
|
@@ -441,9 +441,53 @@ interface AssistantConfig {
|
|
|
441
441
|
customHtml?: string;
|
|
442
442
|
position?: { bottom: string; right: string };
|
|
443
443
|
};
|
|
444
|
+
|
|
445
|
+
// Theme mode: 'glass' (default) or 'solid'
|
|
446
|
+
theme?: 'glass' | 'solid';
|
|
447
|
+
|
|
448
|
+
// Custom colors for solid theme (ignored in glass mode)
|
|
449
|
+
themeColors?: ThemeColors;
|
|
444
450
|
}
|
|
445
451
|
```
|
|
446
452
|
|
|
453
|
+
### `ThemeColors`
|
|
454
|
+
|
|
455
|
+
Custom colors for solid theme mode:
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
interface ThemeColors {
|
|
459
|
+
background?: string; // Background color (e.g., '#1e1e2e')
|
|
460
|
+
text?: string; // Primary text color (e.g., '#ffffff')
|
|
461
|
+
accent?: string; // Accent color for highlights (e.g., '#89b4fa' or a gradient)
|
|
462
|
+
userBubbleBg?: string; // User message bubble background
|
|
463
|
+
systemBubbleBg?: string; // System message bubble background
|
|
464
|
+
border?: string; // Border color
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Theme Customization Example
|
|
469
|
+
|
|
470
|
+
```tsx
|
|
471
|
+
const config = {
|
|
472
|
+
commands: [...],
|
|
473
|
+
// Use solid theme with custom colors
|
|
474
|
+
theme: 'solid',
|
|
475
|
+
themeColors: {
|
|
476
|
+
background: '#1e1e2e',
|
|
477
|
+
text: '#cdd6f4',
|
|
478
|
+
accent: '#89b4fa',
|
|
479
|
+
userBubbleBg: 'rgba(137, 180, 250, 0.2)',
|
|
480
|
+
systemBubbleBg: 'rgba(255, 255, 255, 0.05)',
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
<AssistantProvider config={config}>
|
|
485
|
+
<App />
|
|
486
|
+
</AssistantProvider>
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
> **Note**: Glass theme (default) uses glassmorphism with blur effects and adapts to light/dark mode via `prefers-color-scheme`. Solid theme ignores system preferences and uses configured colors.
|
|
490
|
+
|
|
447
491
|
---
|
|
448
492
|
|
|
449
493
|
## Advanced Usage
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const E=require("react/jsx-runtime"),S=require("react"),O={};function N(){return E.jsx("div",{className:O.container,children:E.jsx("h1",{children:"Welcome to ReactWrapper!"})})}class H{constructor(t){this.endpoint=t||"https://foisit-ninja.netlify.app/.netlify/functions/intent"}async determineIntent(t,e,i){try{const s={userInput:t,commands:e.map(o=>({id:o.id,command:o.command,description:o.description,parameters:o.parameters})),context:i},n=await fetch(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!n.ok)throw new Error(`Proxy API Error: ${n.statusText}`);return await n.json()}catch(s){return console.error("OpenAIService Error:",s),{type:"unknown"}}}}class z{constructor(t=!0){if(this.commands=new Map,this.openAIService=null,this.context=null,this.pendingConfirmation=null,this.enableSmartIntent=!0,this.selectOptionsCache=new Map,typeof t=="boolean"){this.enableSmartIntent=t,this.enableSmartIntent&&(this.openAIService=new H);return}this.enableSmartIntent=t.enableSmartIntent??!0,this.enableSmartIntent&&(this.openAIService=new H(t.intentEndpoint))}addCommand(t,e){let i;if(typeof t=="string"){if(!e)throw new Error("Action required when adding command by string.");i={id:t.toLowerCase().replace(/\s+/g,"_"),command:t.toLowerCase(),action:e}}else i={...t},i.id||(i.id=i.command.toLowerCase().replace(/\s+/g,"_"));this.commands.set(i.command.toLowerCase(),i),i.id&&(i.id,i.command)}removeCommand(t){this.commands.delete(t.toLowerCase())}async executeCommand(t){if(typeof t=="object"&&t!==null){if(this.isStructured(t)){const c=String(t.commandId),f=t.params??{},r=this.getCommandById(c);if(!r)return{message:"That command is not available.",type:"error"};const p=this.sanitizeParamsForCommand(r,f),m=(r.parameters??[]).filter(C=>C.required).filter(C=>p[C.name]==null||p[C.name]==="");return m.length>0?(this.context={commandId:this.getCommandIdentifier(r),params:p},{message:`Please provide the required details for "${r.command}".`,type:"form",fields:m}):r.critical?(this.pendingConfirmation={commandId:this.getCommandIdentifier(r),params:p},this.buildConfirmResponse(r)):this.safeRunAction(r,p)}if(!this.context)return{message:"Session expired or invalid context.",type:"error"};const n=this.getCommandById(this.context.commandId);if(!n)return this.context=null,{message:"Session expired or invalid context.",type:"error"};if(Array.isArray(t))return{message:"Invalid form payload.",type:"error"};const a={...this.context.params,...t};if(n.critical)return this.context=null,this.pendingConfirmation={commandId:this.getCommandIdentifier(n),params:a},this.buildConfirmResponse(n);const o=await this.safeRunAction(n,a);return this.context=null,this.normalizeResponse(o)}const e=t.trim().toLowerCase();if(this.pendingConfirmation){const n=e;if(["yes","y","confirm","ok","okay"].includes(n)){const{commandId:a,params:o}=this.pendingConfirmation;this.pendingConfirmation=null;const c=this.getCommandById(a);return c?this.safeRunAction(c,o):{message:"That action is no longer available.",type:"error"}}return["no","n","cancel","stop"].includes(n)?(this.pendingConfirmation=null,{message:"Cancelled.",type:"success"}):{message:"Please confirm: Yes or No.",type:"confirm",options:[{label:"Yes",value:"yes"},{label:"No",value:"no"}]}}const i=this.commands.get(e);if(i){const n=i;if(n.macro)return this.safeRunAction(n,{});const a=(n.parameters??[]).filter(o=>o.required);return a.length>0?(this.context={commandId:this.getCommandIdentifier(n),params:{}},{message:`Please provide the required details for "${n.command}".`,type:"form",fields:a}):n.critical?(this.pendingConfirmation={commandId:this.getCommandIdentifier(n),params:{}},this.buildConfirmResponse(n)):this.safeRunAction(n,{})}const s=await this.tryDeterministicMatch(e);if(s)return s;if(this.enableSmartIntent&&this.openAIService){const n=await this.getCommandsForAI(),a=await this.openAIService.determineIntent(e,n,this.context);return this.handleAIResult(a)}return this.enableSmartIntent?this.listAllCommands():{message:"I'm not sure what you mean.",type:"error"}}async handleAIResult(t){if(t.type==="match"&&t.match){const e=this.getCommandById(t.match);if(!e)return{message:"I'm not sure what you mean.",type:"error"};if(e.macro)return this.safeRunAction(e,{});const i=t.params??{},s=this.sanitizeParamsForCommand(e,i),n=e.allowAiParamExtraction===!1?{}:s,o=(e.parameters??[]).filter(f=>f.required).filter(f=>n[f.name]==null||n[f.name]==="");if(t.incomplete||o.length>0){if(this.context={commandId:this.getCommandIdentifier(e),params:n},!(e.collectRequiredViaForm!==!1)&&this.shouldAskSingleQuestion(o)){const p=o.map(g=>g.name).join(" and ");return{message:t.message||`Please provide ${p}.`,type:"question"}}return{message:t.message||`Please fill in the missing details for "${e.command}".`,type:"form",fields:o}}if(e.critical)return this.pendingConfirmation={commandId:this.getCommandIdentifier(e),params:n},this.buildConfirmResponse(e);const c=await e.action(n);return this.normalizeResponse(c)}return t.type==="ambiguous"&&t.options&&t.options.length?{message:t.message||"Did you mean one of these?",type:"ambiguous",options:t.options.map(e=>({label:e.label,value:e.commandId??e.label,commandId:e.commandId}))}:this.listAllCommands()}sanitizeParamsForCommand(t,e){const i={...e??{}};for(const s of t.parameters??[]){const n=i[s.name];if(s.type==="string"){if(typeof n!="string"){delete i[s.name];continue}const a=n.trim();if(!a){delete i[s.name];continue}i[s.name]=a}if(s.type==="number"){const a=typeof n=="number"?n:Number(n==null?void 0:n.toString().trim());if(Number.isNaN(a)){delete i[s.name];continue}if(typeof s.min=="number"&&a<s.min){delete i[s.name];continue}if(typeof s.max=="number"&&a>s.max){delete i[s.name];continue}i[s.name]=a}if(s.type==="date"){const a=i[s.name];if(typeof a=="string"){const o=a.trim();this.isIsoDateString(o)?i[s.name]=o:delete i[s.name]}else delete i[s.name]}if(s.type==="select"){const a=typeof n=="string"?n:n==null?void 0:n.toString();if(!a){delete i[s.name];continue}if(Array.isArray(s.options)&&s.options.length>0&&!s.options.some(c=>String(c.value)===String(a))){delete i[s.name];continue}i[s.name]=a}if(s.type==="file"){const a=i[s.name],o=a&&typeof a=="object"&&typeof a.name=="string"&&typeof a.size=="number",c=typeof a=="string"&&/^data:[^;]+;base64,/.test(a);(s.delivery??"file")==="base64"?!c&&!o&&delete i[s.name]:o||delete i[s.name]}}return i}isIsoDateString(t){if(!/^\d{4}-\d{2}-\d{2}$/.test(t))return!1;const e=new Date(`${t}T00:00:00Z`);return!Number.isNaN(e.getTime())}shouldAskSingleQuestion(t){if(t.length!==1)return!1;const e=t[0].type;return e==="string"||e==="number"||e==="date"}buildConfirmResponse(t){return{message:`Are you sure you want to run "${t.command}"?`,type:"confirm",options:[{label:"Yes",value:"yes"},{label:"No",value:"no"}]}}async tryDeterministicMatch(t){const e=[];for(const o of this.commands.values()){let c=0;const f=o.command.toLowerCase();t.includes(f)&&(c+=5);const r=o.keywords??[];for(const p of r){const g=p.toLowerCase().trim();g&&(t===g?c+=4:t.includes(g)&&(c+=3))}c>0&&e.push({cmd:o,score:c})}if(e.length===0)return null;e.sort((o,c)=>c.score-o.score);const i=e[0].score,s=e.filter(o=>o.score===i).slice(0,3);if(s.length>1)return{message:"I think you mean one of these. Which one should I run?",type:"ambiguous",options:s.map(o=>({label:o.cmd.command,value:o.cmd.command,commandId:o.cmd.id}))};const n=s[0].cmd,a=(n.parameters??[]).filter(o=>o.required);return a.length>0?(this.context={commandId:this.getCommandIdentifier(n),params:{}},{message:`Please provide the required details for "${n.command}".`,type:"form",fields:a}):n.critical?(this.pendingConfirmation={commandId:this.getCommandIdentifier(n),params:{}},this.buildConfirmResponse(n)):this.safeRunAction(n,{})}async safeRunAction(t,e){try{const i=await t.action(e??{});return this.normalizeResponse(i)}catch{return{message:"Something went wrong while running that command.",type:"error"}}}async getCommandsForAI(){const t=Array.from(this.commands.values()).map(e=>({...e,parameters:e.parameters?e.parameters.map(i=>({...i})):void 0}));return await Promise.all(t.map(async e=>{e.parameters&&await Promise.all(e.parameters.map(async i=>{if(i.type!=="select"||!i.getOptions||i.options&&i.options.length)return;const s=`${e.id??e.command}:${i.name}`,n=this.selectOptionsCache.get(s),a=Date.now();if(n&&a-n.ts<6e4){i.options=n.options;return}try{const o=await i.getOptions();this.selectOptionsCache.set(s,{options:o,ts:a}),i.options=o}catch{}}))})),t}getCommandById(t){for(const e of this.commands.values())if(e.id===t)return e}listAllCommands(){return{message:"Here are the available commands:",type:"ambiguous",options:Array.from(this.commands.values()).map(e=>({label:e.command,value:e.id??e.command,commandId:e.id??e.command}))}}normalizeResponse(t){return typeof t=="string"?{message:t,type:"success"}:t&&typeof t=="object"?t:{message:"Done",type:"success"}}isStructured(t){return typeof t.commandId=="string"}getCommandIdentifier(t){return t.id||(t.id=t.command.toLowerCase().replace(/\s+/g,"_")),t.id}getCommands(){return Array.from(this.commands.keys())}}class P{constructor(){this.synth=typeof window<"u"?window.speechSynthesis:null}speak(t,e){if(!this.synth){console.error("SpeechSynthesis API is not supported in this environment.");return}const i=new SpeechSynthesisUtterance(t);e&&(i.pitch=e.pitch||1,i.rate=e.rate||1,i.volume=e.volume||1),typeof window<"u"&&(i.onstart=()=>{window.dispatchEvent(new CustomEvent("foisit:tts-start"))},i.onend=()=>{console.log("Speech finished."),window.dispatchEvent(new CustomEvent("foisit:tts-end"))}),i.onerror=s=>{console.error("Error during speech synthesis:",s.error)},this.synth.speak(i)}stopSpeaking(){this.synth&&this.synth.cancel()}}class W{constructor(){this.fallbackMessage="Sorry, I didn’t understand that."}setFallbackMessage(t){this.fallbackMessage=t}handleFallback(t){t&&console.log(`Fallback triggered for: "${t}"`),console.log(this.fallbackMessage),new P().speak(this.fallbackMessage)}getFallbackMessage(){return this.fallbackMessage}}const $=()=>{if(typeof window>"u")return null;const d=window;return d.SpeechRecognition??d.webkitSpeechRecognition??null};class B{constructor(t="en-US",e={}){this.recognition=null,this.isListening=!1,this.engineActive=!1,this.intentionallyStopped=!1,this.restartAllowed=!0,this.lastStart=0,this.backoffMs=250,this.destroyed=!1,this.resultCallback=null,this.ttsSpeaking=!1,this.debugEnabled=!0,this.restartTimer=null,this.prewarmed=!1,this.hadResultThisSession=!1,this.onTTSStart=()=>{var s;this.ttsSpeaking=!0;try{(s=this.recognition)==null||s.stop()}catch{}this.isListening&&this.emitStatus("speaking")},this.onTTSEnd=()=>{this.ttsSpeaking=!1,this.isListening&&this.restartAllowed?this.safeRestart():this.emitStatus(this.isListening?"listening":"idle")};const i=$();if(i){this.recognition=new i,this.recognition.lang=t,this.recognition.interimResults=e.interimResults??!0,this.recognition.continuous=e.continuous??!0,this.recognition.onresult=n=>this.handleResult(n,e),this.recognition.onend=()=>this.handleEnd(),this.recognition.onstart=()=>{this.log("recognition onstart"),this.engineActive=!0,this.hadResultThisSession=!1,this.restartTimer&&(clearTimeout(this.restartTimer),this.restartTimer=null),this.backoffMs=250,this.isListening&&!this.ttsSpeaking&&this.emitStatus("listening")};const s=this.recognition;s.onaudiostart=()=>this.log("onaudiostart"),s.onsoundstart=()=>this.log("onsoundstart"),s.onspeechstart=()=>this.log("onspeechstart"),s.onspeechend=()=>this.log("onspeechend"),s.onsoundend=()=>this.log("onsoundend"),s.onaudioend=()=>this.log("onaudioend"),this.recognition.onerror=n=>this.handleError(n)}else this.recognition=null,this.emitStatus("unsupported");typeof window<"u"?(window.addEventListener("foisit:tts-start",this.onTTSStart),window.addEventListener("foisit:tts-end",this.onTTSEnd),this.visibilityHandler=()=>{var s;if(typeof document<"u"&&document.hidden){try{(s=this.recognition)==null||s.stop()}catch{}this.emitStatus(this.ttsSpeaking?"speaking":"idle")}else this.isListening&&!this.ttsSpeaking&&this.safeRestart()},typeof document<"u"&&document.addEventListener("visibilitychange",this.visibilityHandler)):this.visibilityHandler=void 0}log(t){this.debugEnabled&&t&&console.log("[VoiceProcessor]",t)}warn(t){this.debugEnabled&&t&&console.warn("[VoiceProcessor]",t)}error(t){this.debugEnabled&&t&&console.error("[VoiceProcessor]",t)}isSupported(){return $()!==null}onStatusChange(t){this.statusCallback=t}startListening(t){if(!this.isSupported()||!this.recognition){this.warn("VoiceProcessor: SpeechRecognition is not supported in this browser."),this.emitStatus("unsupported");return}if(this.isListening){this.warn("VoiceProcessor: Already listening."),this.resultCallback=t;return}this.resultCallback=t,this.intentionallyStopped=!1,this.restartAllowed=!0,this.isListening=!0,this.emitStatus("listening"),this.prewarmAudio().finally(()=>{this.safeRestart()})}stopListening(){var t;this.intentionallyStopped=!0,this.restartAllowed=!1,this.isListening=!1,this.emitStatus(this.ttsSpeaking?"speaking":"idle");try{(t=this.recognition)==null||t.stop()}catch{}}destroy(){this.destroyed=!0,this.stopListening(),this.resultCallback=null,window.removeEventListener("foisit:tts-start",this.onTTSStart),window.removeEventListener("foisit:tts-end",this.onTTSEnd),this.visibilityHandler&&(document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=void 0)}handleResult(t,e){var s,n;if(!this.resultCallback)return;const i=e.confidenceThreshold??.6;for(let a=t.resultIndex;a<t.results.length;a++){const o=t.results[a],c=o&&o[0],f=((n=(s=c==null?void 0:c.transcript)==null?void 0:s.trim)==null?void 0:n.call(s))||"",r=(c==null?void 0:c.confidence)??0;if(f&&!(!o.isFinal&&e.interimResults===!1)&&!(o.isFinal&&r<i))try{this.hadResultThisSession=!0,this.resultCallback(f,!!o.isFinal)}catch{this.error("VoiceProcessor: result callback error")}}}handleEnd(){if(this.log("recognition onend"),this.engineActive=!1,this.destroyed||this.intentionallyStopped||!this.restartAllowed||this.ttsSpeaking){this.ttsSpeaking||(this.isListening=!1,this.emitStatus("idle"));return}this.isListening=!0,this.scheduleRestart()}handleError(t){const e=t==null?void 0:t.error;if(this.warn(`Error occurred: ${e??"unknown"}`),e&&["not-allowed","service-not-allowed","bad-grammar","language-not-supported"].includes(e)){this.intentionallyStopped=!0,this.restartAllowed=!1,this.isListening=!1,this.emitStatus("error",{error:e});return}this.scheduleRestart()}safeRestart(){if(!this.recognition)return;if(this.engineActive){this.log("safeRestart: engine already active, skipping start");return}const t=Date.now();if(t-this.lastStart<300){setTimeout(()=>this.safeRestart(),300);return}this.lastStart=t;try{this.log("calling recognition.start()"),this.recognition.start(),this.backoffMs=250,this.isListening&&!this.ttsSpeaking&&this.emitStatus("listening")}catch{this.error("recognition.start() threw; scheduling restart"),this.scheduleRestart()}}scheduleRestart(){if(this.destroyed||this.intentionallyStopped||!this.restartAllowed||this.ttsSpeaking)return;if(this.engineActive){this.log("scheduleRestart: engine active, not scheduling");return}const t=Math.min(this.backoffMs,2e3);if(this.log(`scheduleRestart in ${t}ms`),this.restartTimer){this.log("scheduleRestart: restart already scheduled");return}this.restartTimer=setTimeout(()=>{this.restartTimer=null,!(this.destroyed||this.intentionallyStopped||!this.restartAllowed||this.ttsSpeaking)&&this.safeRestart()},t),this.backoffMs=Math.min(this.backoffMs*2,2e3)}async prewarmAudio(){if(!this.prewarmed)try{if(typeof navigator>"u"||!("mediaDevices"in navigator))return;const t=navigator.mediaDevices;if(!(t!=null&&t.getUserMedia))return;this.log("prewarmAudio: requesting mic");const e=await t.getUserMedia({audio:!0});for(const i of e.getTracks())i.stop();this.prewarmed=!0,this.log("prewarmAudio: mic ready")}catch{this.warn("prewarmAudio: failed to get mic")}}emitStatus(t,e){if(this.statusCallback)try{this.statusCallback(t,e)}catch{this.error("VoiceProcessor: status callback error")}}}class R{constructor(){this.lastTap=0,this.tapCount=0}setupTripleTapListener(t){this.destroy(),this.clickListener=()=>{this.tapCount++,this.tapCount===1?this.tapTimeout=window.setTimeout(()=>{this.tapCount=0},500):this.tapCount===3&&(clearTimeout(this.tapTimeout),this.tapCount=0,t())},document.addEventListener("click",this.clickListener),this.touchEndListener=()=>{const e=new Date().getTime(),i=e-this.lastTap;i<500&&i>0?(this.tapCount++,this.tapCount===3&&(this.tapCount=0,t())):this.tapCount=1,this.lastTap=e},document.addEventListener("touchend",this.touchEndListener)}destroy(){this.dblClickListener&&document.removeEventListener("dblclick",this.dblClickListener),this.touchEndListener&&document.removeEventListener("touchend",this.touchEndListener),this.clickListener&&document.removeEventListener("click",this.clickListener),this.tapTimeout&&clearTimeout(this.tapTimeout),this.dblClickListener=void 0,this.touchEndListener=void 0,this.clickListener=void 0,this.tapTimeout=void 0,this.tapCount=0}}function j(){if(document.querySelector("#assistant-styles")){console.log("Styles already injected");return}const t=document.createElement("style");t.id="assistant-styles",t.innerHTML=`
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const E=require("react/jsx-runtime"),S=require("react"),O={};function B(){return E.jsx("div",{className:O.container,children:E.jsx("h1",{children:"Welcome to ReactWrapper!"})})}class ${constructor(t){this.endpoint=t||"https://foisit-ninja.netlify.app/.netlify/functions/intent"}async determineIntent(t,e,i){try{const s={userInput:t,commands:e.map(o=>({id:o.id,command:o.command,description:o.description,parameters:o.parameters})),context:i},n=await fetch(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!n.ok)throw new Error(`Proxy API Error: ${n.statusText}`);return await n.json()}catch(s){return console.error("OpenAIService Error:",s),{type:"unknown"}}}}class N{constructor(t=!0){if(this.commands=new Map,this.openAIService=null,this.context=null,this.pendingConfirmation=null,this.enableSmartIntent=!0,this.selectOptionsCache=new Map,typeof t=="boolean"){this.enableSmartIntent=t,this.enableSmartIntent&&(this.openAIService=new $);return}this.enableSmartIntent=t.enableSmartIntent??!0,this.enableSmartIntent&&(this.openAIService=new $(t.intentEndpoint))}addCommand(t,e){let i;if(typeof t=="string"){if(!e)throw new Error("Action required when adding command by string.");i={id:t.toLowerCase().replace(/\s+/g,"_"),command:t.toLowerCase(),action:e}}else i={...t},i.id||(i.id=i.command.toLowerCase().replace(/\s+/g,"_"));this.commands.set(i.command.toLowerCase(),i),i.id&&(i.id,i.command)}removeCommand(t){this.commands.delete(t.toLowerCase())}async executeCommand(t){if(typeof t=="object"&&t!==null){if(this.isStructured(t)){const c=String(t.commandId),u=t.params??{},r=this.getCommandById(c);if(!r)return{message:"That command is not available.",type:"error"};const p=this.sanitizeParamsForCommand(r,u),m=(r.parameters??[]).filter(C=>C.required).filter(C=>p[C.name]==null||p[C.name]==="");return m.length>0?(this.context={commandId:this.getCommandIdentifier(r),params:p},{message:`Please provide the required details for "${r.command}".`,type:"form",fields:m}):r.critical?(this.pendingConfirmation={commandId:this.getCommandIdentifier(r),params:p},this.buildConfirmResponse(r)):this.safeRunAction(r,p)}if(!this.context)return{message:"Session expired or invalid context.",type:"error"};const n=this.getCommandById(this.context.commandId);if(!n)return this.context=null,{message:"Session expired or invalid context.",type:"error"};if(Array.isArray(t))return{message:"Invalid form payload.",type:"error"};const a={...this.context.params,...t};if(n.critical)return this.context=null,this.pendingConfirmation={commandId:this.getCommandIdentifier(n),params:a},this.buildConfirmResponse(n);const o=await this.safeRunAction(n,a);return this.context=null,this.normalizeResponse(o)}const e=t.trim().toLowerCase();if(this.pendingConfirmation){const n=e;if(["yes","y","confirm","ok","okay"].includes(n)){const{commandId:a,params:o}=this.pendingConfirmation;this.pendingConfirmation=null;const c=this.getCommandById(a);return c?this.safeRunAction(c,o):{message:"That action is no longer available.",type:"error"}}return["no","n","cancel","stop"].includes(n)?(this.pendingConfirmation=null,{message:"Cancelled.",type:"success"}):{message:"Please confirm: Yes or No.",type:"confirm",options:[{label:"Yes",value:"yes"},{label:"No",value:"no"}]}}const i=this.commands.get(e);if(i){const n=i;if(n.macro)return this.safeRunAction(n,{});const a=(n.parameters??[]).filter(o=>o.required);return a.length>0?(this.context={commandId:this.getCommandIdentifier(n),params:{}},{message:`Please provide the required details for "${n.command}".`,type:"form",fields:a}):n.critical?(this.pendingConfirmation={commandId:this.getCommandIdentifier(n),params:{}},this.buildConfirmResponse(n)):this.safeRunAction(n,{})}const s=await this.tryDeterministicMatch(e);if(s)return s;if(this.enableSmartIntent&&this.openAIService){const n=await this.getCommandsForAI(),a=await this.openAIService.determineIntent(e,n,this.context);return this.handleAIResult(a)}return this.enableSmartIntent?this.listAllCommands():{message:"I'm not sure what you mean.",type:"error"}}async handleAIResult(t){if(t.type==="match"&&t.match){const e=this.getCommandById(t.match);if(!e)return{message:"I'm not sure what you mean.",type:"error"};if(e.macro)return this.safeRunAction(e,{});const i=t.params??{},s=this.sanitizeParamsForCommand(e,i),n=e.allowAiParamExtraction===!1?{}:s,o=(e.parameters??[]).filter(u=>u.required).filter(u=>n[u.name]==null||n[u.name]==="");if(t.incomplete||o.length>0){if(this.context={commandId:this.getCommandIdentifier(e),params:n},!(e.collectRequiredViaForm!==!1)&&this.shouldAskSingleQuestion(o)){const p=o.map(g=>g.name).join(" and ");return{message:t.message||`Please provide ${p}.`,type:"question"}}return{message:t.message||`Please fill in the missing details for "${e.command}".`,type:"form",fields:o}}if(e.critical)return this.pendingConfirmation={commandId:this.getCommandIdentifier(e),params:n},this.buildConfirmResponse(e);const c=await e.action(n);return this.normalizeResponse(c)}return t.type==="ambiguous"&&t.options&&t.options.length?{message:t.message||"Did you mean one of these?",type:"ambiguous",options:t.options.map(e=>({label:e.label,value:e.commandId??e.label,commandId:e.commandId}))}:this.listAllCommands()}sanitizeParamsForCommand(t,e){const i={...e??{}};for(const s of t.parameters??[]){const n=i[s.name];if(s.type==="string"){if(typeof n!="string"){delete i[s.name];continue}const a=n.trim();if(!a){delete i[s.name];continue}i[s.name]=a}if(s.type==="number"){const a=typeof n=="number"?n:Number(n==null?void 0:n.toString().trim());if(Number.isNaN(a)){delete i[s.name];continue}if(typeof s.min=="number"&&a<s.min){delete i[s.name];continue}if(typeof s.max=="number"&&a>s.max){delete i[s.name];continue}i[s.name]=a}if(s.type==="date"){const a=i[s.name];if(typeof a=="string"){const o=a.trim();this.isIsoDateString(o)?i[s.name]=o:delete i[s.name]}else delete i[s.name]}if(s.type==="select"){const a=typeof n=="string"?n:n==null?void 0:n.toString();if(!a){delete i[s.name];continue}if(Array.isArray(s.options)&&s.options.length>0&&!s.options.some(c=>String(c.value)===String(a))){delete i[s.name];continue}i[s.name]=a}if(s.type==="file"){const a=i[s.name],o=a&&typeof a=="object"&&typeof a.name=="string"&&typeof a.size=="number",c=typeof a=="string"&&/^data:[^;]+;base64,/.test(a);(s.delivery??"file")==="base64"?!c&&!o&&delete i[s.name]:o||delete i[s.name]}}return i}isIsoDateString(t){if(!/^\d{4}-\d{2}-\d{2}$/.test(t))return!1;const e=new Date(`${t}T00:00:00Z`);return!Number.isNaN(e.getTime())}shouldAskSingleQuestion(t){if(t.length!==1)return!1;const e=t[0].type;return e==="string"||e==="number"||e==="date"}buildConfirmResponse(t){return{message:`Are you sure you want to run "${t.command}"?`,type:"confirm",options:[{label:"Yes",value:"yes"},{label:"No",value:"no"}]}}async tryDeterministicMatch(t){const e=[];for(const o of this.commands.values()){let c=0;const u=o.command.toLowerCase();t.includes(u)&&(c+=5);const r=o.keywords??[];for(const p of r){const g=p.toLowerCase().trim();g&&(t===g?c+=4:t.includes(g)&&(c+=3))}c>0&&e.push({cmd:o,score:c})}if(e.length===0)return null;e.sort((o,c)=>c.score-o.score);const i=e[0].score,s=e.filter(o=>o.score===i).slice(0,3);if(s.length>1)return{message:"I think you mean one of these. Which one should I run?",type:"ambiguous",options:s.map(o=>({label:o.cmd.command,value:o.cmd.command,commandId:o.cmd.id}))};const n=s[0].cmd,a=(n.parameters??[]).filter(o=>o.required);return a.length>0?(this.context={commandId:this.getCommandIdentifier(n),params:{}},{message:`Please provide the required details for "${n.command}".`,type:"form",fields:a}):n.critical?(this.pendingConfirmation={commandId:this.getCommandIdentifier(n),params:{}},this.buildConfirmResponse(n)):this.safeRunAction(n,{})}async safeRunAction(t,e){try{const i=await t.action(e??{});return this.normalizeResponse(i)}catch{return{message:"Something went wrong while running that command.",type:"error"}}}async getCommandsForAI(){const t=Array.from(this.commands.values()).map(e=>({...e,parameters:e.parameters?e.parameters.map(i=>({...i})):void 0}));return await Promise.all(t.map(async e=>{e.parameters&&await Promise.all(e.parameters.map(async i=>{if(i.type!=="select"||!i.getOptions||i.options&&i.options.length)return;const s=`${e.id??e.command}:${i.name}`,n=this.selectOptionsCache.get(s),a=Date.now();if(n&&a-n.ts<6e4){i.options=n.options;return}try{const o=await i.getOptions();this.selectOptionsCache.set(s,{options:o,ts:a}),i.options=o}catch{}}))})),t}getCommandById(t){for(const e of this.commands.values())if(e.id===t)return e}listAllCommands(){return{message:"Here are the available commands:",type:"ambiguous",options:Array.from(this.commands.values()).map(e=>({label:e.command,value:e.id??e.command,commandId:e.id??e.command}))}}normalizeResponse(t){return typeof t=="string"?{message:t,type:"success"}:t&&typeof t=="object"?t:{message:"Done",type:"success"}}isStructured(t){return typeof t.commandId=="string"}getCommandIdentifier(t){return t.id||(t.id=t.command.toLowerCase().replace(/\s+/g,"_")),t.id}getCommands(){return Array.from(this.commands.keys())}}class P{constructor(){this.synth=typeof window<"u"?window.speechSynthesis:null}speak(t,e){if(!this.synth){console.error("SpeechSynthesis API is not supported in this environment.");return}const i=new SpeechSynthesisUtterance(t);e&&(i.pitch=e.pitch||1,i.rate=e.rate||1,i.volume=e.volume||1),typeof window<"u"&&(i.onstart=()=>{window.dispatchEvent(new CustomEvent("foisit:tts-start"))},i.onend=()=>{console.log("Speech finished."),window.dispatchEvent(new CustomEvent("foisit:tts-end"))}),i.onerror=s=>{console.error("Error during speech synthesis:",s.error)},this.synth.speak(i)}stopSpeaking(){this.synth&&this.synth.cancel()}}class z{constructor(){this.fallbackMessage="Sorry, I didn’t understand that."}setFallbackMessage(t){this.fallbackMessage=t}handleFallback(t){t&&console.log(`Fallback triggered for: "${t}"`),console.log(this.fallbackMessage),new P().speak(this.fallbackMessage)}getFallbackMessage(){return this.fallbackMessage}}const H=()=>{if(typeof window>"u")return null;const d=window;return d.SpeechRecognition??d.webkitSpeechRecognition??null};class W{constructor(t="en-US",e={}){this.recognition=null,this.isListening=!1,this.engineActive=!1,this.intentionallyStopped=!1,this.restartAllowed=!0,this.lastStart=0,this.backoffMs=250,this.destroyed=!1,this.resultCallback=null,this.ttsSpeaking=!1,this.debugEnabled=!0,this.restartTimer=null,this.prewarmed=!1,this.hadResultThisSession=!1,this.onTTSStart=()=>{var s;this.ttsSpeaking=!0;try{(s=this.recognition)==null||s.stop()}catch{}this.isListening&&this.emitStatus("speaking")},this.onTTSEnd=()=>{this.ttsSpeaking=!1,this.isListening&&this.restartAllowed?this.safeRestart():this.emitStatus(this.isListening?"listening":"idle")};const i=H();if(i){this.recognition=new i,this.recognition.lang=t,this.recognition.interimResults=e.interimResults??!0,this.recognition.continuous=e.continuous??!0,this.recognition.onresult=n=>this.handleResult(n,e),this.recognition.onend=()=>this.handleEnd(),this.recognition.onstart=()=>{this.log("recognition onstart"),this.engineActive=!0,this.hadResultThisSession=!1,this.restartTimer&&(clearTimeout(this.restartTimer),this.restartTimer=null),this.backoffMs=250,this.isListening&&!this.ttsSpeaking&&this.emitStatus("listening")};const s=this.recognition;s.onaudiostart=()=>this.log("onaudiostart"),s.onsoundstart=()=>this.log("onsoundstart"),s.onspeechstart=()=>this.log("onspeechstart"),s.onspeechend=()=>this.log("onspeechend"),s.onsoundend=()=>this.log("onsoundend"),s.onaudioend=()=>this.log("onaudioend"),this.recognition.onerror=n=>this.handleError(n)}else this.recognition=null,this.emitStatus("unsupported");typeof window<"u"?(window.addEventListener("foisit:tts-start",this.onTTSStart),window.addEventListener("foisit:tts-end",this.onTTSEnd),this.visibilityHandler=()=>{var s;if(typeof document<"u"&&document.hidden){try{(s=this.recognition)==null||s.stop()}catch{}this.emitStatus(this.ttsSpeaking?"speaking":"idle")}else this.isListening&&!this.ttsSpeaking&&this.safeRestart()},typeof document<"u"&&document.addEventListener("visibilitychange",this.visibilityHandler)):this.visibilityHandler=void 0}log(t){this.debugEnabled&&t&&console.log("[VoiceProcessor]",t)}warn(t){this.debugEnabled&&t&&console.warn("[VoiceProcessor]",t)}error(t){this.debugEnabled&&t&&console.error("[VoiceProcessor]",t)}isSupported(){return H()!==null}onStatusChange(t){this.statusCallback=t}startListening(t){if(!this.isSupported()||!this.recognition){this.warn("VoiceProcessor: SpeechRecognition is not supported in this browser."),this.emitStatus("unsupported");return}if(this.isListening){this.warn("VoiceProcessor: Already listening."),this.resultCallback=t;return}this.resultCallback=t,this.intentionallyStopped=!1,this.restartAllowed=!0,this.isListening=!0,this.emitStatus("listening"),this.prewarmAudio().finally(()=>{this.safeRestart()})}stopListening(){var t;this.intentionallyStopped=!0,this.restartAllowed=!1,this.isListening=!1,this.emitStatus(this.ttsSpeaking?"speaking":"idle");try{(t=this.recognition)==null||t.stop()}catch{}}destroy(){this.destroyed=!0,this.stopListening(),this.resultCallback=null,window.removeEventListener("foisit:tts-start",this.onTTSStart),window.removeEventListener("foisit:tts-end",this.onTTSEnd),this.visibilityHandler&&(document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=void 0)}handleResult(t,e){var s,n;if(!this.resultCallback)return;const i=e.confidenceThreshold??.6;for(let a=t.resultIndex;a<t.results.length;a++){const o=t.results[a],c=o&&o[0],u=((n=(s=c==null?void 0:c.transcript)==null?void 0:s.trim)==null?void 0:n.call(s))||"",r=(c==null?void 0:c.confidence)??0;if(u&&!(!o.isFinal&&e.interimResults===!1)&&!(o.isFinal&&r<i))try{this.hadResultThisSession=!0,this.resultCallback(u,!!o.isFinal)}catch{this.error("VoiceProcessor: result callback error")}}}handleEnd(){if(this.log("recognition onend"),this.engineActive=!1,this.destroyed||this.intentionallyStopped||!this.restartAllowed||this.ttsSpeaking){this.ttsSpeaking||(this.isListening=!1,this.emitStatus("idle"));return}this.isListening=!0,this.scheduleRestart()}handleError(t){const e=t==null?void 0:t.error;if(this.warn(`Error occurred: ${e??"unknown"}`),e&&["not-allowed","service-not-allowed","bad-grammar","language-not-supported"].includes(e)){this.intentionallyStopped=!0,this.restartAllowed=!1,this.isListening=!1,this.emitStatus("error",{error:e});return}this.scheduleRestart()}safeRestart(){if(!this.recognition)return;if(this.engineActive){this.log("safeRestart: engine already active, skipping start");return}const t=Date.now();if(t-this.lastStart<300){setTimeout(()=>this.safeRestart(),300);return}this.lastStart=t;try{this.log("calling recognition.start()"),this.recognition.start(),this.backoffMs=250,this.isListening&&!this.ttsSpeaking&&this.emitStatus("listening")}catch{this.error("recognition.start() threw; scheduling restart"),this.scheduleRestart()}}scheduleRestart(){if(this.destroyed||this.intentionallyStopped||!this.restartAllowed||this.ttsSpeaking)return;if(this.engineActive){this.log("scheduleRestart: engine active, not scheduling");return}const t=Math.min(this.backoffMs,2e3);if(this.log(`scheduleRestart in ${t}ms`),this.restartTimer){this.log("scheduleRestart: restart already scheduled");return}this.restartTimer=setTimeout(()=>{this.restartTimer=null,!(this.destroyed||this.intentionallyStopped||!this.restartAllowed||this.ttsSpeaking)&&this.safeRestart()},t),this.backoffMs=Math.min(this.backoffMs*2,2e3)}async prewarmAudio(){if(!this.prewarmed)try{if(typeof navigator>"u"||!("mediaDevices"in navigator))return;const t=navigator.mediaDevices;if(!(t!=null&&t.getUserMedia))return;this.log("prewarmAudio: requesting mic");const e=await t.getUserMedia({audio:!0});for(const i of e.getTracks())i.stop();this.prewarmed=!0,this.log("prewarmAudio: mic ready")}catch{this.warn("prewarmAudio: failed to get mic")}}emitStatus(t,e){if(this.statusCallback)try{this.statusCallback(t,e)}catch{this.error("VoiceProcessor: status callback error")}}}class R{constructor(){this.lastTap=0,this.tapCount=0}setupTripleTapListener(t){this.destroy(),this.clickListener=()=>{this.tapCount++,this.tapCount===1?this.tapTimeout=window.setTimeout(()=>{this.tapCount=0},500):this.tapCount===3&&(clearTimeout(this.tapTimeout),this.tapCount=0,t())},document.addEventListener("click",this.clickListener),this.touchEndListener=()=>{const e=new Date().getTime(),i=e-this.lastTap;i<500&&i>0?(this.tapCount++,this.tapCount===3&&(this.tapCount=0,t())):this.tapCount=1,this.lastTap=e},document.addEventListener("touchend",this.touchEndListener)}destroy(){this.dblClickListener&&document.removeEventListener("dblclick",this.dblClickListener),this.touchEndListener&&document.removeEventListener("touchend",this.touchEndListener),this.clickListener&&document.removeEventListener("click",this.clickListener),this.tapTimeout&&clearTimeout(this.tapTimeout),this.dblClickListener=void 0,this.touchEndListener=void 0,this.clickListener=void 0,this.tapTimeout=void 0,this.tapCount=0}}function j(){if(document.querySelector("#assistant-styles")){console.log("Styles already injected");return}const t=document.createElement("style");t.id="assistant-styles",t.innerHTML=`
|
|
2
2
|
/* Rounded shape with gradient animation */
|
|
3
3
|
.gradient-indicator {
|
|
4
4
|
position: fixed;
|
|
@@ -31,57 +31,86 @@
|
|
|
31
31
|
border-radius: 50%;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
`,document.head.appendChild(t),console.log("Gradient styles injected")}function U(){if(document.querySelector("#gradient-indicator"))return;const d=document.createElement("div");d.id="gradient-indicator",j(),d.classList.add("gradient-indicator"),document.body.appendChild(d),console.log("Gradient indicator added to the DOM")}function D(){const d=document.querySelector("#gradient-indicator");d&&(d.remove(),console.log("Gradient indicator removed from the DOM"))}function V(){return typeof window<"u"&&typeof document<"u"}class _{constructor(){this.state="idle",this.subscribers=[]}getState(){return this.state}setState(t){this.state=t,this.notifySubscribers(),console.log("State updated:",t),t==="listening"?U():D()}subscribe(t){this.subscribers.push(t)}notifySubscribers(){this.subscribers.forEach(t=>t(this.state))}}class G{constructor(t){this.container=null,this.chatWindow=null,this.messagesContainer=null,this.input=null,this.isOpen=!1,this.loadingEl=null,this.commandHandlers=new Map,this.active=V(),this.handleClickOutside=e=>{const i=e.target;this.chatWindow&&this.chatWindow.contains(i)||i.closest(".foisit-floating-btn")||this.toggle()},this.config=t,this.active&&this.init()}registerCommandHandler(t,e){!t||typeof e!="function"||this.commandHandlers.set(t,e)}hasCommandHandler(t){return this.commandHandlers.has(t)}setExternalCommandExecutor(t){this.externalCommandExecutor=t}unregisterCommandHandler(t){t&&this.commandHandlers.delete(t)}async runCommand(t){if(!t||!t.commandId)throw new Error("runCommand requires a commandId");const{commandId:e,params:i,openOverlay:s=!0,showInvocation:n=!0}=t;s&&!this.isOpen&&this.toggle();const a=this.commandHandlers.get(e);if(a&&n&&this.messagesContainer&&this.addMessage(`Command: ${e}`,"user"),a)try{this.showLoading();const o=await a(i);if(this.hideLoading(),typeof o=="string")this.addMessage(o,"system");else if(o&&typeof o=="object")try{this.addMessage(JSON.stringify(o,null,2),"system")}catch{this.addMessage(String(o),"system")}else o==null||this.addMessage(String(o),"system");return o}catch(o){throw this.hideLoading(),this.addMessage(`Command "${e}" failed: ${String(o)}`,"system"),o}if(this.externalCommandExecutor)try{this.showLoading();const o=await this.externalCommandExecutor({commandId:e,params:i});return this.hideLoading(),o}catch(o){throw this.hideLoading(),this.addMessage(`Command "${e}" failed: ${String(o)}`,"system"),o}this.addMessage(`No handler registered for command "${e}".`,"system")}init(){var e,i;if(this.container)return;this.injectOverlayStyles();const t=document.getElementById("foisit-overlay-container");if(t&&t instanceof HTMLElement){this.container=t,this.chatWindow=t.querySelector(".foisit-chat"),this.messagesContainer=t.querySelector(".foisit-messages"),this.input=t.querySelector("input.foisit-input"),((e=this.config.floatingButton)==null?void 0:e.visible)!==!1&&!t.querySelector(".foisit-floating-btn")&&this.renderFloatingButton(),this.chatWindow||this.renderChatWindow(),this.config.enableGestureActivation&&(this.gestureHandler=new R,this.gestureHandler.setupTripleTapListener(()=>this.toggle()));return}this.container=document.createElement("div"),this.container.id="foisit-overlay-container",this.container.className="foisit-overlay-container",document.body.appendChild(this.container),((i=this.config.floatingButton)==null?void 0:i.visible)!==!1&&this.renderFloatingButton(),this.renderChatWindow(),this.config.enableGestureActivation&&(this.gestureHandler=new R,this.gestureHandler.setupTripleTapListener(()=>this.toggle()))}renderFloatingButton(){var s,n,a,o,c,f;const t=document.createElement("button");t.innerHTML=((s=this.config.floatingButton)==null?void 0:s.customHtml)||"🎙️";const e=((a=(n=this.config.floatingButton)==null?void 0:n.position)==null?void 0:a.bottom)||"20px",i=((c=(o=this.config.floatingButton)==null?void 0:o.position)==null?void 0:c.right)||"20px";t.className="foisit-floating-btn",t.style.bottom=e,t.style.right=i,t.onclick=()=>this.toggle(),t.onmouseenter=()=>t.style.transform="scale(1.05)",t.onmouseleave=()=>t.style.transform="scale(1)",(f=this.container)==null||f.appendChild(t)}renderChatWindow(){var n;if(this.chatWindow)return;this.chatWindow=document.createElement("div"),this.chatWindow.className="foisit-chat";const t=document.createElement("div");t.className="foisit-header";const e=document.createElement("span");e.className="foisit-title",e.textContent="Foisit";const i=document.createElement("button");i.type="button",i.className="foisit-close",i.setAttribute("aria-label","Close"),i.innerHTML="×",i.addEventListener("click",()=>this.toggle()),t.appendChild(e),t.appendChild(i),this.messagesContainer=document.createElement("div"),this.messagesContainer.className="foisit-messages";const s=document.createElement("div");s.className="foisit-input-area",this.input=document.createElement("input"),this.input.placeholder=this.config.inputPlaceholder||"Type a command...",this.input.className="foisit-input",this.input.addEventListener("keydown",a=>{var o;if(a.key==="Enter"&&((o=this.input)!=null&&o.value.trim())){const c=this.input.value.trim();this.input.value="",this.onSubmit&&this.onSubmit(c)}}),s.appendChild(this.input),this.chatWindow.appendChild(t),this.chatWindow.appendChild(this.messagesContainer),this.chatWindow.appendChild(s),(n=this.container)==null||n.appendChild(this.chatWindow)}registerCallbacks(t,e){this.active&&(this.onSubmit=t,this.onClose=e)}toggle(t,e){this.active&&(t&&(this.onSubmit=t),e&&(this.onClose=e),this.isOpen=!this.isOpen,this.chatWindow&&(this.isOpen?(this.container&&(this.container.style.pointerEvents="auto"),this.chatWindow.style.display="flex",requestAnimationFrame(()=>{this.chatWindow&&(this.chatWindow.style.opacity="1",this.chatWindow.style.transform="translateY(0) scale(1)")}),setTimeout(()=>{var i;return(i=this.input)==null?void 0:i.focus()},100),this.addClickOutsideListener()):(this.chatWindow.style.opacity="0",this.chatWindow.style.transform="translateY(20px) scale(0.95)",setTimeout(()=>{this.chatWindow&&!this.isOpen&&(this.chatWindow.style.display="none")},200),this.onClose&&this.onClose(),this.removeClickOutsideListener(),this.container&&(this.container.style.pointerEvents="none"))))}addClickOutsideListener(){this.container&&(this.removeClickOutsideListener(),this.container.addEventListener("click",this.handleClickOutside))}removeClickOutsideListener(){this.container&&this.container.removeEventListener("click",this.handleClickOutside)}addMessage(t,e){if(!this.messagesContainer)return;const i=document.createElement("div");e==="system"?i.innerHTML=this.renderMarkdown(t):i.textContent=t,i.className=e==="user"?"foisit-bubble user":"foisit-bubble system";const s=(t||"").length||0,n=Math.max(120,700-Math.min(600,Math.floor(s*6)));i.style.opacity="0",i.style.transform="translateY(8px)",i.style.transition="none",this.messagesContainer.appendChild(i),this.animateMessageEntrance(i,n),this.scrollToBottom()}addOptions(t){if(!this.messagesContainer)return;const e=document.createElement("div");e.className="foisit-options-container",t.forEach(i=>{const s=document.createElement("button");s.textContent=i.label,s.className="foisit-option-chip",s.setAttribute("type","button"),s.setAttribute("aria-label",i.label);const n=()=>{if(i.commandId){this.onSubmit&&this.onSubmit({commandId:i.commandId});return}const a=i&&typeof i.value=="string"&&i.value.trim()?i.value:i.label;this.onSubmit&&this.onSubmit(a)};s.onclick=n,s.onkeydown=a=>{(a.key==="Enter"||a.key===" ")&&(a.preventDefault(),n())},e.appendChild(s)}),this.messagesContainer.appendChild(e),this.scrollToBottom()}addForm(t,e,i){if(!this.messagesContainer)return;this.addMessage(t,"system");const s=document.createElement("form");s.className="foisit-form";const n=[],a=(r,p)=>{const g=document.createElement("div");return g.className="foisit-form-label",g.innerHTML=r+(p?' <span class="foisit-req-star">*</span>':""),g},o=()=>{const r=document.createElement("div");return r.className="foisit-form-error",r.style.display="none",r};(e??[]).forEach(r=>{const p=document.createElement("div");p.className="foisit-form-group";const g=r.description||r.name;p.appendChild(a(g,r.required));let m;if(r.type==="select"){const l=document.createElement("select");l.className="foisit-form-input";const y=document.createElement("option");y.value="",y.textContent="Select...",l.appendChild(y);const w=h=>{(h??[]).forEach(x=>{const b=document.createElement("option");b.value=String(x.value??x.label??""),b.textContent=String(x.label??x.value??""),l.appendChild(b)})};if(Array.isArray(r.options)&&r.options.length)w(r.options);else if(typeof r.getOptions=="function"){const h=r.getOptions,x=document.createElement("option");x.value="",x.textContent="Loading...",l.appendChild(x),Promise.resolve().then(()=>h()).then(b=>{for(;l.options.length>1;)l.remove(1);w(b)}).catch(()=>{for(;l.options.length>1;)l.remove(1);const b=document.createElement("option");b.value="",b.textContent="Error loading options",l.appendChild(b)})}r.defaultValue!=null&&(l.value=String(r.defaultValue)),m=l}else if(r.type==="file"){const l=r,y=document.createElement("input");y.className="foisit-form-input",y.type="file",l.accept&&Array.isArray(l.accept)&&(y.accept=l.accept.join(",")),l.multiple&&(y.multiple=!0),l.capture&&(l.capture===!0?y.setAttribute("capture",""):y.setAttribute("capture",String(l.capture))),y.addEventListener("change",async()=>{const w=Array.from(y.files||[]),h=C;if(h.style.display="none",h.textContent="",w.length===0)return;const x=l.maxFiles??(l.multiple?10:1);if(w.length>x){h.textContent=`Please select at most ${x} file(s).`,h.style.display="block";return}const b=l.maxSizeBytes??1/0,u=w.reduce((v,A)=>v+A.size,0);if(w.some(v=>v.size>b)){h.textContent=`One or more files exceed the maximum size of ${Math.round(b/1024)} KB.`,h.style.display="block";return}const k=l.maxTotalBytes??1/0;if(u>k){h.textContent=`Total selected files exceed the maximum of ${Math.round(k/1024)} KB.`,h.style.display="block";return}if(l.accept&&Array.isArray(l.accept)){const v=l.accept;if(!w.every(L=>L.type?v.some(M=>M.startsWith(".")?L.name.toLowerCase().endsWith(M.toLowerCase()):L.type===M||L.type.startsWith(M.split("/")[0]+"/")):!0)){h.textContent="One or more files have an unsupported type.",h.style.display="block";return}}}),m=y}else{const l=document.createElement("input");l.className="foisit-form-input",r.type==="string"&&(l.placeholder=r.placeholder||"Type here..."),r.type==="number"?(l.type="number",typeof r.min=="number"&&(l.min=String(r.min)),typeof r.max=="number"&&(l.max=String(r.max)),typeof r.step=="number"&&(l.step=String(r.step)),r.defaultValue!=null&&(l.value=String(r.defaultValue))):r.type==="date"?(l.type="date",typeof r.min=="string"&&(l.min=r.min),typeof r.max=="string"&&(l.max=r.max),r.defaultValue!=null&&(l.value=String(r.defaultValue))):(l.type="text",r.defaultValue!=null&&(l.value=String(r.defaultValue))),m=l}const C=o();p.appendChild(m),p.appendChild(C),n.push({name:r.name,type:r.type,el:m,required:r.required}),s.appendChild(p)});const c=document.createElement("div");c.className="foisit-form-actions";const f=document.createElement("button");f.type="submit",f.textContent="Submit",f.className="foisit-option-chip",f.style.fontWeight="600",c.appendChild(f),s.appendChild(c),s.onsubmit=async r=>{r.preventDefault();const p={};let g=!1;s.querySelectorAll(".foisit-form-error").forEach(m=>{m.style.display="none",m.textContent=""}),s.querySelectorAll(".foisit-form-input").forEach(m=>{m.classList.remove("foisit-error-border")});for(const m of n){if(m.type==="file"){const w=m.el.parentElement,h=w==null?void 0:w.querySelector(".foisit-form-error"),x=m.el,b=Array.from(x.files||[]);if(m.required&&b.length===0){g=!0,x.classList.add("foisit-error-border"),h&&(h.textContent="This file is required",h.style.display="block");continue}if(b.length===0)continue;const u=(e??[]).find(v=>v.name===m.name),k=(u==null?void 0:u.delivery)??"file";if(u!=null&&u.maxWidth||u!=null&&u.maxHeight)try{const v=await this.getImageDimensions(b[0]);if(u.maxWidth&&v.width>u.maxWidth){g=!0,h&&(h.textContent=`Image width must be ≤ ${u.maxWidth}px`,h.style.display="block");continue}if(u.maxHeight&&v.height>u.maxHeight){g=!0,h&&(h.textContent=`Image height must be ≤ ${u.maxHeight}px`,h.style.display="block");continue}}catch{}if(u!=null&&u.maxDurationSec)try{const v=await this.getMediaDuration(b[0]);if(v&&v>u.maxDurationSec){g=!0,h&&(h.textContent=`Media duration must be ≤ ${u.maxDurationSec}s`,h.style.display="block");continue}}catch{}if(k==="file")p[m.name]=u!=null&&u.multiple?b:b[0];else if(k==="base64")try{const v=await Promise.all(b.map(A=>this.readFileAsDataURL(A)));p[m.name]=u!=null&&u.multiple?v:v[0]}catch{g=!0,h&&(h.textContent="Failed to encode file(s) to base64.",h.style.display="block");continue}continue}const C=(m.el.value??"").toString().trim(),l=m.el.parentElement,y=l==null?void 0:l.querySelector(".foisit-form-error");if(m.required&&(C==null||C==="")){g=!0,m.el.classList.add("foisit-error-border"),y&&(y.textContent="This field is required",y.style.display="block");continue}if(C!=="")if(m.type==="number"){const w=Number(C);Number.isNaN(w)||(p[m.name]=w)}else p[m.name]=C}if(g){s.classList.add("foisit-shake"),setTimeout(()=>s.classList.remove("foisit-shake"),400);return}f.disabled=!0,f.style.opacity="0.6",n.forEach(m=>{m.el.disabled=!0}),i(p)},this.messagesContainer.appendChild(s),this.scrollToBottom()}showLoading(){if(this.messagesContainer&&!this.loadingEl){this.loadingEl=document.createElement("div"),this.loadingEl.className="foisit-loading-dots foisit-bubble system";for(let t=0;t<3;t++){const e=document.createElement("div");e.className="foisit-dot",e.style.animation=`foisitPulse 1.4s infinite ease-in-out ${t*.2}s`,this.loadingEl.appendChild(e)}this.messagesContainer.appendChild(this.loadingEl),this.scrollToBottom()}}hideLoading(){var t;(t=this.loadingEl)==null||t.remove(),this.loadingEl=null}scrollToBottom(){this.messagesContainer&&(this.messagesContainer.scrollTop=this.messagesContainer.scrollHeight)}animateMessageEntrance(t,e){if(!t)return;t.style.transition=`opacity ${e}ms cubic-bezier(0.22, 0.9, 0.32, 1), transform ${Math.max(120,e)}ms cubic-bezier(0.22, 0.9, 0.32, 1)`,requestAnimationFrame(()=>{t.style.opacity="1",t.style.transform="translateY(0)"});const i=()=>{try{t.style.transition=""}catch{}t.removeEventListener("transitionend",i)};t.addEventListener("transitionend",i)}animateScrollToBottom(t){if(!this.messagesContainer)return;const e=this.messagesContainer,i=e.scrollTop,s=e.scrollHeight-e.clientHeight;if(s<=i||t<=0){e.scrollTop=s;return}const n=s-i,a=performance.now(),o=c=>{const f=Math.min(1,(c-a)/t),r=1-Math.pow(1-f,3);e.scrollTop=Math.round(i+n*r),f<1&&requestAnimationFrame(o)};requestAnimationFrame(o)}destroy(){var t;this.removeClickOutsideListener(),(t=this.container)==null||t.remove(),this.container=null,this.chatWindow=null,this.messagesContainer=null,this.input=null,this.isOpen=!1}escapeHtml(t){const e={"&":"&","<":"<",">":">",'"':""","'":"'"};return t.replace(/[&<>"']/g,i=>e[i])}renderMarkdown(t){let e=this.escapeHtml(t);return e=e.replace(/```(\w*)\n([\s\S]*?)```/g,(i,s,n)=>`<pre class="foisit-code-block"><code${s?` class="language-${s}"`:""}>${n.trim()}</code></pre>`),e=e.replace(/`([^`]+)`/g,'<code class="foisit-inline-code">$1</code>'),e=e.replace(/^###### (.+)$/gm,'<h6 class="foisit-md-h6">$1</h6>'),e=e.replace(/^##### (.+)$/gm,'<h5 class="foisit-md-h5">$1</h5>'),e=e.replace(/^#### (.+)$/gm,'<h4 class="foisit-md-h4">$1</h4>'),e=e.replace(/^### (.+)$/gm,'<h3 class="foisit-md-h3">$1</h3>'),e=e.replace(/^## (.+)$/gm,'<h2 class="foisit-md-h2">$1</h2>'),e=e.replace(/^# (.+)$/gm,'<h1 class="foisit-md-h1">$1</h1>'),e=e.replace(/\*\*([^*]+)\*\*/g,"<strong>$1</strong>"),e=e.replace(/__([^_]+)__/g,"<strong>$1</strong>"),e=e.replace(/\*([^*]+)\*/g,"<em>$1</em>"),e=e.replace(new RegExp("(?<!_)_([^_]+)_(?!_)","g"),"<em>$1</em>"),e=e.replace(/~~([^~]+)~~/g,"<del>$1</del>"),e=e.replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2" target="_blank" rel="noopener noreferrer" class="foisit-md-link">$1</a>'),e=e.replace(/^[\-\*] (.+)$/gm,'<li class="foisit-md-li">$1</li>'),e=e.replace(/(<li class="foisit-md-li">.*<\/li>\n?)+/g,i=>`<ul class="foisit-md-ul">${i}</ul>`),e=e.replace(/^\d+\. (.+)$/gm,'<li class="foisit-md-li">$1</li>'),e=e.replace(new RegExp('(?<!<\\/ul>)(<li class="foisit-md-li">.*<\\/li>\\n?)+',"g"),i=>i.includes("<ul")?i:`<ol class="foisit-md-ol">${i}</ol>`),e=e.replace(/^> (.+)$/gm,'<blockquote class="foisit-md-blockquote">$1</blockquote>'),e=e.replace(/^(---|___|\*\*\*)$/gm,'<hr class="foisit-md-hr">'),e=e.replace(/\n\n+/g,'</p><p class="foisit-md-p">'),e=e.replace(/\n/g,"<br>"),e.match(/^<(h[1-6]|ul|ol|pre|blockquote|hr|p)/)||(e=`<p class="foisit-md-p">${e}</p>`),e}readFileAsDataURL(t){return new Promise((e,i)=>{const s=new FileReader;s.onerror=()=>i(new Error("Failed to read file")),s.onload=()=>e(String(s.result)),s.readAsDataURL(t)})}getImageDimensions(t){return new Promise(e=>{try{const i=URL.createObjectURL(t),s=new Image;s.onload=()=>{const n={width:s.naturalWidth||s.width,height:s.naturalHeight||s.height};URL.revokeObjectURL(i),e(n)},s.onerror=()=>{URL.revokeObjectURL(i),e({width:0,height:0})},s.src=i}catch{e({width:0,height:0})}})}getMediaDuration(t){return new Promise(e=>{try{const i=URL.createObjectURL(t),s=t.type.startsWith("audio")?document.createElement("audio"):document.createElement("video");let n=!1;const a=setTimeout(()=>{n||(n=!0,URL.revokeObjectURL(i),e(0))},5e3);s.preload="metadata",s.onloadedmetadata=()=>{if(n)return;n=!0,clearTimeout(a);const c=s.duration||0;URL.revokeObjectURL(i),e(c)},s.onerror=()=>{n||(n=!0,clearTimeout(a),URL.revokeObjectURL(i),e(0))},s.src=i}catch{e(0)}})}injectOverlayStyles(){if(document.getElementById("foisit-overlay-styles"))return;const t=document.createElement("style");t.id="foisit-overlay-styles",t.textContent=`
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
/* Input */
|
|
44
|
-
--foisit-input-color: #333;
|
|
45
|
-
--foisit-input-placeholder: rgba(60, 60, 67, 0.6);
|
|
34
|
+
`,document.head.appendChild(t),console.log("Gradient styles injected")}function U(){if(document.querySelector("#gradient-indicator"))return;const d=document.createElement("div");d.id="gradient-indicator",j(),d.classList.add("gradient-indicator"),document.body.appendChild(d),console.log("Gradient indicator added to the DOM")}function D(){const d=document.querySelector("#gradient-indicator");d&&(d.remove(),console.log("Gradient indicator removed from the DOM"))}function V(){return typeof window<"u"&&typeof document<"u"}class _{constructor(){this.state="idle",this.subscribers=[]}getState(){return this.state}setState(t){this.state=t,this.notifySubscribers(),console.log("State updated:",t),t==="listening"?U():D()}subscribe(t){this.subscribers.push(t)}notifySubscribers(){this.subscribers.forEach(t=>t(this.state))}}class G{constructor(t){this.container=null,this.chatWindow=null,this.messagesContainer=null,this.input=null,this.isOpen=!1,this.loadingEl=null,this.commandHandlers=new Map,this.active=V(),this.handleClickOutside=e=>{const i=e.target;this.chatWindow&&this.chatWindow.contains(i)||i.closest(".foisit-floating-btn")||this.toggle()},this.config=t,this.active&&this.init()}registerCommandHandler(t,e){!t||typeof e!="function"||this.commandHandlers.set(t,e)}hasCommandHandler(t){return this.commandHandlers.has(t)}setExternalCommandExecutor(t){this.externalCommandExecutor=t}unregisterCommandHandler(t){t&&this.commandHandlers.delete(t)}async runCommand(t){if(!t||!t.commandId)throw new Error("runCommand requires a commandId");const{commandId:e,params:i,openOverlay:s=!0,showInvocation:n=!0}=t;s&&!this.isOpen&&this.toggle();const a=this.commandHandlers.get(e);if(a&&n&&this.messagesContainer&&this.addMessage(`Command: ${e}`,"user"),a)try{this.showLoading();const o=await a(i);if(this.hideLoading(),typeof o=="string")this.addMessage(o,"system");else if(o&&typeof o=="object")try{this.addMessage(JSON.stringify(o,null,2),"system")}catch{this.addMessage(String(o),"system")}else o==null||this.addMessage(String(o),"system");return o}catch(o){throw this.hideLoading(),this.addMessage(`Command "${e}" failed: ${String(o)}`,"system"),o}if(this.externalCommandExecutor)try{this.showLoading();const o=await this.externalCommandExecutor({commandId:e,params:i});return this.hideLoading(),o}catch(o){throw this.hideLoading(),this.addMessage(`Command "${e}" failed: ${String(o)}`,"system"),o}this.addMessage(`No handler registered for command "${e}".`,"system")}init(){var e,i;if(this.container)return;this.injectOverlayStyles();const t=document.getElementById("foisit-overlay-container");if(t&&t instanceof HTMLElement){this.container=t,this.chatWindow=t.querySelector(".foisit-chat"),this.messagesContainer=t.querySelector(".foisit-messages"),this.input=t.querySelector("input.foisit-input"),((e=this.config.floatingButton)==null?void 0:e.visible)!==!1&&!t.querySelector(".foisit-floating-btn")&&this.renderFloatingButton(),this.chatWindow||this.renderChatWindow(),this.config.enableGestureActivation&&(this.gestureHandler=new R,this.gestureHandler.setupTripleTapListener(()=>this.toggle()));return}this.container=document.createElement("div"),this.container.id="foisit-overlay-container",this.container.className="foisit-overlay-container",document.body.appendChild(this.container),((i=this.config.floatingButton)==null?void 0:i.visible)!==!1&&this.renderFloatingButton(),this.renderChatWindow(),this.config.enableGestureActivation&&(this.gestureHandler=new R,this.gestureHandler.setupTripleTapListener(()=>this.toggle()))}renderFloatingButton(){var s,n,a,o,c,u;const t=document.createElement("button");t.innerHTML=((s=this.config.floatingButton)==null?void 0:s.customHtml)||"🎙️";const e=((a=(n=this.config.floatingButton)==null?void 0:n.position)==null?void 0:a.bottom)||"20px",i=((c=(o=this.config.floatingButton)==null?void 0:o.position)==null?void 0:c.right)||"20px";t.className="foisit-floating-btn",t.style.bottom=e,t.style.right=i,t.onclick=()=>this.toggle(),t.onmouseenter=()=>t.style.transform="scale(1.05)",t.onmouseleave=()=>t.style.transform="scale(1)",(u=this.container)==null||u.appendChild(t)}renderChatWindow(){var n;if(this.chatWindow)return;this.chatWindow=document.createElement("div"),this.chatWindow.className="foisit-chat";const t=document.createElement("div");t.className="foisit-header";const e=document.createElement("span");e.className="foisit-title",e.textContent="Foisit";const i=document.createElement("button");i.type="button",i.className="foisit-close",i.setAttribute("aria-label","Close"),i.innerHTML="×",i.addEventListener("click",()=>this.toggle()),t.appendChild(e),t.appendChild(i),this.messagesContainer=document.createElement("div"),this.messagesContainer.className="foisit-messages";const s=document.createElement("div");s.className="foisit-input-area",this.input=document.createElement("input"),this.input.placeholder=this.config.inputPlaceholder||"Type a command...",this.input.className="foisit-input",this.input.addEventListener("keydown",a=>{var o;if(a.key==="Enter"&&((o=this.input)!=null&&o.value.trim())){const c=this.input.value.trim();this.input.value="",this.onSubmit&&this.onSubmit(c)}}),s.appendChild(this.input),this.chatWindow.appendChild(t),this.chatWindow.appendChild(this.messagesContainer),this.chatWindow.appendChild(s),(n=this.container)==null||n.appendChild(this.chatWindow)}registerCallbacks(t,e){this.active&&(this.onSubmit=t,this.onClose=e)}toggle(t,e){this.active&&(t&&(this.onSubmit=t),e&&(this.onClose=e),this.isOpen=!this.isOpen,this.chatWindow&&(this.isOpen?(this.container&&(this.container.style.pointerEvents="auto"),this.chatWindow.style.display="flex",requestAnimationFrame(()=>{this.chatWindow&&(this.chatWindow.style.opacity="1",this.chatWindow.style.transform="translateY(0) scale(1)")}),setTimeout(()=>{var i;return(i=this.input)==null?void 0:i.focus()},100),this.addClickOutsideListener()):(this.chatWindow.style.opacity="0",this.chatWindow.style.transform="translateY(20px) scale(0.95)",setTimeout(()=>{this.chatWindow&&!this.isOpen&&(this.chatWindow.style.display="none")},200),this.onClose&&this.onClose(),this.removeClickOutsideListener(),this.container&&(this.container.style.pointerEvents="none"))))}addClickOutsideListener(){this.container&&(this.removeClickOutsideListener(),this.container.addEventListener("click",this.handleClickOutside))}removeClickOutsideListener(){this.container&&this.container.removeEventListener("click",this.handleClickOutside)}addMessage(t,e){if(!this.messagesContainer)return;const i=document.createElement("div");e==="system"?i.innerHTML=this.renderMarkdown(t):i.textContent=t,i.className=e==="user"?"foisit-bubble user":"foisit-bubble system";const s=(t||"").length||0,n=Math.max(120,700-Math.min(600,Math.floor(s*6)));i.style.opacity="0",i.style.transform="translateY(8px)",i.style.transition="none",this.messagesContainer.appendChild(i),this.animateMessageEntrance(i,n),this.scrollToBottom()}addOptions(t){if(!this.messagesContainer)return;const e=document.createElement("div");e.className="foisit-options-container",t.forEach(i=>{const s=document.createElement("button");s.textContent=i.label,s.className="foisit-option-chip",s.setAttribute("type","button"),s.setAttribute("aria-label",i.label);const n=()=>{if(i.commandId){this.onSubmit&&this.onSubmit({commandId:i.commandId});return}const a=i&&typeof i.value=="string"&&i.value.trim()?i.value:i.label;this.onSubmit&&this.onSubmit(a)};s.onclick=n,s.onkeydown=a=>{(a.key==="Enter"||a.key===" ")&&(a.preventDefault(),n())},e.appendChild(s)}),this.messagesContainer.appendChild(e),this.scrollToBottom()}addForm(t,e,i){if(!this.messagesContainer)return;this.addMessage(t,"system");const s=document.createElement("form");s.className="foisit-form";const n=[],a=(r,p)=>{const g=document.createElement("div");return g.className="foisit-form-label",g.innerHTML=r+(p?' <span class="foisit-req-star">*</span>':""),g},o=()=>{const r=document.createElement("div");return r.className="foisit-form-error",r.style.display="none",r};(e??[]).forEach(r=>{const p=document.createElement("div");p.className="foisit-form-group";const g=r.description||r.name;p.appendChild(a(g,r.required));let m;if(r.type==="select"){const l=document.createElement("select");l.className="foisit-form-input";const y=document.createElement("option");y.value="",y.textContent="Select...",l.appendChild(y);const x=h=>{(h??[]).forEach(w=>{const b=document.createElement("option");b.value=String(w.value??w.label??""),b.textContent=String(w.label??w.value??""),l.appendChild(b)})};if(Array.isArray(r.options)&&r.options.length)x(r.options);else if(typeof r.getOptions=="function"){const h=r.getOptions,w=document.createElement("option");w.value="",w.textContent="Loading...",l.appendChild(w),Promise.resolve().then(()=>h()).then(b=>{for(;l.options.length>1;)l.remove(1);x(b)}).catch(()=>{for(;l.options.length>1;)l.remove(1);const b=document.createElement("option");b.value="",b.textContent="Error loading options",l.appendChild(b)})}r.defaultValue!=null&&(l.value=String(r.defaultValue)),m=l}else if(r.type==="file"){const l=r,y=document.createElement("input");y.className="foisit-form-input",y.type="file",l.accept&&Array.isArray(l.accept)&&(y.accept=l.accept.join(",")),l.multiple&&(y.multiple=!0),l.capture&&(l.capture===!0?y.setAttribute("capture",""):y.setAttribute("capture",String(l.capture))),y.addEventListener("change",async()=>{const x=Array.from(y.files||[]),h=C;if(h.style.display="none",h.textContent="",x.length===0)return;const w=l.maxFiles??(l.multiple?10:1);if(x.length>w){h.textContent=`Please select at most ${w} file(s).`,h.style.display="block";return}const b=l.maxSizeBytes??1/0,f=x.reduce((v,A)=>v+A.size,0);if(x.some(v=>v.size>b)){h.textContent=`One or more files exceed the maximum size of ${Math.round(b/1024)} KB.`,h.style.display="block";return}const k=l.maxTotalBytes??1/0;if(f>k){h.textContent=`Total selected files exceed the maximum of ${Math.round(k/1024)} KB.`,h.style.display="block";return}if(l.accept&&Array.isArray(l.accept)){const v=l.accept;if(!x.every(L=>L.type?v.some(M=>M.startsWith(".")?L.name.toLowerCase().endsWith(M.toLowerCase()):L.type===M||L.type.startsWith(M.split("/")[0]+"/")):!0)){h.textContent="One or more files have an unsupported type.",h.style.display="block";return}}}),m=y}else{const l=document.createElement("input");l.className="foisit-form-input",r.type==="string"&&(l.placeholder=r.placeholder||"Type here..."),r.type==="number"?(l.type="number",typeof r.min=="number"&&(l.min=String(r.min)),typeof r.max=="number"&&(l.max=String(r.max)),typeof r.step=="number"&&(l.step=String(r.step)),r.defaultValue!=null&&(l.value=String(r.defaultValue))):r.type==="date"?(l.type="date",typeof r.min=="string"&&(l.min=r.min),typeof r.max=="string"&&(l.max=r.max),r.defaultValue!=null&&(l.value=String(r.defaultValue))):(l.type="text",r.defaultValue!=null&&(l.value=String(r.defaultValue))),m=l}const C=o();p.appendChild(m),p.appendChild(C),n.push({name:r.name,type:r.type,el:m,required:r.required}),s.appendChild(p)});const c=document.createElement("div");c.className="foisit-form-actions";const u=document.createElement("button");u.type="submit",u.textContent="Submit",u.className="foisit-option-chip",u.style.fontWeight="600",c.appendChild(u),s.appendChild(c),s.onsubmit=async r=>{r.preventDefault();const p={};let g=!1;s.querySelectorAll(".foisit-form-error").forEach(m=>{m.style.display="none",m.textContent=""}),s.querySelectorAll(".foisit-form-input").forEach(m=>{m.classList.remove("foisit-error-border")});for(const m of n){if(m.type==="file"){const x=m.el.parentElement,h=x==null?void 0:x.querySelector(".foisit-form-error"),w=m.el,b=Array.from(w.files||[]);if(m.required&&b.length===0){g=!0,w.classList.add("foisit-error-border"),h&&(h.textContent="This file is required",h.style.display="block");continue}if(b.length===0)continue;const f=(e??[]).find(v=>v.name===m.name),k=(f==null?void 0:f.delivery)??"file";if(f!=null&&f.maxWidth||f!=null&&f.maxHeight)try{const v=await this.getImageDimensions(b[0]);if(f.maxWidth&&v.width>f.maxWidth){g=!0,h&&(h.textContent=`Image width must be ≤ ${f.maxWidth}px`,h.style.display="block");continue}if(f.maxHeight&&v.height>f.maxHeight){g=!0,h&&(h.textContent=`Image height must be ≤ ${f.maxHeight}px`,h.style.display="block");continue}}catch{}if(f!=null&&f.maxDurationSec)try{const v=await this.getMediaDuration(b[0]);if(v&&v>f.maxDurationSec){g=!0,h&&(h.textContent=`Media duration must be ≤ ${f.maxDurationSec}s`,h.style.display="block");continue}}catch{}if(k==="file")p[m.name]=f!=null&&f.multiple?b:b[0];else if(k==="base64")try{const v=await Promise.all(b.map(A=>this.readFileAsDataURL(A)));p[m.name]=f!=null&&f.multiple?v:v[0]}catch{g=!0,h&&(h.textContent="Failed to encode file(s) to base64.",h.style.display="block");continue}continue}const C=(m.el.value??"").toString().trim(),l=m.el.parentElement,y=l==null?void 0:l.querySelector(".foisit-form-error");if(m.required&&(C==null||C==="")){g=!0,m.el.classList.add("foisit-error-border"),y&&(y.textContent="This field is required",y.style.display="block");continue}if(C!=="")if(m.type==="number"){const x=Number(C);Number.isNaN(x)||(p[m.name]=x)}else p[m.name]=C}if(g){s.classList.add("foisit-shake"),setTimeout(()=>s.classList.remove("foisit-shake"),400);return}u.disabled=!0,u.style.opacity="0.6",n.forEach(m=>{m.el.disabled=!0}),i(p)},this.messagesContainer.appendChild(s),this.scrollToBottom()}showLoading(){if(this.messagesContainer&&!this.loadingEl){this.loadingEl=document.createElement("div"),this.loadingEl.className="foisit-loading-dots foisit-bubble system";for(let t=0;t<3;t++){const e=document.createElement("div");e.className="foisit-dot",e.style.animation=`foisitPulse 1.4s infinite ease-in-out ${t*.2}s`,this.loadingEl.appendChild(e)}this.messagesContainer.appendChild(this.loadingEl),this.scrollToBottom()}}hideLoading(){var t;(t=this.loadingEl)==null||t.remove(),this.loadingEl=null}scrollToBottom(){this.messagesContainer&&(this.messagesContainer.scrollTop=this.messagesContainer.scrollHeight)}animateMessageEntrance(t,e){if(!t)return;t.style.transition=`opacity ${e}ms cubic-bezier(0.22, 0.9, 0.32, 1), transform ${Math.max(120,e)}ms cubic-bezier(0.22, 0.9, 0.32, 1)`,requestAnimationFrame(()=>{t.style.opacity="1",t.style.transform="translateY(0)"});const i=()=>{try{t.style.transition=""}catch{}t.removeEventListener("transitionend",i)};t.addEventListener("transitionend",i)}animateScrollToBottom(t){if(!this.messagesContainer)return;const e=this.messagesContainer,i=e.scrollTop,s=e.scrollHeight-e.clientHeight;if(s<=i||t<=0){e.scrollTop=s;return}const n=s-i,a=performance.now(),o=c=>{const u=Math.min(1,(c-a)/t),r=1-Math.pow(1-u,3);e.scrollTop=Math.round(i+n*r),u<1&&requestAnimationFrame(o)};requestAnimationFrame(o)}destroy(){var t;this.removeClickOutsideListener(),(t=this.container)==null||t.remove(),this.container=null,this.chatWindow=null,this.messagesContainer=null,this.input=null,this.isOpen=!1}escapeHtml(t){const e={"&":"&","<":"<",">":">",'"':""","'":"'"};return t.replace(/[&<>"']/g,i=>e[i])}renderMarkdown(t){let e=this.escapeHtml(t);return e=e.replace(/```(\w*)\n([\s\S]*?)```/g,(i,s,n)=>`<pre class="foisit-code-block"><code${s?` class="language-${s}"`:""}>${n.trim()}</code></pre>`),e=e.replace(/`([^`]+)`/g,'<code class="foisit-inline-code">$1</code>'),e=e.replace(/^###### (.+)$/gm,'<h6 class="foisit-md-h6">$1</h6>'),e=e.replace(/^##### (.+)$/gm,'<h5 class="foisit-md-h5">$1</h5>'),e=e.replace(/^#### (.+)$/gm,'<h4 class="foisit-md-h4">$1</h4>'),e=e.replace(/^### (.+)$/gm,'<h3 class="foisit-md-h3">$1</h3>'),e=e.replace(/^## (.+)$/gm,'<h2 class="foisit-md-h2">$1</h2>'),e=e.replace(/^# (.+)$/gm,'<h1 class="foisit-md-h1">$1</h1>'),e=e.replace(/\*\*([^*]+)\*\*/g,"<strong>$1</strong>"),e=e.replace(/__([^_]+)__/g,"<strong>$1</strong>"),e=e.replace(/\*([^*]+)\*/g,"<em>$1</em>"),e=e.replace(new RegExp("(?<!_)_([^_]+)_(?!_)","g"),"<em>$1</em>"),e=e.replace(/~~([^~]+)~~/g,"<del>$1</del>"),e=e.replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2" target="_blank" rel="noopener noreferrer" class="foisit-md-link">$1</a>'),e=e.replace(/^[\-\*] (.+)$/gm,'<li class="foisit-md-li">$1</li>'),e=e.replace(/(<li class="foisit-md-li">.*<\/li>\n?)+/g,i=>`<ul class="foisit-md-ul">${i}</ul>`),e=e.replace(/^\d+\. (.+)$/gm,'<li class="foisit-md-li">$1</li>'),e=e.replace(new RegExp('(?<!<\\/ul>)(<li class="foisit-md-li">.*<\\/li>\\n?)+',"g"),i=>i.includes("<ul")?i:`<ol class="foisit-md-ol">${i}</ol>`),e=e.replace(/^> (.+)$/gm,'<blockquote class="foisit-md-blockquote">$1</blockquote>'),e=e.replace(/^(---|___|\*\*\*)$/gm,'<hr class="foisit-md-hr">'),e=e.replace(/\n\n+/g,'</p><p class="foisit-md-p">'),e=e.replace(/\n/g,"<br>"),e.match(/^<(h[1-6]|ul|ol|pre|blockquote|hr|p)/)||(e=`<p class="foisit-md-p">${e}</p>`),e}readFileAsDataURL(t){return new Promise((e,i)=>{const s=new FileReader;s.onerror=()=>i(new Error("Failed to read file")),s.onload=()=>e(String(s.result)),s.readAsDataURL(t)})}getImageDimensions(t){return new Promise(e=>{try{const i=URL.createObjectURL(t),s=new Image;s.onload=()=>{const n={width:s.naturalWidth||s.width,height:s.naturalHeight||s.height};URL.revokeObjectURL(i),e(n)},s.onerror=()=>{URL.revokeObjectURL(i),e({width:0,height:0})},s.src=i}catch{e({width:0,height:0})}})}getMediaDuration(t){return new Promise(e=>{try{const i=URL.createObjectURL(t),s=t.type.startsWith("audio")?document.createElement("audio"):document.createElement("video");let n=!1;const a=setTimeout(()=>{n||(n=!0,URL.revokeObjectURL(i),e(0))},5e3);s.preload="metadata",s.onloadedmetadata=()=>{if(n)return;n=!0,clearTimeout(a);const c=s.duration||0;URL.revokeObjectURL(i),e(c)},s.onerror=()=>{n||(n=!0,clearTimeout(a),URL.revokeObjectURL(i),e(0))},s.src=i}catch{e(0)}})}injectOverlayStyles(){if(document.getElementById("foisit-overlay-styles"))return;const t=document.createElement("style");t.id="foisit-overlay-styles";const e=this.config.theme||"glass",i=this.config.themeColors||{};if(e==="solid"){const s=i.background||"#1a1a2e",n=i.text||"#ffffff",a=i.accent||"linear-gradient(135deg, #667eea 0%, #764ba2 100%)",o=i.userBubbleBg||"rgba(102, 126, 234, 0.2)",c=i.systemBubbleBg||"rgba(255, 255, 255, 0.08)",u=i.border||"rgba(255, 255, 255, 0.1)";t.textContent=`
|
|
35
|
+
:root {
|
|
36
|
+
/* SOLID THEME - Custom Colors */
|
|
37
|
+
--foisit-bg: ${s};
|
|
38
|
+
--foisit-border: 1px solid ${u};
|
|
39
|
+
--foisit-shadow: 0 16px 48px rgba(0, 0, 0, 0.4);
|
|
40
|
+
--foisit-text: ${n};
|
|
41
|
+
--foisit-accent: ${a};
|
|
42
|
+
--foisit-backdrop: none;
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
/* Input */
|
|
45
|
+
--foisit-input-color: ${n};
|
|
46
|
+
--foisit-input-placeholder: ${n}99;
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
/* Bubbles */
|
|
49
|
+
--foisit-bubble-user-bg: ${o};
|
|
50
|
+
--foisit-bubble-user-text: ${n};
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
--foisit-error-text: #dc2626;
|
|
57
|
-
--foisit-error-border: #fca5a5;
|
|
58
|
-
}
|
|
52
|
+
--foisit-bubble-sys-bg: ${c};
|
|
53
|
+
--foisit-bubble-sys-text: ${n}ee;
|
|
59
54
|
|
|
60
|
-
|
|
55
|
+
/* Form Colors */
|
|
56
|
+
--foisit-req-star: #f87171;
|
|
57
|
+
--foisit-error-text: #fca5a5;
|
|
58
|
+
--foisit-error-border: #f87171;
|
|
59
|
+
}
|
|
60
|
+
`}else t.textContent=`
|
|
61
61
|
:root {
|
|
62
|
-
/*
|
|
63
|
-
|
|
64
|
-
--foisit-
|
|
65
|
-
--foisit-
|
|
66
|
-
--foisit-
|
|
62
|
+
/* LIGHT MODE (Default) - Smoother gradient */
|
|
63
|
+
/* Changed: Softer, right-focused radial highlight to avoid a heavy white bottom */
|
|
64
|
+
--foisit-bg: radial-gradient(ellipse at 75% 30%, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.03));
|
|
65
|
+
--foisit-border: 1px solid rgba(255, 255, 255, 0.25);
|
|
66
|
+
--foisit-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
|
|
67
|
+
--foisit-text: #333;
|
|
68
|
+
--foisit-backdrop: blur(20px);
|
|
67
69
|
|
|
68
70
|
/* Input */
|
|
69
|
-
--foisit-input-color:
|
|
70
|
-
--foisit-input-placeholder: rgba(
|
|
71
|
+
--foisit-input-color: #333;
|
|
72
|
+
--foisit-input-placeholder: rgba(60, 60, 67, 0.6);
|
|
71
73
|
|
|
72
74
|
/* Bubbles */
|
|
73
|
-
--foisit-bubble-user-bg: rgba(
|
|
74
|
-
--foisit-bubble-user-text:
|
|
75
|
+
--foisit-bubble-user-bg: rgba(0, 0, 0, 0.04);
|
|
76
|
+
--foisit-bubble-user-text: #333;
|
|
75
77
|
|
|
76
|
-
--foisit-bubble-sys-bg: rgba(255, 255, 255, 0.
|
|
77
|
-
--foisit-bubble-sys-text:
|
|
78
|
+
--foisit-bubble-sys-bg: rgba(255, 255, 255, 0.45);
|
|
79
|
+
--foisit-bubble-sys-text: #333;
|
|
78
80
|
|
|
79
81
|
/* Form Colors */
|
|
80
|
-
--foisit-req-star: #
|
|
81
|
-
--foisit-error-text: #
|
|
82
|
-
--foisit-error-border: #
|
|
82
|
+
--foisit-req-star: #ef4444; /* Red asterisk */
|
|
83
|
+
--foisit-error-text: #dc2626;
|
|
84
|
+
--foisit-error-border: #fca5a5;
|
|
83
85
|
}
|
|
84
|
-
|
|
86
|
+
|
|
87
|
+
@media (prefers-color-scheme: dark) {
|
|
88
|
+
:root {
|
|
89
|
+
/* DARK MODE */
|
|
90
|
+
--foisit-bg: linear-gradient(135deg, rgba(40, 40, 40, 0.65), rgba(40, 40, 40, 0.25));
|
|
91
|
+
--foisit-border: 1px solid rgba(255, 255, 255, 0.1);
|
|
92
|
+
--foisit-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
|
|
93
|
+
--foisit-text: #fff;
|
|
94
|
+
--foisit-backdrop: blur(20px);
|
|
95
|
+
|
|
96
|
+
/* Input */
|
|
97
|
+
--foisit-input-color: white;
|
|
98
|
+
--foisit-input-placeholder: rgba(235, 235, 245, 0.5);
|
|
99
|
+
|
|
100
|
+
/* Bubbles */
|
|
101
|
+
--foisit-bubble-user-bg: rgba(255, 255, 255, 0.1);
|
|
102
|
+
--foisit-bubble-user-text: white;
|
|
103
|
+
|
|
104
|
+
--foisit-bubble-sys-bg: rgba(255, 255, 255, 0.05);
|
|
105
|
+
--foisit-bubble-sys-text: rgba(255, 255, 255, 0.9);
|
|
106
|
+
|
|
107
|
+
/* Form Colors */
|
|
108
|
+
--foisit-req-star: #f87171;
|
|
109
|
+
--foisit-error-text: #fca5a5;
|
|
110
|
+
--foisit-error-border: #f87171;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
`;t.textContent+=`
|
|
85
114
|
|
|
86
115
|
@keyframes foisitPulse {
|
|
87
116
|
0%, 100% { transform: scale(0.8); opacity: 0.5; }
|
|
@@ -129,8 +158,8 @@
|
|
|
129
158
|
border: var(--foisit-border);
|
|
130
159
|
box-shadow: var(--foisit-shadow);
|
|
131
160
|
|
|
132
|
-
backdrop-filter:
|
|
133
|
-
-webkit-backdrop-filter:
|
|
161
|
+
backdrop-filter: var(--foisit-backdrop);
|
|
162
|
+
-webkit-backdrop-filter: var(--foisit-backdrop);
|
|
134
163
|
|
|
135
164
|
border-radius: 18px;
|
|
136
165
|
display: none;
|
|
@@ -339,8 +368,8 @@
|
|
|
339
368
|
border: 1px solid rgba(255,255,255,0.2);
|
|
340
369
|
background: var(--foisit-bg);
|
|
341
370
|
color: var(--foisit-text);
|
|
342
|
-
backdrop-filter:
|
|
343
|
-
-webkit-backdrop-filter:
|
|
371
|
+
backdrop-filter: var(--foisit-backdrop);
|
|
372
|
+
-webkit-backdrop-filter: var(--foisit-backdrop);
|
|
344
373
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
345
374
|
cursor: pointer;
|
|
346
375
|
pointer-events: auto;
|
|
@@ -437,4 +466,4 @@
|
|
|
437
466
|
.foisit-bubble.system .foisit-code-block { background: rgba(255,255,255,0.08); }
|
|
438
467
|
.foisit-bubble.system .foisit-inline-code { background: rgba(255,255,255,0.1); }
|
|
439
468
|
}
|
|
440
|
-
`,document.head.appendChild(t)}}class q{constructor(t){this.config=t,this.isActivated=!1,this.lastProcessedInput="",this.processingLock=!1,this.defaultIntroMessage="How can I help you?",this.commandHandler=new
|
|
469
|
+
`,document.head.appendChild(t)}}class q{constructor(t){this.config=t,this.isActivated=!1,this.lastProcessedInput="",this.processingLock=!1,this.defaultIntroMessage="How can I help you?",this.commandHandler=new N({enableSmartIntent:this.config.enableSmartIntent!==!1,intentEndpoint:this.config.intentEndpoint}),this.fallbackHandler=new z,typeof window<"u"&&typeof document<"u"?(this.voiceProcessor=new W,this.textToSpeech=new P,this.stateManager=new _,this.gestureHandler=new R,this.overlayManager=new G({floatingButton:this.config.floatingButton,inputPlaceholder:this.config.inputPlaceholder,enableGestureActivation:this.config.enableGestureActivation,theme:this.config.theme,themeColors:this.config.themeColors}),this.overlayManager.setExternalCommandExecutor(async e=>this.commandHandler.executeCommand(e)),this.config.commands.forEach(e=>this.commandHandler.addCommand(e)),this.config.fallbackResponse&&this.fallbackHandler.setFallbackMessage(this.config.fallbackResponse),this.overlayManager.registerCallbacks(async e=>{if(typeof e=="string"){this.overlayManager.addMessage(e,"user"),await this.handleCommand(e);return}if(e&&typeof e=="object"){const i=e,s=i.label??i.commandId??"Selection";this.overlayManager.addMessage(String(s),"user"),this.overlayManager.showLoading();const n=await this.commandHandler.executeCommand(i);this.overlayManager.hideLoading(),this.processResponse(n)}},()=>console.log("AssistantService: Overlay closed."))):(this.voiceProcessor=void 0,this.textToSpeech=void 0,this.stateManager=void 0,this.gestureHandler=void 0,this.overlayManager=void 0,this.config.commands.forEach(e=>this.commandHandler.addCommand(e)),this.config.fallbackResponse&&this.fallbackHandler.setFallbackMessage(this.config.fallbackResponse))}startListening(){if(typeof window>"u"||!this.voiceProcessor){console.log("AssistantService: Voice is disabled or unavailable; startListening() is a no-op.");return}}stopListening(){if(typeof window>"u"||!this.voiceProcessor){console.log("AssistantService: Voice unavailable; stopListening() is a no-op."),this.isActivated=!1;return}this.voiceProcessor.stopListening(),this.isActivated=!1}reactivate(){console.log("AssistantService: Reactivating assistant..."),this.isActivated=!1;try{this.startListening()}catch{}}async processActivation(t){var i;const e=(i=this.config.activationCommand)==null?void 0:i.toLowerCase();e&&(t===e?(console.log("AssistantService: Activation matched."),this.isActivated=!0,this.textToSpeech.speak(this.config.introMessage||this.defaultIntroMessage)):console.log("AssistantService: Activation command not recognized."))}async handleCommand(t){this.overlayManager.showLoading();let e;try{e=await this.commandHandler.executeCommand(t)}finally{this.overlayManager.hideLoading()}if(e.type==="form"&&e.fields){this.overlayManager.addForm(e.message,e.fields,async i=>{this.overlayManager.showLoading();let s;try{s=await this.commandHandler.executeCommand(i)}finally{this.overlayManager.hideLoading()}this.processResponse(s)});return}if(e.type==="error"){this.fallbackHandler.handleFallback(t),this.overlayManager.addMessage(this.fallbackHandler.getFallbackMessage(),"system");return}if((e.type==="ambiguous"||e.type==="confirm")&&e.options){e.message&&this.overlayManager.addMessage(e.message,"system"),this.overlayManager.addOptions(e.options);return}e.message&&this.overlayManager.addMessage(e.message,"system")}destroy(){this.voiceProcessor.stopListening(),this.gestureHandler.destroy(),this.overlayManager.destroy()}processResponse(t){if(t){if(t.type==="form"&&t.fields){this.overlayManager.addForm(t.message,t.fields,async e=>{this.overlayManager.showLoading();let i;try{i=await this.commandHandler.executeCommand(e)}finally{this.overlayManager.hideLoading()}this.processResponse(i)});return}if((t.type==="ambiguous"||t.type==="confirm")&&t.options){t.message&&this.overlayManager.addMessage(t.message,"system"),this.overlayManager.addOptions(t.options);return}t.message&&this.overlayManager.addMessage(t.message,"system")}}addCommand(t,e){console.log(typeof t=="string"?`AssistantService: Adding command "${t}".`:`AssistantService: Adding rich command "${t.command}".`),this.commandHandler.addCommand(t,e)}removeCommand(t){console.log(`AssistantService: Removing command "${t}".`),this.commandHandler.removeCommand(t)}registerCommandHandler(t,e){this.overlayManager&&this.overlayManager.registerCommandHandler(t,e)}unregisterCommandHandler(t){this.overlayManager&&this.overlayManager.unregisterCommandHandler(t)}async runCommand(t){if(!this.overlayManager)throw new Error("Overlay manager not available.");const e=await this.overlayManager.runCommand(t);return e&&typeof e=="object"&&"type"in e&&this.processResponse(e),e}getCommands(){return this.commandHandler.getCommands()}toggle(t,e){console.log("AssistantService: Toggling overlay..."),this.overlayManager.toggle(async i=>{if(typeof i=="string"){this.overlayManager.addMessage(i,"user"),t&&t(i),await this.handleCommand(i);return}if(i&&typeof i=="object"){const s=i,n=s.label??s.commandId??"Selection";this.overlayManager.addMessage(String(n),"user"),this.overlayManager.showLoading();let a;try{a=await this.commandHandler.executeCommand(s)}finally{this.overlayManager.hideLoading()}this.processResponse(a)}},()=>{console.log("AssistantService: Overlay closed."),e&&e()})}}const T=S.createContext(null);let I=null;const Y=({config:d,children:t})=>{const[e,i]=S.useState(null),[s,n]=S.useState(!1);return S.useEffect(()=>{I?console.warn("Multiple AssistantProvider instances detected. Reusing global AssistantService."):(console.log("Initializing global AssistantService..."),I=new q(d));const a=I;return i(a),n(!0),()=>{var o;console.log("Cleaning up AssistantService..."),(o=a.destroy)==null||o.call(a),I=null}},[d]),!s||!e?E.jsx("div",{children:"Loading Assistant..."}):E.jsx(T.Provider,{value:e,children:t})},F=()=>{const d=S.useContext(T);if(console.log("assistant",d),!d)throw new Error("useAssistant must be used within an AssistantProvider");return d},K=({label:d="Activate Assistant",onActivate:t})=>{const e=F(),i=()=>{t&&t(),e.reactivate()};return E.jsx("button",{onClick:i,className:"assistant-activator",children:d})},X=d=>{const[t,e]=S.useState(d.getState());return S.useEffect(()=>{const i=s=>{e(s)};return d.subscribe(i),()=>{d.subscribe(()=>{})}},[d]),t};exports.AssistantActivator=K;exports.AssistantContext=T;exports.AssistantProvider=Y;exports.AssistantService=q;exports.ReactWrapper=B;exports.useAssistant=F;exports.useAssistantState=X;
|
package/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as k } from "react/jsx-runtime";
|
|
2
|
-
import { createContext as F, useState as I, useEffect as
|
|
2
|
+
import { createContext as F, useState as I, useEffect as H, useContext as B } from "react";
|
|
3
3
|
const O = {};
|
|
4
4
|
function Q() {
|
|
5
5
|
return /* @__PURE__ */ k("div", { className: O.container, children: /* @__PURE__ */ k("h1", { children: "Welcome to ReactWrapper!" }) });
|
|
@@ -35,7 +35,7 @@ class T {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
class
|
|
38
|
+
class N {
|
|
39
39
|
constructor(t = !0) {
|
|
40
40
|
if (this.commands = /* @__PURE__ */ new Map(), this.openAIService = null, this.context = null, this.pendingConfirmation = null, this.enableSmartIntent = !0, this.selectOptionsCache = /* @__PURE__ */ new Map(), typeof t == "boolean") {
|
|
41
41
|
this.enableSmartIntent = t, this.enableSmartIntent && (this.openAIService = new T());
|
|
@@ -66,9 +66,9 @@ class z {
|
|
|
66
66
|
async executeCommand(t) {
|
|
67
67
|
if (typeof t == "object" && t !== null) {
|
|
68
68
|
if (this.isStructured(t)) {
|
|
69
|
-
const c = String(t.commandId),
|
|
69
|
+
const c = String(t.commandId), u = t.params ?? {}, r = this.getCommandById(c);
|
|
70
70
|
if (!r) return { message: "That command is not available.", type: "error" };
|
|
71
|
-
const p = this.sanitizeParamsForCommand(r,
|
|
71
|
+
const p = this.sanitizeParamsForCommand(r, u), m = (r.parameters ?? []).filter((C) => C.required).filter((C) => p[C.name] == null || p[C.name] === "");
|
|
72
72
|
return m.length > 0 ? (this.context = { commandId: this.getCommandIdentifier(r), params: p }, {
|
|
73
73
|
message: `Please provide the required details for "${r.command}".`,
|
|
74
74
|
type: "form",
|
|
@@ -143,7 +143,7 @@ class z {
|
|
|
143
143
|
return { message: "I'm not sure what you mean.", type: "error" };
|
|
144
144
|
if (e.macro)
|
|
145
145
|
return this.safeRunAction(e, {});
|
|
146
|
-
const i = t.params ?? {}, s = this.sanitizeParamsForCommand(e, i), n = e.allowAiParamExtraction === !1 ? {} : s, o = (e.parameters ?? []).filter((
|
|
146
|
+
const i = t.params ?? {}, s = this.sanitizeParamsForCommand(e, i), n = e.allowAiParamExtraction === !1 ? {} : s, o = (e.parameters ?? []).filter((u) => u.required).filter((u) => n[u.name] == null || n[u.name] === "");
|
|
147
147
|
if (t.incomplete || o.length > 0) {
|
|
148
148
|
if (this.context = { commandId: this.getCommandIdentifier(e), params: n }, !(e.collectRequiredViaForm !== !1) && this.shouldAskSingleQuestion(o)) {
|
|
149
149
|
const p = o.map((g) => g.name).join(" and ");
|
|
@@ -259,8 +259,8 @@ class z {
|
|
|
259
259
|
const e = [];
|
|
260
260
|
for (const o of this.commands.values()) {
|
|
261
261
|
let c = 0;
|
|
262
|
-
const
|
|
263
|
-
t.includes(
|
|
262
|
+
const u = o.command.toLowerCase();
|
|
263
|
+
t.includes(u) && (c += 5);
|
|
264
264
|
const r = o.keywords ?? [];
|
|
265
265
|
for (const p of r) {
|
|
266
266
|
const g = p.toLowerCase().trim();
|
|
@@ -372,7 +372,7 @@ class P {
|
|
|
372
372
|
this.synth && this.synth.cancel();
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
|
-
class
|
|
375
|
+
class z {
|
|
376
376
|
constructor() {
|
|
377
377
|
this.fallbackMessage = "Sorry, I didn’t understand that.";
|
|
378
378
|
}
|
|
@@ -386,12 +386,12 @@ class W {
|
|
|
386
386
|
return this.fallbackMessage;
|
|
387
387
|
}
|
|
388
388
|
}
|
|
389
|
-
const
|
|
389
|
+
const $ = () => {
|
|
390
390
|
if (typeof window > "u") return null;
|
|
391
391
|
const d = window;
|
|
392
392
|
return d.SpeechRecognition ?? d.webkitSpeechRecognition ?? null;
|
|
393
393
|
};
|
|
394
|
-
class
|
|
394
|
+
class W {
|
|
395
395
|
constructor(t = "en-US", e = {}) {
|
|
396
396
|
this.recognition = null, this.isListening = !1, this.engineActive = !1, this.intentionallyStopped = !1, this.restartAllowed = !0, this.lastStart = 0, this.backoffMs = 250, this.destroyed = !1, this.resultCallback = null, this.ttsSpeaking = !1, this.debugEnabled = !0, this.restartTimer = null, this.prewarmed = !1, this.hadResultThisSession = !1, this.onTTSStart = () => {
|
|
397
397
|
var s;
|
|
@@ -404,7 +404,7 @@ class B {
|
|
|
404
404
|
}, this.onTTSEnd = () => {
|
|
405
405
|
this.ttsSpeaking = !1, this.isListening && this.restartAllowed ? this.safeRestart() : this.emitStatus(this.isListening ? "listening" : "idle");
|
|
406
406
|
};
|
|
407
|
-
const i =
|
|
407
|
+
const i = $();
|
|
408
408
|
if (i) {
|
|
409
409
|
this.recognition = new i(), this.recognition.lang = t, this.recognition.interimResults = e.interimResults ?? !0, this.recognition.continuous = e.continuous ?? !0, this.recognition.onresult = (n) => this.handleResult(n, e), this.recognition.onend = () => this.handleEnd(), this.recognition.onstart = () => {
|
|
410
410
|
this.log("recognition onstart"), this.engineActive = !0, this.hadResultThisSession = !1, this.restartTimer && (clearTimeout(this.restartTimer), this.restartTimer = null), this.backoffMs = 250, this.isListening && !this.ttsSpeaking && this.emitStatus("listening");
|
|
@@ -436,7 +436,7 @@ class B {
|
|
|
436
436
|
}
|
|
437
437
|
/** Check if SpeechRecognition is available */
|
|
438
438
|
isSupported() {
|
|
439
|
-
return
|
|
439
|
+
return $() !== null;
|
|
440
440
|
}
|
|
441
441
|
/** Allow consumers (wrappers) to observe status changes */
|
|
442
442
|
onStatusChange(t) {
|
|
@@ -475,10 +475,10 @@ class B {
|
|
|
475
475
|
if (!this.resultCallback) return;
|
|
476
476
|
const i = e.confidenceThreshold ?? 0.6;
|
|
477
477
|
for (let a = t.resultIndex; a < t.results.length; a++) {
|
|
478
|
-
const o = t.results[a], c = o && o[0],
|
|
479
|
-
if (
|
|
478
|
+
const o = t.results[a], c = o && o[0], u = ((n = (s = c == null ? void 0 : c.transcript) == null ? void 0 : s.trim) == null ? void 0 : n.call(s)) || "", r = (c == null ? void 0 : c.confidence) ?? 0;
|
|
479
|
+
if (u && !(!o.isFinal && e.interimResults === !1) && !(o.isFinal && r < i))
|
|
480
480
|
try {
|
|
481
|
-
this.hadResultThisSession = !0, this.resultCallback(
|
|
481
|
+
this.hadResultThisSession = !0, this.resultCallback(u, !!o.isFinal);
|
|
482
482
|
} catch {
|
|
483
483
|
this.error("VoiceProcessor: result callback error");
|
|
484
484
|
}
|
|
@@ -681,8 +681,14 @@ class G {
|
|
|
681
681
|
* - `showInvocation`: if true, show the invocation as a user message
|
|
682
682
|
*/
|
|
683
683
|
async runCommand(t) {
|
|
684
|
-
if (!t || !t.commandId)
|
|
685
|
-
|
|
684
|
+
if (!t || !t.commandId)
|
|
685
|
+
throw new Error("runCommand requires a commandId");
|
|
686
|
+
const {
|
|
687
|
+
commandId: e,
|
|
688
|
+
params: i,
|
|
689
|
+
openOverlay: s = !0,
|
|
690
|
+
showInvocation: n = !0
|
|
691
|
+
} = t;
|
|
686
692
|
s && !this.isOpen && this.toggle();
|
|
687
693
|
const a = this.commandHandlers.get(e);
|
|
688
694
|
if (a && n && this.messagesContainer && this.addMessage(`Command: ${e}`, "user"), a)
|
|
@@ -700,7 +706,10 @@ class G {
|
|
|
700
706
|
else o == null || this.addMessage(String(o), "system");
|
|
701
707
|
return o;
|
|
702
708
|
} catch (o) {
|
|
703
|
-
throw this.hideLoading(), this.addMessage(
|
|
709
|
+
throw this.hideLoading(), this.addMessage(
|
|
710
|
+
`Command "${e}" failed: ${String(o)}`,
|
|
711
|
+
"system"
|
|
712
|
+
), o;
|
|
704
713
|
}
|
|
705
714
|
if (this.externalCommandExecutor)
|
|
706
715
|
try {
|
|
@@ -708,9 +717,15 @@ class G {
|
|
|
708
717
|
const o = await this.externalCommandExecutor({ commandId: e, params: i });
|
|
709
718
|
return this.hideLoading(), o;
|
|
710
719
|
} catch (o) {
|
|
711
|
-
throw this.hideLoading(), this.addMessage(
|
|
712
|
-
|
|
713
|
-
|
|
720
|
+
throw this.hideLoading(), this.addMessage(
|
|
721
|
+
`Command "${e}" failed: ${String(o)}`,
|
|
722
|
+
"system"
|
|
723
|
+
), o;
|
|
724
|
+
}
|
|
725
|
+
this.addMessage(
|
|
726
|
+
`No handler registered for command "${e}".`,
|
|
727
|
+
"system"
|
|
728
|
+
);
|
|
714
729
|
}
|
|
715
730
|
init() {
|
|
716
731
|
var e, i;
|
|
@@ -718,17 +733,23 @@ class G {
|
|
|
718
733
|
this.injectOverlayStyles();
|
|
719
734
|
const t = document.getElementById("foisit-overlay-container");
|
|
720
735
|
if (t && t instanceof HTMLElement) {
|
|
721
|
-
this.container = t, this.chatWindow = t.querySelector(
|
|
736
|
+
this.container = t, this.chatWindow = t.querySelector(
|
|
737
|
+
".foisit-chat"
|
|
738
|
+
), this.messagesContainer = t.querySelector(
|
|
739
|
+
".foisit-messages"
|
|
740
|
+
), this.input = t.querySelector(
|
|
741
|
+
"input.foisit-input"
|
|
742
|
+
), ((e = this.config.floatingButton) == null ? void 0 : e.visible) !== !1 && !t.querySelector(".foisit-floating-btn") && this.renderFloatingButton(), this.chatWindow || this.renderChatWindow(), this.config.enableGestureActivation && (this.gestureHandler = new R(), this.gestureHandler.setupTripleTapListener(() => this.toggle()));
|
|
722
743
|
return;
|
|
723
744
|
}
|
|
724
745
|
this.container = document.createElement("div"), this.container.id = "foisit-overlay-container", this.container.className = "foisit-overlay-container", document.body.appendChild(this.container), ((i = this.config.floatingButton) == null ? void 0 : i.visible) !== !1 && this.renderFloatingButton(), this.renderChatWindow(), this.config.enableGestureActivation && (this.gestureHandler = new R(), this.gestureHandler.setupTripleTapListener(() => this.toggle()));
|
|
725
746
|
}
|
|
726
747
|
renderFloatingButton() {
|
|
727
|
-
var s, n, a, o, c,
|
|
748
|
+
var s, n, a, o, c, u;
|
|
728
749
|
const t = document.createElement("button");
|
|
729
750
|
t.innerHTML = ((s = this.config.floatingButton) == null ? void 0 : s.customHtml) || "🎙️";
|
|
730
751
|
const e = ((a = (n = this.config.floatingButton) == null ? void 0 : n.position) == null ? void 0 : a.bottom) || "20px", i = ((c = (o = this.config.floatingButton) == null ? void 0 : o.position) == null ? void 0 : c.right) || "20px";
|
|
731
|
-
t.className = "foisit-floating-btn", t.style.bottom = e, t.style.right = i, t.onclick = () => this.toggle(), t.onmouseenter = () => t.style.transform = "scale(1.05)", t.onmouseleave = () => t.style.transform = "scale(1)", (
|
|
752
|
+
t.className = "foisit-floating-btn", t.style.bottom = e, t.style.right = i, t.onclick = () => this.toggle(), t.onmouseenter = () => t.style.transform = "scale(1.05)", t.onmouseleave = () => t.style.transform = "scale(1)", (u = this.container) == null || u.appendChild(t);
|
|
732
753
|
}
|
|
733
754
|
renderChatWindow() {
|
|
734
755
|
var n;
|
|
@@ -817,19 +838,19 @@ class G {
|
|
|
817
838
|
l.className = "foisit-form-input";
|
|
818
839
|
const y = document.createElement("option");
|
|
819
840
|
y.value = "", y.textContent = "Select...", l.appendChild(y);
|
|
820
|
-
const
|
|
821
|
-
(h ?? []).forEach((
|
|
841
|
+
const x = (h) => {
|
|
842
|
+
(h ?? []).forEach((w) => {
|
|
822
843
|
const b = document.createElement("option");
|
|
823
|
-
b.value = String(
|
|
844
|
+
b.value = String(w.value ?? w.label ?? ""), b.textContent = String(w.label ?? w.value ?? ""), l.appendChild(b);
|
|
824
845
|
});
|
|
825
846
|
};
|
|
826
847
|
if (Array.isArray(r.options) && r.options.length)
|
|
827
|
-
|
|
848
|
+
x(r.options);
|
|
828
849
|
else if (typeof r.getOptions == "function") {
|
|
829
|
-
const h = r.getOptions,
|
|
830
|
-
|
|
850
|
+
const h = r.getOptions, w = document.createElement("option");
|
|
851
|
+
w.value = "", w.textContent = "Loading...", l.appendChild(w), Promise.resolve().then(() => h()).then((b) => {
|
|
831
852
|
for (; l.options.length > 1; ) l.remove(1);
|
|
832
|
-
|
|
853
|
+
x(b);
|
|
833
854
|
}).catch(() => {
|
|
834
855
|
for (; l.options.length > 1; ) l.remove(1);
|
|
835
856
|
const b = document.createElement("option");
|
|
@@ -840,26 +861,32 @@ class G {
|
|
|
840
861
|
} else if (r.type === "file") {
|
|
841
862
|
const l = r, y = document.createElement("input");
|
|
842
863
|
y.className = "foisit-form-input", y.type = "file", l.accept && Array.isArray(l.accept) && (y.accept = l.accept.join(",")), l.multiple && (y.multiple = !0), l.capture && (l.capture === !0 ? y.setAttribute("capture", "") : y.setAttribute("capture", String(l.capture))), y.addEventListener("change", async () => {
|
|
843
|
-
const
|
|
844
|
-
if (h.style.display = "none", h.textContent = "",
|
|
845
|
-
const
|
|
846
|
-
if (
|
|
847
|
-
h.textContent = `Please select at most ${
|
|
864
|
+
const x = Array.from(y.files || []), h = C;
|
|
865
|
+
if (h.style.display = "none", h.textContent = "", x.length === 0) return;
|
|
866
|
+
const w = l.maxFiles ?? (l.multiple ? 10 : 1);
|
|
867
|
+
if (x.length > w) {
|
|
868
|
+
h.textContent = `Please select at most ${w} file(s).`, h.style.display = "block";
|
|
848
869
|
return;
|
|
849
870
|
}
|
|
850
|
-
const b = l.maxSizeBytes ?? 1 / 0,
|
|
851
|
-
if (
|
|
852
|
-
h.textContent = `One or more files exceed the maximum size of ${Math.round(
|
|
871
|
+
const b = l.maxSizeBytes ?? 1 / 0, f = x.reduce((v, E) => v + E.size, 0);
|
|
872
|
+
if (x.some((v) => v.size > b)) {
|
|
873
|
+
h.textContent = `One or more files exceed the maximum size of ${Math.round(
|
|
874
|
+
b / 1024
|
|
875
|
+
)} KB.`, h.style.display = "block";
|
|
853
876
|
return;
|
|
854
877
|
}
|
|
855
878
|
const S = l.maxTotalBytes ?? 1 / 0;
|
|
856
|
-
if (
|
|
857
|
-
h.textContent = `Total selected files exceed the maximum of ${Math.round(
|
|
879
|
+
if (f > S) {
|
|
880
|
+
h.textContent = `Total selected files exceed the maximum of ${Math.round(
|
|
881
|
+
S / 1024
|
|
882
|
+
)} KB.`, h.style.display = "block";
|
|
858
883
|
return;
|
|
859
884
|
}
|
|
860
885
|
if (l.accept && Array.isArray(l.accept)) {
|
|
861
886
|
const v = l.accept;
|
|
862
|
-
if (!
|
|
887
|
+
if (!x.every((L) => L.type ? v.some(
|
|
888
|
+
(A) => A.startsWith(".") ? L.name.toLowerCase().endsWith(A.toLowerCase()) : L.type === A || L.type.startsWith(A.split("/")[0] + "/")
|
|
889
|
+
) : !0)) {
|
|
863
890
|
h.textContent = "One or more files have an unsupported type.", h.style.display = "block";
|
|
864
891
|
return;
|
|
865
892
|
}
|
|
@@ -879,8 +906,8 @@ class G {
|
|
|
879
906
|
});
|
|
880
907
|
const c = document.createElement("div");
|
|
881
908
|
c.className = "foisit-form-actions";
|
|
882
|
-
const
|
|
883
|
-
|
|
909
|
+
const u = document.createElement("button");
|
|
910
|
+
u.type = "submit", u.textContent = "Submit", u.className = "foisit-option-chip", u.style.fontWeight = "600", c.appendChild(u), s.appendChild(c), s.onsubmit = async (r) => {
|
|
884
911
|
r.preventDefault();
|
|
885
912
|
const p = {};
|
|
886
913
|
let g = !1;
|
|
@@ -891,56 +918,62 @@ class G {
|
|
|
891
918
|
});
|
|
892
919
|
for (const m of n) {
|
|
893
920
|
if (m.type === "file") {
|
|
894
|
-
const
|
|
921
|
+
const x = m.el.parentElement, h = x == null ? void 0 : x.querySelector(
|
|
922
|
+
".foisit-form-error"
|
|
923
|
+
), w = m.el, b = Array.from(w.files || []);
|
|
895
924
|
if (m.required && b.length === 0) {
|
|
896
|
-
g = !0,
|
|
925
|
+
g = !0, w.classList.add("foisit-error-border"), h && (h.textContent = "This file is required", h.style.display = "block");
|
|
897
926
|
continue;
|
|
898
927
|
}
|
|
899
928
|
if (b.length === 0) continue;
|
|
900
|
-
const
|
|
901
|
-
if (
|
|
929
|
+
const f = (e ?? []).find((v) => v.name === m.name), S = (f == null ? void 0 : f.delivery) ?? "file";
|
|
930
|
+
if (f != null && f.maxWidth || f != null && f.maxHeight)
|
|
902
931
|
try {
|
|
903
932
|
const v = await this.getImageDimensions(b[0]);
|
|
904
|
-
if (
|
|
905
|
-
g = !0, h && (h.textContent = `Image width must be ≤ ${
|
|
933
|
+
if (f.maxWidth && v.width > f.maxWidth) {
|
|
934
|
+
g = !0, h && (h.textContent = `Image width must be ≤ ${f.maxWidth}px`, h.style.display = "block");
|
|
906
935
|
continue;
|
|
907
936
|
}
|
|
908
|
-
if (
|
|
909
|
-
g = !0, h && (h.textContent = `Image height must be ≤ ${
|
|
937
|
+
if (f.maxHeight && v.height > f.maxHeight) {
|
|
938
|
+
g = !0, h && (h.textContent = `Image height must be ≤ ${f.maxHeight}px`, h.style.display = "block");
|
|
910
939
|
continue;
|
|
911
940
|
}
|
|
912
941
|
} catch {
|
|
913
942
|
}
|
|
914
|
-
if (
|
|
943
|
+
if (f != null && f.maxDurationSec)
|
|
915
944
|
try {
|
|
916
945
|
const v = await this.getMediaDuration(b[0]);
|
|
917
|
-
if (v && v >
|
|
918
|
-
g = !0, h && (h.textContent = `Media duration must be ≤ ${
|
|
946
|
+
if (v && v > f.maxDurationSec) {
|
|
947
|
+
g = !0, h && (h.textContent = `Media duration must be ≤ ${f.maxDurationSec}s`, h.style.display = "block");
|
|
919
948
|
continue;
|
|
920
949
|
}
|
|
921
950
|
} catch {
|
|
922
951
|
}
|
|
923
952
|
if (S === "file")
|
|
924
|
-
p[m.name] =
|
|
953
|
+
p[m.name] = f != null && f.multiple ? b : b[0];
|
|
925
954
|
else if (S === "base64")
|
|
926
955
|
try {
|
|
927
|
-
const v = await Promise.all(
|
|
928
|
-
|
|
956
|
+
const v = await Promise.all(
|
|
957
|
+
b.map((E) => this.readFileAsDataURL(E))
|
|
958
|
+
);
|
|
959
|
+
p[m.name] = f != null && f.multiple ? v : v[0];
|
|
929
960
|
} catch {
|
|
930
961
|
g = !0, h && (h.textContent = "Failed to encode file(s) to base64.", h.style.display = "block");
|
|
931
962
|
continue;
|
|
932
963
|
}
|
|
933
964
|
continue;
|
|
934
965
|
}
|
|
935
|
-
const C = (m.el.value ?? "").toString().trim(), l = m.el.parentElement, y = l == null ? void 0 : l.querySelector(
|
|
966
|
+
const C = (m.el.value ?? "").toString().trim(), l = m.el.parentElement, y = l == null ? void 0 : l.querySelector(
|
|
967
|
+
".foisit-form-error"
|
|
968
|
+
);
|
|
936
969
|
if (m.required && (C == null || C === "")) {
|
|
937
970
|
g = !0, m.el.classList.add("foisit-error-border"), y && (y.textContent = "This field is required", y.style.display = "block");
|
|
938
971
|
continue;
|
|
939
972
|
}
|
|
940
973
|
if (C !== "")
|
|
941
974
|
if (m.type === "number") {
|
|
942
|
-
const
|
|
943
|
-
Number.isNaN(
|
|
975
|
+
const x = Number(C);
|
|
976
|
+
Number.isNaN(x) || (p[m.name] = x);
|
|
944
977
|
} else
|
|
945
978
|
p[m.name] = C;
|
|
946
979
|
}
|
|
@@ -948,7 +981,7 @@ class G {
|
|
|
948
981
|
s.classList.add("foisit-shake"), setTimeout(() => s.classList.remove("foisit-shake"), 400);
|
|
949
982
|
return;
|
|
950
983
|
}
|
|
951
|
-
|
|
984
|
+
u.disabled = !0, u.style.opacity = "0.6", n.forEach((m) => {
|
|
952
985
|
m.el.disabled = !0;
|
|
953
986
|
}), i(p);
|
|
954
987
|
}, this.messagesContainer.appendChild(s), this.scrollToBottom();
|
|
@@ -973,7 +1006,10 @@ class G {
|
|
|
973
1006
|
/** Subtle entrance animation for new messages */
|
|
974
1007
|
animateMessageEntrance(t, e) {
|
|
975
1008
|
if (!t) return;
|
|
976
|
-
t.style.transition = `opacity ${e}ms cubic-bezier(0.22, 0.9, 0.32, 1), transform ${Math.max(
|
|
1009
|
+
t.style.transition = `opacity ${e}ms cubic-bezier(0.22, 0.9, 0.32, 1), transform ${Math.max(
|
|
1010
|
+
120,
|
|
1011
|
+
e
|
|
1012
|
+
)}ms cubic-bezier(0.22, 0.9, 0.32, 1)`, requestAnimationFrame(() => {
|
|
977
1013
|
t.style.opacity = "1", t.style.transform = "translateY(0)";
|
|
978
1014
|
});
|
|
979
1015
|
const i = () => {
|
|
@@ -994,8 +1030,8 @@ class G {
|
|
|
994
1030
|
return;
|
|
995
1031
|
}
|
|
996
1032
|
const n = s - i, a = performance.now(), o = (c) => {
|
|
997
|
-
const
|
|
998
|
-
e.scrollTop = Math.round(i + n * r),
|
|
1033
|
+
const u = Math.min(1, (c - a) / t), r = 1 - Math.pow(1 - u, 3);
|
|
1034
|
+
e.scrollTop = Math.round(i + n * r), u < 1 && requestAnimationFrame(o);
|
|
999
1035
|
};
|
|
1000
1036
|
requestAnimationFrame(o);
|
|
1001
1037
|
}
|
|
@@ -1017,7 +1053,22 @@ class G {
|
|
|
1017
1053
|
/** Simple markdown renderer for AI responses */
|
|
1018
1054
|
renderMarkdown(t) {
|
|
1019
1055
|
let e = this.escapeHtml(t);
|
|
1020
|
-
return e = e.replace(/```(\w*)\n([\s\S]*?)```/g, (i, s, n) => `<pre class="foisit-code-block"><code${s ? ` class="language-${s}"` : ""}>${n.trim()}</code></pre>`), e = e.replace(
|
|
1056
|
+
return e = e.replace(/```(\w*)\n([\s\S]*?)```/g, (i, s, n) => `<pre class="foisit-code-block"><code${s ? ` class="language-${s}"` : ""}>${n.trim()}</code></pre>`), e = e.replace(
|
|
1057
|
+
/`([^`]+)`/g,
|
|
1058
|
+
'<code class="foisit-inline-code">$1</code>'
|
|
1059
|
+
), e = e.replace(/^###### (.+)$/gm, '<h6 class="foisit-md-h6">$1</h6>'), e = e.replace(/^##### (.+)$/gm, '<h5 class="foisit-md-h5">$1</h5>'), e = e.replace(/^#### (.+)$/gm, '<h4 class="foisit-md-h4">$1</h4>'), e = e.replace(/^### (.+)$/gm, '<h3 class="foisit-md-h3">$1</h3>'), e = e.replace(/^## (.+)$/gm, '<h2 class="foisit-md-h2">$1</h2>'), e = e.replace(/^# (.+)$/gm, '<h1 class="foisit-md-h1">$1</h1>'), e = e.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>"), e = e.replace(/__([^_]+)__/g, "<strong>$1</strong>"), e = e.replace(/\*([^*]+)\*/g, "<em>$1</em>"), e = e.replace(new RegExp("(?<!_)_([^_]+)_(?!_)", "g"), "<em>$1</em>"), e = e.replace(/~~([^~]+)~~/g, "<del>$1</del>"), e = e.replace(
|
|
1060
|
+
/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
1061
|
+
'<a href="$2" target="_blank" rel="noopener noreferrer" class="foisit-md-link">$1</a>'
|
|
1062
|
+
), e = e.replace(/^[\-\*] (.+)$/gm, '<li class="foisit-md-li">$1</li>'), e = e.replace(
|
|
1063
|
+
/(<li class="foisit-md-li">.*<\/li>\n?)+/g,
|
|
1064
|
+
(i) => `<ul class="foisit-md-ul">${i}</ul>`
|
|
1065
|
+
), e = e.replace(/^\d+\. (.+)$/gm, '<li class="foisit-md-li">$1</li>'), e = e.replace(
|
|
1066
|
+
new RegExp('(?<!<\\/ul>)(<li class="foisit-md-li">.*<\\/li>\\n?)+', "g"),
|
|
1067
|
+
(i) => i.includes("<ul") ? i : `<ol class="foisit-md-ol">${i}</ol>`
|
|
1068
|
+
), e = e.replace(
|
|
1069
|
+
/^> (.+)$/gm,
|
|
1070
|
+
'<blockquote class="foisit-md-blockquote">$1</blockquote>'
|
|
1071
|
+
), e = e.replace(/^(---|___|\*\*\*)$/gm, '<hr class="foisit-md-hr">'), e = e.replace(/\n\n+/g, '</p><p class="foisit-md-p">'), e = e.replace(/\n/g, "<br>"), e.match(/^<(h[1-6]|ul|ol|pre|blockquote|hr|p)/) || (e = `<p class="foisit-md-p">${e}</p>`), e;
|
|
1021
1072
|
}
|
|
1022
1073
|
readFileAsDataURL(t) {
|
|
1023
1074
|
return new Promise((e, i) => {
|
|
@@ -1030,7 +1081,10 @@ class G {
|
|
|
1030
1081
|
try {
|
|
1031
1082
|
const i = URL.createObjectURL(t), s = new Image();
|
|
1032
1083
|
s.onload = () => {
|
|
1033
|
-
const n = {
|
|
1084
|
+
const n = {
|
|
1085
|
+
width: s.naturalWidth || s.width,
|
|
1086
|
+
height: s.naturalHeight || s.height
|
|
1087
|
+
};
|
|
1034
1088
|
URL.revokeObjectURL(i), e(n);
|
|
1035
1089
|
}, s.onerror = () => {
|
|
1036
1090
|
URL.revokeObjectURL(i), e({ width: 0, height: 0 });
|
|
@@ -1064,57 +1118,93 @@ class G {
|
|
|
1064
1118
|
injectOverlayStyles() {
|
|
1065
1119
|
if (document.getElementById("foisit-overlay-styles")) return;
|
|
1066
1120
|
const t = document.createElement("style");
|
|
1067
|
-
t.id = "foisit-overlay-styles"
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1121
|
+
t.id = "foisit-overlay-styles";
|
|
1122
|
+
const e = this.config.theme || "glass", i = this.config.themeColors || {};
|
|
1123
|
+
if (e === "solid") {
|
|
1124
|
+
const s = i.background || "#1a1a2e", n = i.text || "#ffffff", a = i.accent || "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", o = i.userBubbleBg || "rgba(102, 126, 234, 0.2)", c = i.systemBubbleBg || "rgba(255, 255, 255, 0.08)", u = i.border || "rgba(255, 255, 255, 0.1)";
|
|
1125
|
+
t.textContent = `
|
|
1126
|
+
:root {
|
|
1127
|
+
/* SOLID THEME - Custom Colors */
|
|
1128
|
+
--foisit-bg: ${s};
|
|
1129
|
+
--foisit-border: 1px solid ${u};
|
|
1130
|
+
--foisit-shadow: 0 16px 48px rgba(0, 0, 0, 0.4);
|
|
1131
|
+
--foisit-text: ${n};
|
|
1132
|
+
--foisit-accent: ${a};
|
|
1133
|
+
--foisit-backdrop: none;
|
|
1079
1134
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1135
|
+
/* Input */
|
|
1136
|
+
--foisit-input-color: ${n};
|
|
1137
|
+
--foisit-input-placeholder: ${n}99;
|
|
1083
1138
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1139
|
+
/* Bubbles */
|
|
1140
|
+
--foisit-bubble-user-bg: ${o};
|
|
1141
|
+
--foisit-bubble-user-text: ${n};
|
|
1086
1142
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
--foisit-error-text: #dc2626;
|
|
1090
|
-
--foisit-error-border: #fca5a5;
|
|
1091
|
-
}
|
|
1143
|
+
--foisit-bubble-sys-bg: ${c};
|
|
1144
|
+
--foisit-bubble-sys-text: ${n}ee;
|
|
1092
1145
|
|
|
1093
|
-
|
|
1146
|
+
/* Form Colors */
|
|
1147
|
+
--foisit-req-star: #f87171;
|
|
1148
|
+
--foisit-error-text: #fca5a5;
|
|
1149
|
+
--foisit-error-border: #f87171;
|
|
1150
|
+
}
|
|
1151
|
+
`;
|
|
1152
|
+
} else
|
|
1153
|
+
t.textContent = `
|
|
1094
1154
|
:root {
|
|
1095
|
-
/*
|
|
1096
|
-
|
|
1097
|
-
--foisit-
|
|
1098
|
-
--foisit-
|
|
1099
|
-
--foisit-
|
|
1155
|
+
/* LIGHT MODE (Default) - Smoother gradient */
|
|
1156
|
+
/* Changed: Softer, right-focused radial highlight to avoid a heavy white bottom */
|
|
1157
|
+
--foisit-bg: radial-gradient(ellipse at 75% 30%, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.03));
|
|
1158
|
+
--foisit-border: 1px solid rgba(255, 255, 255, 0.25);
|
|
1159
|
+
--foisit-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
|
|
1160
|
+
--foisit-text: #333;
|
|
1161
|
+
--foisit-backdrop: blur(20px);
|
|
1100
1162
|
|
|
1101
1163
|
/* Input */
|
|
1102
|
-
--foisit-input-color:
|
|
1103
|
-
--foisit-input-placeholder: rgba(
|
|
1164
|
+
--foisit-input-color: #333;
|
|
1165
|
+
--foisit-input-placeholder: rgba(60, 60, 67, 0.6);
|
|
1104
1166
|
|
|
1105
1167
|
/* Bubbles */
|
|
1106
|
-
--foisit-bubble-user-bg: rgba(
|
|
1107
|
-
--foisit-bubble-user-text:
|
|
1168
|
+
--foisit-bubble-user-bg: rgba(0, 0, 0, 0.04);
|
|
1169
|
+
--foisit-bubble-user-text: #333;
|
|
1108
1170
|
|
|
1109
|
-
--foisit-bubble-sys-bg: rgba(255, 255, 255, 0.
|
|
1110
|
-
--foisit-bubble-sys-text:
|
|
1171
|
+
--foisit-bubble-sys-bg: rgba(255, 255, 255, 0.45);
|
|
1172
|
+
--foisit-bubble-sys-text: #333;
|
|
1111
1173
|
|
|
1112
1174
|
/* Form Colors */
|
|
1113
|
-
--foisit-req-star: #
|
|
1114
|
-
--foisit-error-text: #
|
|
1115
|
-
--foisit-error-border: #
|
|
1175
|
+
--foisit-req-star: #ef4444; /* Red asterisk */
|
|
1176
|
+
--foisit-error-text: #dc2626;
|
|
1177
|
+
--foisit-error-border: #fca5a5;
|
|
1116
1178
|
}
|
|
1117
|
-
|
|
1179
|
+
|
|
1180
|
+
@media (prefers-color-scheme: dark) {
|
|
1181
|
+
:root {
|
|
1182
|
+
/* DARK MODE */
|
|
1183
|
+
--foisit-bg: linear-gradient(135deg, rgba(40, 40, 40, 0.65), rgba(40, 40, 40, 0.25));
|
|
1184
|
+
--foisit-border: 1px solid rgba(255, 255, 255, 0.1);
|
|
1185
|
+
--foisit-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
|
|
1186
|
+
--foisit-text: #fff;
|
|
1187
|
+
--foisit-backdrop: blur(20px);
|
|
1188
|
+
|
|
1189
|
+
/* Input */
|
|
1190
|
+
--foisit-input-color: white;
|
|
1191
|
+
--foisit-input-placeholder: rgba(235, 235, 245, 0.5);
|
|
1192
|
+
|
|
1193
|
+
/* Bubbles */
|
|
1194
|
+
--foisit-bubble-user-bg: rgba(255, 255, 255, 0.1);
|
|
1195
|
+
--foisit-bubble-user-text: white;
|
|
1196
|
+
|
|
1197
|
+
--foisit-bubble-sys-bg: rgba(255, 255, 255, 0.05);
|
|
1198
|
+
--foisit-bubble-sys-text: rgba(255, 255, 255, 0.9);
|
|
1199
|
+
|
|
1200
|
+
/* Form Colors */
|
|
1201
|
+
--foisit-req-star: #f87171;
|
|
1202
|
+
--foisit-error-text: #fca5a5;
|
|
1203
|
+
--foisit-error-border: #f87171;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
`;
|
|
1207
|
+
t.textContent += `
|
|
1118
1208
|
|
|
1119
1209
|
@keyframes foisitPulse {
|
|
1120
1210
|
0%, 100% { transform: scale(0.8); opacity: 0.5; }
|
|
@@ -1162,8 +1252,8 @@ class G {
|
|
|
1162
1252
|
border: var(--foisit-border);
|
|
1163
1253
|
box-shadow: var(--foisit-shadow);
|
|
1164
1254
|
|
|
1165
|
-
backdrop-filter:
|
|
1166
|
-
-webkit-backdrop-filter:
|
|
1255
|
+
backdrop-filter: var(--foisit-backdrop);
|
|
1256
|
+
-webkit-backdrop-filter: var(--foisit-backdrop);
|
|
1167
1257
|
|
|
1168
1258
|
border-radius: 18px;
|
|
1169
1259
|
display: none;
|
|
@@ -1372,8 +1462,8 @@ class G {
|
|
|
1372
1462
|
border: 1px solid rgba(255,255,255,0.2);
|
|
1373
1463
|
background: var(--foisit-bg);
|
|
1374
1464
|
color: var(--foisit-text);
|
|
1375
|
-
backdrop-filter:
|
|
1376
|
-
-webkit-backdrop-filter:
|
|
1465
|
+
backdrop-filter: var(--foisit-backdrop);
|
|
1466
|
+
-webkit-backdrop-filter: var(--foisit-backdrop);
|
|
1377
1467
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
1378
1468
|
cursor: pointer;
|
|
1379
1469
|
pointer-events: auto;
|
|
@@ -1475,14 +1565,18 @@ class G {
|
|
|
1475
1565
|
}
|
|
1476
1566
|
class Y {
|
|
1477
1567
|
constructor(t) {
|
|
1478
|
-
this.config = t, this.isActivated = !1, this.lastProcessedInput = "", this.processingLock = !1, this.defaultIntroMessage = "How can I help you?", this.commandHandler = new
|
|
1568
|
+
this.config = t, this.isActivated = !1, this.lastProcessedInput = "", this.processingLock = !1, this.defaultIntroMessage = "How can I help you?", this.commandHandler = new N({
|
|
1479
1569
|
enableSmartIntent: this.config.enableSmartIntent !== !1,
|
|
1480
1570
|
intentEndpoint: this.config.intentEndpoint
|
|
1481
|
-
}), this.fallbackHandler = new
|
|
1571
|
+
}), this.fallbackHandler = new z(), typeof window < "u" && typeof document < "u" ? (this.voiceProcessor = new W(), this.textToSpeech = new P(), this.stateManager = new _(), this.gestureHandler = new R(), this.overlayManager = new G({
|
|
1482
1572
|
floatingButton: this.config.floatingButton,
|
|
1483
1573
|
inputPlaceholder: this.config.inputPlaceholder,
|
|
1484
|
-
enableGestureActivation: this.config.enableGestureActivation
|
|
1485
|
-
|
|
1574
|
+
enableGestureActivation: this.config.enableGestureActivation,
|
|
1575
|
+
theme: this.config.theme,
|
|
1576
|
+
themeColors: this.config.themeColors
|
|
1577
|
+
}), this.overlayManager.setExternalCommandExecutor(async (e) => this.commandHandler.executeCommand(e)), this.config.commands.forEach(
|
|
1578
|
+
(e) => this.commandHandler.addCommand(e)
|
|
1579
|
+
), this.config.fallbackResponse && this.fallbackHandler.setFallbackMessage(this.config.fallbackResponse), this.overlayManager.registerCallbacks(
|
|
1486
1580
|
async (e) => {
|
|
1487
1581
|
if (typeof e == "string") {
|
|
1488
1582
|
this.overlayManager.addMessage(e, "user"), await this.handleCommand(e);
|
|
@@ -1491,24 +1585,32 @@ class Y {
|
|
|
1491
1585
|
if (e && typeof e == "object") {
|
|
1492
1586
|
const i = e, s = i.label ?? i.commandId ?? "Selection";
|
|
1493
1587
|
this.overlayManager.addMessage(String(s), "user"), this.overlayManager.showLoading();
|
|
1494
|
-
const n = await this.commandHandler.executeCommand(
|
|
1588
|
+
const n = await this.commandHandler.executeCommand(
|
|
1589
|
+
i
|
|
1590
|
+
);
|
|
1495
1591
|
this.overlayManager.hideLoading(), this.processResponse(n);
|
|
1496
1592
|
}
|
|
1497
1593
|
},
|
|
1498
1594
|
() => console.log("AssistantService: Overlay closed.")
|
|
1499
|
-
)) : (this.voiceProcessor = void 0, this.textToSpeech = void 0, this.stateManager = void 0, this.gestureHandler = void 0, this.overlayManager = void 0, this.config.commands.forEach(
|
|
1595
|
+
)) : (this.voiceProcessor = void 0, this.textToSpeech = void 0, this.stateManager = void 0, this.gestureHandler = void 0, this.overlayManager = void 0, this.config.commands.forEach(
|
|
1596
|
+
(e) => this.commandHandler.addCommand(e)
|
|
1597
|
+
), this.config.fallbackResponse && this.fallbackHandler.setFallbackMessage(this.config.fallbackResponse));
|
|
1500
1598
|
}
|
|
1501
1599
|
/** Start listening for activation and commands */
|
|
1502
1600
|
startListening() {
|
|
1503
1601
|
if (typeof window > "u" || !this.voiceProcessor) {
|
|
1504
|
-
console.log(
|
|
1602
|
+
console.log(
|
|
1603
|
+
"AssistantService: Voice is disabled or unavailable; startListening() is a no-op."
|
|
1604
|
+
);
|
|
1505
1605
|
return;
|
|
1506
1606
|
}
|
|
1507
1607
|
}
|
|
1508
1608
|
/** Stop listening */
|
|
1509
1609
|
stopListening() {
|
|
1510
1610
|
if (typeof window > "u" || !this.voiceProcessor) {
|
|
1511
|
-
console.log(
|
|
1611
|
+
console.log(
|
|
1612
|
+
"AssistantService: Voice unavailable; stopListening() is a no-op."
|
|
1613
|
+
), this.isActivated = !1;
|
|
1512
1614
|
return;
|
|
1513
1615
|
}
|
|
1514
1616
|
this.voiceProcessor.stopListening(), this.isActivated = !1;
|
|
@@ -1559,7 +1661,10 @@ class Y {
|
|
|
1559
1661
|
return;
|
|
1560
1662
|
}
|
|
1561
1663
|
if (e.type === "error") {
|
|
1562
|
-
this.fallbackHandler.handleFallback(t), this.overlayManager.addMessage(
|
|
1664
|
+
this.fallbackHandler.handleFallback(t), this.overlayManager.addMessage(
|
|
1665
|
+
this.fallbackHandler.getFallbackMessage(),
|
|
1666
|
+
"system"
|
|
1667
|
+
);
|
|
1563
1668
|
return;
|
|
1564
1669
|
}
|
|
1565
1670
|
if ((e.type === "ambiguous" || e.type === "confirm") && e.options) {
|
|
@@ -1585,7 +1690,9 @@ class Y {
|
|
|
1585
1690
|
this.overlayManager.showLoading();
|
|
1586
1691
|
let i;
|
|
1587
1692
|
try {
|
|
1588
|
-
i = await this.commandHandler.executeCommand(
|
|
1693
|
+
i = await this.commandHandler.executeCommand(
|
|
1694
|
+
e
|
|
1695
|
+
);
|
|
1589
1696
|
} finally {
|
|
1590
1697
|
this.overlayManager.hideLoading();
|
|
1591
1698
|
}
|
|
@@ -1639,7 +1746,9 @@ class Y {
|
|
|
1639
1746
|
this.overlayManager.addMessage(String(n), "user"), this.overlayManager.showLoading();
|
|
1640
1747
|
let a;
|
|
1641
1748
|
try {
|
|
1642
|
-
a = await this.commandHandler.executeCommand(
|
|
1749
|
+
a = await this.commandHandler.executeCommand(
|
|
1750
|
+
s
|
|
1751
|
+
);
|
|
1643
1752
|
} finally {
|
|
1644
1753
|
this.overlayManager.hideLoading();
|
|
1645
1754
|
}
|
|
@@ -1656,7 +1765,7 @@ const q = F(null);
|
|
|
1656
1765
|
let M = null;
|
|
1657
1766
|
const Z = ({ config: d, children: t }) => {
|
|
1658
1767
|
const [e, i] = I(null), [s, n] = I(!1);
|
|
1659
|
-
return
|
|
1768
|
+
return H(() => {
|
|
1660
1769
|
M ? console.warn(
|
|
1661
1770
|
"Multiple AssistantProvider instances detected. Reusing global AssistantService."
|
|
1662
1771
|
) : (console.log("Initializing global AssistantService..."), M = new Y(d));
|
|
@@ -1667,7 +1776,7 @@ const Z = ({ config: d, children: t }) => {
|
|
|
1667
1776
|
};
|
|
1668
1777
|
}, [d]), !s || !e ? /* @__PURE__ */ k("div", { children: "Loading Assistant..." }) : /* @__PURE__ */ k(q.Provider, { value: e, children: t });
|
|
1669
1778
|
}, K = () => {
|
|
1670
|
-
const d =
|
|
1779
|
+
const d = B(q);
|
|
1671
1780
|
if (console.log("assistant", d), !d)
|
|
1672
1781
|
throw new Error("useAssistant must be used within an AssistantProvider");
|
|
1673
1782
|
return d;
|
|
@@ -1681,7 +1790,7 @@ const Z = ({ config: d, children: t }) => {
|
|
|
1681
1790
|
}, className: "assistant-activator", children: d });
|
|
1682
1791
|
}, et = (d) => {
|
|
1683
1792
|
const [t, e] = I(d.getState());
|
|
1684
|
-
return
|
|
1793
|
+
return H(() => {
|
|
1685
1794
|
const i = (s) => {
|
|
1686
1795
|
e(s);
|
|
1687
1796
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foisit/react-wrapper",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"main": "./index.js",
|
|
5
5
|
"types": "./index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"accessibility",
|
|
30
30
|
"website-integration"
|
|
31
31
|
],
|
|
32
|
-
"homepage": "https://foisit-react
|
|
32
|
+
"homepage": "https://foisit-react.netlify.app/",
|
|
33
33
|
"funding": {
|
|
34
34
|
"type": "github",
|
|
35
35
|
"url": "https://github.com/sponsors/boluwatifee4"
|