@object-ui/plugin-chatbot 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 ObjectQL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # @object-ui/plugin-chatbot
2
+
3
+ Chatbot interface plugin for Object UI.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @object-ui/plugin-chatbot
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```tsx
14
+ import { Chatbot } from '@object-ui/plugin-chatbot';
15
+
16
+ function App() {
17
+ const [messages, setMessages] = useState([
18
+ {
19
+ id: '1',
20
+ role: 'assistant',
21
+ content: 'Hello! How can I help you today?'
22
+ }
23
+ ]);
24
+
25
+ const handleSend = (content: string) => {
26
+ const newMessage = {
27
+ id: Date.now().toString(),
28
+ role: 'user',
29
+ content
30
+ };
31
+ setMessages([...messages, newMessage]);
32
+ };
33
+
34
+ return (
35
+ <Chatbot
36
+ messages={messages}
37
+ onSendMessage={handleSend}
38
+ placeholder="Type your message..."
39
+ />
40
+ );
41
+ }
42
+ ```
43
+
44
+ ## Schema-Driven Usage
45
+
46
+ This plugin automatically registers with ObjectUI's component registry when imported:
47
+
48
+ ```tsx
49
+ import '@object-ui/plugin-chatbot';
50
+
51
+ const schema = {
52
+ component: 'chatbot',
53
+ messages: [
54
+ { id: '1', role: 'assistant', content: 'Hello!' }
55
+ ],
56
+ placeholder: 'Type your message...',
57
+ autoResponse: true
58
+ };
59
+ ```
60
+
61
+ ## License
62
+
63
+ MIT © ObjectStack Inc.
@@ -0,0 +1,2 @@
1
+ export * from './src/index'
2
+ export {}
package/dist/index.js ADDED
@@ -0,0 +1,554 @@
1
+ import * as h from "react";
2
+ import oe, { useState as le } from "react";
3
+ import { cn as A, ScrollArea as ie, Input as ue, Button as ce, Avatar as J, AvatarImage as G, AvatarFallback as X } from "@object-ui/components";
4
+ import { Send as de } from "lucide-react";
5
+ import { ComponentRegistry as fe } from "@object-ui/core";
6
+ var O = { exports: {} }, _ = {};
7
+ var H;
8
+ function me() {
9
+ if (H) return _;
10
+ H = 1;
11
+ var a = /* @__PURE__ */ Symbol.for("react.transitional.element"), d = /* @__PURE__ */ Symbol.for("react.fragment");
12
+ function m(c, o, u) {
13
+ var s = null;
14
+ if (u !== void 0 && (s = "" + u), o.key !== void 0 && (s = "" + o.key), "key" in o) {
15
+ u = {};
16
+ for (var p in o)
17
+ p !== "key" && (u[p] = o[p]);
18
+ } else u = o;
19
+ return o = u.ref, {
20
+ $$typeof: a,
21
+ type: c,
22
+ key: s,
23
+ ref: o !== void 0 ? o : null,
24
+ props: u
25
+ };
26
+ }
27
+ return _.Fragment = d, _.jsx = m, _.jsxs = m, _;
28
+ }
29
+ var w = {};
30
+ var q;
31
+ function pe() {
32
+ return q || (q = 1, process.env.NODE_ENV !== "production" && (function() {
33
+ function a(e) {
34
+ if (e == null) return null;
35
+ if (typeof e == "function")
36
+ return e.$$typeof === re ? null : e.displayName || e.name || null;
37
+ if (typeof e == "string") return e;
38
+ switch (e) {
39
+ case g:
40
+ return "Fragment";
41
+ case P:
42
+ return "Profiler";
43
+ case S:
44
+ return "StrictMode";
45
+ case Q:
46
+ return "Suspense";
47
+ case ee:
48
+ return "SuspenseList";
49
+ case te:
50
+ return "Activity";
51
+ }
52
+ if (typeof e == "object")
53
+ switch (typeof e.tag == "number" && console.error(
54
+ "Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."
55
+ ), e.$$typeof) {
56
+ case E:
57
+ return "Portal";
58
+ case Z:
59
+ return e.displayName || "Context";
60
+ case f:
61
+ return (e._context.displayName || "Context") + ".Consumer";
62
+ case K:
63
+ var t = e.render;
64
+ return e = e.displayName, e || (e = t.displayName || t.name || "", e = e !== "" ? "ForwardRef(" + e + ")" : "ForwardRef"), e;
65
+ case ae:
66
+ return t = e.displayName || null, t !== null ? t : a(e.type) || "Memo";
67
+ case C:
68
+ t = e._payload, e = e._init;
69
+ try {
70
+ return a(e(t));
71
+ } catch {
72
+ }
73
+ }
74
+ return null;
75
+ }
76
+ function d(e) {
77
+ return "" + e;
78
+ }
79
+ function m(e) {
80
+ try {
81
+ d(e);
82
+ var t = !1;
83
+ } catch {
84
+ t = !0;
85
+ }
86
+ if (t) {
87
+ t = console;
88
+ var n = t.error, l = typeof Symbol == "function" && Symbol.toStringTag && e[Symbol.toStringTag] || e.constructor.name || "Object";
89
+ return n.call(
90
+ t,
91
+ "The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
92
+ l
93
+ ), d(e);
94
+ }
95
+ }
96
+ function c(e) {
97
+ if (e === g) return "<>";
98
+ if (typeof e == "object" && e !== null && e.$$typeof === C)
99
+ return "<...>";
100
+ try {
101
+ var t = a(e);
102
+ return t ? "<" + t + ">" : "<...>";
103
+ } catch {
104
+ return "<...>";
105
+ }
106
+ }
107
+ function o() {
108
+ var e = F.A;
109
+ return e === null ? null : e.getOwner();
110
+ }
111
+ function u() {
112
+ return Error("react-stack-top-frame");
113
+ }
114
+ function s(e) {
115
+ if (Y.call(e, "key")) {
116
+ var t = Object.getOwnPropertyDescriptor(e, "key").get;
117
+ if (t && t.isReactWarning) return !1;
118
+ }
119
+ return e.key !== void 0;
120
+ }
121
+ function p(e, t) {
122
+ function n() {
123
+ M || (M = !0, console.error(
124
+ "%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",
125
+ t
126
+ ));
127
+ }
128
+ n.isReactWarning = !0, Object.defineProperty(e, "key", {
129
+ get: n,
130
+ configurable: !0
131
+ });
132
+ }
133
+ function v() {
134
+ var e = a(this.type);
135
+ return $[e] || ($[e] = !0, console.error(
136
+ "Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release."
137
+ )), e = this.props.ref, e !== void 0 ? e : null;
138
+ }
139
+ function x(e, t, n, l, N, D) {
140
+ var i = n.ref;
141
+ return e = {
142
+ $$typeof: T,
143
+ type: e,
144
+ key: t,
145
+ props: n,
146
+ _owner: l
147
+ }, (i !== void 0 ? i : null) !== null ? Object.defineProperty(e, "ref", {
148
+ enumerable: !1,
149
+ get: v
150
+ }) : Object.defineProperty(e, "ref", { enumerable: !1, value: null }), e._store = {}, Object.defineProperty(e._store, "validated", {
151
+ configurable: !1,
152
+ enumerable: !1,
153
+ writable: !0,
154
+ value: 0
155
+ }), Object.defineProperty(e, "_debugInfo", {
156
+ configurable: !1,
157
+ enumerable: !1,
158
+ writable: !0,
159
+ value: null
160
+ }), Object.defineProperty(e, "_debugStack", {
161
+ configurable: !1,
162
+ enumerable: !1,
163
+ writable: !0,
164
+ value: N
165
+ }), Object.defineProperty(e, "_debugTask", {
166
+ configurable: !1,
167
+ enumerable: !1,
168
+ writable: !0,
169
+ value: D
170
+ }), Object.freeze && (Object.freeze(e.props), Object.freeze(e)), e;
171
+ }
172
+ function y(e, t, n, l, N, D) {
173
+ var i = t.children;
174
+ if (i !== void 0)
175
+ if (l)
176
+ if (se(i)) {
177
+ for (l = 0; l < i.length; l++)
178
+ k(i[l]);
179
+ Object.freeze && Object.freeze(i);
180
+ } else
181
+ console.error(
182
+ "React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead."
183
+ );
184
+ else k(i);
185
+ if (Y.call(t, "key")) {
186
+ i = a(e);
187
+ var R = Object.keys(t).filter(function(ne) {
188
+ return ne !== "key";
189
+ });
190
+ l = 0 < R.length ? "{key: someKey, " + R.join(": ..., ") + ": ...}" : "{key: someKey}", W[i + l] || (R = 0 < R.length ? "{" + R.join(": ..., ") + ": ...}" : "{}", console.error(
191
+ `A props object containing a "key" prop is being spread into JSX:
192
+ let props = %s;
193
+ <%s {...props} />
194
+ React keys must be passed directly to JSX without using spread:
195
+ let props = %s;
196
+ <%s key={someKey} {...props} />`,
197
+ l,
198
+ i,
199
+ R,
200
+ i
201
+ ), W[i + l] = !0);
202
+ }
203
+ if (i = null, n !== void 0 && (m(n), i = "" + n), s(t) && (m(t.key), i = "" + t.key), "key" in t) {
204
+ n = {};
205
+ for (var U in t)
206
+ U !== "key" && (n[U] = t[U]);
207
+ } else n = t;
208
+ return i && p(
209
+ n,
210
+ typeof e == "function" ? e.displayName || e.name || "Unknown" : e
211
+ ), x(
212
+ e,
213
+ i,
214
+ n,
215
+ o(),
216
+ N,
217
+ D
218
+ );
219
+ }
220
+ function k(e) {
221
+ j(e) ? e._store && (e._store.validated = 1) : typeof e == "object" && e !== null && e.$$typeof === C && (e._payload.status === "fulfilled" ? j(e._payload.value) && e._payload.value._store && (e._payload.value._store.validated = 1) : e._store && (e._store.validated = 1));
222
+ }
223
+ function j(e) {
224
+ return typeof e == "object" && e !== null && e.$$typeof === T;
225
+ }
226
+ var b = oe, T = /* @__PURE__ */ Symbol.for("react.transitional.element"), E = /* @__PURE__ */ Symbol.for("react.portal"), g = /* @__PURE__ */ Symbol.for("react.fragment"), S = /* @__PURE__ */ Symbol.for("react.strict_mode"), P = /* @__PURE__ */ Symbol.for("react.profiler"), f = /* @__PURE__ */ Symbol.for("react.consumer"), Z = /* @__PURE__ */ Symbol.for("react.context"), K = /* @__PURE__ */ Symbol.for("react.forward_ref"), Q = /* @__PURE__ */ Symbol.for("react.suspense"), ee = /* @__PURE__ */ Symbol.for("react.suspense_list"), ae = /* @__PURE__ */ Symbol.for("react.memo"), C = /* @__PURE__ */ Symbol.for("react.lazy"), te = /* @__PURE__ */ Symbol.for("react.activity"), re = /* @__PURE__ */ Symbol.for("react.client.reference"), F = b.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Y = Object.prototype.hasOwnProperty, se = Array.isArray, I = console.createTask ? console.createTask : function() {
227
+ return null;
228
+ };
229
+ b = {
230
+ react_stack_bottom_frame: function(e) {
231
+ return e();
232
+ }
233
+ };
234
+ var M, $ = {}, V = b.react_stack_bottom_frame.bind(
235
+ b,
236
+ u
237
+ )(), L = I(c(u)), W = {};
238
+ w.Fragment = g, w.jsx = function(e, t, n) {
239
+ var l = 1e4 > F.recentlyCreatedOwnerStacks++;
240
+ return y(
241
+ e,
242
+ t,
243
+ n,
244
+ !1,
245
+ l ? Error("react-stack-top-frame") : V,
246
+ l ? I(c(e)) : L
247
+ );
248
+ }, w.jsxs = function(e, t, n) {
249
+ var l = 1e4 > F.recentlyCreatedOwnerStacks++;
250
+ return y(
251
+ e,
252
+ t,
253
+ n,
254
+ !0,
255
+ l ? Error("react-stack-top-frame") : V,
256
+ l ? I(c(e)) : L
257
+ );
258
+ };
259
+ })()), w;
260
+ }
261
+ var z;
262
+ function be() {
263
+ return z || (z = 1, process.env.NODE_ENV === "production" ? O.exports = me() : O.exports = pe()), O.exports;
264
+ }
265
+ var r = be();
266
+ fe.register(
267
+ "chatbot",
268
+ ({ schema: a, className: d, ...m }) => {
269
+ const [c, o] = le(
270
+ a.messages?.map((s, p) => ({
271
+ id: s.id || `msg-${p}`,
272
+ role: s.role || "user",
273
+ content: s.content || "",
274
+ timestamp: typeof s.timestamp == "string" ? s.timestamp : s.timestamp instanceof Date ? s.timestamp.toISOString() : void 0,
275
+ avatar: s.avatar,
276
+ avatarFallback: s.avatarFallback
277
+ })) || []
278
+ ), u = (s) => {
279
+ const p = {
280
+ id: crypto?.randomUUID?.() || `msg-${Date.now()}-${Math.random().toString(36).slice(2)}`,
281
+ role: "user",
282
+ content: s,
283
+ timestamp: a.showTimestamp ? (/* @__PURE__ */ new Date()).toLocaleTimeString() : void 0
284
+ }, v = [...c, p];
285
+ o(v), a.onSend && a.onSend(s, v), a.autoResponse && setTimeout(() => {
286
+ const x = {
287
+ id: crypto?.randomUUID?.() || `msg-${Date.now()}-${Math.random().toString(36).slice(2)}`,
288
+ role: "assistant",
289
+ content: a.autoResponseText || "Thank you for your message!",
290
+ timestamp: a.showTimestamp ? (/* @__PURE__ */ new Date()).toLocaleTimeString() : void 0
291
+ };
292
+ o((y) => [...y, x]);
293
+ }, a.autoResponseDelay || 1e3);
294
+ };
295
+ return /* @__PURE__ */ r.jsx(
296
+ B,
297
+ {
298
+ messages: c,
299
+ placeholder: a.placeholder,
300
+ onSendMessage: u,
301
+ disabled: a.disabled,
302
+ showTimestamp: a.showTimestamp,
303
+ userAvatarUrl: a.userAvatarUrl,
304
+ userAvatarFallback: a.userAvatarFallback,
305
+ assistantAvatarUrl: a.assistantAvatarUrl,
306
+ assistantAvatarFallback: a.assistantAvatarFallback,
307
+ maxHeight: a.maxHeight,
308
+ className: d,
309
+ ...m
310
+ }
311
+ );
312
+ },
313
+ {
314
+ label: "Chatbot",
315
+ inputs: [
316
+ {
317
+ name: "messages",
318
+ type: "array",
319
+ label: "Initial Messages",
320
+ description: "Array of message objects with id, role, content, and optional timestamp"
321
+ },
322
+ {
323
+ name: "placeholder",
324
+ type: "string",
325
+ label: "Input Placeholder",
326
+ defaultValue: "Type your message..."
327
+ },
328
+ {
329
+ name: "showTimestamp",
330
+ type: "boolean",
331
+ label: "Show Timestamps",
332
+ defaultValue: !1
333
+ },
334
+ {
335
+ name: "disabled",
336
+ type: "boolean",
337
+ label: "Disabled",
338
+ defaultValue: !1
339
+ },
340
+ {
341
+ name: "userAvatarUrl",
342
+ type: "string",
343
+ label: "User Avatar URL",
344
+ description: "URL of the user avatar image"
345
+ },
346
+ {
347
+ name: "userAvatarFallback",
348
+ type: "string",
349
+ label: "User Avatar Fallback",
350
+ defaultValue: "You",
351
+ description: "Fallback text shown when user avatar image is not available"
352
+ },
353
+ {
354
+ name: "assistantAvatarUrl",
355
+ type: "string",
356
+ label: "Assistant Avatar URL",
357
+ description: "URL of the assistant avatar image"
358
+ },
359
+ {
360
+ name: "assistantAvatarFallback",
361
+ type: "string",
362
+ label: "Assistant Avatar Fallback",
363
+ defaultValue: "AI",
364
+ description: "Fallback text shown when assistant avatar image is not available"
365
+ },
366
+ {
367
+ name: "maxHeight",
368
+ type: "string",
369
+ label: "Max Height",
370
+ defaultValue: "500px"
371
+ },
372
+ {
373
+ name: "autoResponse",
374
+ type: "boolean",
375
+ label: "Enable Auto Response (Demo)",
376
+ defaultValue: !1,
377
+ description: "Automatically send a response after user message (for demo purposes)"
378
+ },
379
+ {
380
+ name: "autoResponseText",
381
+ type: "string",
382
+ label: "Auto Response Text",
383
+ defaultValue: "Thank you for your message!"
384
+ },
385
+ {
386
+ name: "autoResponseDelay",
387
+ type: "number",
388
+ label: "Auto Response Delay (ms)",
389
+ defaultValue: 1e3
390
+ },
391
+ {
392
+ name: "className",
393
+ type: "string",
394
+ label: "CSS Class"
395
+ }
396
+ ],
397
+ defaultProps: {
398
+ messages: [
399
+ {
400
+ id: "welcome",
401
+ role: "assistant",
402
+ content: "Hello! How can I help you today?"
403
+ }
404
+ ],
405
+ placeholder: "Type your message...",
406
+ showTimestamp: !1,
407
+ disabled: !1,
408
+ userAvatarFallback: "You",
409
+ assistantAvatarFallback: "AI",
410
+ maxHeight: "500px",
411
+ autoResponse: !0,
412
+ autoResponseText: "Thank you for your message! This is an automated response.",
413
+ autoResponseDelay: 1e3,
414
+ className: "w-full max-w-2xl"
415
+ }
416
+ }
417
+ );
418
+ const B = h.forwardRef(
419
+ ({
420
+ className: a,
421
+ messages: d = [],
422
+ placeholder: m = "Type your message...",
423
+ onSendMessage: c,
424
+ disabled: o = !1,
425
+ showTimestamp: u = !1,
426
+ userAvatarUrl: s,
427
+ userAvatarFallback: p = "You",
428
+ assistantAvatarUrl: v,
429
+ assistantAvatarFallback: x = "AI",
430
+ maxHeight: y = "500px",
431
+ ...k
432
+ }, j) => {
433
+ const [b, T] = h.useState(""), E = h.useRef(null), g = h.useRef(null);
434
+ h.useEffect(() => {
435
+ if (E.current) {
436
+ const f = E.current.querySelector("[data-radix-scroll-area-viewport]");
437
+ f && (f.scrollTop = f.scrollHeight);
438
+ }
439
+ }, [d]);
440
+ const S = () => {
441
+ b.trim() && c && (c(b.trim()), T(""), g.current?.focus());
442
+ }, P = (f) => {
443
+ f.key === "Enter" && !f.shiftKey && (f.preventDefault(), S());
444
+ };
445
+ return /* @__PURE__ */ r.jsxs(
446
+ "div",
447
+ {
448
+ ref: j,
449
+ className: A(
450
+ "flex flex-col border rounded-lg bg-background overflow-hidden",
451
+ a
452
+ ),
453
+ style: { maxHeight: y },
454
+ ...k,
455
+ children: [
456
+ /* @__PURE__ */ r.jsx(ie, { ref: E, className: "flex-1 p-4", children: /* @__PURE__ */ r.jsx("div", { className: "space-y-4", children: d.length === 0 ? /* @__PURE__ */ r.jsx("div", { className: "flex items-center justify-center h-32 text-muted-foreground text-sm", children: "No messages yet. Start a conversation!" }) : d.map((f) => /* @__PURE__ */ r.jsx(
457
+ ve,
458
+ {
459
+ message: f,
460
+ showTimestamp: u,
461
+ userAvatarUrl: s,
462
+ userAvatarFallback: p,
463
+ assistantAvatarUrl: v,
464
+ assistantAvatarFallback: x
465
+ },
466
+ f.id
467
+ )) }) }),
468
+ /* @__PURE__ */ r.jsx("div", { className: "border-t p-4", children: /* @__PURE__ */ r.jsxs("div", { className: "flex gap-2", children: [
469
+ /* @__PURE__ */ r.jsx(
470
+ ue,
471
+ {
472
+ ref: g,
473
+ value: b,
474
+ onChange: (f) => T(f.target.value),
475
+ onKeyDown: P,
476
+ placeholder: m,
477
+ disabled: o,
478
+ className: "flex-1"
479
+ }
480
+ ),
481
+ /* @__PURE__ */ r.jsx(
482
+ ce,
483
+ {
484
+ onClick: S,
485
+ disabled: o || !b.trim(),
486
+ size: "icon",
487
+ children: /* @__PURE__ */ r.jsx(de, { className: "h-4 w-4" })
488
+ }
489
+ )
490
+ ] }) })
491
+ ]
492
+ }
493
+ );
494
+ }
495
+ );
496
+ B.displayName = "Chatbot";
497
+ const ve = ({
498
+ message: a,
499
+ showTimestamp: d,
500
+ userAvatarUrl: m,
501
+ userAvatarFallback: c,
502
+ assistantAvatarUrl: o,
503
+ assistantAvatarFallback: u
504
+ }) => {
505
+ const s = a.role === "user";
506
+ if (a.role === "system")
507
+ return /* @__PURE__ */ r.jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ r.jsx("div", { className: "text-xs text-muted-foreground bg-muted px-3 py-1 rounded-full", children: a.content }) });
508
+ const v = s ? a.avatar || m : a.avatar || o, x = s ? a.avatarFallback || c : a.avatarFallback || u;
509
+ return /* @__PURE__ */ r.jsxs(
510
+ "div",
511
+ {
512
+ className: A(
513
+ "flex gap-3",
514
+ s ? "flex-row-reverse" : "flex-row"
515
+ ),
516
+ children: [
517
+ /* @__PURE__ */ r.jsxs(J, { className: "h-8 w-8", children: [
518
+ /* @__PURE__ */ r.jsx(G, { src: v }),
519
+ /* @__PURE__ */ r.jsx(X, { className: "text-xs", children: x })
520
+ ] }),
521
+ /* @__PURE__ */ r.jsxs("div", { className: A("flex flex-col gap-1", s ? "items-end" : "items-start"), children: [
522
+ /* @__PURE__ */ r.jsx(
523
+ "div",
524
+ {
525
+ className: A(
526
+ "rounded-lg px-4 py-2 max-w-[70%] break-words",
527
+ s ? "bg-primary text-primary-foreground" : "bg-muted"
528
+ ),
529
+ children: /* @__PURE__ */ r.jsx("p", { className: "text-sm whitespace-pre-wrap", children: a.content })
530
+ }
531
+ ),
532
+ d && a.timestamp && /* @__PURE__ */ r.jsx("span", { className: "text-xs text-muted-foreground", children: a.timestamp })
533
+ ] })
534
+ ]
535
+ }
536
+ );
537
+ }, xe = h.forwardRef(
538
+ ({ className: a, avatarSrc: d, avatarFallback: m = "AI", ...c }, o) => /* @__PURE__ */ r.jsxs("div", { ref: o, className: A("flex gap-3", a), ...c, children: [
539
+ /* @__PURE__ */ r.jsxs(J, { className: "h-8 w-8", children: [
540
+ /* @__PURE__ */ r.jsx(G, { src: d }),
541
+ /* @__PURE__ */ r.jsx(X, { className: "text-xs", children: m })
542
+ ] }),
543
+ /* @__PURE__ */ r.jsx("div", { className: "flex items-center bg-muted rounded-lg px-4 py-2", children: /* @__PURE__ */ r.jsxs("div", { className: "flex gap-1", children: [
544
+ /* @__PURE__ */ r.jsx("span", { className: "w-2 h-2 bg-muted-foreground rounded-full animate-bounce [animation-delay:-0.3s]" }),
545
+ /* @__PURE__ */ r.jsx("span", { className: "w-2 h-2 bg-muted-foreground rounded-full animate-bounce [animation-delay:-0.15s]" }),
546
+ /* @__PURE__ */ r.jsx("span", { className: "w-2 h-2 bg-muted-foreground rounded-full animate-bounce" })
547
+ ] }) })
548
+ ] })
549
+ );
550
+ xe.displayName = "TypingIndicator";
551
+ export {
552
+ B as Chatbot,
553
+ xe as TypingIndicator
554
+ };
@@ -0,0 +1,6 @@
1
+ (function(v,g){typeof exports=="object"&&typeof module<"u"?g(exports,require("react"),require("@object-ui/components"),require("lucide-react"),require("@object-ui/core")):typeof define=="function"&&define.amd?define(["exports","react","@object-ui/components","lucide-react","@object-ui/core"],g):(v=typeof globalThis<"u"?globalThis:v||self,g(v.ObjectUIPluginChatbot={},v.React,v.ObjectUIComponents,v.LucideReact,v.ObjectUICore))})(this,(function(v,g,m,Z,K){"use strict";function Q(a){const u=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(a){for(const c in a)if(c!=="default"){const d=Object.getOwnPropertyDescriptor(a,c);Object.defineProperty(u,c,d.get?d:{enumerable:!0,get:()=>a[c]})}}return u.default=a,Object.freeze(u)}const R=Q(g);var S={exports:{}},_={};var V;function ee(){if(V)return _;V=1;var a=Symbol.for("react.transitional.element"),u=Symbol.for("react.fragment");function c(d,o,f){var s=null;if(f!==void 0&&(s=""+f),o.key!==void 0&&(s=""+o.key),"key"in o){f={};for(var b in o)b!=="key"&&(f[b]=o[b])}else f=o;return o=f.ref,{$$typeof:a,type:d,key:s,ref:o!==void 0?o:null,props:f}}return _.Fragment=u,_.jsx=c,_.jsxs=c,_}var A={};var L;function ae(){return L||(L=1,process.env.NODE_ENV!=="production"&&(function(){function a(e){if(e==null)return null;if(typeof e=="function")return e.$$typeof===ce?null:e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case j:return"Fragment";case F:return"Profiler";case P:return"StrictMode";case oe:return"Suspense";case le:return"SuspenseList";case ue:return"Activity"}if(typeof e=="object")switch(typeof e.tag=="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),e.$$typeof){case k:return"Portal";case se:return e.displayName||"Context";case p:return(e._context.displayName||"Context")+".Consumer";case ne:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case ie:return t=e.displayName||null,t!==null?t:a(e.type)||"Memo";case D:t=e._payload,e=e._init;try{return a(e(t))}catch{}}return null}function u(e){return""+e}function c(e){try{u(e);var t=!1}catch{t=!0}if(t){t=console;var n=t.error,l=typeof Symbol=="function"&&Symbol.toStringTag&&e[Symbol.toStringTag]||e.constructor.name||"Object";return n.call(t,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",l),u(e)}}function d(e){if(e===j)return"<>";if(typeof e=="object"&&e!==null&&e.$$typeof===D)return"<...>";try{var t=a(e);return t?"<"+t+">":"<...>"}catch{return"<...>"}}function o(){var e=U.A;return e===null?null:e.getOwner()}function f(){return Error("react-stack-top-frame")}function s(e){if(H.call(e,"key")){var t=Object.getOwnPropertyDescriptor(e,"key").get;if(t&&t.isReactWarning)return!1}return e.key!==void 0}function b(e,t){function n(){z||(z=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",t))}n.isReactWarning=!0,Object.defineProperty(e,"key",{get:n,configurable:!0})}function y(){var e=a(this.type);return J[e]||(J[e]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),e=this.props.ref,e!==void 0?e:null}function h(e,t,n,l,C,Y){var i=n.ref;return e={$$typeof:w,type:e,key:t,props:n,_owner:l},(i!==void 0?i:null)!==null?Object.defineProperty(e,"ref",{enumerable:!1,get:y}):Object.defineProperty(e,"ref",{enumerable:!1,value:null}),e._store={},Object.defineProperty(e._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(e,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(e,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:C}),Object.defineProperty(e,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:Y}),Object.freeze&&(Object.freeze(e.props),Object.freeze(e)),e}function T(e,t,n,l,C,Y){var i=t.children;if(i!==void 0)if(l)if(de(i)){for(l=0;l<i.length;l++)N(i[l]);Object.freeze&&Object.freeze(i)}else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else N(i);if(H.call(t,"key")){i=a(e);var E=Object.keys(t).filter(function(fe){return fe!=="key"});l=0<E.length?"{key: someKey, "+E.join(": ..., ")+": ...}":"{key: someKey}",B[i+l]||(E=0<E.length?"{"+E.join(": ..., ")+": ...}":"{}",console.error(`A props object containing a "key" prop is being spread into JSX:
2
+ let props = %s;
3
+ <%s {...props} />
4
+ React keys must be passed directly to JSX without using spread:
5
+ let props = %s;
6
+ <%s key={someKey} {...props} />`,l,i,E,i),B[i+l]=!0)}if(i=null,n!==void 0&&(c(n),i=""+n),s(t)&&(c(t.key),i=""+t.key),"key"in t){n={};for(var $ in t)$!=="key"&&(n[$]=t[$])}else n=t;return i&&b(n,typeof e=="function"?e.displayName||e.name||"Unknown":e),h(e,i,n,o(),C,Y)}function N(e){O(e)?e._store&&(e._store.validated=1):typeof e=="object"&&e!==null&&e.$$typeof===D&&(e._payload.status==="fulfilled"?O(e._payload.value)&&e._payload.value._store&&(e._payload.value._store.validated=1):e._store&&(e._store.validated=1))}function O(e){return typeof e=="object"&&e!==null&&e.$$typeof===w}var x=g,w=Symbol.for("react.transitional.element"),k=Symbol.for("react.portal"),j=Symbol.for("react.fragment"),P=Symbol.for("react.strict_mode"),F=Symbol.for("react.profiler"),p=Symbol.for("react.consumer"),se=Symbol.for("react.context"),ne=Symbol.for("react.forward_ref"),oe=Symbol.for("react.suspense"),le=Symbol.for("react.suspense_list"),ie=Symbol.for("react.memo"),D=Symbol.for("react.lazy"),ue=Symbol.for("react.activity"),ce=Symbol.for("react.client.reference"),U=x.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,H=Object.prototype.hasOwnProperty,de=Array.isArray,M=console.createTask?console.createTask:function(){return null};x={react_stack_bottom_frame:function(e){return e()}};var z,J={},G=x.react_stack_bottom_frame.bind(x,f)(),X=M(d(f)),B={};A.Fragment=j,A.jsx=function(e,t,n){var l=1e4>U.recentlyCreatedOwnerStacks++;return T(e,t,n,!1,l?Error("react-stack-top-frame"):G,l?M(d(e)):X)},A.jsxs=function(e,t,n){var l=1e4>U.recentlyCreatedOwnerStacks++;return T(e,t,n,!0,l?Error("react-stack-top-frame"):G,l?M(d(e)):X)}})()),A}var q;function te(){return q||(q=1,process.env.NODE_ENV==="production"?S.exports=ee():S.exports=ae()),S.exports}var r=te();K.ComponentRegistry.register("chatbot",({schema:a,className:u,...c})=>{const[d,o]=g.useState(a.messages?.map((s,b)=>({id:s.id||`msg-${b}`,role:s.role||"user",content:s.content||"",timestamp:typeof s.timestamp=="string"?s.timestamp:s.timestamp instanceof Date?s.timestamp.toISOString():void 0,avatar:s.avatar,avatarFallback:s.avatarFallback}))||[]),f=s=>{const b={id:crypto?.randomUUID?.()||`msg-${Date.now()}-${Math.random().toString(36).slice(2)}`,role:"user",content:s,timestamp:a.showTimestamp?new Date().toLocaleTimeString():void 0},y=[...d,b];o(y),a.onSend&&a.onSend(s,y),a.autoResponse&&setTimeout(()=>{const h={id:crypto?.randomUUID?.()||`msg-${Date.now()}-${Math.random().toString(36).slice(2)}`,role:"assistant",content:a.autoResponseText||"Thank you for your message!",timestamp:a.showTimestamp?new Date().toLocaleTimeString():void 0};o(T=>[...T,h])},a.autoResponseDelay||1e3)};return r.jsx(I,{messages:d,placeholder:a.placeholder,onSendMessage:f,disabled:a.disabled,showTimestamp:a.showTimestamp,userAvatarUrl:a.userAvatarUrl,userAvatarFallback:a.userAvatarFallback,assistantAvatarUrl:a.assistantAvatarUrl,assistantAvatarFallback:a.assistantAvatarFallback,maxHeight:a.maxHeight,className:u,...c})},{label:"Chatbot",inputs:[{name:"messages",type:"array",label:"Initial Messages",description:"Array of message objects with id, role, content, and optional timestamp"},{name:"placeholder",type:"string",label:"Input Placeholder",defaultValue:"Type your message..."},{name:"showTimestamp",type:"boolean",label:"Show Timestamps",defaultValue:!1},{name:"disabled",type:"boolean",label:"Disabled",defaultValue:!1},{name:"userAvatarUrl",type:"string",label:"User Avatar URL",description:"URL of the user avatar image"},{name:"userAvatarFallback",type:"string",label:"User Avatar Fallback",defaultValue:"You",description:"Fallback text shown when user avatar image is not available"},{name:"assistantAvatarUrl",type:"string",label:"Assistant Avatar URL",description:"URL of the assistant avatar image"},{name:"assistantAvatarFallback",type:"string",label:"Assistant Avatar Fallback",defaultValue:"AI",description:"Fallback text shown when assistant avatar image is not available"},{name:"maxHeight",type:"string",label:"Max Height",defaultValue:"500px"},{name:"autoResponse",type:"boolean",label:"Enable Auto Response (Demo)",defaultValue:!1,description:"Automatically send a response after user message (for demo purposes)"},{name:"autoResponseText",type:"string",label:"Auto Response Text",defaultValue:"Thank you for your message!"},{name:"autoResponseDelay",type:"number",label:"Auto Response Delay (ms)",defaultValue:1e3},{name:"className",type:"string",label:"CSS Class"}],defaultProps:{messages:[{id:"welcome",role:"assistant",content:"Hello! How can I help you today?"}],placeholder:"Type your message...",showTimestamp:!1,disabled:!1,userAvatarFallback:"You",assistantAvatarFallback:"AI",maxHeight:"500px",autoResponse:!0,autoResponseText:"Thank you for your message! This is an automated response.",autoResponseDelay:1e3,className:"w-full max-w-2xl"}});const I=R.forwardRef(({className:a,messages:u=[],placeholder:c="Type your message...",onSendMessage:d,disabled:o=!1,showTimestamp:f=!1,userAvatarUrl:s,userAvatarFallback:b="You",assistantAvatarUrl:y,assistantAvatarFallback:h="AI",maxHeight:T="500px",...N},O)=>{const[x,w]=R.useState(""),k=R.useRef(null),j=R.useRef(null);R.useEffect(()=>{if(k.current){const p=k.current.querySelector("[data-radix-scroll-area-viewport]");p&&(p.scrollTop=p.scrollHeight)}},[u]);const P=()=>{x.trim()&&d&&(d(x.trim()),w(""),j.current?.focus())},F=p=>{p.key==="Enter"&&!p.shiftKey&&(p.preventDefault(),P())};return r.jsxs("div",{ref:O,className:m.cn("flex flex-col border rounded-lg bg-background overflow-hidden",a),style:{maxHeight:T},...N,children:[r.jsx(m.ScrollArea,{ref:k,className:"flex-1 p-4",children:r.jsx("div",{className:"space-y-4",children:u.length===0?r.jsx("div",{className:"flex items-center justify-center h-32 text-muted-foreground text-sm",children:"No messages yet. Start a conversation!"}):u.map(p=>r.jsx(re,{message:p,showTimestamp:f,userAvatarUrl:s,userAvatarFallback:b,assistantAvatarUrl:y,assistantAvatarFallback:h},p.id))})}),r.jsx("div",{className:"border-t p-4",children:r.jsxs("div",{className:"flex gap-2",children:[r.jsx(m.Input,{ref:j,value:x,onChange:p=>w(p.target.value),onKeyDown:F,placeholder:c,disabled:o,className:"flex-1"}),r.jsx(m.Button,{onClick:P,disabled:o||!x.trim(),size:"icon",children:r.jsx(Z.Send,{className:"h-4 w-4"})})]})})]})});I.displayName="Chatbot";const re=({message:a,showTimestamp:u,userAvatarUrl:c,userAvatarFallback:d,assistantAvatarUrl:o,assistantAvatarFallback:f})=>{const s=a.role==="user";if(a.role==="system")return r.jsx("div",{className:"flex justify-center",children:r.jsx("div",{className:"text-xs text-muted-foreground bg-muted px-3 py-1 rounded-full",children:a.content})});const y=s?a.avatar||c:a.avatar||o,h=s?a.avatarFallback||d:a.avatarFallback||f;return r.jsxs("div",{className:m.cn("flex gap-3",s?"flex-row-reverse":"flex-row"),children:[r.jsxs(m.Avatar,{className:"h-8 w-8",children:[r.jsx(m.AvatarImage,{src:y}),r.jsx(m.AvatarFallback,{className:"text-xs",children:h})]}),r.jsxs("div",{className:m.cn("flex flex-col gap-1",s?"items-end":"items-start"),children:[r.jsx("div",{className:m.cn("rounded-lg px-4 py-2 max-w-[70%] break-words",s?"bg-primary text-primary-foreground":"bg-muted"),children:r.jsx("p",{className:"text-sm whitespace-pre-wrap",children:a.content})}),u&&a.timestamp&&r.jsx("span",{className:"text-xs text-muted-foreground",children:a.timestamp})]})]})},W=R.forwardRef(({className:a,avatarSrc:u,avatarFallback:c="AI",...d},o)=>r.jsxs("div",{ref:o,className:m.cn("flex gap-3",a),...d,children:[r.jsxs(m.Avatar,{className:"h-8 w-8",children:[r.jsx(m.AvatarImage,{src:u}),r.jsx(m.AvatarFallback,{className:"text-xs",children:c})]}),r.jsx("div",{className:"flex items-center bg-muted rounded-lg px-4 py-2",children:r.jsxs("div",{className:"flex gap-1",children:[r.jsx("span",{className:"w-2 h-2 bg-muted-foreground rounded-full animate-bounce [animation-delay:-0.3s]"}),r.jsx("span",{className:"w-2 h-2 bg-muted-foreground rounded-full animate-bounce [animation-delay:-0.15s]"}),r.jsx("span",{className:"w-2 h-2 bg-muted-foreground rounded-full animate-bounce"})]})})]}));W.displayName="TypingIndicator",v.Chatbot=I,v.TypingIndicator=W,Object.defineProperty(v,Symbol.toStringTag,{value:"Module"})}));
@@ -0,0 +1,45 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ import * as React from "react";
9
+ export interface ChatMessage {
10
+ id: string;
11
+ role: "user" | "assistant" | "system";
12
+ content: string;
13
+ timestamp?: string;
14
+ avatar?: string;
15
+ avatarFallback?: string;
16
+ }
17
+ export interface ChatbotProps extends React.HTMLAttributes<HTMLDivElement> {
18
+ messages?: ChatMessage[];
19
+ placeholder?: string;
20
+ onSendMessage?: (message: string) => void;
21
+ disabled?: boolean;
22
+ showTimestamp?: boolean;
23
+ userAvatarUrl?: string;
24
+ userAvatarFallback?: string;
25
+ assistantAvatarUrl?: string;
26
+ assistantAvatarFallback?: string;
27
+ maxHeight?: string;
28
+ }
29
+ declare const Chatbot: React.ForwardRefExoticComponent<ChatbotProps & React.RefAttributes<HTMLDivElement>>;
30
+ export interface ChatMessageProps {
31
+ message: ChatMessage;
32
+ showTimestamp?: boolean;
33
+ userAvatarUrl?: string;
34
+ userAvatarFallback?: string;
35
+ assistantAvatarUrl?: string;
36
+ assistantAvatarFallback?: string;
37
+ }
38
+ export interface TypingIndicatorProps extends React.HTMLAttributes<HTMLDivElement> {
39
+ avatarSrc?: string;
40
+ avatarFallback?: string;
41
+ }
42
+ declare const TypingIndicator: React.ForwardRefExoticComponent<TypingIndicatorProps & React.RefAttributes<HTMLDivElement>>;
43
+ export { Chatbot, TypingIndicator };
44
+ export * from './renderer';
45
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAA;IACrC,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAGD,MAAM,WAAW,YAAa,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACxE,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;IACxB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAGD,QAAA,MAAM,OAAO,qFAwGZ,CAAA;AAID,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,WAAW,CAAA;IACpB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,uBAAuB,CAAC,EAAE,MAAM,CAAA;CACjC;AAiED,MAAM,WAAW,oBAAqB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAChF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,QAAA,MAAM,eAAe,6FAkBpB,CAAA;AAGD,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,CAAA;AAGnC,cAAc,YAAY,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/renderer.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@object-ui/plugin-chatbot",
3
+ "version": "0.3.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "description": "Chatbot interface plugin for Object UI",
7
+ "homepage": "https://www.objectui.org",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/objectstack-ai/objectui.git",
11
+ "directory": "packages/plugin-chatbot"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/objectstack-ai/objectui/issues"
15
+ },
16
+ "main": "dist/index.umd.cjs",
17
+ "module": "dist/index.js",
18
+ "types": "dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js",
23
+ "require": "./dist/index.umd.cjs"
24
+ }
25
+ },
26
+ "dependencies": {
27
+ "lucide-react": "^0.563.0",
28
+ "@object-ui/components": "0.3.0",
29
+ "@object-ui/core": "0.3.0",
30
+ "@object-ui/react": "0.3.0",
31
+ "@object-ui/types": "0.3.0"
32
+ },
33
+ "peerDependencies": {
34
+ "react": "^18.0.0 || ^19.0.0",
35
+ "react-dom": "^18.0.0 || ^19.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/react": "^19.2.9",
39
+ "@types/react-dom": "^19.2.3",
40
+ "@vitejs/plugin-react": "^4.2.1",
41
+ "typescript": "^5.9.3",
42
+ "vite": "^7.3.1",
43
+ "vite-plugin-dts": "^4.5.4"
44
+ },
45
+ "scripts": {
46
+ "build": "vite build",
47
+ "test": "vitest run",
48
+ "test:watch": "vitest",
49
+ "type-check": "tsc --noEmit",
50
+ "lint": "eslint ."
51
+ }
52
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,248 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ import * as React from "react"
10
+ import { cn } from "@object-ui/components"
11
+ import { Button, Input, ScrollArea, Avatar, AvatarFallback, AvatarImage } from "@object-ui/components"
12
+ import { Send } from "lucide-react"
13
+
14
+ // Message type definition
15
+ export interface ChatMessage {
16
+ id: string
17
+ role: "user" | "assistant" | "system"
18
+ content: string
19
+ timestamp?: string
20
+ avatar?: string
21
+ avatarFallback?: string
22
+ }
23
+
24
+ // Chatbot container props
25
+ export interface ChatbotProps extends React.HTMLAttributes<HTMLDivElement> {
26
+ messages?: ChatMessage[]
27
+ placeholder?: string
28
+ onSendMessage?: (message: string) => void
29
+ disabled?: boolean
30
+ showTimestamp?: boolean
31
+ userAvatarUrl?: string
32
+ userAvatarFallback?: string
33
+ assistantAvatarUrl?: string
34
+ assistantAvatarFallback?: string
35
+ maxHeight?: string
36
+ }
37
+
38
+ // Chatbot container component
39
+ const Chatbot = React.forwardRef<HTMLDivElement, ChatbotProps>(
40
+ (
41
+ {
42
+ className,
43
+ messages = [],
44
+ placeholder = "Type your message...",
45
+ onSendMessage,
46
+ disabled = false,
47
+ showTimestamp = false,
48
+ userAvatarUrl,
49
+ userAvatarFallback = "You",
50
+ assistantAvatarUrl,
51
+ assistantAvatarFallback = "AI",
52
+ maxHeight = "500px",
53
+ ...props
54
+ },
55
+ ref
56
+ ) => {
57
+ const [inputValue, setInputValue] = React.useState("")
58
+ const scrollRef = React.useRef<HTMLDivElement>(null)
59
+ const inputRef = React.useRef<HTMLInputElement>(null)
60
+
61
+ // Auto-scroll to bottom when new messages arrive
62
+ React.useEffect(() => {
63
+ if (scrollRef.current) {
64
+ const scrollElement = scrollRef.current.querySelector('[data-radix-scroll-area-viewport]')
65
+ if (scrollElement) {
66
+ scrollElement.scrollTop = scrollElement.scrollHeight
67
+ }
68
+ }
69
+ }, [messages])
70
+
71
+ const handleSend = () => {
72
+ if (inputValue.trim() && onSendMessage) {
73
+ onSendMessage(inputValue.trim())
74
+ setInputValue("")
75
+ inputRef.current?.focus()
76
+ }
77
+ }
78
+
79
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
80
+ if (e.key === "Enter" && !e.shiftKey) {
81
+ e.preventDefault()
82
+ handleSend()
83
+ }
84
+ }
85
+
86
+ return (
87
+ <div
88
+ ref={ref}
89
+ className={cn(
90
+ "flex flex-col border rounded-lg bg-background overflow-hidden",
91
+ className
92
+ )}
93
+ style={{ maxHeight }}
94
+ {...props}
95
+ >
96
+ {/* Messages area */}
97
+ <ScrollArea ref={scrollRef} className="flex-1 p-4">
98
+ <div className="space-y-4">
99
+ {messages.length === 0 ? (
100
+ <div className="flex items-center justify-center h-32 text-muted-foreground text-sm">
101
+ No messages yet. Start a conversation!
102
+ </div>
103
+ ) : (
104
+ messages.map((message) => (
105
+ <ChatMessageItem
106
+ key={message.id}
107
+ message={message}
108
+ showTimestamp={showTimestamp}
109
+ userAvatarUrl={userAvatarUrl}
110
+ userAvatarFallback={userAvatarFallback}
111
+ assistantAvatarUrl={assistantAvatarUrl}
112
+ assistantAvatarFallback={assistantAvatarFallback}
113
+ />
114
+ ))
115
+ )}
116
+ </div>
117
+ </ScrollArea>
118
+
119
+ {/* Input area */}
120
+ <div className="border-t p-4">
121
+ <div className="flex gap-2">
122
+ <Input
123
+ ref={inputRef}
124
+ value={inputValue}
125
+ onChange={(e) => setInputValue(e.target.value)}
126
+ onKeyDown={handleKeyDown}
127
+ placeholder={placeholder}
128
+ disabled={disabled}
129
+ className="flex-1"
130
+ />
131
+ <Button
132
+ onClick={handleSend}
133
+ disabled={disabled || !inputValue.trim()}
134
+ size="icon"
135
+ >
136
+ <Send className="h-4 w-4" />
137
+ </Button>
138
+ </div>
139
+ </div>
140
+ </div>
141
+ )
142
+ }
143
+ )
144
+ Chatbot.displayName = "Chatbot"
145
+
146
+ // Individual message component
147
+ export interface ChatMessageProps {
148
+ message: ChatMessage
149
+ showTimestamp?: boolean
150
+ userAvatarUrl?: string
151
+ userAvatarFallback?: string
152
+ assistantAvatarUrl?: string
153
+ assistantAvatarFallback?: string
154
+ }
155
+
156
+ const ChatMessageItem: React.FC<ChatMessageProps> = ({
157
+ message,
158
+ showTimestamp,
159
+ userAvatarUrl,
160
+ userAvatarFallback,
161
+ assistantAvatarUrl,
162
+ assistantAvatarFallback,
163
+ }) => {
164
+ const isUser = message.role === "user"
165
+ const isSystem = message.role === "system"
166
+
167
+ if (isSystem) {
168
+ return (
169
+ <div className="flex justify-center">
170
+ <div className="text-xs text-muted-foreground bg-muted px-3 py-1 rounded-full">
171
+ {message.content}
172
+ </div>
173
+ </div>
174
+ )
175
+ }
176
+
177
+ const avatar = isUser
178
+ ? (message.avatar || userAvatarUrl)
179
+ : (message.avatar || assistantAvatarUrl)
180
+
181
+ const avatarFallback = isUser
182
+ ? (message.avatarFallback || userAvatarFallback)
183
+ : (message.avatarFallback || assistantAvatarFallback)
184
+
185
+ return (
186
+ <div
187
+ className={cn(
188
+ "flex gap-3",
189
+ isUser ? "flex-row-reverse" : "flex-row"
190
+ )}
191
+ >
192
+ <Avatar className="h-8 w-8">
193
+ <AvatarImage src={avatar} />
194
+ <AvatarFallback className="text-xs">{avatarFallback}</AvatarFallback>
195
+ </Avatar>
196
+
197
+ <div className={cn("flex flex-col gap-1", isUser ? "items-end" : "items-start")}>
198
+ <div
199
+ className={cn(
200
+ "rounded-lg px-4 py-2 max-w-[70%] break-words",
201
+ isUser
202
+ ? "bg-primary text-primary-foreground"
203
+ : "bg-muted"
204
+ )}
205
+ >
206
+ <p className="text-sm whitespace-pre-wrap">{message.content}</p>
207
+ </div>
208
+ {showTimestamp && message.timestamp && (
209
+ <span className="text-xs text-muted-foreground">
210
+ {message.timestamp}
211
+ </span>
212
+ )}
213
+ </div>
214
+ </div>
215
+ )
216
+ }
217
+
218
+ // Typing indicator component
219
+ export interface TypingIndicatorProps extends React.HTMLAttributes<HTMLDivElement> {
220
+ avatarSrc?: string
221
+ avatarFallback?: string
222
+ }
223
+
224
+ const TypingIndicator = React.forwardRef<HTMLDivElement, TypingIndicatorProps>(
225
+ ({ className, avatarSrc, avatarFallback = "AI", ...props }, ref) => {
226
+ return (
227
+ <div ref={ref} className={cn("flex gap-3", className)} {...props}>
228
+ <Avatar className="h-8 w-8">
229
+ <AvatarImage src={avatarSrc} />
230
+ <AvatarFallback className="text-xs">{avatarFallback}</AvatarFallback>
231
+ </Avatar>
232
+ <div className="flex items-center bg-muted rounded-lg px-4 py-2">
233
+ <div className="flex gap-1">
234
+ <span className="w-2 h-2 bg-muted-foreground rounded-full animate-bounce [animation-delay:-0.3s]"></span>
235
+ <span className="w-2 h-2 bg-muted-foreground rounded-full animate-bounce [animation-delay:-0.15s]"></span>
236
+ <span className="w-2 h-2 bg-muted-foreground rounded-full animate-bounce"></span>
237
+ </div>
238
+ </div>
239
+ </div>
240
+ )
241
+ }
242
+ )
243
+ TypingIndicator.displayName = "TypingIndicator"
244
+
245
+ export { Chatbot, TypingIndicator }
246
+
247
+ // Export renderer to register the component with ObjectUI
248
+ export * from './renderer';
@@ -0,0 +1,193 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ import { ComponentRegistry } from '@object-ui/core';
10
+ import type { ChatbotSchema, ChatMessage } from '@object-ui/types';
11
+ import { Chatbot } from './index';
12
+ import { useState } from 'react';
13
+
14
+ /**
15
+ * Chatbot component for Object UI
16
+ *
17
+ * @remarks
18
+ * This component supports an optional `onSend` callback in the schema:
19
+ * - Signature: `onSend(content: string, messages: ChatMessage[]): void`
20
+ * - Parameters:
21
+ * - content: The message text that was sent
22
+ * - messages: Array of all messages including the newly added message
23
+ * - Called when: User sends a message via the input field
24
+ * - Use case: Connect to backend API or trigger custom actions on message send
25
+ */
26
+ ComponentRegistry.register('chatbot',
27
+ ({ schema, className, ...props }: { schema: ChatbotSchema; className?: string; [key: string]: any }) => {
28
+ // Initialize messages from schema or use empty array
29
+ const [messages, setMessages] = useState<ChatMessage[]>(
30
+ schema.messages?.map((msg: any, idx: number) => ({
31
+ id: msg.id || `msg-${idx}`,
32
+ role: msg.role || 'user',
33
+ content: msg.content || '',
34
+ timestamp: typeof msg.timestamp === 'string' ? msg.timestamp : (msg.timestamp instanceof Date ? msg.timestamp.toISOString() : undefined),
35
+ avatar: msg.avatar,
36
+ avatarFallback: msg.avatarFallback,
37
+ })) || []
38
+ );
39
+
40
+ // Handle sending new messages
41
+ const handleSendMessage = (content: string) => {
42
+ // Create user message with robust ID generation
43
+ const userMessage: ChatMessage = {
44
+ id: crypto?.randomUUID?.() || `msg-${Date.now()}-${Math.random().toString(36).slice(2)}`,
45
+ role: 'user',
46
+ content,
47
+ timestamp: schema.showTimestamp ? new Date().toLocaleTimeString() : undefined,
48
+ };
49
+
50
+ const updatedMessages = [...messages, userMessage];
51
+ setMessages(updatedMessages);
52
+
53
+ // If onSend callback is provided in schema, call it with updated messages
54
+ if (schema.onSend) {
55
+ schema.onSend(content, updatedMessages);
56
+ }
57
+
58
+ // Auto-response feature for demo purposes
59
+ if (schema.autoResponse) {
60
+ setTimeout(() => {
61
+ const assistantMessage: ChatMessage = {
62
+ id: crypto?.randomUUID?.() || `msg-${Date.now()}-${Math.random().toString(36).slice(2)}`,
63
+ role: 'assistant',
64
+ content: schema.autoResponseText || 'Thank you for your message!',
65
+ timestamp: schema.showTimestamp ? new Date().toLocaleTimeString() : undefined,
66
+ };
67
+ setMessages((prev) => [...prev, assistantMessage]);
68
+ }, schema.autoResponseDelay || 1000);
69
+ }
70
+ };
71
+
72
+ return (
73
+ <Chatbot
74
+ messages={messages as any}
75
+ placeholder={schema.placeholder}
76
+ onSendMessage={handleSendMessage}
77
+ disabled={schema.disabled}
78
+ showTimestamp={schema.showTimestamp}
79
+ userAvatarUrl={schema.userAvatarUrl}
80
+ userAvatarFallback={schema.userAvatarFallback}
81
+ assistantAvatarUrl={schema.assistantAvatarUrl}
82
+ assistantAvatarFallback={schema.assistantAvatarFallback}
83
+ maxHeight={schema.maxHeight}
84
+ className={className}
85
+ {...props}
86
+ />
87
+ );
88
+ },
89
+ {
90
+ label: 'Chatbot',
91
+ inputs: [
92
+ {
93
+ name: 'messages',
94
+ type: 'array',
95
+ label: 'Initial Messages',
96
+ description: 'Array of message objects with id, role, content, and optional timestamp'
97
+ },
98
+ {
99
+ name: 'placeholder',
100
+ type: 'string',
101
+ label: 'Input Placeholder',
102
+ defaultValue: 'Type your message...'
103
+ },
104
+ {
105
+ name: 'showTimestamp',
106
+ type: 'boolean',
107
+ label: 'Show Timestamps',
108
+ defaultValue: false
109
+ },
110
+ {
111
+ name: 'disabled',
112
+ type: 'boolean',
113
+ label: 'Disabled',
114
+ defaultValue: false
115
+ },
116
+ {
117
+ name: 'userAvatarUrl',
118
+ type: 'string',
119
+ label: 'User Avatar URL',
120
+ description: 'URL of the user avatar image'
121
+ },
122
+ {
123
+ name: 'userAvatarFallback',
124
+ type: 'string',
125
+ label: 'User Avatar Fallback',
126
+ defaultValue: 'You',
127
+ description: 'Fallback text shown when user avatar image is not available'
128
+ },
129
+ {
130
+ name: 'assistantAvatarUrl',
131
+ type: 'string',
132
+ label: 'Assistant Avatar URL',
133
+ description: 'URL of the assistant avatar image'
134
+ },
135
+ {
136
+ name: 'assistantAvatarFallback',
137
+ type: 'string',
138
+ label: 'Assistant Avatar Fallback',
139
+ defaultValue: 'AI',
140
+ description: 'Fallback text shown when assistant avatar image is not available'
141
+ },
142
+ {
143
+ name: 'maxHeight',
144
+ type: 'string',
145
+ label: 'Max Height',
146
+ defaultValue: '500px'
147
+ },
148
+ {
149
+ name: 'autoResponse',
150
+ type: 'boolean',
151
+ label: 'Enable Auto Response (Demo)',
152
+ defaultValue: false,
153
+ description: 'Automatically send a response after user message (for demo purposes)'
154
+ },
155
+ {
156
+ name: 'autoResponseText',
157
+ type: 'string',
158
+ label: 'Auto Response Text',
159
+ defaultValue: 'Thank you for your message!'
160
+ },
161
+ {
162
+ name: 'autoResponseDelay',
163
+ type: 'number',
164
+ label: 'Auto Response Delay (ms)',
165
+ defaultValue: 1000
166
+ },
167
+ {
168
+ name: 'className',
169
+ type: 'string',
170
+ label: 'CSS Class'
171
+ }
172
+ ],
173
+ defaultProps: {
174
+ messages: [
175
+ {
176
+ id: 'welcome',
177
+ role: 'assistant',
178
+ content: 'Hello! How can I help you today?',
179
+ }
180
+ ],
181
+ placeholder: 'Type your message...',
182
+ showTimestamp: false,
183
+ disabled: false,
184
+ userAvatarFallback: 'You',
185
+ assistantAvatarFallback: 'AI',
186
+ maxHeight: '500px',
187
+ autoResponse: true,
188
+ autoResponseText: 'Thank you for your message! This is an automated response.',
189
+ autoResponseDelay: 1000,
190
+ className: 'w-full max-w-2xl'
191
+ }
192
+ }
193
+ );
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "jsx": "react-jsx",
6
+ "baseUrl": ".",
7
+ "paths": {
8
+ "@/*": ["src/*"]
9
+ },
10
+ "noEmit": false,
11
+ "declaration": true,
12
+ "composite": true,
13
+ "declarationMap": true,
14
+ "skipLibCheck": true
15
+ },
16
+ "include": ["src"],
17
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"]
18
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ import { defineConfig } from 'vite';
10
+ import react from '@vitejs/plugin-react';
11
+ import dts from 'vite-plugin-dts';
12
+ import { resolve } from 'path';
13
+
14
+ export default defineConfig({
15
+ plugins: [
16
+ react(),
17
+ dts({
18
+ insertTypesEntry: true,
19
+ include: ['src'],
20
+ exclude: ['**/*.test.ts', '**/*.test.tsx', 'node_modules'],
21
+ skipDiagnostics: true,
22
+ }),
23
+ ],
24
+ resolve: {
25
+ alias: {
26
+ '@': resolve(__dirname, './src'),
27
+ },
28
+ },
29
+ build: {
30
+ lib: {
31
+ entry: resolve(__dirname, 'src/index.tsx'),
32
+ name: 'ObjectUIPluginChatbot',
33
+ fileName: 'index',
34
+ },
35
+ rollupOptions: {
36
+ external: ['react', 'react-dom', '@object-ui/components', '@object-ui/core', '@object-ui/react', 'lucide-react'],
37
+ output: {
38
+ globals: {
39
+ react: 'React',
40
+ 'react-dom': 'ReactDOM',
41
+ '@object-ui/components': 'ObjectUIComponents',
42
+ '@object-ui/core': 'ObjectUICore',
43
+ '@object-ui/react': 'ObjectUIReact',
44
+ 'lucide-react': 'LucideReact',
45
+ },
46
+ },
47
+ },
48
+ },
49
+ });