@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/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
+ }