@openchatwidget/sdk 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/dist/index.js ADDED
@@ -0,0 +1,1135 @@
1
+ // widget/src/OpenChatWidget.tsx
2
+ import * as React2 from "react";
3
+ import { useChat } from "@ai-sdk/react";
4
+ import { DefaultChatTransport } from "ai";
5
+
6
+ // widget/src/components/ChatToggleButton.tsx
7
+ import * as React from "react";
8
+
9
+ // widget/src/theme.ts
10
+ var HELPFUL_CHAT_LOGO_DATA_URI = "data:image/svg+xml,%3Csvg width='200' height='200' viewBox='0 0 200 200' fill='none' xmlns='http://www.w3.org/2000/svg'%3E %3Cg clip-path='url(%23clip0_10_20)'%3E %3Cmask id='mask0_10_20' style='mask-type:luminance' maskUnits='userSpaceOnUse' x='-20' y='0' width='240' height='200'%3E %3Cpath d='M220 0H-20V200H220V0Z' fill='white'/%3E %3Cpath d='M-20 76C0 64 20 88 40 76C60 64 80 88 100 76C120 64 140 88 160 76C180 64 200 88 220 76V102C200 114 180 90 160 102C140 114 120 90 100 102C80 114 60 90 40 102C20 114 0 90 -20 102V76Z' fill='black'/%3E %3C/mask%3E %3Cg mask='url(%23mask0_10_20)'%3E %3Cpath d='M100 156C143.078 156 178 125.555 178 88C178 50.4446 143.078 20 100 20C56.9218 20 22 50.4446 22 88C22 125.555 56.9218 156 100 156Z' fill='%2300E46A'/%3E %3Cpath d='M76 146L64 184L106 152L76 146Z' fill='%2300E46A'/%3E %3C/g%3E %3C/g%3E %3Cdefs%3E %3CclipPath id='clip0_10_20'%3E %3Crect width='200' height='200' fill='white'/%3E %3C/clipPath%3E %3C/defs%3E %3C/svg%3E";
11
+ var OPENCHAT_THEME_SCOPE = "[data-openchatwidget-root]";
12
+ function buildOpenChatWidgetThemeCss(scopeSelector = OPENCHAT_THEME_SCOPE) {
13
+ return `
14
+ @import url("https://fonts.googleapis.com/css2?family=Sora:wght@600;700&family=Space+Grotesk:wght@400;500;700&display=swap");
15
+
16
+ ${scopeSelector},
17
+ ${scopeSelector} *,
18
+ ${scopeSelector} *::before,
19
+ ${scopeSelector} *::after {
20
+ box-sizing: border-box;
21
+ }
22
+
23
+ ${scopeSelector} {
24
+ color: #1b1d22;
25
+ font-size: 16px;
26
+ line-height: 1.4;
27
+ font-family: "Space Grotesk", "Avenir Next", "Segoe UI", sans-serif;
28
+ text-size-adjust: 100%;
29
+ -webkit-text-size-adjust: 100%;
30
+ -webkit-font-smoothing: antialiased;
31
+ -moz-osx-font-smoothing: grayscale;
32
+ }
33
+
34
+ ${scopeSelector} button,
35
+ ${scopeSelector} input,
36
+ ${scopeSelector} select,
37
+ ${scopeSelector} textarea {
38
+ font: inherit;
39
+ color: inherit;
40
+ }
41
+
42
+ @keyframes helpfulChatDotPulse {
43
+ 0%, 100% {
44
+ opacity: 0.2;
45
+ transform: translateY(0);
46
+ }
47
+ 50% {
48
+ opacity: 1;
49
+ transform: translateY(-2px);
50
+ }
51
+ }
52
+
53
+ @keyframes helpfulChatEmptyFadeIn {
54
+ from {
55
+ opacity: 0;
56
+ transform: translateY(4px);
57
+ }
58
+ to {
59
+ opacity: 1;
60
+ transform: translateY(0);
61
+ }
62
+ }
63
+ `;
64
+ }
65
+
66
+ // widget/src/components/ChatToggleButton.tsx
67
+ import { jsx, jsxs } from "react/jsx-runtime";
68
+ var DEFAULT_LOGO_SRC = HELPFUL_CHAT_LOGO_DATA_URI;
69
+ var BUTTON_SIZE_PX = 68;
70
+ var ICON_SIZE_PX = 54;
71
+ var CLOSE_ICON_SIZE_PX = 22;
72
+ var MOBILE_BUTTON_SIZE_PX = 56;
73
+ var MOBILE_ICON_SIZE_PX = 42;
74
+ var MOBILE_CLOSE_ICON_SIZE_PX = 18;
75
+ function CloseIcon({ size }) {
76
+ return /* @__PURE__ */ jsx(
77
+ "svg",
78
+ {
79
+ viewBox: "0 0 24 24",
80
+ width: size,
81
+ height: size,
82
+ fill: "none",
83
+ "aria-hidden": "true",
84
+ style: { display: "block" },
85
+ children: /* @__PURE__ */ jsx(
86
+ "path",
87
+ {
88
+ d: "M6 6l12 12M18 6 6 18",
89
+ stroke: "currentColor",
90
+ strokeWidth: "2",
91
+ strokeLinecap: "round"
92
+ }
93
+ )
94
+ }
95
+ );
96
+ }
97
+ var ChatToggleButton = React.forwardRef(function ChatToggleButton2({
98
+ isOpen,
99
+ onToggle,
100
+ isMobile = false,
101
+ logoSrc = DEFAULT_LOGO_SRC,
102
+ unreadCount = 0
103
+ }, ref) {
104
+ const [logoLoadFailed, setLogoLoadFailed] = React.useState(false);
105
+ const buttonSize = isMobile ? MOBILE_BUTTON_SIZE_PX : BUTTON_SIZE_PX;
106
+ const iconSize = isMobile ? MOBILE_ICON_SIZE_PX : ICON_SIZE_PX;
107
+ const closeIconSize = isMobile ? MOBILE_CLOSE_ICON_SIZE_PX : CLOSE_ICON_SIZE_PX;
108
+ const offset = isMobile ? "12px" : "16px";
109
+ const hasUnread = !isOpen && unreadCount > 0;
110
+ const unreadLabel = unreadCount > 99 ? "99+" : String(unreadCount);
111
+ return /* @__PURE__ */ jsxs(
112
+ "button",
113
+ {
114
+ ref,
115
+ type: "button",
116
+ onClick: onToggle,
117
+ "aria-label": isOpen ? "Close chat" : "Open chat",
118
+ style: {
119
+ position: "fixed",
120
+ right: offset,
121
+ bottom: offset,
122
+ borderRadius: "9999px",
123
+ border: "none",
124
+ width: `${buttonSize}px`,
125
+ height: `${buttonSize}px`,
126
+ padding: "0",
127
+ background: isOpen ? "#111827" : "#00E46A",
128
+ color: "#ffffff",
129
+ cursor: "pointer",
130
+ zIndex: 1e3,
131
+ display: "flex",
132
+ alignItems: "center",
133
+ justifyContent: "center"
134
+ },
135
+ children: [
136
+ hasUnread ? /* @__PURE__ */ jsx(
137
+ "span",
138
+ {
139
+ "aria-label": `${unreadCount} unread support message${unreadCount === 1 ? "" : "s"}`,
140
+ style: {
141
+ position: "absolute",
142
+ top: isMobile ? "-2px" : "0px",
143
+ right: isMobile ? "-2px" : "0px",
144
+ minWidth: isMobile ? "18px" : "20px",
145
+ height: isMobile ? "18px" : "20px",
146
+ borderRadius: "9999px",
147
+ padding: "0 5px",
148
+ background: "#ef4444",
149
+ color: "#ffffff",
150
+ fontSize: isMobile ? "10px" : "11px",
151
+ fontWeight: 700,
152
+ lineHeight: 1,
153
+ border: "1.5px solid #ffffff",
154
+ display: "inline-flex",
155
+ alignItems: "center",
156
+ justifyContent: "center"
157
+ },
158
+ children: unreadLabel
159
+ }
160
+ ) : null,
161
+ isOpen ? /* @__PURE__ */ jsx(CloseIcon, { size: closeIconSize }) : !logoLoadFailed ? /* @__PURE__ */ jsx(
162
+ "img",
163
+ {
164
+ src: logoSrc,
165
+ alt: "",
166
+ "aria-hidden": "true",
167
+ width: iconSize,
168
+ height: iconSize,
169
+ onError: () => setLogoLoadFailed(true),
170
+ style: {
171
+ display: "block",
172
+ filter: "brightness(0) invert(1)"
173
+ }
174
+ }
175
+ ) : /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { fontSize: "24px", lineHeight: 1 }, children: "+" })
176
+ ]
177
+ }
178
+ );
179
+ });
180
+
181
+ // widget/src/components/WidgetPanel.tsx
182
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
183
+ var DEFAULT_LOGO_SRC2 = HELPFUL_CHAT_LOGO_DATA_URI;
184
+ function WidgetPanel({
185
+ isOpen,
186
+ isMobileViewport,
187
+ title,
188
+ onClose,
189
+ panelStyle,
190
+ panelRef,
191
+ logoSrc = DEFAULT_LOGO_SRC2,
192
+ activeSupport,
193
+ children
194
+ }) {
195
+ if (!isOpen) {
196
+ return null;
197
+ }
198
+ return /* @__PURE__ */ jsxs2("section", { ref: panelRef, style: panelStyle, "aria-label": title, children: [
199
+ /* @__PURE__ */ jsxs2(
200
+ "header",
201
+ {
202
+ style: {
203
+ padding: isMobileViewport ? "calc(8px + env(safe-area-inset-top, 0px)) 14px 8px" : "14px 16px 8px",
204
+ display: "grid",
205
+ gridTemplateColumns: "36px minmax(0, 1fr) 36px",
206
+ alignItems: "center",
207
+ gap: "10px"
208
+ },
209
+ children: [
210
+ /* @__PURE__ */ jsx2(
211
+ "img",
212
+ {
213
+ src: logoSrc,
214
+ alt: "",
215
+ "aria-hidden": "true",
216
+ width: 36,
217
+ height: 36,
218
+ style: { display: "block" }
219
+ }
220
+ ),
221
+ activeSupport ? /* @__PURE__ */ jsxs2(
222
+ "div",
223
+ {
224
+ style: {
225
+ display: "flex",
226
+ alignItems: "center",
227
+ justifyContent: "center",
228
+ gap: "8px",
229
+ minWidth: 0
230
+ },
231
+ children: [
232
+ /* @__PURE__ */ jsxs2(
233
+ "div",
234
+ {
235
+ style: {
236
+ position: "relative",
237
+ width: "30px",
238
+ height: "30px",
239
+ borderRadius: "9999px",
240
+ overflow: "hidden",
241
+ background: "#e2e8f0",
242
+ display: "flex",
243
+ alignItems: "center",
244
+ justifyContent: "center",
245
+ color: "#334155",
246
+ fontSize: "11px",
247
+ fontWeight: 600,
248
+ flexShrink: 0
249
+ },
250
+ children: [
251
+ activeSupport.pictureUrl ? /* @__PURE__ */ jsx2(
252
+ "img",
253
+ {
254
+ src: activeSupport.pictureUrl,
255
+ alt: "",
256
+ "aria-hidden": "true",
257
+ width: 30,
258
+ height: 30,
259
+ style: { display: "block", width: "100%", height: "100%", objectFit: "cover" }
260
+ }
261
+ ) : /* @__PURE__ */ jsx2("span", { children: activeSupport.name.slice(0, 1).toUpperCase() }),
262
+ activeSupport.isOnline ? /* @__PURE__ */ jsx2(
263
+ "span",
264
+ {
265
+ "aria-hidden": "true",
266
+ style: {
267
+ position: "absolute",
268
+ right: "1px",
269
+ bottom: "1px",
270
+ width: "8px",
271
+ height: "8px",
272
+ borderRadius: "9999px",
273
+ background: "#22c55e",
274
+ border: "1.5px solid #ffffff"
275
+ }
276
+ }
277
+ ) : null
278
+ ]
279
+ }
280
+ ),
281
+ /* @__PURE__ */ jsxs2(
282
+ "div",
283
+ {
284
+ style: {
285
+ minWidth: 0
286
+ },
287
+ children: [
288
+ /* @__PURE__ */ jsx2(
289
+ "p",
290
+ {
291
+ style: {
292
+ margin: 0,
293
+ fontSize: isMobileViewport ? "12px" : "13px",
294
+ lineHeight: 1.2,
295
+ color: "#0f172a",
296
+ fontWeight: 600,
297
+ whiteSpace: "nowrap",
298
+ overflow: "hidden",
299
+ textOverflow: "ellipsis"
300
+ },
301
+ children: activeSupport.name
302
+ }
303
+ ),
304
+ /* @__PURE__ */ jsx2(
305
+ "p",
306
+ {
307
+ style: {
308
+ margin: "1px 0 0",
309
+ fontSize: "11px",
310
+ lineHeight: 1.2,
311
+ color: activeSupport.isOnline ? "#166534" : "#64748b",
312
+ whiteSpace: "nowrap",
313
+ overflow: "hidden",
314
+ textOverflow: "ellipsis"
315
+ },
316
+ children: activeSupport.statusLabel ?? (activeSupport.isOnline ? "Online now" : "Offline")
317
+ }
318
+ )
319
+ ]
320
+ }
321
+ )
322
+ ]
323
+ }
324
+ ) : /* @__PURE__ */ jsx2("div", {}),
325
+ /* @__PURE__ */ jsx2(
326
+ "button",
327
+ {
328
+ type: "button",
329
+ onClick: onClose,
330
+ "aria-label": "Close chat",
331
+ style: {
332
+ width: isMobileViewport ? "36px" : "32px",
333
+ height: isMobileViewport ? "36px" : "32px",
334
+ borderRadius: "9999px",
335
+ border: "none",
336
+ background: "transparent",
337
+ color: "#4b5563",
338
+ cursor: "pointer",
339
+ fontSize: "20px",
340
+ lineHeight: 1
341
+ },
342
+ children: "x"
343
+ }
344
+ )
345
+ ]
346
+ }
347
+ ),
348
+ children
349
+ ] });
350
+ }
351
+
352
+ // widget/src/components/MessageList.tsx
353
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
354
+ function getAvatarFallback(label) {
355
+ if (!label || label.trim().length === 0) {
356
+ return "S";
357
+ }
358
+ const words = label.trim().split(/\s+/).filter((value) => value.length > 0).slice(0, 2);
359
+ if (words.length === 0) {
360
+ return "S";
361
+ }
362
+ return words.map((word) => word[0]?.toUpperCase() ?? "").join("");
363
+ }
364
+ function MessageList({
365
+ messages,
366
+ isGenerating,
367
+ error,
368
+ messageContainerRef,
369
+ isMobileViewport,
370
+ getMessageText,
371
+ renderMarkdown,
372
+ messageDecorations,
373
+ hasApiKey = true,
374
+ emptyState
375
+ }) {
376
+ return /* @__PURE__ */ jsxs3(
377
+ "div",
378
+ {
379
+ ref: messageContainerRef,
380
+ style: {
381
+ padding: isMobileViewport ? "10px 14px 12px" : "8px 18px 14px",
382
+ overflowY: "auto",
383
+ WebkitOverflowScrolling: "touch",
384
+ overscrollBehaviorY: "contain",
385
+ display: "flex",
386
+ flexDirection: "column",
387
+ gap: isMobileViewport ? "16px" : "14px",
388
+ flex: 1
389
+ },
390
+ children: [
391
+ messages.length === 0 ? /* @__PURE__ */ jsx3(
392
+ "div",
393
+ {
394
+ style: {
395
+ padding: isMobileViewport ? "4px 2px 2px" : "2px 2px 4px",
396
+ flex: 1,
397
+ display: "flex",
398
+ alignItems: "center",
399
+ justifyContent: "center",
400
+ width: "100%"
401
+ },
402
+ children: emptyState ?? null
403
+ }
404
+ ) : null,
405
+ messages.map((message) => {
406
+ const text = getMessageText(message.parts);
407
+ if (!text.trim()) {
408
+ return null;
409
+ }
410
+ const decoration = messageDecorations?.[message.id];
411
+ const isUser = message.role === "user";
412
+ const isSystem = Boolean(decoration?.isSystem);
413
+ const messageTimestampLabel = decoration?.messageTimestampLabel;
414
+ const showAssistantAvatar = !isUser && !isSystem && Boolean(decoration?.avatarUrl || decoration?.assistantBubble);
415
+ const article = /* @__PURE__ */ jsx3(
416
+ "article",
417
+ {
418
+ style: {
419
+ alignSelf: isSystem ? "center" : isUser ? "flex-end" : "flex-start",
420
+ background: isSystem ? "transparent" : isUser ? "#f3f4f6" : "transparent",
421
+ border: "none",
422
+ borderRadius: isSystem ? "0" : isUser ? "20px" : "0",
423
+ padding: isSystem ? "0" : isUser ? isMobileViewport ? "11px 14px" : "10px 14px" : "0",
424
+ maxWidth: isSystem ? "92%" : isUser ? isMobileViewport ? "90%" : "86%" : showAssistantAvatar ? isMobileViewport ? "88%" : "84%" : "100%",
425
+ color: isSystem ? "#64748b" : "#111827",
426
+ fontStyle: isSystem ? "italic" : "normal",
427
+ fontSize: isSystem ? "13px" : isMobileViewport ? "16px" : "15px",
428
+ lineHeight: 1.5,
429
+ boxShadow: "none"
430
+ },
431
+ children: renderMarkdown(text)
432
+ }
433
+ );
434
+ const messageBody = showAssistantAvatar ? /* @__PURE__ */ jsxs3(
435
+ "div",
436
+ {
437
+ style: {
438
+ alignSelf: "flex-start",
439
+ display: "flex",
440
+ alignItems: "flex-start",
441
+ gap: "8px",
442
+ maxWidth: "100%"
443
+ },
444
+ children: [
445
+ /* @__PURE__ */ jsx3(
446
+ "div",
447
+ {
448
+ style: {
449
+ width: "26px",
450
+ height: "26px",
451
+ borderRadius: "9999px",
452
+ background: "#e2e8f0",
453
+ overflow: "hidden",
454
+ flexShrink: 0,
455
+ display: "flex",
456
+ alignItems: "center",
457
+ justifyContent: "center",
458
+ color: "#334155",
459
+ fontSize: "11px",
460
+ fontWeight: 600
461
+ },
462
+ children: decoration?.avatarUrl ? /* @__PURE__ */ jsx3(
463
+ "img",
464
+ {
465
+ src: decoration.avatarUrl,
466
+ alt: "",
467
+ "aria-hidden": "true",
468
+ width: 26,
469
+ height: 26,
470
+ style: { display: "block", width: "100%", height: "100%", objectFit: "cover" }
471
+ }
472
+ ) : /* @__PURE__ */ jsx3("span", { children: getAvatarFallback(decoration?.avatarFallbackLabel) })
473
+ }
474
+ ),
475
+ article
476
+ ]
477
+ }
478
+ ) : article;
479
+ return /* @__PURE__ */ jsxs3(
480
+ "div",
481
+ {
482
+ style: {
483
+ display: "flex",
484
+ flexDirection: "column",
485
+ gap: messageTimestampLabel ? "3px" : "0"
486
+ },
487
+ children: [
488
+ messageBody,
489
+ messageTimestampLabel ? /* @__PURE__ */ jsx3(
490
+ "p",
491
+ {
492
+ style: {
493
+ margin: 0,
494
+ alignSelf: isSystem ? "center" : isUser ? "flex-end" : "flex-start",
495
+ marginLeft: !isSystem && !isUser && showAssistantAvatar ? "34px" : "0",
496
+ fontSize: "10px",
497
+ lineHeight: 1.2,
498
+ color: "#94a3b8",
499
+ letterSpacing: "0.01em"
500
+ },
501
+ children: messageTimestampLabel
502
+ }
503
+ ) : null
504
+ ]
505
+ },
506
+ message.id
507
+ );
508
+ }),
509
+ isGenerating ? /* @__PURE__ */ jsx3(
510
+ "div",
511
+ {
512
+ "aria-label": "Assistant is responding",
513
+ style: {
514
+ alignSelf: "flex-start",
515
+ display: "inline-flex",
516
+ alignItems: "center",
517
+ justifyContent: "center",
518
+ width: "18px",
519
+ height: "18px"
520
+ },
521
+ children: /* @__PURE__ */ jsx3(
522
+ "span",
523
+ {
524
+ style: {
525
+ width: "8px",
526
+ height: "8px",
527
+ borderRadius: "9999px",
528
+ background: "#9ca3af",
529
+ animation: "helpfulChatDotPulse 1.1s ease-in-out infinite"
530
+ }
531
+ }
532
+ )
533
+ }
534
+ ) : null,
535
+ error ? /* @__PURE__ */ jsx3("p", { style: { margin: 0, color: "#b91c1c", fontSize: "14px" }, children: error.message }) : null,
536
+ !hasApiKey ? /* @__PURE__ */ jsx3("p", { style: { margin: 0, color: "#b91c1c", fontSize: "14px" }, children: "Missing apiKey prop." }) : null
537
+ ]
538
+ }
539
+ );
540
+ }
541
+
542
+ // widget/src/components/Composer.tsx
543
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
544
+ var POWERED_BY_LOGO_SRC = HELPFUL_CHAT_LOGO_DATA_URI;
545
+ function SendIcon() {
546
+ return /* @__PURE__ */ jsx4(
547
+ "svg",
548
+ {
549
+ viewBox: "0 0 24 24",
550
+ width: "17",
551
+ height: "17",
552
+ fill: "none",
553
+ "aria-hidden": "true",
554
+ style: { display: "block" },
555
+ children: /* @__PURE__ */ jsx4(
556
+ "path",
557
+ {
558
+ d: "M12 19V5m0 0-5 5m5-5 5 5",
559
+ stroke: "currentColor",
560
+ strokeWidth: "2",
561
+ strokeLinecap: "round",
562
+ strokeLinejoin: "round"
563
+ }
564
+ )
565
+ }
566
+ );
567
+ }
568
+ function StopIcon() {
569
+ return /* @__PURE__ */ jsx4(
570
+ "span",
571
+ {
572
+ "aria-hidden": "true",
573
+ style: {
574
+ display: "block",
575
+ width: "10px",
576
+ height: "10px",
577
+ background: "currentColor",
578
+ borderRadius: "2px"
579
+ }
580
+ }
581
+ );
582
+ }
583
+ function Composer({
584
+ input,
585
+ setInput,
586
+ placeholder,
587
+ isGenerating,
588
+ canSend,
589
+ isMobileViewport,
590
+ isInputFocused,
591
+ textareaRef,
592
+ onSubmit,
593
+ onStop,
594
+ onInputKeyDown,
595
+ onFocus,
596
+ onBlur,
597
+ logoSrc = POWERED_BY_LOGO_SRC
598
+ }) {
599
+ return /* @__PURE__ */ jsxs4(
600
+ "form",
601
+ {
602
+ onSubmit,
603
+ style: {
604
+ display: "flex",
605
+ flexDirection: "column",
606
+ padding: isMobileViewport ? "8px 12px calc(10px + env(safe-area-inset-bottom, 0px))" : "0 16px 16px",
607
+ background: "#ffffff",
608
+ gap: "6px"
609
+ },
610
+ children: [
611
+ /* @__PURE__ */ jsxs4(
612
+ "div",
613
+ {
614
+ style: {
615
+ flex: 1,
616
+ borderRadius: "9999px",
617
+ border: isInputFocused ? "1px solid #cbd5e1" : "1px solid #e5e7eb",
618
+ display: "flex",
619
+ alignItems: "center",
620
+ gap: "8px",
621
+ padding: isMobileViewport ? "4px 6px 4px 16px" : "4px 6px 4px 14px",
622
+ background: "#ffffff",
623
+ minHeight: isMobileViewport ? "52px" : "44px",
624
+ boxShadow: isInputFocused ? "0 0 0 3px rgba(15, 23, 42, 0.06)" : "none",
625
+ transition: "box-shadow 120ms ease, border-color 120ms ease"
626
+ },
627
+ children: [
628
+ /* @__PURE__ */ jsx4(
629
+ "textarea",
630
+ {
631
+ ref: textareaRef,
632
+ value: input,
633
+ onChange: (event) => setInput(event.target.value),
634
+ onKeyDown: onInputKeyDown,
635
+ onFocus,
636
+ onBlur,
637
+ placeholder,
638
+ rows: 1,
639
+ enterKeyHint: "send",
640
+ style: {
641
+ flex: 1,
642
+ border: "none",
643
+ background: "transparent",
644
+ outline: "none",
645
+ padding: isMobileViewport ? "12px 0 11px" : "10px 0 9px",
646
+ fontSize: isMobileViewport ? "16px" : "15px",
647
+ lineHeight: 1.4,
648
+ color: "#111827",
649
+ resize: "none"
650
+ }
651
+ }
652
+ ),
653
+ /* @__PURE__ */ jsx4(
654
+ "button",
655
+ {
656
+ type: isGenerating ? "button" : "submit",
657
+ onClick: isGenerating ? onStop : void 0,
658
+ disabled: isGenerating ? false : !canSend,
659
+ "aria-label": isGenerating ? "Stop generating response" : "Send message",
660
+ style: {
661
+ width: isMobileViewport ? "38px" : "36px",
662
+ height: isMobileViewport ? "38px" : "36px",
663
+ borderRadius: "9999px",
664
+ border: "none",
665
+ background: isGenerating ? "#111827" : canSend ? "#111827" : "#d1d5db",
666
+ color: "#ffffff",
667
+ display: "inline-flex",
668
+ alignItems: "center",
669
+ justifyContent: "center",
670
+ cursor: "pointer"
671
+ },
672
+ children: isGenerating ? /* @__PURE__ */ jsx4(StopIcon, {}) : /* @__PURE__ */ jsx4(SendIcon, {})
673
+ }
674
+ )
675
+ ]
676
+ }
677
+ ),
678
+ /* @__PURE__ */ jsxs4(
679
+ "a",
680
+ {
681
+ href: "https://helpfulchatapp.com",
682
+ target: "_blank",
683
+ rel: "noreferrer",
684
+ style: {
685
+ display: "flex",
686
+ alignItems: "center",
687
+ justifyContent: "center",
688
+ gap: "5px",
689
+ color: "#9ca3af",
690
+ fontSize: "10px",
691
+ lineHeight: 1.2,
692
+ userSelect: "none",
693
+ textDecoration: "none",
694
+ width: "100%"
695
+ },
696
+ children: [
697
+ /* @__PURE__ */ jsx4(
698
+ "img",
699
+ {
700
+ src: logoSrc,
701
+ alt: "",
702
+ width: 10,
703
+ height: 10,
704
+ style: {
705
+ display: "block",
706
+ opacity: 0.5
707
+ }
708
+ }
709
+ ),
710
+ /* @__PURE__ */ jsx4("span", { children: "Powered by HelpfulChat" })
711
+ ]
712
+ }
713
+ )
714
+ ]
715
+ }
716
+ );
717
+ }
718
+
719
+ // widget/src/components/MarkdownMessage.tsx
720
+ import ReactMarkdown from "react-markdown";
721
+ import remarkGfm from "remark-gfm";
722
+ import { jsx as jsx5 } from "react/jsx-runtime";
723
+ function MarkdownMessage({ text }) {
724
+ return /* @__PURE__ */ jsx5(
725
+ ReactMarkdown,
726
+ {
727
+ remarkPlugins: [remarkGfm],
728
+ components: {
729
+ p: ({ children }) => /* @__PURE__ */ jsx5("p", { style: { margin: "0 0 10px", lineHeight: 1.5 }, children }),
730
+ h1: ({ children }) => /* @__PURE__ */ jsx5("h1", { style: { margin: "0 0 10px", fontSize: "1.2rem", lineHeight: 1.3 }, children }),
731
+ h2: ({ children }) => /* @__PURE__ */ jsx5("h2", { style: { margin: "0 0 10px", fontSize: "1.1rem", lineHeight: 1.3 }, children }),
732
+ h3: ({ children }) => /* @__PURE__ */ jsx5("h3", { style: { margin: "0 0 10px", fontSize: "1rem", lineHeight: 1.3 }, children }),
733
+ ul: ({ children }) => /* @__PURE__ */ jsx5("ul", { style: { margin: "0 0 10px", paddingLeft: "20px" }, children }),
734
+ ol: ({ children }) => /* @__PURE__ */ jsx5("ol", { style: { margin: "0 0 10px", paddingLeft: "20px" }, children }),
735
+ li: ({ children }) => /* @__PURE__ */ jsx5("li", { style: { marginBottom: "6px" }, children }),
736
+ a: ({ href, children }) => /* @__PURE__ */ jsx5("a", { href, style: { color: "#0f172a", textDecoration: "underline" }, target: "_blank", rel: "noreferrer", children }),
737
+ code: ({ children }) => /* @__PURE__ */ jsx5(
738
+ "code",
739
+ {
740
+ style: {
741
+ background: "#f3f4f6",
742
+ borderRadius: "6px",
743
+ padding: "2px 6px",
744
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
745
+ fontSize: "0.9em"
746
+ },
747
+ children
748
+ }
749
+ ),
750
+ pre: ({ children }) => /* @__PURE__ */ jsx5(
751
+ "pre",
752
+ {
753
+ style: {
754
+ margin: "0 0 10px",
755
+ background: "#f9fafb",
756
+ borderRadius: "10px",
757
+ border: "1px solid #e5e7eb",
758
+ padding: "10px 12px",
759
+ overflowX: "auto"
760
+ },
761
+ children
762
+ }
763
+ ),
764
+ blockquote: ({ children }) => /* @__PURE__ */ jsx5(
765
+ "blockquote",
766
+ {
767
+ style: {
768
+ margin: "0 0 10px",
769
+ paddingLeft: "12px",
770
+ borderLeft: "2px solid #e5e7eb",
771
+ color: "#374151"
772
+ },
773
+ children
774
+ }
775
+ )
776
+ },
777
+ children: text
778
+ }
779
+ );
780
+ }
781
+
782
+ // widget/src/components/EmptyState.tsx
783
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
784
+ function EmptyState({ isMobileViewport, children }) {
785
+ return /* @__PURE__ */ jsx6(
786
+ "section",
787
+ {
788
+ "aria-label": "Conversation introduction",
789
+ style: {
790
+ display: "flex",
791
+ flexDirection: "column",
792
+ alignItems: "center",
793
+ justifyContent: "center",
794
+ minHeight: isMobileViewport ? "110px" : "124px"
795
+ },
796
+ children: /* @__PURE__ */ jsxs5(
797
+ "div",
798
+ {
799
+ style: {
800
+ display: "flex",
801
+ flexDirection: "column",
802
+ alignItems: "center",
803
+ justifyContent: "center",
804
+ gap: "12px",
805
+ width: "100%",
806
+ animation: "helpfulChatEmptyFadeIn 240ms ease-out both"
807
+ },
808
+ children: [
809
+ /* @__PURE__ */ jsx6(
810
+ "h2",
811
+ {
812
+ style: {
813
+ margin: 0,
814
+ color: "#0f172a",
815
+ fontSize: isMobileViewport ? "17px" : "18px",
816
+ lineHeight: 1.25,
817
+ fontWeight: 500,
818
+ letterSpacing: "-0.005em",
819
+ textAlign: "center"
820
+ },
821
+ children: "What can I help you with today?"
822
+ }
823
+ ),
824
+ children ? /* @__PURE__ */ jsx6("div", { style: { width: "100%" }, children }) : null
825
+ ]
826
+ }
827
+ )
828
+ }
829
+ );
830
+ }
831
+
832
+ // widget/src/utils/chat.ts
833
+ function extractMessageText(message) {
834
+ if (typeof message?.content === "string") {
835
+ return message.content;
836
+ }
837
+ if (!Array.isArray(message.parts)) {
838
+ return "";
839
+ }
840
+ const textChunks = message.parts.map((part) => {
841
+ if (!part || typeof part !== "object") {
842
+ return "";
843
+ }
844
+ const typedPart = part;
845
+ if (typedPart.type !== "text" || typeof typedPart.text !== "string") {
846
+ return "";
847
+ }
848
+ return typedPart.text;
849
+ }).filter(Boolean);
850
+ return textChunks.join("");
851
+ }
852
+
853
+ // widget/src/OpenChatWidget.tsx
854
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
855
+ var MOBILE_BREAKPOINT_PX = 768;
856
+ var DEFAULT_TITLE = "Helpful Chat";
857
+ var DEFAULT_PLACEHOLDER = "Ask a question...";
858
+ function OpenChatWidget({ url }) {
859
+ const [input, setInput] = React2.useState("");
860
+ const [isOpen, setIsOpen] = React2.useState(false);
861
+ const [isInputFocused, setIsInputFocused] = React2.useState(false);
862
+ const [isMobileViewport, setIsMobileViewport] = React2.useState(() => {
863
+ if (typeof window === "undefined") {
864
+ return false;
865
+ }
866
+ return window.innerWidth <= MOBILE_BREAKPOINT_PX;
867
+ });
868
+ const [visualViewportHeight, setVisualViewportHeight] = React2.useState(() => {
869
+ if (typeof window === "undefined") {
870
+ return 0;
871
+ }
872
+ return window.visualViewport?.height ?? window.innerHeight;
873
+ });
874
+ const [visualViewportOffsetTop, setVisualViewportOffsetTop] = React2.useState(() => {
875
+ if (typeof window === "undefined") {
876
+ return 0;
877
+ }
878
+ return window.visualViewport?.offsetTop ?? 0;
879
+ });
880
+ const messageListRef = React2.useRef(null);
881
+ const textareaRef = React2.useRef(null);
882
+ const panelRef = React2.useRef(null);
883
+ const toggleRef = React2.useRef(null);
884
+ const transport = React2.useMemo(
885
+ () => new DefaultChatTransport({
886
+ api: url
887
+ }),
888
+ [url]
889
+ );
890
+ const themeCss = React2.useMemo(() => buildOpenChatWidgetThemeCss(), []);
891
+ const {
892
+ messages,
893
+ sendMessage,
894
+ status,
895
+ error,
896
+ stop
897
+ } = useChat({
898
+ transport
899
+ });
900
+ const isGenerating = status === "submitted" || status === "streaming";
901
+ const canSend = input.trim().length > 0 && !isGenerating;
902
+ React2.useEffect(() => {
903
+ const handleResize = () => {
904
+ setIsMobileViewport(window.innerWidth <= MOBILE_BREAKPOINT_PX);
905
+ };
906
+ handleResize();
907
+ window.addEventListener("resize", handleResize);
908
+ return () => {
909
+ window.removeEventListener("resize", handleResize);
910
+ };
911
+ }, []);
912
+ React2.useEffect(() => {
913
+ const handleViewportChange = () => {
914
+ if (typeof window === "undefined") {
915
+ return;
916
+ }
917
+ setVisualViewportHeight(window.visualViewport?.height ?? window.innerHeight);
918
+ setVisualViewportOffsetTop(window.visualViewport?.offsetTop ?? 0);
919
+ };
920
+ handleViewportChange();
921
+ window.addEventListener("resize", handleViewportChange);
922
+ window.visualViewport?.addEventListener("resize", handleViewportChange);
923
+ window.visualViewport?.addEventListener("scroll", handleViewportChange);
924
+ return () => {
925
+ window.removeEventListener("resize", handleViewportChange);
926
+ window.visualViewport?.removeEventListener("resize", handleViewportChange);
927
+ window.visualViewport?.removeEventListener("scroll", handleViewportChange);
928
+ };
929
+ }, []);
930
+ React2.useEffect(() => {
931
+ if (!isMobileViewport || !isOpen || typeof document === "undefined") {
932
+ return;
933
+ }
934
+ const { body } = document;
935
+ const previousOverflow = body.style.overflow;
936
+ const previousOverscroll = body.style.overscrollBehavior;
937
+ body.style.overflow = "hidden";
938
+ body.style.overscrollBehavior = "none";
939
+ return () => {
940
+ body.style.overflow = previousOverflow;
941
+ body.style.overscrollBehavior = previousOverscroll;
942
+ };
943
+ }, [isMobileViewport, isOpen]);
944
+ React2.useEffect(() => {
945
+ const scrollToBottom = () => {
946
+ const container = messageListRef.current;
947
+ if (!container) {
948
+ return;
949
+ }
950
+ container.scrollTo({
951
+ top: container.scrollHeight,
952
+ behavior: isGenerating ? "auto" : "smooth"
953
+ });
954
+ };
955
+ scrollToBottom();
956
+ }, [messages, isGenerating]);
957
+ React2.useEffect(() => {
958
+ if (!isOpen || typeof document === "undefined") {
959
+ return;
960
+ }
961
+ const handlePointerDown = (event) => {
962
+ const target = event.target;
963
+ if (!(target instanceof Node)) {
964
+ return;
965
+ }
966
+ const clickedPanel = panelRef.current?.contains(target) ?? false;
967
+ const clickedToggle = toggleRef.current?.contains(target) ?? false;
968
+ if (!clickedPanel && !clickedToggle) {
969
+ setIsOpen(false);
970
+ }
971
+ };
972
+ document.addEventListener("pointerdown", handlePointerDown);
973
+ return () => {
974
+ document.removeEventListener("pointerdown", handlePointerDown);
975
+ };
976
+ }, [isOpen]);
977
+ const toggleOpen = React2.useCallback(() => {
978
+ setIsOpen((prev) => !prev);
979
+ }, []);
980
+ const closeChat = React2.useCallback(() => {
981
+ textareaRef.current?.blur();
982
+ setIsOpen(false);
983
+ }, []);
984
+ const submitMessage = React2.useCallback(() => {
985
+ const nextInput = input.trim();
986
+ if (!nextInput || isGenerating) {
987
+ return;
988
+ }
989
+ void sendMessage({
990
+ text: nextInput
991
+ });
992
+ setInput("");
993
+ textareaRef.current?.focus();
994
+ }, [sendMessage, input, isGenerating, setInput]);
995
+ const handleSubmit = React2.useCallback(
996
+ (event) => {
997
+ event.preventDefault();
998
+ submitMessage();
999
+ },
1000
+ [submitMessage]
1001
+ );
1002
+ const handleInputKeyDown = React2.useCallback(
1003
+ (event) => {
1004
+ if (event.key !== "Enter") {
1005
+ return;
1006
+ }
1007
+ if (event.shiftKey || event.nativeEvent.isComposing) {
1008
+ return;
1009
+ }
1010
+ event.preventDefault();
1011
+ submitMessage();
1012
+ },
1013
+ [submitMessage]
1014
+ );
1015
+ const handleFocusInput = React2.useCallback(() => {
1016
+ setIsInputFocused(true);
1017
+ }, []);
1018
+ const handleBlurInput = React2.useCallback(() => {
1019
+ setIsInputFocused(false);
1020
+ }, []);
1021
+ const mobilePanelHeight = visualViewportHeight > 0 ? `${Math.round(visualViewportHeight)}px` : "100dvh";
1022
+ const mobilePanelTop = visualViewportOffsetTop > 0 ? `${Math.round(visualViewportOffsetTop)}px` : "0px";
1023
+ const panelStyle = isMobileViewport ? {
1024
+ position: "fixed",
1025
+ top: mobilePanelTop,
1026
+ left: "0",
1027
+ right: "0",
1028
+ width: "100vw",
1029
+ height: mobilePanelHeight,
1030
+ borderRadius: "0",
1031
+ background: "#ffffff",
1032
+ display: "flex",
1033
+ flexDirection: "column",
1034
+ overflow: "hidden",
1035
+ zIndex: 1001
1036
+ } : {
1037
+ position: "fixed",
1038
+ right: "16px",
1039
+ bottom: "96px",
1040
+ width: "min(700px, calc(100vw - 20px))",
1041
+ height: "min(820px, calc(100vh - 116px))",
1042
+ borderRadius: "20px",
1043
+ background: "#ffffff",
1044
+ display: "flex",
1045
+ flexDirection: "column",
1046
+ overflow: "hidden",
1047
+ zIndex: 1e3,
1048
+ boxShadow: "0 20px 48px rgba(15, 23, 42, 0.16), 0 3px 10px rgba(15, 23, 42, 0.08)"
1049
+ };
1050
+ const emptyState = /* @__PURE__ */ jsx7(EmptyState, { isMobileViewport });
1051
+ return /* @__PURE__ */ jsxs6("div", { "data-openchatwidget-root": "", children: [
1052
+ /* @__PURE__ */ jsx7("style", { children: themeCss }),
1053
+ /* @__PURE__ */ jsx7(
1054
+ ChatToggleButton,
1055
+ {
1056
+ ref: toggleRef,
1057
+ isOpen,
1058
+ isMobile: isMobileViewport,
1059
+ onToggle: toggleOpen,
1060
+ unreadCount: 0,
1061
+ logoSrc: HELPFUL_CHAT_LOGO_DATA_URI
1062
+ }
1063
+ ),
1064
+ /* @__PURE__ */ jsx7(
1065
+ WidgetPanel,
1066
+ {
1067
+ isOpen,
1068
+ isMobileViewport,
1069
+ title: DEFAULT_TITLE,
1070
+ onClose: closeChat,
1071
+ panelStyle,
1072
+ panelRef,
1073
+ logoSrc: HELPFUL_CHAT_LOGO_DATA_URI,
1074
+ children: /* @__PURE__ */ jsxs6(
1075
+ "div",
1076
+ {
1077
+ style: {
1078
+ position: "relative",
1079
+ display: "flex",
1080
+ flexDirection: "column",
1081
+ flex: 1,
1082
+ minHeight: 0
1083
+ },
1084
+ children: [
1085
+ /* @__PURE__ */ jsx7(
1086
+ MessageList,
1087
+ {
1088
+ messages,
1089
+ isGenerating,
1090
+ error,
1091
+ isMobileViewport,
1092
+ messageContainerRef: messageListRef,
1093
+ getMessageText: (parts) => extractMessageText({ parts }),
1094
+ renderMarkdown: (text) => /* @__PURE__ */ jsx7(MarkdownMessage, { text }),
1095
+ emptyState,
1096
+ hasApiKey: true
1097
+ }
1098
+ ),
1099
+ /* @__PURE__ */ jsx7(
1100
+ Composer,
1101
+ {
1102
+ input,
1103
+ setInput,
1104
+ placeholder: DEFAULT_PLACEHOLDER,
1105
+ isGenerating,
1106
+ canSend,
1107
+ isMobileViewport,
1108
+ isInputFocused,
1109
+ textareaRef,
1110
+ onSubmit: handleSubmit,
1111
+ onStop: () => void stop(),
1112
+ onInputKeyDown: handleInputKeyDown,
1113
+ onFocus: handleFocusInput,
1114
+ onBlur: handleBlurInput,
1115
+ logoSrc: HELPFUL_CHAT_LOGO_DATA_URI
1116
+ }
1117
+ )
1118
+ ]
1119
+ }
1120
+ )
1121
+ }
1122
+ )
1123
+ ] });
1124
+ }
1125
+
1126
+ // widget/src/index.ts
1127
+ import { createOpenAI } from "@ai-sdk/openai";
1128
+ import { convertToModelMessages, streamText } from "ai";
1129
+ export {
1130
+ OpenChatWidget,
1131
+ convertToModelMessages,
1132
+ createOpenAI,
1133
+ streamText
1134
+ };
1135
+ //# sourceMappingURL=index.js.map