@ozwell/react 1.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 +207 -0
- package/dist/index.cjs +394 -0
- package/dist/index.d.cts +282 -0
- package/dist/index.d.ts +282 -0
- package/dist/index.js +366 -0
- package/package.json +68 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
// src/OzwellChat.tsx
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
4
|
+
function OzwellChat(props) {
|
|
5
|
+
const {
|
|
6
|
+
// Layout
|
|
7
|
+
width = 360,
|
|
8
|
+
height = 420,
|
|
9
|
+
containerId,
|
|
10
|
+
defaultUI = true,
|
|
11
|
+
// Configuration
|
|
12
|
+
endpoint,
|
|
13
|
+
model,
|
|
14
|
+
system,
|
|
15
|
+
welcomeMessage,
|
|
16
|
+
placeholder,
|
|
17
|
+
title,
|
|
18
|
+
tools,
|
|
19
|
+
debug,
|
|
20
|
+
openaiApiKey,
|
|
21
|
+
headers,
|
|
22
|
+
widgetUrl,
|
|
23
|
+
autoOpenOnReply,
|
|
24
|
+
apiKey,
|
|
25
|
+
// Future props (not yet implemented in vanilla widget)
|
|
26
|
+
// These are accepted but ignored until backend support is added
|
|
27
|
+
agentId,
|
|
28
|
+
theme: _theme,
|
|
29
|
+
// Prefix with _ to indicate intentionally unused
|
|
30
|
+
position: _position,
|
|
31
|
+
primaryColor: _primaryColor,
|
|
32
|
+
autoOpen: _autoOpen,
|
|
33
|
+
// Callbacks
|
|
34
|
+
onReady,
|
|
35
|
+
onOpen,
|
|
36
|
+
onClose,
|
|
37
|
+
onToolCall,
|
|
38
|
+
onUserShare,
|
|
39
|
+
onError,
|
|
40
|
+
// React-specific
|
|
41
|
+
children
|
|
42
|
+
} = props;
|
|
43
|
+
const [scriptStatus, setScriptStatus] = useState("idle");
|
|
44
|
+
const [isWidgetReady, setIsWidgetReady] = useState(false);
|
|
45
|
+
const instanceContainerId = useRef(
|
|
46
|
+
containerId || `ozwell-container-${Math.random().toString(36).slice(2, 11)}`
|
|
47
|
+
);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (window.OzwellChat) {
|
|
50
|
+
setScriptStatus("ready");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const handleLoad = () => setScriptStatus("ready");
|
|
54
|
+
const handleError = () => {
|
|
55
|
+
setScriptStatus("error");
|
|
56
|
+
console.error("[OzwellChat] Failed to load ozwell-loader.js");
|
|
57
|
+
onError?.({
|
|
58
|
+
code: "SCRIPT_LOAD_ERROR",
|
|
59
|
+
message: "Failed to load Ozwell widget. Check the widget host URL and network access."
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
const existingScript = document.querySelector(
|
|
63
|
+
'script[data-ozwell-loader], script[src*="ozwell-loader.js"]'
|
|
64
|
+
);
|
|
65
|
+
if (existingScript) {
|
|
66
|
+
setScriptStatus("loading");
|
|
67
|
+
existingScript.addEventListener("load", handleLoad);
|
|
68
|
+
existingScript.addEventListener("error", handleError);
|
|
69
|
+
return () => {
|
|
70
|
+
existingScript.removeEventListener("load", handleLoad);
|
|
71
|
+
existingScript.removeEventListener("error", handleError);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
window.OzwellChatConfig = {
|
|
75
|
+
...window.OzwellChatConfig,
|
|
76
|
+
autoMount: false
|
|
77
|
+
};
|
|
78
|
+
setScriptStatus("loading");
|
|
79
|
+
const scriptSrc = new URL(
|
|
80
|
+
"/widget",
|
|
81
|
+
widgetUrl ? new URL(widgetUrl, window.location.href) : "https://ozwellapi.os.mieweb.org"
|
|
82
|
+
).href;
|
|
83
|
+
const script = document.createElement("script");
|
|
84
|
+
script.dataset.ozwellLoader = "true";
|
|
85
|
+
script.src = scriptSrc;
|
|
86
|
+
script.async = true;
|
|
87
|
+
script.addEventListener("load", handleLoad);
|
|
88
|
+
script.addEventListener("error", handleError);
|
|
89
|
+
document.head.appendChild(script);
|
|
90
|
+
return () => {
|
|
91
|
+
script.removeEventListener("load", handleLoad);
|
|
92
|
+
script.removeEventListener("error", handleError);
|
|
93
|
+
};
|
|
94
|
+
}, [widgetUrl]);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (scriptStatus !== "ready" || !window.OzwellChat) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const config = {
|
|
100
|
+
// Core config
|
|
101
|
+
endpoint,
|
|
102
|
+
model,
|
|
103
|
+
system,
|
|
104
|
+
welcomeMessage,
|
|
105
|
+
placeholder,
|
|
106
|
+
title,
|
|
107
|
+
tools,
|
|
108
|
+
debug,
|
|
109
|
+
openaiApiKey,
|
|
110
|
+
headers,
|
|
111
|
+
widgetUrl,
|
|
112
|
+
apiKey,
|
|
113
|
+
// Layout config
|
|
114
|
+
defaultUI,
|
|
115
|
+
autoMount: false,
|
|
116
|
+
// Prevent auto-mount, we'll mount manually
|
|
117
|
+
autoOpenOnReply,
|
|
118
|
+
agentId
|
|
119
|
+
};
|
|
120
|
+
if (!defaultUI) {
|
|
121
|
+
config.containerId = instanceContainerId.current;
|
|
122
|
+
}
|
|
123
|
+
const cleanConfig = Object.fromEntries(
|
|
124
|
+
Object.entries(config).filter(([, value]) => value !== void 0)
|
|
125
|
+
);
|
|
126
|
+
window.OzwellChatConfig = cleanConfig;
|
|
127
|
+
try {
|
|
128
|
+
const mountOptions = {};
|
|
129
|
+
if (!defaultUI) {
|
|
130
|
+
mountOptions.containerId = instanceContainerId.current;
|
|
131
|
+
}
|
|
132
|
+
if (width) {
|
|
133
|
+
mountOptions.width = typeof width === "string" ? parseInt(width) : width;
|
|
134
|
+
}
|
|
135
|
+
if (height) {
|
|
136
|
+
mountOptions.height = typeof height === "string" ? parseInt(height) : height;
|
|
137
|
+
}
|
|
138
|
+
if (window.OzwellChat.iframe) {
|
|
139
|
+
window.OzwellChat.configure(cleanConfig);
|
|
140
|
+
} else {
|
|
141
|
+
window.OzwellChat.mount(mountOptions);
|
|
142
|
+
}
|
|
143
|
+
window.OzwellChat.ready().then(() => {
|
|
144
|
+
setIsWidgetReady(true);
|
|
145
|
+
onReady?.();
|
|
146
|
+
});
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error("[OzwellChat] Failed to mount widget:", error);
|
|
149
|
+
onError?.({
|
|
150
|
+
code: "MOUNT_ERROR",
|
|
151
|
+
message: "Failed to mount Ozwell widget",
|
|
152
|
+
details: error
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}, [
|
|
156
|
+
scriptStatus,
|
|
157
|
+
endpoint,
|
|
158
|
+
model,
|
|
159
|
+
system,
|
|
160
|
+
welcomeMessage,
|
|
161
|
+
placeholder,
|
|
162
|
+
title,
|
|
163
|
+
tools,
|
|
164
|
+
debug,
|
|
165
|
+
openaiApiKey,
|
|
166
|
+
headers,
|
|
167
|
+
widgetUrl,
|
|
168
|
+
defaultUI,
|
|
169
|
+
autoOpenOnReply,
|
|
170
|
+
width,
|
|
171
|
+
height,
|
|
172
|
+
apiKey,
|
|
173
|
+
agentId,
|
|
174
|
+
onReady,
|
|
175
|
+
onError
|
|
176
|
+
]);
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
if (!isWidgetReady) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const handleMessage = (event) => {
|
|
182
|
+
const iframe = window.OzwellChat?.iframe;
|
|
183
|
+
if (!iframe || event.source !== iframe.contentWindow || event.origin !== new URL(iframe.src, window.location.href).origin) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const data = event.data;
|
|
187
|
+
if (!data || typeof data !== "object" || data.source !== "ozwell-chat-widget") {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
switch (data.type) {
|
|
191
|
+
case "closed":
|
|
192
|
+
onClose?.();
|
|
193
|
+
break;
|
|
194
|
+
case "opened":
|
|
195
|
+
onOpen?.();
|
|
196
|
+
break;
|
|
197
|
+
case "user-share":
|
|
198
|
+
onUserShare?.(data.payload);
|
|
199
|
+
break;
|
|
200
|
+
case "error":
|
|
201
|
+
onError?.(data.payload);
|
|
202
|
+
break;
|
|
203
|
+
case "tool_call":
|
|
204
|
+
if (onToolCall) {
|
|
205
|
+
const { tool, tool_call_id, payload: args } = data;
|
|
206
|
+
const sendResult = (result) => {
|
|
207
|
+
const iframe2 = window.OzwellChat?.iframe;
|
|
208
|
+
if (iframe2?.contentWindow) {
|
|
209
|
+
const targetOrigin = iframe2.src ? new URL(iframe2.src).origin : "*";
|
|
210
|
+
iframe2.contentWindow.postMessage(
|
|
211
|
+
{
|
|
212
|
+
source: "ozwell-chat-parent",
|
|
213
|
+
type: "tool_result",
|
|
214
|
+
tool_call_id,
|
|
215
|
+
result
|
|
216
|
+
},
|
|
217
|
+
targetOrigin
|
|
218
|
+
);
|
|
219
|
+
} else {
|
|
220
|
+
console.error("[OzwellChat] Could not find widget iframe to send tool result");
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
onToolCall(tool, args || {}, sendResult);
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
window.addEventListener("message", handleMessage);
|
|
229
|
+
return () => {
|
|
230
|
+
window.removeEventListener("message", handleMessage);
|
|
231
|
+
};
|
|
232
|
+
}, [isWidgetReady, onClose, onOpen, onUserShare, onError, onToolCall]);
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
const handleToolCall = (event) => {
|
|
235
|
+
const { name, arguments: args, respond, error } = event.detail;
|
|
236
|
+
if (!onToolCall) {
|
|
237
|
+
error(`No handler configured for tool "${name}".`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const outcome = onToolCall(name, args || {}, respond);
|
|
242
|
+
Promise.resolve(outcome).catch(() => error(`Tool "${name}" failed.`));
|
|
243
|
+
} catch {
|
|
244
|
+
error(`Tool "${name}" failed.`);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
document.addEventListener("ozwell-tool-call", handleToolCall);
|
|
248
|
+
return () => document.removeEventListener("ozwell-tool-call", handleToolCall);
|
|
249
|
+
}, [onToolCall]);
|
|
250
|
+
if (defaultUI) {
|
|
251
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
252
|
+
}
|
|
253
|
+
return /* @__PURE__ */ jsx(
|
|
254
|
+
"div",
|
|
255
|
+
{
|
|
256
|
+
id: instanceContainerId.current,
|
|
257
|
+
style: {
|
|
258
|
+
width: typeof width === "number" ? `${width}px` : width,
|
|
259
|
+
height: typeof height === "number" ? `${height}px` : height
|
|
260
|
+
},
|
|
261
|
+
children
|
|
262
|
+
}
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
var OzwellChat_default = OzwellChat;
|
|
266
|
+
|
|
267
|
+
// src/useOzwell.ts
|
|
268
|
+
import { useEffect as useEffect2, useState as useState2, useCallback } from "react";
|
|
269
|
+
function useOzwell() {
|
|
270
|
+
const [isReady, setIsReady] = useState2(false);
|
|
271
|
+
const [isOpen, setIsOpen] = useState2(false);
|
|
272
|
+
const [hasUnread, setHasUnread] = useState2(false);
|
|
273
|
+
const [iframe, setIframe] = useState2(null);
|
|
274
|
+
useEffect2(() => {
|
|
275
|
+
if (window.OzwellChat) {
|
|
276
|
+
setIsReady(true);
|
|
277
|
+
setIframe(window.OzwellChat.iframe);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const handleReady = () => {
|
|
281
|
+
setIsReady(true);
|
|
282
|
+
setIframe(window.OzwellChat?.iframe || null);
|
|
283
|
+
};
|
|
284
|
+
document.addEventListener("ozwell-chat-ready", handleReady);
|
|
285
|
+
return () => {
|
|
286
|
+
document.removeEventListener("ozwell-chat-ready", handleReady);
|
|
287
|
+
};
|
|
288
|
+
}, []);
|
|
289
|
+
useEffect2(() => {
|
|
290
|
+
if (!isReady) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const handleMessage = (event) => {
|
|
294
|
+
const widgetIframe = window.OzwellChat?.iframe;
|
|
295
|
+
if (widgetIframe && event.source !== widgetIframe.contentWindow) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const data = event.data;
|
|
299
|
+
if (!data || typeof data !== "object" || data.source !== "ozwell-chat-widget") {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (data.type === "opened") {
|
|
303
|
+
setIsOpen(true);
|
|
304
|
+
setHasUnread(false);
|
|
305
|
+
} else if (data.type === "closed") {
|
|
306
|
+
setIsOpen(false);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
const handleUnread = () => {
|
|
310
|
+
setHasUnread(true);
|
|
311
|
+
};
|
|
312
|
+
window.addEventListener("message", handleMessage);
|
|
313
|
+
document.addEventListener("ozwell-chat-unread", handleUnread);
|
|
314
|
+
return () => {
|
|
315
|
+
window.removeEventListener("message", handleMessage);
|
|
316
|
+
document.removeEventListener("ozwell-chat-unread", handleUnread);
|
|
317
|
+
};
|
|
318
|
+
}, [isReady]);
|
|
319
|
+
const open = useCallback(() => {
|
|
320
|
+
if (!isReady) {
|
|
321
|
+
console.warn("[useOzwell] Widget not ready yet");
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
window.OzwellChat?.open?.();
|
|
325
|
+
}, [isReady]);
|
|
326
|
+
const close = useCallback(() => {
|
|
327
|
+
if (!isReady) {
|
|
328
|
+
console.warn("[useOzwell] Widget not ready yet");
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
window.OzwellChat?.close?.();
|
|
332
|
+
}, [isReady]);
|
|
333
|
+
const toggle = useCallback(() => {
|
|
334
|
+
if (isOpen) {
|
|
335
|
+
close();
|
|
336
|
+
} else {
|
|
337
|
+
open();
|
|
338
|
+
}
|
|
339
|
+
}, [isOpen, open, close]);
|
|
340
|
+
const sendMessage = useCallback((content) => {
|
|
341
|
+
if (!isReady) {
|
|
342
|
+
console.warn("[useOzwell] Widget not ready yet");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (!content || typeof content !== "string") {
|
|
346
|
+
console.warn("[useOzwell] sendMessage requires a non-empty string");
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
console.warn("[useOzwell] sendMessage() not yet implemented in vanilla widget");
|
|
350
|
+
}, [isReady]);
|
|
351
|
+
return {
|
|
352
|
+
isReady,
|
|
353
|
+
isOpen,
|
|
354
|
+
hasUnread,
|
|
355
|
+
open,
|
|
356
|
+
close,
|
|
357
|
+
toggle,
|
|
358
|
+
sendMessage,
|
|
359
|
+
iframe
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
export {
|
|
363
|
+
OzwellChat,
|
|
364
|
+
OzwellChat_default as default,
|
|
365
|
+
useOzwell
|
|
366
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ozwell/react",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React components for Ozwell chat widget",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"publishConfig": {"access": "public"},
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/index.d.cts",
|
|
18
|
+
"default": "./dist/index.cjs"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist/**/*",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
28
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
29
|
+
"test": "echo \"Tests coming soon\"",
|
|
30
|
+
"lint": "tsc --noEmit",
|
|
31
|
+
"prepublishOnly": "npm run build"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"ozwell",
|
|
35
|
+
"react",
|
|
36
|
+
"chat",
|
|
37
|
+
"widget",
|
|
38
|
+
"chatbot",
|
|
39
|
+
"ai",
|
|
40
|
+
"assistant"
|
|
41
|
+
],
|
|
42
|
+
"author": "mieweb",
|
|
43
|
+
"license": "Apache-2.0",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/mieweb/ozwellai-api.git",
|
|
47
|
+
"directory": "packages/react"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://github.com/mieweb/ozwellai-api/tree/main/packages/react",
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/mieweb/ozwellai-api/issues"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
55
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/react": "^18.3.12",
|
|
59
|
+
"@types/react-dom": "^18.3.1",
|
|
60
|
+
"react": "^19.0.0",
|
|
61
|
+
"react-dom": "^19.0.0",
|
|
62
|
+
"tsup": "^8.3.5",
|
|
63
|
+
"typescript": "^5.6.0"
|
|
64
|
+
},
|
|
65
|
+
"engines": {
|
|
66
|
+
"node": ">=18.0.0"
|
|
67
|
+
}
|
|
68
|
+
}
|