@voidx-ai/react 0.1.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 ADDED
@@ -0,0 +1,263 @@
1
+ # @voidx/react
2
+
3
+ React/Next.js wrapper for [VoidX AI Addon](https://voidx.ai). Install one package, drop one component, and get a 3D AI agent with chat and voice on your site.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @voidx/react
9
+ ```
10
+
11
+ > Requires React 18 or later.
12
+
13
+ ## Quick Start
14
+
15
+ ```tsx
16
+ // app/layout.tsx (Next.js App Router)
17
+ import { VoidxAddon } from '@voidx/react';
18
+
19
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
20
+ return (
21
+ <html>
22
+ <body>
23
+ <VoidxAddon apiKey={process.env.NEXT_PUBLIC_VOIDX_API_KEY!} />
24
+ {children}
25
+ </body>
26
+ </html>
27
+ );
28
+ }
29
+ ```
30
+
31
+ This alone gives you a 3D AI agent with chat and voice.
32
+
33
+ ## Props
34
+
35
+ | Prop | Type | Required | Description |
36
+ |------|------|----------|-------------|
37
+ | `apiKey` | `string` | Yes | JWT API key from VoidX dashboard |
38
+ | `locale` | `'ko' \| 'en' \| 'ja' \| 'zh'` | No | Language (default: `'ko'`) |
39
+ | `features` | `VoidxFeatures` | No | Enable/disable features |
40
+ | `theme` | `VoidxTheme` | No | Color, font, border-radius customization |
41
+ | `position` | `'follow-cursor' \| 'bottom-right' \| 'bottom-left'` | No | Agent position |
42
+ | `actions` | `Record<string, (params) => Promise<any>>` | No | Custom actions the AI can invoke |
43
+ | `systemEvents` | `Record<string, SystemEventHandler>` | No | Window event listeners that trigger AI responses |
44
+ | `onReady` | `() => void` | No | Called when SDK is fully loaded |
45
+ | `onError` | `(error: Error) => void` | No | Called on SDK errors |
46
+ | `disabled` | `boolean` | No | Set `true` to skip SDK loading |
47
+ | `children` | `React.ReactNode` | No | Children can use `useVoidx()` hook |
48
+
49
+ ## Features
50
+
51
+ Control which features are enabled:
52
+
53
+ ```tsx
54
+ <VoidxAddon
55
+ apiKey="..."
56
+ features={{
57
+ chat: true, // Chat popup (default: true)
58
+ cursor: true, // 3D cursor agent (default: true)
59
+ voice: true, // Voice agent (default: true)
60
+ hoverDetection: true, // Hover detection (default: true)
61
+ agentMode: '3d', // '3d' or 'parallax' (default: '3d')
62
+ }}
63
+ />
64
+ ```
65
+
66
+ ## Actions
67
+
68
+ Actions are functions **you define** that the AI can call during a conversation. The AI decides when to call them based on what the user asks.
69
+
70
+ ```tsx
71
+ <VoidxAddon
72
+ apiKey="..."
73
+ actions={{
74
+ // AI calls this when user asks to search
75
+ search_products: async (params) => {
76
+ const res = await fetch(`/api/products?q=${params.query}`);
77
+ return res.json();
78
+ },
79
+
80
+ // AI calls this when user wants to add to cart
81
+ add_to_cart: async (params) => {
82
+ cartStore.addItem(params.productId);
83
+ return { success: true, message: 'Added to cart' };
84
+ },
85
+
86
+ // AI calls this when user wants to navigate
87
+ navigate_to: async (params) => {
88
+ router.push(params.path);
89
+ return { success: true };
90
+ },
91
+ }}
92
+ />
93
+ ```
94
+
95
+ **How it works:**
96
+
97
+ 1. You register action functions with names like `search_products`, `add_to_cart`
98
+ 2. The AI learns that these actions are available
99
+ 3. During conversation, the AI decides which action to call based on user intent
100
+ 4. Your function runs, and the result is sent back to the AI
101
+ 5. The AI uses the result to generate a natural response
102
+
103
+ You can put any JavaScript logic inside an action: API calls, state management, DOM manipulation, page navigation, etc.
104
+
105
+ ## System Events
106
+
107
+ Listen for custom window events (e.g., order completed) and trigger an AI response automatically:
108
+
109
+ ```tsx
110
+ <VoidxAddon
111
+ apiKey="..."
112
+ systemEvents={{
113
+ 'order-completed': {
114
+ handler: (detail, instance, locale) => ({
115
+ context: `Order ${detail.orderNumber} completed. Total: $${detail.total}`,
116
+ instruction: 'Thank the customer and mention shipping info.',
117
+ openChat: true, // Auto-open chat popup
118
+ }),
119
+ },
120
+ }}
121
+ />
122
+ ```
123
+
124
+ Then dispatch the event from anywhere in your app:
125
+
126
+ ```tsx
127
+ // e.g., after checkout
128
+ window.dispatchEvent(
129
+ new CustomEvent('order-completed', {
130
+ detail: { orderNumber: 'ORD-123', total: 39.99 },
131
+ }),
132
+ );
133
+ ```
134
+
135
+ The AI will automatically respond with a contextual message like "Thank you for your order!"
136
+
137
+ ## useVoidx() Hook
138
+
139
+ Access the SDK instance from any child component:
140
+
141
+ ```tsx
142
+ import { VoidxAddon, useVoidx } from '@voidx/react';
143
+
144
+ // Parent: wrap children with VoidxAddon
145
+ function Layout({ children }) {
146
+ return (
147
+ <VoidxAddon apiKey="...">
148
+ {children}
149
+ </VoidxAddon>
150
+ );
151
+ }
152
+
153
+ // Child: use the hook
154
+ function AskAIButton() {
155
+ const voidx = useVoidx(); // null until SDK is ready
156
+
157
+ return (
158
+ <button onClick={() => voidx?.sendMessage('Recommend something')}>
159
+ Ask AI
160
+ </button>
161
+ );
162
+ }
163
+ ```
164
+
165
+ ### Available methods on `voidx`:
166
+
167
+ | Method | Description |
168
+ |--------|-------------|
169
+ | `show()` | Open chat popup |
170
+ | `hide()` | Close chat popup |
171
+ | `sendMessage(text)` | Send a message as the user |
172
+ | `announceSystemEvent(context, options?)` | Trigger an AI response without user message |
173
+ | `setLocale(locale)` | Change language |
174
+ | `setTheme(theme)` | Update theme |
175
+ | `on(event, handler)` | Listen to SDK events |
176
+ | `off(event, handler)` | Remove event listener |
177
+ | `destroy()` | Remove SDK from page |
178
+
179
+ ## Theme
180
+
181
+ ```tsx
182
+ <VoidxAddon
183
+ apiKey="..."
184
+ theme={{
185
+ primaryColor: '#6366f1',
186
+ gradientFrom: '#8b5cf6',
187
+ gradientTo: '#6366f1',
188
+ fontFamily: 'Pretendard, sans-serif',
189
+ borderRadius: 16,
190
+ }}
191
+ />
192
+ ```
193
+
194
+ ## SDK Events
195
+
196
+ Listen to SDK lifecycle events via `useVoidx()`:
197
+
198
+ ```tsx
199
+ const voidx = useVoidx();
200
+
201
+ useEffect(() => {
202
+ if (!voidx) return;
203
+
204
+ const onMessage = (msg) => console.log('New message:', msg);
205
+ voidx.on('voidx:message', onMessage);
206
+
207
+ return () => voidx.off('voidx:message', onMessage);
208
+ }, [voidx]);
209
+ ```
210
+
211
+ Available events: `voidx:ready`, `voidx:error`, `voidx:message`, `voidx:chat:open`, `voidx:chat:close`, `voidx:voice:start`, `voidx:voice:end`, `voidx:voice:error`, `voidx:auth:expired`, `voidx:action:executed`, `voidx:action:error`, `voidx:destroy`
212
+
213
+ ## Full Example
214
+
215
+ ```tsx
216
+ // app/layout.tsx
217
+ import { VoidxAddon } from '@voidx/react';
218
+
219
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
220
+ return (
221
+ <html>
222
+ <body>
223
+ <VoidxAddon
224
+ apiKey={process.env.NEXT_PUBLIC_VOIDX_API_KEY!}
225
+ locale="ko"
226
+ features={{ chat: true, cursor: true, voice: true, agentMode: '3d' }}
227
+ theme={{ primaryColor: '#6366f1', borderRadius: 16 }}
228
+ actions={{
229
+ search: async ({ query }) => {
230
+ const res = await fetch(`/api/search?q=${query}`);
231
+ return res.json();
232
+ },
233
+ navigate: async ({ path }) => {
234
+ window.location.href = path;
235
+ return { success: true };
236
+ },
237
+ }}
238
+ systemEvents={{
239
+ 'checkout-done': {
240
+ handler: (detail, _instance, locale) => ({
241
+ context: locale === 'en'
242
+ ? `Order ${detail.id} placed, total $${detail.total}`
243
+ : `주문 ${detail.id} 완료, 합계 $${detail.total}`,
244
+ instruction: locale === 'en'
245
+ ? 'Thank the user warmly.'
246
+ : '따뜻하게 감사 인사를 전해주세요.',
247
+ openChat: true,
248
+ }),
249
+ },
250
+ }}
251
+ onReady={() => console.log('VoidX ready')}
252
+ >
253
+ {children}
254
+ </VoidxAddon>
255
+ </body>
256
+ </html>
257
+ );
258
+ }
259
+ ```
260
+
261
+ ## License
262
+
263
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,171 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/VoidxAddon.tsx
7
+ var VoidxContext = react.createContext(null);
8
+ function useVoidx() {
9
+ return react.useContext(VoidxContext);
10
+ }
11
+ function useScript(src, disabled) {
12
+ const [status, setStatus] = react.useState("idle");
13
+ react.useEffect(() => {
14
+ if (disabled) {
15
+ setStatus("idle");
16
+ return;
17
+ }
18
+ const existing = document.querySelector(
19
+ `script[src="${src}"]`
20
+ );
21
+ if (existing) {
22
+ if (window.VoidxAIAddon) {
23
+ setStatus("ready");
24
+ } else {
25
+ const onLoad = () => setStatus("ready");
26
+ const onError = () => setStatus("error");
27
+ existing.addEventListener("load", onLoad);
28
+ existing.addEventListener("error", onError);
29
+ setStatus("loading");
30
+ return () => {
31
+ existing.removeEventListener("load", onLoad);
32
+ existing.removeEventListener("error", onError);
33
+ };
34
+ }
35
+ return;
36
+ }
37
+ const script = document.createElement("script");
38
+ script.src = src;
39
+ script.async = true;
40
+ script.addEventListener("load", () => setStatus("ready"));
41
+ script.addEventListener("error", () => setStatus("error"));
42
+ setStatus("loading");
43
+ document.body.appendChild(script);
44
+ return () => {
45
+ if (document.body.contains(script)) {
46
+ document.body.removeChild(script);
47
+ }
48
+ };
49
+ }, [src, disabled]);
50
+ return status;
51
+ }
52
+
53
+ // src/utils.ts
54
+ var DEFAULT_CDN_URL = "https://addon-cdn.voidx.ai/latest/voidx-ai-addon.standalone.js";
55
+ function normalizeLang(value) {
56
+ switch (value) {
57
+ case "en":
58
+ return "en";
59
+ case "ja":
60
+ return "ja";
61
+ case "zh":
62
+ return "zh";
63
+ default:
64
+ return "ko";
65
+ }
66
+ }
67
+ function VoidxAddon({
68
+ apiKey,
69
+ features,
70
+ theme,
71
+ locale,
72
+ position,
73
+ baseUrl,
74
+ scriptUrl,
75
+ cdnUrl,
76
+ voiceApiUrl,
77
+ hoverTargets,
78
+ actions,
79
+ systemEvents,
80
+ onReady,
81
+ onError,
82
+ onMessage,
83
+ disabled = false,
84
+ children
85
+ }) {
86
+ const instanceRef = react.useRef(null);
87
+ const [instance, setInstance] = react.useState(null);
88
+ const localeRef = react.useRef(normalizeLang(locale));
89
+ const resolvedScriptUrl = scriptUrl ?? DEFAULT_CDN_URL;
90
+ const scriptStatus = useScript(resolvedScriptUrl, disabled);
91
+ react.useEffect(() => {
92
+ if (disabled || scriptStatus !== "ready" || !window.VoidxAIAddon) return;
93
+ const normalizedLocale = normalizeLang(locale);
94
+ localeRef.current = normalizedLocale;
95
+ const config = {
96
+ apiKey,
97
+ locale: normalizedLocale,
98
+ ...features && { features },
99
+ ...theme && { theme },
100
+ ...position && { position },
101
+ ...baseUrl && { baseUrl },
102
+ ...cdnUrl && { cdnUrl },
103
+ ...voiceApiUrl && { voiceApiUrl },
104
+ ...hoverTargets && { hoverTargets },
105
+ ...actions && { actions },
106
+ ...onReady && { onReady },
107
+ ...onError && { onError },
108
+ ...onMessage && { onMessage }
109
+ };
110
+ try {
111
+ const inst = window.VoidxAIAddon.init(config);
112
+ instanceRef.current = inst;
113
+ setInstance(inst);
114
+ } catch (error) {
115
+ const err = error instanceof Error ? error : new Error(String(error));
116
+ console.error("[VoidxAddon] init failed:", err);
117
+ onError?.(err);
118
+ }
119
+ return () => {
120
+ instanceRef.current?.destroy();
121
+ instanceRef.current = null;
122
+ setInstance(null);
123
+ };
124
+ }, [apiKey, scriptStatus, disabled]);
125
+ react.useEffect(() => {
126
+ const normalized = normalizeLang(locale);
127
+ localeRef.current = normalized;
128
+ instanceRef.current?.setLocale(normalized);
129
+ }, [locale]);
130
+ react.useEffect(() => {
131
+ if (theme && instanceRef.current) {
132
+ instanceRef.current.setTheme(theme);
133
+ }
134
+ }, [theme]);
135
+ const systemEventsRef = react.useRef(systemEvents);
136
+ systemEventsRef.current = systemEvents;
137
+ react.useEffect(() => {
138
+ const inst = instanceRef.current;
139
+ if (!inst || !systemEventsRef.current) return;
140
+ const entries = Object.entries(systemEventsRef.current);
141
+ if (entries.length === 0) return;
142
+ const cleanups = [];
143
+ for (const [eventName, { handler }] of entries) {
144
+ const listener = (event) => {
145
+ const detail = event.detail;
146
+ const result = handler(detail, inst, localeRef.current);
147
+ if (result) {
148
+ void inst.announceSystemEvent(result.context, {
149
+ instruction: result.instruction,
150
+ openChat: result.openChat
151
+ });
152
+ }
153
+ };
154
+ window.addEventListener(eventName, listener);
155
+ cleanups.push(() => window.removeEventListener(eventName, listener));
156
+ }
157
+ return () => {
158
+ for (const cleanup of cleanups) cleanup();
159
+ };
160
+ }, [instance]);
161
+ if (disabled) return null;
162
+ return /* @__PURE__ */ jsxRuntime.jsxs(VoidxContext.Provider, { value: instance, children: [
163
+ /* @__PURE__ */ jsxRuntime.jsx("div", { id: "voidx-ai-addon-root" }),
164
+ children
165
+ ] });
166
+ }
167
+
168
+ exports.VoidxAddon = VoidxAddon;
169
+ exports.useVoidx = useVoidx;
170
+ //# sourceMappingURL=index.cjs.map
171
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context.ts","../src/use-script.ts","../src/utils.ts","../src/VoidxAddon.tsx"],"names":["createContext","useContext","useState","useEffect","useRef","jsxs","jsx"],"mappings":";;;;;;AAGO,IAAM,YAAA,GAAeA,oBAAoC,IAAI,CAAA;AAkB7D,SAAS,QAAA,GAAiC;AAC/C,EAAA,OAAOC,iBAAW,YAAY,CAAA;AAChC;ACfO,SAAS,SAAA,CAAU,KAAa,QAAA,EAAiC;AACtE,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAIC,eAAuB,MAAM,CAAA;AAEzD,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,SAAA,CAAU,MAAM,CAAA;AAChB,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,WAAW,QAAA,CAAS,aAAA;AAAA,MACxB,eAAe,GAAG,CAAA,EAAA;AAAA,KACpB;AAEA,IAAA,IAAI,QAAA,EAAU;AAEZ,MAAA,IAAI,OAAO,YAAA,EAAc;AACvB,QAAA,SAAA,CAAU,OAAO,CAAA;AAAA,MACnB,CAAA,MAAO;AAEL,QAAA,MAAM,MAAA,GAAS,MAAM,SAAA,CAAU,OAAO,CAAA;AACtC,QAAA,MAAM,OAAA,GAAU,MAAM,SAAA,CAAU,OAAO,CAAA;AACvC,QAAA,QAAA,CAAS,gBAAA,CAAiB,QAAQ,MAAM,CAAA;AACxC,QAAA,QAAA,CAAS,gBAAA,CAAiB,SAAS,OAAO,CAAA;AAC1C,QAAA,SAAA,CAAU,SAAS,CAAA;AACnB,QAAA,OAAO,MAAM;AACX,UAAA,QAAA,CAAS,mBAAA,CAAoB,QAAQ,MAAM,CAAA;AAC3C,UAAA,QAAA,CAAS,mBAAA,CAAoB,SAAS,OAAO,CAAA;AAAA,QAC/C,CAAA;AAAA,MACF;AACA,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,QAAQ,CAAA;AAC9C,IAAA,MAAA,CAAO,GAAA,GAAM,GAAA;AACb,IAAA,MAAA,CAAO,KAAA,GAAQ,IAAA;AAEf,IAAA,MAAA,CAAO,gBAAA,CAAiB,MAAA,EAAQ,MAAM,SAAA,CAAU,OAAO,CAAC,CAAA;AACxD,IAAA,MAAA,CAAO,gBAAA,CAAiB,OAAA,EAAS,MAAM,SAAA,CAAU,OAAO,CAAC,CAAA;AAEzD,IAAA,SAAA,CAAU,SAAS,CAAA;AACnB,IAAA,QAAA,CAAS,IAAA,CAAK,YAAY,MAAM,CAAA;AAEhC,IAAA,OAAO,MAAM;AACX,MAAA,IAAI,QAAA,CAAS,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,EAAG;AAClC,QAAA,QAAA,CAAS,IAAA,CAAK,YAAY,MAAM,CAAA;AAAA,MAClC;AAAA,IACF,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,GAAA,EAAK,QAAQ,CAAC,CAAA;AAElB,EAAA,OAAO,MAAA;AACT;;;ACzDO,IAAM,eAAA,GACX,gEAAA;AAEK,SAAS,cAAc,KAAA,EAAwC;AACpE,EAAA,QAAQ,KAAA;AAAO,IACb,KAAK,IAAA;AACH,MAAA,OAAO,IAAA;AAAA,IACT,KAAK,IAAA;AACH,MAAA,OAAO,IAAA;AAAA,IACT,KAAK,IAAA;AACH,MAAA,OAAO,IAAA;AAAA,IACT;AACE,MAAA,OAAO,IAAA;AAAA;AAEb;ACCO,SAAS,UAAA,CAAW;AAAA,EACzB,MAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,OAAA;AAAA,EACA,YAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA,GAAW,KAAA;AAAA,EACX;AACF,CAAA,EAAoB;AAClB,EAAA,MAAM,WAAA,GAAcC,aAA6B,IAAI,CAAA;AACrD,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIF,eAA+B,IAAI,CAAA;AACnE,EAAA,MAAM,SAAA,GAAYE,YAAA,CAAoB,aAAA,CAAc,MAAM,CAAC,CAAA;AAE3D,EAAA,MAAM,oBAAoB,SAAA,IAAa,eAAA;AACvC,EAAA,MAAM,YAAA,GAAe,SAAA,CAAU,iBAAA,EAAmB,QAAQ,CAAA;AAG1D,EAAAD,gBAAU,MAAM;AACd,IAAA,IAAI,QAAA,IAAY,YAAA,KAAiB,OAAA,IAAW,CAAC,OAAO,YAAA,EAAc;AAElE,IAAA,MAAM,gBAAA,GAAmB,cAAc,MAAM,CAAA;AAC7C,IAAA,SAAA,CAAU,OAAA,GAAU,gBAAA;AAEpB,IAAA,MAAM,MAAA,GAA0B;AAAA,MAC9B,MAAA;AAAA,MACA,MAAA,EAAQ,gBAAA;AAAA,MACR,GAAI,QAAA,IAAY,EAAE,QAAA,EAAS;AAAA,MAC3B,GAAI,KAAA,IAAS,EAAE,KAAA,EAAM;AAAA,MACrB,GAAI,QAAA,IAAY,EAAE,QAAA,EAAS;AAAA,MAC3B,GAAI,OAAA,IAAW,EAAE,OAAA,EAAQ;AAAA,MACzB,GAAI,MAAA,IAAU,EAAE,MAAA,EAAO;AAAA,MACvB,GAAI,WAAA,IAAe,EAAE,WAAA,EAAY;AAAA,MACjC,GAAI,YAAA,IAAgB,EAAE,YAAA,EAAa;AAAA,MACnC,GAAI,OAAA,IAAW,EAAE,OAAA,EAAQ;AAAA,MACzB,GAAI,OAAA,IAAW,EAAE,OAAA,EAAQ;AAAA,MACzB,GAAI,OAAA,IAAW,EAAE,OAAA,EAAQ;AAAA,MACzB,GAAI,SAAA,IAAa,EAAE,SAAA;AAAU,KAC/B;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,MAAA,CAAO,YAAA,CAAc,IAAA,CAAK,MAAM,CAAA;AAC7C,MAAA,WAAA,CAAY,OAAA,GAAU,IAAA;AACtB,MAAA,WAAA,CAAY,IAAI,CAAA;AAAA,IAClB,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,GAAA,GAAM,iBAAiB,KAAA,GAAQ,KAAA,GAAQ,IAAI,KAAA,CAAM,MAAA,CAAO,KAAK,CAAC,CAAA;AACpE,MAAA,OAAA,CAAQ,KAAA,CAAM,6BAA6B,GAAG,CAAA;AAC9C,MAAA,OAAA,GAAU,GAAG,CAAA;AAAA,IACf;AAEA,IAAA,OAAO,MAAM;AACX,MAAA,WAAA,CAAY,SAAS,OAAA,EAAQ;AAC7B,MAAA,WAAA,CAAY,OAAA,GAAU,IAAA;AACtB,MAAA,WAAA,CAAY,IAAI,CAAA;AAAA,IAClB,CAAA;AAAA,EAIF,CAAA,EAAG,CAAC,MAAA,EAAQ,YAAA,EAAc,QAAQ,CAAC,CAAA;AAGnC,EAAAA,gBAAU,MAAM;AACd,IAAA,MAAM,UAAA,GAAa,cAAc,MAAM,CAAA;AACvC,IAAA,SAAA,CAAU,OAAA,GAAU,UAAA;AACpB,IAAA,WAAA,CAAY,OAAA,EAAS,UAAU,UAAU,CAAA;AAAA,EAC3C,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAGX,EAAAA,gBAAU,MAAM;AACd,IAAA,IAAI,KAAA,IAAS,YAAY,OAAA,EAAS;AAChC,MAAA,WAAA,CAAY,OAAA,CAAQ,SAAS,KAAK,CAAA;AAAA,IACpC;AAAA,EACF,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAGV,EAAA,MAAM,eAAA,GAAkBC,aAAO,YAAY,CAAA;AAC3C,EAAA,eAAA,CAAgB,OAAA,GAAU,YAAA;AAE1B,EAAAD,gBAAU,MAAM;AACd,IAAA,MAAM,OAAO,WAAA,CAAY,OAAA;AACzB,IAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,eAAA,CAAgB,OAAA,EAAS;AAEvC,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,eAAA,CAAgB,OAAO,CAAA;AACtD,IAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AAE1B,IAAA,MAAM,WAA2B,EAAC;AAElC,IAAA,KAAA,MAAW,CAAC,SAAA,EAAW,EAAE,OAAA,EAAS,KAAK,OAAA,EAAS;AAC9C,MAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAAiB;AACjC,QAAA,MAAM,SAAU,KAAA,CAAsB,MAAA;AACtC,QAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,EAAQ,IAAA,EAAM,UAAU,OAAO,CAAA;AACtD,QAAA,IAAI,MAAA,EAAQ;AACV,UAAA,KAAK,IAAA,CAAK,mBAAA,CAAoB,MAAA,CAAO,OAAA,EAAS;AAAA,YAC5C,aAAa,MAAA,CAAO,WAAA;AAAA,YACpB,UAAU,MAAA,CAAO;AAAA,WAClB,CAAA;AAAA,QACH;AAAA,MACF,CAAA;AAEA,MAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,QAAQ,CAAA;AAC3C,MAAA,QAAA,CAAS,KAAK,MAAM,MAAA,CAAO,mBAAA,CAAoB,SAAA,EAAW,QAAQ,CAAC,CAAA;AAAA,IACrE;AAEA,IAAA,OAAO,MAAM;AACX,MAAA,KAAA,MAAW,OAAA,IAAW,UAAU,OAAA,EAAQ;AAAA,IAC1C,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,IAAI,UAAU,OAAO,IAAA;AAErB,EAAA,uBACEE,eAAA,CAAC,YAAA,CAAa,QAAA,EAAb,EAAsB,OAAO,QAAA,EAC5B,QAAA,EAAA;AAAA,oBAAAC,cAAA,CAAC,KAAA,EAAA,EAAI,IAAG,qBAAA,EAAsB,CAAA;AAAA,IAC7B;AAAA,GAAA,EACH,CAAA;AAEJ","file":"index.cjs","sourcesContent":["import { createContext, useContext } from 'react';\nimport type { VoidxInstance } from './types';\n\nexport const VoidxContext = createContext<VoidxInstance | null>(null);\n\n/**\n * VoidxAddon 인스턴스에 접근하는 훅.\n * SDK 로딩 전이면 null을 반환한다.\n *\n * @example\n * ```tsx\n * function AskAIButton() {\n * const voidx = useVoidx();\n * return (\n * <button onClick={() => voidx?.sendMessage('추천해줘')}>\n * AI에게 물어보기\n * </button>\n * );\n * }\n * ```\n */\nexport function useVoidx(): VoidxInstance | null {\n return useContext(VoidxContext);\n}\n","import { useEffect, useState } from 'react';\n\ntype ScriptStatus = 'idle' | 'loading' | 'ready' | 'error';\n\n/**\n * CDN 스크립트를 동적으로 로딩하는 훅.\n * 이미 DOM에 동일 src가 있으면 중복 삽입하지 않는다.\n */\nexport function useScript(src: string, disabled: boolean): ScriptStatus {\n const [status, setStatus] = useState<ScriptStatus>('idle');\n\n useEffect(() => {\n if (disabled) {\n setStatus('idle');\n return;\n }\n\n // 이미 로드된 스크립트가 있으면 재사용\n const existing = document.querySelector(\n `script[src=\"${src}\"]`,\n ) as HTMLScriptElement | null;\n\n if (existing) {\n // window.VoidxAIAddon이 이미 있으면 ready\n if (window.VoidxAIAddon) {\n setStatus('ready');\n } else {\n // 아직 로딩 중인 스크립트 — 이벤트 대기\n const onLoad = () => setStatus('ready');\n const onError = () => setStatus('error');\n existing.addEventListener('load', onLoad);\n existing.addEventListener('error', onError);\n setStatus('loading');\n return () => {\n existing.removeEventListener('load', onLoad);\n existing.removeEventListener('error', onError);\n };\n }\n return;\n }\n\n const script = document.createElement('script');\n script.src = src;\n script.async = true;\n\n script.addEventListener('load', () => setStatus('ready'));\n script.addEventListener('error', () => setStatus('error'));\n\n setStatus('loading');\n document.body.appendChild(script);\n\n return () => {\n if (document.body.contains(script)) {\n document.body.removeChild(script);\n }\n };\n }, [src, disabled]);\n\n return status;\n}\n","import type { VoidxLocale } from './types';\n\nexport const DEFAULT_CDN_URL =\n 'https://addon-cdn.voidx.ai/latest/voidx-ai-addon.standalone.js';\n\nexport function normalizeLang(value: string | undefined): VoidxLocale {\n switch (value) {\n case 'en':\n return 'en';\n case 'ja':\n return 'ja';\n case 'zh':\n return 'zh';\n default:\n return 'ko';\n }\n}\n","import { useEffect, useRef, useState } from 'react';\nimport { VoidxContext } from './context';\nimport type {\n VoidxAddonProps,\n VoidxInitConfig,\n VoidxInstance,\n VoidxLocale,\n} from './types';\nimport { useScript } from './use-script';\nimport { DEFAULT_CDN_URL, normalizeLang } from './utils';\n\n/**\n * VoidX AI Addon React 래퍼 컴포넌트.\n *\n * CDN standalone 스크립트를 동적 로딩하고, SDK를 init/destroy 한다.\n * children에서 useVoidx() 훅으로 인스턴스에 접근할 수 있다.\n */\nexport function VoidxAddon({\n apiKey,\n features,\n theme,\n locale,\n position,\n baseUrl,\n scriptUrl,\n cdnUrl,\n voiceApiUrl,\n hoverTargets,\n actions,\n systemEvents,\n onReady,\n onError,\n onMessage,\n disabled = false,\n children,\n}: VoidxAddonProps) {\n const instanceRef = useRef<VoidxInstance | null>(null);\n const [instance, setInstance] = useState<VoidxInstance | null>(null);\n const localeRef = useRef<VoidxLocale>(normalizeLang(locale));\n\n const resolvedScriptUrl = scriptUrl ?? DEFAULT_CDN_URL;\n const scriptStatus = useScript(resolvedScriptUrl, disabled);\n\n // ── init / destroy ──\n useEffect(() => {\n if (disabled || scriptStatus !== 'ready' || !window.VoidxAIAddon) return;\n\n const normalizedLocale = normalizeLang(locale);\n localeRef.current = normalizedLocale;\n\n const config: VoidxInitConfig = {\n apiKey,\n locale: normalizedLocale,\n ...(features && { features }),\n ...(theme && { theme }),\n ...(position && { position }),\n ...(baseUrl && { baseUrl }),\n ...(cdnUrl && { cdnUrl }),\n ...(voiceApiUrl && { voiceApiUrl }),\n ...(hoverTargets && { hoverTargets }),\n ...(actions && { actions }),\n ...(onReady && { onReady }),\n ...(onError && { onError }),\n ...(onMessage && { onMessage }),\n };\n\n try {\n const inst = window.VoidxAIAddon!.init(config);\n instanceRef.current = inst;\n setInstance(inst);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n console.error('[VoidxAddon] init failed:', err);\n onError?.(err);\n }\n\n return () => {\n instanceRef.current?.destroy();\n instanceRef.current = null;\n setInstance(null);\n };\n // apiKey 변경 시 재초기화, actions/systemEvents는 ref로 관리하지 않고\n // init 시점에만 전달 (SDK가 런타임 교체를 지원하지 않음)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [apiKey, scriptStatus, disabled]);\n\n // ── locale sync ──\n useEffect(() => {\n const normalized = normalizeLang(locale);\n localeRef.current = normalized;\n instanceRef.current?.setLocale(normalized);\n }, [locale]);\n\n // ── theme sync ──\n useEffect(() => {\n if (theme && instanceRef.current) {\n instanceRef.current.setTheme(theme);\n }\n }, [theme]);\n\n // ── system events ──\n const systemEventsRef = useRef(systemEvents);\n systemEventsRef.current = systemEvents;\n\n useEffect(() => {\n const inst = instanceRef.current;\n if (!inst || !systemEventsRef.current) return;\n\n const entries = Object.entries(systemEventsRef.current);\n if (entries.length === 0) return;\n\n const cleanups: (() => void)[] = [];\n\n for (const [eventName, { handler }] of entries) {\n const listener = (event: Event) => {\n const detail = (event as CustomEvent).detail;\n const result = handler(detail, inst, localeRef.current);\n if (result) {\n void inst.announceSystemEvent(result.context, {\n instruction: result.instruction,\n openChat: result.openChat,\n });\n }\n };\n\n window.addEventListener(eventName, listener);\n cleanups.push(() => window.removeEventListener(eventName, listener));\n }\n\n return () => {\n for (const cleanup of cleanups) cleanup();\n };\n }, [instance]); // instance가 설정된 후에만 이벤트 등록\n\n if (disabled) return null;\n\n return (\n <VoidxContext.Provider value={instance}>\n <div id=\"voidx-ai-addon-root\" />\n {children}\n </VoidxContext.Provider>\n );\n}\n"]}
@@ -0,0 +1,182 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * VoidX SDK 타입 정의
5
+ *
6
+ * voidx-ai-addon SDK의 src/types/sdk.ts를 미러링.
7
+ * SDK를 런타임에 import하면 React 버전 충돌이 발생하므로 자체 정의한다.
8
+ * 향후 SDK가 타입 전용 패키지를 배포하면 import type으로 전환 가능.
9
+ */
10
+ type VoidxLocale = 'ko' | 'en' | 'ja' | 'zh';
11
+ interface VoidxFeatures {
12
+ /** 챗봇 팝업 (기본: true) */
13
+ chat?: boolean;
14
+ /** 커서 에이전트 (기본: true) */
15
+ cursor?: boolean;
16
+ /** 음성 에이전트 (기본: true) */
17
+ voice?: boolean;
18
+ /** 호버 감지 (기본: true) */
19
+ hoverDetection?: boolean;
20
+ /** 에이전트 렌더링 모드 (기본: '3d') */
21
+ agentMode?: '3d' | 'parallax';
22
+ }
23
+ interface VoidxTheme {
24
+ primaryColor?: string;
25
+ gradientFrom?: string;
26
+ gradientTo?: string;
27
+ fontFamily?: string;
28
+ borderRadius?: number;
29
+ }
30
+ type HostActionHandler = (params: any) => Promise<any>;
31
+ type HostActions = Record<string, HostActionHandler>;
32
+ interface AnnounceSystemEventOptions {
33
+ /** 에이전트 응답 가이드 메시지 */
34
+ instruction?: string;
35
+ /** 챗봇 팝업 자동 열기 (기본: false) */
36
+ openChat?: boolean;
37
+ }
38
+ interface VoidxEventMap {
39
+ 'voidx:ready': void;
40
+ 'voidx:error': Error;
41
+ 'voidx:message': {
42
+ id: string;
43
+ role: string;
44
+ content: string;
45
+ type: string;
46
+ };
47
+ 'voidx:auth:expired': {
48
+ status: number;
49
+ };
50
+ 'voidx:chat:open': void;
51
+ 'voidx:chat:close': void;
52
+ 'voidx:voice:start': void;
53
+ 'voidx:voice:end': void;
54
+ 'voidx:voice:error': {
55
+ message: string;
56
+ };
57
+ 'voidx:hover:detected': {
58
+ element: Element;
59
+ };
60
+ 'voidx:action:executed': {
61
+ name: string;
62
+ params: any;
63
+ result: any;
64
+ };
65
+ 'voidx:action:error': {
66
+ name: string;
67
+ params: any;
68
+ error: string;
69
+ };
70
+ 'voidx:destroy': void;
71
+ }
72
+ interface VoidxInstance {
73
+ destroy: () => void;
74
+ show: () => void;
75
+ hide: () => void;
76
+ sendMessage: (message: string) => void;
77
+ announceSystemEvent: (context: string, options?: AnnounceSystemEventOptions) => Promise<void>;
78
+ setTheme: (theme: Partial<VoidxTheme>) => void;
79
+ setLocale: (locale: VoidxLocale) => void;
80
+ on: <K extends keyof VoidxEventMap>(event: K, handler: (data: VoidxEventMap[K]) => void) => void;
81
+ off: <K extends keyof VoidxEventMap>(event: K, handler: (data: VoidxEventMap[K]) => void) => void;
82
+ }
83
+ interface VoidxInitConfig {
84
+ apiKey: string;
85
+ baseUrl?: string;
86
+ cdnUrl?: string;
87
+ voiceApiUrl?: string;
88
+ features?: VoidxFeatures;
89
+ theme?: VoidxTheme;
90
+ locale?: VoidxLocale;
91
+ position?: 'follow-cursor' | 'bottom-right' | 'bottom-left';
92
+ hoverTargets?: {
93
+ classNames?: string[];
94
+ ids?: string[];
95
+ idPatterns?: RegExp[];
96
+ classNamePatterns?: RegExp[];
97
+ };
98
+ actions?: HostActions;
99
+ onError?: (error: Error) => void;
100
+ onMessage?: (message: VoidxEventMap['voidx:message']) => void;
101
+ onReady?: () => void;
102
+ }
103
+ interface VoidxAIAddonGlobal {
104
+ init: (config: VoidxInitConfig) => VoidxInstance;
105
+ }
106
+ declare global {
107
+ interface Window {
108
+ VoidxAIAddon?: VoidxAIAddonGlobal;
109
+ }
110
+ }
111
+ interface SystemEventResult {
112
+ context: string;
113
+ instruction?: string;
114
+ openChat?: boolean;
115
+ }
116
+ interface SystemEventHandler<D = any> {
117
+ handler: (detail: D, instance: VoidxInstance, locale: VoidxLocale) => SystemEventResult | void | null;
118
+ }
119
+ interface VoidxAddonProps {
120
+ /** JWT API key — 필수 */
121
+ apiKey: string;
122
+ /** 기능 설정 */
123
+ features?: VoidxFeatures;
124
+ /** 테마 커스터마이징 */
125
+ theme?: VoidxTheme;
126
+ /** 언어 (반응형 — 변경 시 setLocale 자동 호출) */
127
+ locale?: VoidxLocale;
128
+ /** 팝업 위치 */
129
+ position?: 'follow-cursor' | 'bottom-right' | 'bottom-left';
130
+ /** API base URL 오버라이드 */
131
+ baseUrl?: string;
132
+ /** standalone.js 스크립트 URL 오버라이드 (기본: addon-cdn.voidx.ai) */
133
+ scriptUrl?: string;
134
+ /** SDK 내부 CDN URL 오버라이드 (3D 모델, 이미지 등) */
135
+ cdnUrl?: string;
136
+ /** Voice API URL 오버라이드 */
137
+ voiceApiUrl?: string;
138
+ /** 호버 감지 대상 */
139
+ hoverTargets?: VoidxInitConfig['hoverTargets'];
140
+ /** 호스트 액션 */
141
+ actions?: HostActions;
142
+ /** 시스템 이벤트 → announceSystemEvent 브리지 */
143
+ systemEvents?: Record<string, SystemEventHandler>;
144
+ /** SDK 준비 완료 콜백 */
145
+ onReady?: () => void;
146
+ /** 에러 콜백 */
147
+ onError?: (error: Error) => void;
148
+ /** 메시지 수신 콜백 */
149
+ onMessage?: (message: VoidxEventMap['voidx:message']) => void;
150
+ /** 비활성화 (true면 SDK 로딩 안 함) */
151
+ disabled?: boolean;
152
+ /** children (useVoidx() 훅으로 인스턴스 접근 가능) */
153
+ children?: React.ReactNode;
154
+ }
155
+
156
+ /**
157
+ * VoidX AI Addon React 래퍼 컴포넌트.
158
+ *
159
+ * CDN standalone 스크립트를 동적 로딩하고, SDK를 init/destroy 한다.
160
+ * children에서 useVoidx() 훅으로 인스턴스에 접근할 수 있다.
161
+ */
162
+ declare function VoidxAddon({ apiKey, features, theme, locale, position, baseUrl, scriptUrl, cdnUrl, voiceApiUrl, hoverTargets, actions, systemEvents, onReady, onError, onMessage, disabled, children, }: VoidxAddonProps): react_jsx_runtime.JSX.Element | null;
163
+
164
+ /**
165
+ * VoidxAddon 인스턴스에 접근하는 훅.
166
+ * SDK 로딩 전이면 null을 반환한다.
167
+ *
168
+ * @example
169
+ * ```tsx
170
+ * function AskAIButton() {
171
+ * const voidx = useVoidx();
172
+ * return (
173
+ * <button onClick={() => voidx?.sendMessage('추천해줘')}>
174
+ * AI에게 물어보기
175
+ * </button>
176
+ * );
177
+ * }
178
+ * ```
179
+ */
180
+ declare function useVoidx(): VoidxInstance | null;
181
+
182
+ export { type AnnounceSystemEventOptions, type HostActionHandler, type HostActions, type SystemEventHandler, type SystemEventResult, VoidxAddon, type VoidxAddonProps, type VoidxEventMap, type VoidxFeatures, type VoidxInitConfig, type VoidxInstance, type VoidxLocale, type VoidxTheme, useVoidx };
@@ -0,0 +1,182 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * VoidX SDK 타입 정의
5
+ *
6
+ * voidx-ai-addon SDK의 src/types/sdk.ts를 미러링.
7
+ * SDK를 런타임에 import하면 React 버전 충돌이 발생하므로 자체 정의한다.
8
+ * 향후 SDK가 타입 전용 패키지를 배포하면 import type으로 전환 가능.
9
+ */
10
+ type VoidxLocale = 'ko' | 'en' | 'ja' | 'zh';
11
+ interface VoidxFeatures {
12
+ /** 챗봇 팝업 (기본: true) */
13
+ chat?: boolean;
14
+ /** 커서 에이전트 (기본: true) */
15
+ cursor?: boolean;
16
+ /** 음성 에이전트 (기본: true) */
17
+ voice?: boolean;
18
+ /** 호버 감지 (기본: true) */
19
+ hoverDetection?: boolean;
20
+ /** 에이전트 렌더링 모드 (기본: '3d') */
21
+ agentMode?: '3d' | 'parallax';
22
+ }
23
+ interface VoidxTheme {
24
+ primaryColor?: string;
25
+ gradientFrom?: string;
26
+ gradientTo?: string;
27
+ fontFamily?: string;
28
+ borderRadius?: number;
29
+ }
30
+ type HostActionHandler = (params: any) => Promise<any>;
31
+ type HostActions = Record<string, HostActionHandler>;
32
+ interface AnnounceSystemEventOptions {
33
+ /** 에이전트 응답 가이드 메시지 */
34
+ instruction?: string;
35
+ /** 챗봇 팝업 자동 열기 (기본: false) */
36
+ openChat?: boolean;
37
+ }
38
+ interface VoidxEventMap {
39
+ 'voidx:ready': void;
40
+ 'voidx:error': Error;
41
+ 'voidx:message': {
42
+ id: string;
43
+ role: string;
44
+ content: string;
45
+ type: string;
46
+ };
47
+ 'voidx:auth:expired': {
48
+ status: number;
49
+ };
50
+ 'voidx:chat:open': void;
51
+ 'voidx:chat:close': void;
52
+ 'voidx:voice:start': void;
53
+ 'voidx:voice:end': void;
54
+ 'voidx:voice:error': {
55
+ message: string;
56
+ };
57
+ 'voidx:hover:detected': {
58
+ element: Element;
59
+ };
60
+ 'voidx:action:executed': {
61
+ name: string;
62
+ params: any;
63
+ result: any;
64
+ };
65
+ 'voidx:action:error': {
66
+ name: string;
67
+ params: any;
68
+ error: string;
69
+ };
70
+ 'voidx:destroy': void;
71
+ }
72
+ interface VoidxInstance {
73
+ destroy: () => void;
74
+ show: () => void;
75
+ hide: () => void;
76
+ sendMessage: (message: string) => void;
77
+ announceSystemEvent: (context: string, options?: AnnounceSystemEventOptions) => Promise<void>;
78
+ setTheme: (theme: Partial<VoidxTheme>) => void;
79
+ setLocale: (locale: VoidxLocale) => void;
80
+ on: <K extends keyof VoidxEventMap>(event: K, handler: (data: VoidxEventMap[K]) => void) => void;
81
+ off: <K extends keyof VoidxEventMap>(event: K, handler: (data: VoidxEventMap[K]) => void) => void;
82
+ }
83
+ interface VoidxInitConfig {
84
+ apiKey: string;
85
+ baseUrl?: string;
86
+ cdnUrl?: string;
87
+ voiceApiUrl?: string;
88
+ features?: VoidxFeatures;
89
+ theme?: VoidxTheme;
90
+ locale?: VoidxLocale;
91
+ position?: 'follow-cursor' | 'bottom-right' | 'bottom-left';
92
+ hoverTargets?: {
93
+ classNames?: string[];
94
+ ids?: string[];
95
+ idPatterns?: RegExp[];
96
+ classNamePatterns?: RegExp[];
97
+ };
98
+ actions?: HostActions;
99
+ onError?: (error: Error) => void;
100
+ onMessage?: (message: VoidxEventMap['voidx:message']) => void;
101
+ onReady?: () => void;
102
+ }
103
+ interface VoidxAIAddonGlobal {
104
+ init: (config: VoidxInitConfig) => VoidxInstance;
105
+ }
106
+ declare global {
107
+ interface Window {
108
+ VoidxAIAddon?: VoidxAIAddonGlobal;
109
+ }
110
+ }
111
+ interface SystemEventResult {
112
+ context: string;
113
+ instruction?: string;
114
+ openChat?: boolean;
115
+ }
116
+ interface SystemEventHandler<D = any> {
117
+ handler: (detail: D, instance: VoidxInstance, locale: VoidxLocale) => SystemEventResult | void | null;
118
+ }
119
+ interface VoidxAddonProps {
120
+ /** JWT API key — 필수 */
121
+ apiKey: string;
122
+ /** 기능 설정 */
123
+ features?: VoidxFeatures;
124
+ /** 테마 커스터마이징 */
125
+ theme?: VoidxTheme;
126
+ /** 언어 (반응형 — 변경 시 setLocale 자동 호출) */
127
+ locale?: VoidxLocale;
128
+ /** 팝업 위치 */
129
+ position?: 'follow-cursor' | 'bottom-right' | 'bottom-left';
130
+ /** API base URL 오버라이드 */
131
+ baseUrl?: string;
132
+ /** standalone.js 스크립트 URL 오버라이드 (기본: addon-cdn.voidx.ai) */
133
+ scriptUrl?: string;
134
+ /** SDK 내부 CDN URL 오버라이드 (3D 모델, 이미지 등) */
135
+ cdnUrl?: string;
136
+ /** Voice API URL 오버라이드 */
137
+ voiceApiUrl?: string;
138
+ /** 호버 감지 대상 */
139
+ hoverTargets?: VoidxInitConfig['hoverTargets'];
140
+ /** 호스트 액션 */
141
+ actions?: HostActions;
142
+ /** 시스템 이벤트 → announceSystemEvent 브리지 */
143
+ systemEvents?: Record<string, SystemEventHandler>;
144
+ /** SDK 준비 완료 콜백 */
145
+ onReady?: () => void;
146
+ /** 에러 콜백 */
147
+ onError?: (error: Error) => void;
148
+ /** 메시지 수신 콜백 */
149
+ onMessage?: (message: VoidxEventMap['voidx:message']) => void;
150
+ /** 비활성화 (true면 SDK 로딩 안 함) */
151
+ disabled?: boolean;
152
+ /** children (useVoidx() 훅으로 인스턴스 접근 가능) */
153
+ children?: React.ReactNode;
154
+ }
155
+
156
+ /**
157
+ * VoidX AI Addon React 래퍼 컴포넌트.
158
+ *
159
+ * CDN standalone 스크립트를 동적 로딩하고, SDK를 init/destroy 한다.
160
+ * children에서 useVoidx() 훅으로 인스턴스에 접근할 수 있다.
161
+ */
162
+ declare function VoidxAddon({ apiKey, features, theme, locale, position, baseUrl, scriptUrl, cdnUrl, voiceApiUrl, hoverTargets, actions, systemEvents, onReady, onError, onMessage, disabled, children, }: VoidxAddonProps): react_jsx_runtime.JSX.Element | null;
163
+
164
+ /**
165
+ * VoidxAddon 인스턴스에 접근하는 훅.
166
+ * SDK 로딩 전이면 null을 반환한다.
167
+ *
168
+ * @example
169
+ * ```tsx
170
+ * function AskAIButton() {
171
+ * const voidx = useVoidx();
172
+ * return (
173
+ * <button onClick={() => voidx?.sendMessage('추천해줘')}>
174
+ * AI에게 물어보기
175
+ * </button>
176
+ * );
177
+ * }
178
+ * ```
179
+ */
180
+ declare function useVoidx(): VoidxInstance | null;
181
+
182
+ export { type AnnounceSystemEventOptions, type HostActionHandler, type HostActions, type SystemEventHandler, type SystemEventResult, VoidxAddon, type VoidxAddonProps, type VoidxEventMap, type VoidxFeatures, type VoidxInitConfig, type VoidxInstance, type VoidxLocale, type VoidxTheme, useVoidx };
package/dist/index.js ADDED
@@ -0,0 +1,168 @@
1
+ import { createContext, useContext, useRef, useState, useEffect } from 'react';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+
4
+ // src/VoidxAddon.tsx
5
+ var VoidxContext = createContext(null);
6
+ function useVoidx() {
7
+ return useContext(VoidxContext);
8
+ }
9
+ function useScript(src, disabled) {
10
+ const [status, setStatus] = useState("idle");
11
+ useEffect(() => {
12
+ if (disabled) {
13
+ setStatus("idle");
14
+ return;
15
+ }
16
+ const existing = document.querySelector(
17
+ `script[src="${src}"]`
18
+ );
19
+ if (existing) {
20
+ if (window.VoidxAIAddon) {
21
+ setStatus("ready");
22
+ } else {
23
+ const onLoad = () => setStatus("ready");
24
+ const onError = () => setStatus("error");
25
+ existing.addEventListener("load", onLoad);
26
+ existing.addEventListener("error", onError);
27
+ setStatus("loading");
28
+ return () => {
29
+ existing.removeEventListener("load", onLoad);
30
+ existing.removeEventListener("error", onError);
31
+ };
32
+ }
33
+ return;
34
+ }
35
+ const script = document.createElement("script");
36
+ script.src = src;
37
+ script.async = true;
38
+ script.addEventListener("load", () => setStatus("ready"));
39
+ script.addEventListener("error", () => setStatus("error"));
40
+ setStatus("loading");
41
+ document.body.appendChild(script);
42
+ return () => {
43
+ if (document.body.contains(script)) {
44
+ document.body.removeChild(script);
45
+ }
46
+ };
47
+ }, [src, disabled]);
48
+ return status;
49
+ }
50
+
51
+ // src/utils.ts
52
+ var DEFAULT_CDN_URL = "https://addon-cdn.voidx.ai/latest/voidx-ai-addon.standalone.js";
53
+ function normalizeLang(value) {
54
+ switch (value) {
55
+ case "en":
56
+ return "en";
57
+ case "ja":
58
+ return "ja";
59
+ case "zh":
60
+ return "zh";
61
+ default:
62
+ return "ko";
63
+ }
64
+ }
65
+ function VoidxAddon({
66
+ apiKey,
67
+ features,
68
+ theme,
69
+ locale,
70
+ position,
71
+ baseUrl,
72
+ scriptUrl,
73
+ cdnUrl,
74
+ voiceApiUrl,
75
+ hoverTargets,
76
+ actions,
77
+ systemEvents,
78
+ onReady,
79
+ onError,
80
+ onMessage,
81
+ disabled = false,
82
+ children
83
+ }) {
84
+ const instanceRef = useRef(null);
85
+ const [instance, setInstance] = useState(null);
86
+ const localeRef = useRef(normalizeLang(locale));
87
+ const resolvedScriptUrl = scriptUrl ?? DEFAULT_CDN_URL;
88
+ const scriptStatus = useScript(resolvedScriptUrl, disabled);
89
+ useEffect(() => {
90
+ if (disabled || scriptStatus !== "ready" || !window.VoidxAIAddon) return;
91
+ const normalizedLocale = normalizeLang(locale);
92
+ localeRef.current = normalizedLocale;
93
+ const config = {
94
+ apiKey,
95
+ locale: normalizedLocale,
96
+ ...features && { features },
97
+ ...theme && { theme },
98
+ ...position && { position },
99
+ ...baseUrl && { baseUrl },
100
+ ...cdnUrl && { cdnUrl },
101
+ ...voiceApiUrl && { voiceApiUrl },
102
+ ...hoverTargets && { hoverTargets },
103
+ ...actions && { actions },
104
+ ...onReady && { onReady },
105
+ ...onError && { onError },
106
+ ...onMessage && { onMessage }
107
+ };
108
+ try {
109
+ const inst = window.VoidxAIAddon.init(config);
110
+ instanceRef.current = inst;
111
+ setInstance(inst);
112
+ } catch (error) {
113
+ const err = error instanceof Error ? error : new Error(String(error));
114
+ console.error("[VoidxAddon] init failed:", err);
115
+ onError?.(err);
116
+ }
117
+ return () => {
118
+ instanceRef.current?.destroy();
119
+ instanceRef.current = null;
120
+ setInstance(null);
121
+ };
122
+ }, [apiKey, scriptStatus, disabled]);
123
+ useEffect(() => {
124
+ const normalized = normalizeLang(locale);
125
+ localeRef.current = normalized;
126
+ instanceRef.current?.setLocale(normalized);
127
+ }, [locale]);
128
+ useEffect(() => {
129
+ if (theme && instanceRef.current) {
130
+ instanceRef.current.setTheme(theme);
131
+ }
132
+ }, [theme]);
133
+ const systemEventsRef = useRef(systemEvents);
134
+ systemEventsRef.current = systemEvents;
135
+ useEffect(() => {
136
+ const inst = instanceRef.current;
137
+ if (!inst || !systemEventsRef.current) return;
138
+ const entries = Object.entries(systemEventsRef.current);
139
+ if (entries.length === 0) return;
140
+ const cleanups = [];
141
+ for (const [eventName, { handler }] of entries) {
142
+ const listener = (event) => {
143
+ const detail = event.detail;
144
+ const result = handler(detail, inst, localeRef.current);
145
+ if (result) {
146
+ void inst.announceSystemEvent(result.context, {
147
+ instruction: result.instruction,
148
+ openChat: result.openChat
149
+ });
150
+ }
151
+ };
152
+ window.addEventListener(eventName, listener);
153
+ cleanups.push(() => window.removeEventListener(eventName, listener));
154
+ }
155
+ return () => {
156
+ for (const cleanup of cleanups) cleanup();
157
+ };
158
+ }, [instance]);
159
+ if (disabled) return null;
160
+ return /* @__PURE__ */ jsxs(VoidxContext.Provider, { value: instance, children: [
161
+ /* @__PURE__ */ jsx("div", { id: "voidx-ai-addon-root" }),
162
+ children
163
+ ] });
164
+ }
165
+
166
+ export { VoidxAddon, useVoidx };
167
+ //# sourceMappingURL=index.js.map
168
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context.ts","../src/use-script.ts","../src/utils.ts","../src/VoidxAddon.tsx"],"names":["useState","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext, useContext } from 'react';\nimport type { VoidxInstance } from './types';\n\nexport const VoidxContext = createContext<VoidxInstance | null>(null);\n\n/**\n * VoidxAddon 인스턴스에 접근하는 훅.\n * SDK 로딩 전이면 null을 반환한다.\n *\n * @example\n * ```tsx\n * function AskAIButton() {\n * const voidx = useVoidx();\n * return (\n * <button onClick={() => voidx?.sendMessage('추천해줘')}>\n * AI에게 물어보기\n * </button>\n * );\n * }\n * ```\n */\nexport function useVoidx(): VoidxInstance | null {\n return useContext(VoidxContext);\n}\n","import { useEffect, useState } from 'react';\n\ntype ScriptStatus = 'idle' | 'loading' | 'ready' | 'error';\n\n/**\n * CDN 스크립트를 동적으로 로딩하는 훅.\n * 이미 DOM에 동일 src가 있으면 중복 삽입하지 않는다.\n */\nexport function useScript(src: string, disabled: boolean): ScriptStatus {\n const [status, setStatus] = useState<ScriptStatus>('idle');\n\n useEffect(() => {\n if (disabled) {\n setStatus('idle');\n return;\n }\n\n // 이미 로드된 스크립트가 있으면 재사용\n const existing = document.querySelector(\n `script[src=\"${src}\"]`,\n ) as HTMLScriptElement | null;\n\n if (existing) {\n // window.VoidxAIAddon이 이미 있으면 ready\n if (window.VoidxAIAddon) {\n setStatus('ready');\n } else {\n // 아직 로딩 중인 스크립트 — 이벤트 대기\n const onLoad = () => setStatus('ready');\n const onError = () => setStatus('error');\n existing.addEventListener('load', onLoad);\n existing.addEventListener('error', onError);\n setStatus('loading');\n return () => {\n existing.removeEventListener('load', onLoad);\n existing.removeEventListener('error', onError);\n };\n }\n return;\n }\n\n const script = document.createElement('script');\n script.src = src;\n script.async = true;\n\n script.addEventListener('load', () => setStatus('ready'));\n script.addEventListener('error', () => setStatus('error'));\n\n setStatus('loading');\n document.body.appendChild(script);\n\n return () => {\n if (document.body.contains(script)) {\n document.body.removeChild(script);\n }\n };\n }, [src, disabled]);\n\n return status;\n}\n","import type { VoidxLocale } from './types';\n\nexport const DEFAULT_CDN_URL =\n 'https://addon-cdn.voidx.ai/latest/voidx-ai-addon.standalone.js';\n\nexport function normalizeLang(value: string | undefined): VoidxLocale {\n switch (value) {\n case 'en':\n return 'en';\n case 'ja':\n return 'ja';\n case 'zh':\n return 'zh';\n default:\n return 'ko';\n }\n}\n","import { useEffect, useRef, useState } from 'react';\nimport { VoidxContext } from './context';\nimport type {\n VoidxAddonProps,\n VoidxInitConfig,\n VoidxInstance,\n VoidxLocale,\n} from './types';\nimport { useScript } from './use-script';\nimport { DEFAULT_CDN_URL, normalizeLang } from './utils';\n\n/**\n * VoidX AI Addon React 래퍼 컴포넌트.\n *\n * CDN standalone 스크립트를 동적 로딩하고, SDK를 init/destroy 한다.\n * children에서 useVoidx() 훅으로 인스턴스에 접근할 수 있다.\n */\nexport function VoidxAddon({\n apiKey,\n features,\n theme,\n locale,\n position,\n baseUrl,\n scriptUrl,\n cdnUrl,\n voiceApiUrl,\n hoverTargets,\n actions,\n systemEvents,\n onReady,\n onError,\n onMessage,\n disabled = false,\n children,\n}: VoidxAddonProps) {\n const instanceRef = useRef<VoidxInstance | null>(null);\n const [instance, setInstance] = useState<VoidxInstance | null>(null);\n const localeRef = useRef<VoidxLocale>(normalizeLang(locale));\n\n const resolvedScriptUrl = scriptUrl ?? DEFAULT_CDN_URL;\n const scriptStatus = useScript(resolvedScriptUrl, disabled);\n\n // ── init / destroy ──\n useEffect(() => {\n if (disabled || scriptStatus !== 'ready' || !window.VoidxAIAddon) return;\n\n const normalizedLocale = normalizeLang(locale);\n localeRef.current = normalizedLocale;\n\n const config: VoidxInitConfig = {\n apiKey,\n locale: normalizedLocale,\n ...(features && { features }),\n ...(theme && { theme }),\n ...(position && { position }),\n ...(baseUrl && { baseUrl }),\n ...(cdnUrl && { cdnUrl }),\n ...(voiceApiUrl && { voiceApiUrl }),\n ...(hoverTargets && { hoverTargets }),\n ...(actions && { actions }),\n ...(onReady && { onReady }),\n ...(onError && { onError }),\n ...(onMessage && { onMessage }),\n };\n\n try {\n const inst = window.VoidxAIAddon!.init(config);\n instanceRef.current = inst;\n setInstance(inst);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n console.error('[VoidxAddon] init failed:', err);\n onError?.(err);\n }\n\n return () => {\n instanceRef.current?.destroy();\n instanceRef.current = null;\n setInstance(null);\n };\n // apiKey 변경 시 재초기화, actions/systemEvents는 ref로 관리하지 않고\n // init 시점에만 전달 (SDK가 런타임 교체를 지원하지 않음)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [apiKey, scriptStatus, disabled]);\n\n // ── locale sync ──\n useEffect(() => {\n const normalized = normalizeLang(locale);\n localeRef.current = normalized;\n instanceRef.current?.setLocale(normalized);\n }, [locale]);\n\n // ── theme sync ──\n useEffect(() => {\n if (theme && instanceRef.current) {\n instanceRef.current.setTheme(theme);\n }\n }, [theme]);\n\n // ── system events ──\n const systemEventsRef = useRef(systemEvents);\n systemEventsRef.current = systemEvents;\n\n useEffect(() => {\n const inst = instanceRef.current;\n if (!inst || !systemEventsRef.current) return;\n\n const entries = Object.entries(systemEventsRef.current);\n if (entries.length === 0) return;\n\n const cleanups: (() => void)[] = [];\n\n for (const [eventName, { handler }] of entries) {\n const listener = (event: Event) => {\n const detail = (event as CustomEvent).detail;\n const result = handler(detail, inst, localeRef.current);\n if (result) {\n void inst.announceSystemEvent(result.context, {\n instruction: result.instruction,\n openChat: result.openChat,\n });\n }\n };\n\n window.addEventListener(eventName, listener);\n cleanups.push(() => window.removeEventListener(eventName, listener));\n }\n\n return () => {\n for (const cleanup of cleanups) cleanup();\n };\n }, [instance]); // instance가 설정된 후에만 이벤트 등록\n\n if (disabled) return null;\n\n return (\n <VoidxContext.Provider value={instance}>\n <div id=\"voidx-ai-addon-root\" />\n {children}\n </VoidxContext.Provider>\n );\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@voidx-ai/react",
3
+ "version": "0.1.0",
4
+ "description": "React/Next.js wrapper for VoidX AI Addon (CDN standalone loader)",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "sideEffects": false,
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "dev": "tsup --watch",
28
+ "typecheck": "tsc --noEmit",
29
+ "prepublishOnly": "npm run build"
30
+ },
31
+ "peerDependencies": {
32
+ "react": ">=18.0.0",
33
+ "react-dom": ">=18.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/react": "^19.0.0",
37
+ "@types/react-dom": "^19.0.0",
38
+ "react": "^19.0.0",
39
+ "react-dom": "^19.0.0",
40
+ "tsup": "^8.0.0",
41
+ "typescript": "~5.7.0"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public",
45
+ "registry": "https://registry.npmjs.org/"
46
+ },
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "https://github.com/upfall-co/voidx-react.git"
50
+ },
51
+ "keywords": [
52
+ "voidx",
53
+ "ai",
54
+ "agent",
55
+ "chatbot",
56
+ "react",
57
+ "nextjs"
58
+ ],
59
+ "license": "MIT"
60
+ }