@zantopia/zephyr-widget 0.4.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 +277 -0
- package/index.d.ts +108 -0
- package/package.json +42 -0
- package/react/index.d.ts +55 -0
- package/react/index.jsx +186 -0
- package/vue/ZephyrWidget.vue +140 -0
- package/zephyr-widget.js +719 -0
package/README.md
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# 🦊 @zephyr/widget — Embeddable AI Navigation Assistant
|
|
2
|
+
|
|
3
|
+
Composant réutilisable d'assistant IA pour guider les utilisateurs dans n'importe quelle application web.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Option 1 : Script tag (le plus simple)
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<script src="https://your-zephyr-server/api/sdk/zephyr-widget.js"></script>
|
|
11
|
+
<script>
|
|
12
|
+
ZephyrWidget.init({
|
|
13
|
+
server: 'https://your-zephyr-server',
|
|
14
|
+
persona: 'minimal',
|
|
15
|
+
theme: 'dark',
|
|
16
|
+
position: 'bottom-right',
|
|
17
|
+
accentColor: '#ff6b35',
|
|
18
|
+
language: 'fr',
|
|
19
|
+
});
|
|
20
|
+
</script>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Option 2 : npm
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @zephyr/widget
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Utilisation React
|
|
30
|
+
|
|
31
|
+
```jsx
|
|
32
|
+
import { ZephyrChat } from '@zephyr/widget/react';
|
|
33
|
+
|
|
34
|
+
function App() {
|
|
35
|
+
return (
|
|
36
|
+
<ZephyrChat
|
|
37
|
+
server="https://your-zephyr-server"
|
|
38
|
+
persona="spirit"
|
|
39
|
+
theme="auto"
|
|
40
|
+
position="bottom-right"
|
|
41
|
+
accentColor="#6c63ff"
|
|
42
|
+
language="fr"
|
|
43
|
+
onMessage={(msg) => console.log(msg)}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Inline (dans un conteneur)
|
|
50
|
+
|
|
51
|
+
```jsx
|
|
52
|
+
<ZephyrChat
|
|
53
|
+
server="https://your-zephyr-server"
|
|
54
|
+
inline
|
|
55
|
+
style={{ height: '500px', width: '100%' }}
|
|
56
|
+
/>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Hook impératif
|
|
60
|
+
|
|
61
|
+
```jsx
|
|
62
|
+
import { useZephyr } from '@zephyr/widget/react';
|
|
63
|
+
|
|
64
|
+
function HelpButton() {
|
|
65
|
+
const { open, send } = useZephyr();
|
|
66
|
+
return <button onClick={() => { open(); send("Comment faire X ?"); }}>Aide</button>;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Utilisation Vue 3
|
|
71
|
+
|
|
72
|
+
```vue
|
|
73
|
+
<script setup>
|
|
74
|
+
import ZephyrWidget from '@zephyr/widget/vue';
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<template>
|
|
78
|
+
<ZephyrWidget
|
|
79
|
+
server="https://your-zephyr-server"
|
|
80
|
+
persona="futuristic"
|
|
81
|
+
theme="dark"
|
|
82
|
+
position="bottom-left"
|
|
83
|
+
accent-color="#ff9f43"
|
|
84
|
+
@message="onMessage"
|
|
85
|
+
@toggle="onToggle"
|
|
86
|
+
/>
|
|
87
|
+
</template>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Inline + ref
|
|
91
|
+
|
|
92
|
+
```vue
|
|
93
|
+
<template>
|
|
94
|
+
<ZephyrWidget
|
|
95
|
+
ref="zephyr"
|
|
96
|
+
server="..."
|
|
97
|
+
:inline="true"
|
|
98
|
+
style="height: 500px"
|
|
99
|
+
/>
|
|
100
|
+
<button @click="$refs.zephyr.send('Bonjour')">Envoyer</button>
|
|
101
|
+
</template>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Utilisation Vanilla JS (Web Component / n'importe quel framework)
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
import { ZephyrWidget } from '@zephyr/widget';
|
|
108
|
+
|
|
109
|
+
const widget = ZephyrWidget.init({
|
|
110
|
+
server: 'https://your-zephyr-server',
|
|
111
|
+
persona: 'mascot',
|
|
112
|
+
theme: 'light',
|
|
113
|
+
position: 'bottom-right',
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// API impérative
|
|
117
|
+
widget.open();
|
|
118
|
+
widget.send("Où trouver les paramètres ?");
|
|
119
|
+
widget.setTheme('dark');
|
|
120
|
+
widget.setPersona('spirit');
|
|
121
|
+
widget.close();
|
|
122
|
+
widget.destroy();
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Props / Options
|
|
126
|
+
|
|
127
|
+
| Prop | Type | Default | Description |
|
|
128
|
+
|---|---|---|---|
|
|
129
|
+
| `server` | string | **required** | URL du serveur Zephyr |
|
|
130
|
+
| `apiKey` | string | `""` | Clé API pour l'authentification |
|
|
131
|
+
| `persona` | string | `"minimal"` | `"mascot"` \| `"spirit"` \| `"minimal"` \| `"futuristic"` \| URL image custom |
|
|
132
|
+
| `theme` | string | `"dark"` | `"dark"` \| `"light"` \| `"auto"` |
|
|
133
|
+
| `position` | string | `"bottom-right"` | `"bottom-right"` \| `"bottom-left"` \| `"top-right"` \| `"top-left"` |
|
|
134
|
+
| `size` | string | `"md"` | `"sm"` \| `"md"` \| `"lg"` |
|
|
135
|
+
| `language` | string | `"fr"` | `"fr"` \| `"en"` |
|
|
136
|
+
| `greeting` | string | `null` | Message d'accueil custom |
|
|
137
|
+
| `placeholder` | string | `null` | Placeholder du champ de saisie |
|
|
138
|
+
| `accentColor` | string | `"#ff6b35"` | Couleur d'accent (CSS) |
|
|
139
|
+
| `zIndex` | number | `99999` | Z-index CSS |
|
|
140
|
+
| `open` | boolean | `false` | Ouvrir au chargement |
|
|
141
|
+
| `showBadge` | boolean | `true` | Afficher le badge de notification |
|
|
142
|
+
| `features` | array | `["chat","guide","search"]` | Fonctionnalités activées |
|
|
143
|
+
| `appContext` | object | `null` | Contexte applicatif métier (voir ci-dessous) |
|
|
144
|
+
| `inline` | boolean | `false` | Mode inline (dans un conteneur) |
|
|
145
|
+
| `customCSS` | string | `""` | CSS additionnel |
|
|
146
|
+
|
|
147
|
+
## Personas
|
|
148
|
+
|
|
149
|
+
| ID | Style | Description |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| `mascot` | Mascotte complète | Renard Zephyr avec moustaches, détails complets |
|
|
152
|
+
| `spirit` | Esprit flottant | Version éthérée avec glow radial |
|
|
153
|
+
| `minimal` | Ultra minimal SaaS | Fond arrondi, traits simples |
|
|
154
|
+
| `futuristic` | Futuriste | Dégradé, yeux rectangulaires, point lumineux |
|
|
155
|
+
|
|
156
|
+
Vous pouvez aussi passer une **URL d'image** pour un persona custom :
|
|
157
|
+
```js
|
|
158
|
+
ZephyrWidget.init({
|
|
159
|
+
server: '...',
|
|
160
|
+
persona: 'https://example.com/my-custom-avatar.svg',
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Events / Callbacks
|
|
165
|
+
|
|
166
|
+
| Event | Payload | Description |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| `onReady` / `@ready` | `widget` | Widget initialisé |
|
|
169
|
+
| `onMessage` / `@message` | `{ role, text, expression }` | Nouveau message |
|
|
170
|
+
| `onError` / `@error` | `{ message }` | Erreur WebSocket/serveur |
|
|
171
|
+
| `onToggle` / `@toggle` | `boolean` | Panel ouvert/fermé |
|
|
172
|
+
|
|
173
|
+
## API Impérative
|
|
174
|
+
|
|
175
|
+
| Méthode | Description |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `widget.open()` | Ouvre le panel |
|
|
178
|
+
| `widget.close()` | Ferme le panel |
|
|
179
|
+
| `widget.toggle()` | Bascule |
|
|
180
|
+
| `widget.send(text)` | Envoie un message |
|
|
181
|
+
| `widget.setTheme(theme)` | Change le thème |
|
|
182
|
+
| `widget.setPersona(persona)` | Change le persona |
|
|
183
|
+
| `widget.setAccentColor(color)` | Change la couleur d'accent |
|
|
184
|
+
| `widget.setAppContext(ctx)` | Met Ă jour le contexte applicatif (utile pour les SPAs) |
|
|
185
|
+
| `widget.destroy()` | Détruit le widget |
|
|
186
|
+
|
|
187
|
+
## Application Context (`appContext`)
|
|
188
|
+
|
|
189
|
+
Le contexte applicatif permet au chatbot de répondre **instantanément** aux questions métier sans analyser le DOM à chaque fois.
|
|
190
|
+
|
|
191
|
+
Le développeur qui intègre Zephyr décrit son application : ses features, sa FAQ, sa terminologie et ses workflows.
|
|
192
|
+
|
|
193
|
+
### Structure
|
|
194
|
+
|
|
195
|
+
```js
|
|
196
|
+
ZephyrWidget.init({
|
|
197
|
+
server: 'https://...',
|
|
198
|
+
appContext: {
|
|
199
|
+
// Nom et description de l'application
|
|
200
|
+
name: 'MonApp',
|
|
201
|
+
description: 'Plateforme de gestion de projets collaboratifs',
|
|
202
|
+
|
|
203
|
+
// Fonctionnalités principales (utilisé pour guider la navigation)
|
|
204
|
+
features: [
|
|
205
|
+
{ name: 'Dashboard', path: '/dashboard', description: 'Vue d\'ensemble des projets actifs' },
|
|
206
|
+
{ name: 'Kanban', path: '/board', description: 'Tableau de suivi des tâches drag & drop' },
|
|
207
|
+
{ name: 'Paramètres', path: '/settings', description: 'Configuration du compte et des projets' },
|
|
208
|
+
],
|
|
209
|
+
|
|
210
|
+
// FAQ — réponses pré-écrites (le chatbot les renvoie directement)
|
|
211
|
+
faq: [
|
|
212
|
+
{ question: 'Comment créer un projet ?', answer: 'Menu + > Nouveau projet > Remplir le formulaire' },
|
|
213
|
+
{ question: 'Comment inviter un membre ?', answer: 'Paramètres du projet > Membres > Inviter' },
|
|
214
|
+
],
|
|
215
|
+
|
|
216
|
+
// Terminologie métier (le chatbot utilisera ces définitions)
|
|
217
|
+
terminology: {
|
|
218
|
+
sprint: 'période de travail de 2 semaines',
|
|
219
|
+
story: 'tâche utilisateur à compléter',
|
|
220
|
+
backlog: 'liste des tâches à planifier',
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
// Workflows typiques (parcours utilisateur)
|
|
224
|
+
workflows: [
|
|
225
|
+
'Créer un projet → Ajouter des membres → Créer des tâches → Suivre l\'avancement',
|
|
226
|
+
'Se connecter → Dashboard → Sélectionner un projet → Vue Kanban',
|
|
227
|
+
],
|
|
228
|
+
|
|
229
|
+
// Texte libre complémentaire
|
|
230
|
+
custom: 'L\'app supporte 3 rĂ´les : Admin, Manager et Contributeur.',
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Mise Ă jour dynamique (SPA)
|
|
236
|
+
|
|
237
|
+
Pour les Single Page Applications, mettez Ă jour le contexte lors des changements de route :
|
|
238
|
+
|
|
239
|
+
```js
|
|
240
|
+
const widget = ZephyrWidget.init({ server: '...', appContext: globalContext });
|
|
241
|
+
|
|
242
|
+
// Quand l'utilisateur change de page
|
|
243
|
+
router.afterEach((to) => {
|
|
244
|
+
widget.setAppContext({
|
|
245
|
+
...globalContext,
|
|
246
|
+
custom: `L'utilisateur est sur la page ${to.name}.`,
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### API REST
|
|
252
|
+
|
|
253
|
+
Le contexte applicatif peut aussi être défini via l'API REST :
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
# Définir le contexte
|
|
257
|
+
curl -X PUT https://your-server/api/guide/app-context \
|
|
258
|
+
-H 'Content-Type: application/json' \
|
|
259
|
+
-d '{ "session_id": "...", "app_context": { "name": "MonApp", ... } }'
|
|
260
|
+
|
|
261
|
+
# Récupérer le contexte
|
|
262
|
+
curl https://your-server/api/guide/app-context/{session_id}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Comment ça marche
|
|
266
|
+
|
|
267
|
+
1. Le widget envoie le `appContext` au serveur via WebSocket Ă la connexion
|
|
268
|
+
2. Le serveur le stocke dans la session utilisateur
|
|
269
|
+
3. À chaque question, le contexte applicatif est injecté **en priorité** dans le prompt LLM
|
|
270
|
+
4. Si la FAQ contient la réponse, le chatbot la renvoie directement
|
|
271
|
+
5. Sinon, il combine le contexte applicatif + l'analyse DOM pour une réponse complète
|
|
272
|
+
|
|
273
|
+
**Avantages :**
|
|
274
|
+
- ⚡ Réponses instantanées aux questions métier (pas d'analyse DOM nécessaire)
|
|
275
|
+
- 🎯 Réponses plus précises (le dev connaît mieux son app que le DOM)
|
|
276
|
+
- 💰 Moins de tokens LLM consommés
|
|
277
|
+
- 🗣️ Le chatbot parle le "langage" de l'app
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zephyr/widget — TypeScript type definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Application context provided by the integrator to enrich chatbot responses. */
|
|
6
|
+
export interface AppContext {
|
|
7
|
+
/** Application name */
|
|
8
|
+
name?: string;
|
|
9
|
+
/** Short description of what the application does */
|
|
10
|
+
description?: string;
|
|
11
|
+
/** Key features / pages of the application */
|
|
12
|
+
features?: Array<{
|
|
13
|
+
name: string;
|
|
14
|
+
path?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
}>;
|
|
17
|
+
/** Frequently asked questions with pre-written answers */
|
|
18
|
+
faq?: Array<{
|
|
19
|
+
question: string;
|
|
20
|
+
answer: string;
|
|
21
|
+
}>;
|
|
22
|
+
/** Domain-specific terminology definitions */
|
|
23
|
+
terminology?: Record<string, string>;
|
|
24
|
+
/** Typical user workflows described as steps */
|
|
25
|
+
workflows?: string[];
|
|
26
|
+
/** Any extra free-form context the integrator wants the chatbot to know */
|
|
27
|
+
custom?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ZephyrWidgetOptions {
|
|
31
|
+
/** URL of the Zephyr backend server */
|
|
32
|
+
server: string;
|
|
33
|
+
/** API key for authentication (optional) */
|
|
34
|
+
apiKey?: string;
|
|
35
|
+
/** Persona style: "mascot" | "spirit" | "minimal" | "futuristic" | custom image URL */
|
|
36
|
+
persona?: "mascot" | "spirit" | "minimal" | "futuristic" | string;
|
|
37
|
+
/** Color theme */
|
|
38
|
+
theme?: "dark" | "light" | "auto";
|
|
39
|
+
/** Widget position on screen */
|
|
40
|
+
position?: "bottom-right" | "bottom-left" | "top-right" | "top-left";
|
|
41
|
+
/** Avatar size */
|
|
42
|
+
size?: "sm" | "md" | "lg";
|
|
43
|
+
/** Language */
|
|
44
|
+
language?: "fr" | "en";
|
|
45
|
+
/** Custom greeting message */
|
|
46
|
+
greeting?: string | null;
|
|
47
|
+
/** Input placeholder */
|
|
48
|
+
placeholder?: string | null;
|
|
49
|
+
/** Primary accent color (CSS) */
|
|
50
|
+
accentColor?: string;
|
|
51
|
+
/** CSS z-index */
|
|
52
|
+
zIndex?: number;
|
|
53
|
+
/** Allow dragging the trigger button */
|
|
54
|
+
draggable?: boolean;
|
|
55
|
+
/** Start with panel open */
|
|
56
|
+
open?: boolean;
|
|
57
|
+
/** Show notification badge */
|
|
58
|
+
showBadge?: boolean;
|
|
59
|
+
/** Badge count */
|
|
60
|
+
badgeCount?: number;
|
|
61
|
+
/** Enabled features */
|
|
62
|
+
features?: Array<"chat" | "guide" | "search">;
|
|
63
|
+
/**
|
|
64
|
+
* Application context — describe your app so the chatbot can answer
|
|
65
|
+
* user questions faster without needing to analyse the DOM every time.
|
|
66
|
+
*/
|
|
67
|
+
appContext?: AppContext;
|
|
68
|
+
/** Mount inside a specific selector (inline mode — no floating trigger) */
|
|
69
|
+
containerSelector?: string | null;
|
|
70
|
+
/** Additional CSS to inject */
|
|
71
|
+
customCSS?: string;
|
|
72
|
+
|
|
73
|
+
// Callbacks
|
|
74
|
+
onReady?: (widget: ZephyrWidgetInstance) => void;
|
|
75
|
+
onMessage?: (msg: { role: string; text: string; expression: string }) => void;
|
|
76
|
+
onError?: (err: { message: string }) => void;
|
|
77
|
+
onToggle?: (isOpen: boolean) => void;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ZephyrWidgetInstance {
|
|
81
|
+
mount(container?: string | HTMLElement | null): void;
|
|
82
|
+
destroy(): void;
|
|
83
|
+
open(): void;
|
|
84
|
+
close(): void;
|
|
85
|
+
toggle(): void;
|
|
86
|
+
send(text: string): void;
|
|
87
|
+
setTheme(theme: "dark" | "light" | "auto"): void;
|
|
88
|
+
setPersona(persona: string): void;
|
|
89
|
+
setAccentColor(color: string): void;
|
|
90
|
+
/** Update the application context at runtime (useful for SPAs). */
|
|
91
|
+
setAppContext(ctx: AppContext): void;
|
|
92
|
+
on(event: "ready" | "message" | "error" | "toggle", handler: Function): void;
|
|
93
|
+
readonly messages: Array<{ role: string; text: string; expression: string }>;
|
|
94
|
+
readonly isOpen: boolean;
|
|
95
|
+
readonly sessionId: string | null;
|
|
96
|
+
readonly expression: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface ZephyrWidgetStatic {
|
|
100
|
+
init(options: ZephyrWidgetOptions): ZephyrWidgetInstance;
|
|
101
|
+
getInstance(): ZephyrWidgetInstance | null;
|
|
102
|
+
Widget: new (options: ZephyrWidgetOptions) => ZephyrWidgetInstance;
|
|
103
|
+
PERSONAS: Record<string, { svg: (accent: string) => string }>;
|
|
104
|
+
THEMES: Record<string, Record<string, string>>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
declare const ZephyrWidget: ZephyrWidgetStatic;
|
|
108
|
+
export default ZephyrWidget;
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zantopia/zephyr-widget",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Zephyr AI Navigation Assistant — Embeddable widget for any web project",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "zephyr-widget.js",
|
|
7
|
+
"module": "zephyr-widget.js",
|
|
8
|
+
"types": "index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./zephyr-widget.js",
|
|
12
|
+
"types": "./index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./react": {
|
|
15
|
+
"import": "./react/index.jsx",
|
|
16
|
+
"types": "./react/index.d.ts"
|
|
17
|
+
},
|
|
18
|
+
"./vue": {
|
|
19
|
+
"import": "./vue/ZephyrWidget.vue"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"zephyr-widget.js",
|
|
24
|
+
"index.d.ts",
|
|
25
|
+
"react/",
|
|
26
|
+
"vue/",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"react": ">=17.0.0",
|
|
32
|
+
"react-dom": ">=17.0.0",
|
|
33
|
+
"vue": ">=3.0.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependenciesMeta": {
|
|
36
|
+
"react": { "optional": true },
|
|
37
|
+
"react-dom": { "optional": true },
|
|
38
|
+
"vue": { "optional": true }
|
|
39
|
+
},
|
|
40
|
+
"keywords": ["zephyr", "ai", "widget", "assistant", "navigation", "ux"],
|
|
41
|
+
"license": "MIT"
|
|
42
|
+
}
|
package/react/index.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export interface ZephyrChatProps {
|
|
4
|
+
/** Zephyr backend server URL */
|
|
5
|
+
server: string;
|
|
6
|
+
/** API key */
|
|
7
|
+
apiKey?: string;
|
|
8
|
+
/** Persona: "mascot" | "spirit" | "minimal" | "futuristic" | custom URL */
|
|
9
|
+
persona?: "mascot" | "spirit" | "minimal" | "futuristic" | string;
|
|
10
|
+
/** Theme */
|
|
11
|
+
theme?: "dark" | "light" | "auto";
|
|
12
|
+
/** Position */
|
|
13
|
+
position?: "bottom-right" | "bottom-left" | "top-right" | "top-left";
|
|
14
|
+
/** Size */
|
|
15
|
+
size?: "sm" | "md" | "lg";
|
|
16
|
+
/** Language */
|
|
17
|
+
language?: "fr" | "en";
|
|
18
|
+
/** Custom greeting */
|
|
19
|
+
greeting?: string;
|
|
20
|
+
/** Input placeholder */
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
/** Accent color */
|
|
23
|
+
accentColor?: string;
|
|
24
|
+
/** z-index */
|
|
25
|
+
zIndex?: number;
|
|
26
|
+
/** Start open */
|
|
27
|
+
open?: boolean;
|
|
28
|
+
/** Show badge */
|
|
29
|
+
showBadge?: boolean;
|
|
30
|
+
/** Features */
|
|
31
|
+
features?: Array<"chat" | "guide" | "search">;
|
|
32
|
+
/** Inline mode (render inside container) */
|
|
33
|
+
inline?: boolean;
|
|
34
|
+
/** Custom CSS */
|
|
35
|
+
customCSS?: string;
|
|
36
|
+
/** CSS class */
|
|
37
|
+
className?: string;
|
|
38
|
+
/** Inline styles */
|
|
39
|
+
style?: React.CSSProperties;
|
|
40
|
+
|
|
41
|
+
// Callbacks
|
|
42
|
+
onReady?: (widget: any) => void;
|
|
43
|
+
onMessage?: (msg: { role: string; text: string; expression: string }) => void;
|
|
44
|
+
onError?: (err: { message: string }) => void;
|
|
45
|
+
onToggle?: (isOpen: boolean) => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export declare function ZephyrChat(props: ZephyrChatProps): React.ReactElement | null;
|
|
49
|
+
export declare function useZephyr(): {
|
|
50
|
+
send: (text: string) => void;
|
|
51
|
+
open: () => void;
|
|
52
|
+
close: () => void;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default ZephyrChat;
|
package/react/index.jsx
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🦊 @kitsune/widget/react — React component wrapper
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import { KitsuneChat } from '@kitsune/widget/react';
|
|
6
|
+
*
|
|
7
|
+
* function App() {
|
|
8
|
+
* return (
|
|
9
|
+
* <KitsuneChat
|
|
10
|
+
* server="https://your-kitsune-server"
|
|
11
|
+
* persona="minimal"
|
|
12
|
+
* theme="dark"
|
|
13
|
+
* position="bottom-right"
|
|
14
|
+
* accentColor="#ff6b35"
|
|
15
|
+
* onMessage={(msg) => console.log(msg)}
|
|
16
|
+
* />
|
|
17
|
+
* );
|
|
18
|
+
* }
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import React, { useEffect, useRef, useCallback } from "react";
|
|
22
|
+
|
|
23
|
+
// Widget is loaded as an IIFE — import the core
|
|
24
|
+
let WidgetModule = null;
|
|
25
|
+
try {
|
|
26
|
+
WidgetModule = require("../kitsune-widget.js");
|
|
27
|
+
} catch {
|
|
28
|
+
// Dynamic import fallback handled in useEffect
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Configuration props — matches KitsuneWidgetOptions
|
|
33
|
+
*/
|
|
34
|
+
const defaultProps = {
|
|
35
|
+
server: "",
|
|
36
|
+
persona: "minimal",
|
|
37
|
+
theme: "dark",
|
|
38
|
+
position: "bottom-right",
|
|
39
|
+
size: "md",
|
|
40
|
+
language: "fr",
|
|
41
|
+
accentColor: "#ff6b35",
|
|
42
|
+
zIndex: 99999,
|
|
43
|
+
open: false,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function KitsuneChat(props) {
|
|
47
|
+
const containerRef = useRef(null);
|
|
48
|
+
const instanceRef = useRef(null);
|
|
49
|
+
|
|
50
|
+
const {
|
|
51
|
+
server,
|
|
52
|
+
apiKey,
|
|
53
|
+
persona,
|
|
54
|
+
theme,
|
|
55
|
+
position,
|
|
56
|
+
size,
|
|
57
|
+
language,
|
|
58
|
+
greeting,
|
|
59
|
+
placeholder,
|
|
60
|
+
accentColor,
|
|
61
|
+
zIndex,
|
|
62
|
+
draggable,
|
|
63
|
+
open,
|
|
64
|
+
showBadge,
|
|
65
|
+
features,
|
|
66
|
+
customCSS,
|
|
67
|
+
inline,
|
|
68
|
+
onReady,
|
|
69
|
+
onMessage,
|
|
70
|
+
onError,
|
|
71
|
+
onToggle,
|
|
72
|
+
className,
|
|
73
|
+
style,
|
|
74
|
+
...rest
|
|
75
|
+
} = { ...defaultProps, ...props };
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
let widget = null;
|
|
79
|
+
|
|
80
|
+
const init = async () => {
|
|
81
|
+
let Mod = WidgetModule;
|
|
82
|
+
if (!Mod) {
|
|
83
|
+
// Fallback: load from CDN or relative path
|
|
84
|
+
try {
|
|
85
|
+
Mod = await import("../kitsune-widget.js");
|
|
86
|
+
} catch {
|
|
87
|
+
console.error("[KitsuneChat] Could not load kitsune-widget.js");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const Cls = Mod.Widget || Mod.default?.Widget;
|
|
93
|
+
if (!Cls) return;
|
|
94
|
+
|
|
95
|
+
widget = new Cls({
|
|
96
|
+
server,
|
|
97
|
+
apiKey,
|
|
98
|
+
persona,
|
|
99
|
+
theme,
|
|
100
|
+
position,
|
|
101
|
+
size,
|
|
102
|
+
language,
|
|
103
|
+
greeting,
|
|
104
|
+
placeholder,
|
|
105
|
+
accentColor,
|
|
106
|
+
zIndex,
|
|
107
|
+
draggable,
|
|
108
|
+
open,
|
|
109
|
+
showBadge,
|
|
110
|
+
features,
|
|
111
|
+
customCSS,
|
|
112
|
+
containerSelector: inline ? null : null,
|
|
113
|
+
onReady,
|
|
114
|
+
onMessage,
|
|
115
|
+
onError,
|
|
116
|
+
onToggle,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (inline && containerRef.current) {
|
|
120
|
+
widget.mount(containerRef.current);
|
|
121
|
+
} else {
|
|
122
|
+
widget.mount();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
instanceRef.current = widget;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
init();
|
|
129
|
+
|
|
130
|
+
return () => {
|
|
131
|
+
if (widget) widget.destroy();
|
|
132
|
+
};
|
|
133
|
+
}, [server]); // Re-init only on server change
|
|
134
|
+
|
|
135
|
+
// React to prop changes
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (!instanceRef.current) return;
|
|
138
|
+
instanceRef.current.setTheme(theme);
|
|
139
|
+
}, [theme]);
|
|
140
|
+
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (!instanceRef.current) return;
|
|
143
|
+
instanceRef.current.setPersona(persona);
|
|
144
|
+
}, [persona]);
|
|
145
|
+
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (!instanceRef.current) return;
|
|
148
|
+
instanceRef.current.setAccentColor(accentColor);
|
|
149
|
+
}, [accentColor]);
|
|
150
|
+
|
|
151
|
+
// Inline renders a container div; floating renders nothing
|
|
152
|
+
if (inline) {
|
|
153
|
+
return React.createElement("div", {
|
|
154
|
+
ref: containerRef,
|
|
155
|
+
className: `kitsune-react-container ${className || ""}`,
|
|
156
|
+
style: { width: "100%", height: "100%", ...style },
|
|
157
|
+
...rest,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Hook for imperative widget control
|
|
166
|
+
*/
|
|
167
|
+
export function useKitsune() {
|
|
168
|
+
const send = useCallback((text) => {
|
|
169
|
+
const w = typeof KitsuneWidget !== "undefined" ? KitsuneWidget.getInstance() : null;
|
|
170
|
+
if (w) w.send(text);
|
|
171
|
+
}, []);
|
|
172
|
+
|
|
173
|
+
const open = useCallback(() => {
|
|
174
|
+
const w = typeof KitsuneWidget !== "undefined" ? KitsuneWidget.getInstance() : null;
|
|
175
|
+
if (w) w.open();
|
|
176
|
+
}, []);
|
|
177
|
+
|
|
178
|
+
const close = useCallback(() => {
|
|
179
|
+
const w = typeof KitsuneWidget !== "undefined" ? KitsuneWidget.getInstance() : null;
|
|
180
|
+
if (w) w.close();
|
|
181
|
+
}, []);
|
|
182
|
+
|
|
183
|
+
return { send, open, close };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export default KitsuneChat;
|