@mordn/chat-widget 0.4.1 → 0.5.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.d.mts +16 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +75 -41
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +75 -41
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -67,6 +67,21 @@ interface ChatWidgetConfig {
|
|
|
67
67
|
* (e.g. setting their open state, navigating away).
|
|
68
68
|
*/
|
|
69
69
|
onClose?: () => void;
|
|
70
|
+
/**
|
|
71
|
+
* Controlled open state for `popup` layout. When provided, the consumer
|
|
72
|
+
* owns the show/hide lifecycle and the widget will NOT render its own
|
|
73
|
+
* floating toggle button (FAB). The consumer renders whatever trigger
|
|
74
|
+
* they want and toggles `open` themselves.
|
|
75
|
+
*
|
|
76
|
+
* Leave undefined to use the widget's built-in uncontrolled behaviour
|
|
77
|
+
* (FAB appears when closed, click opens, internal state).
|
|
78
|
+
*/
|
|
79
|
+
open?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Called when the widget wants to change its open state (e.g. user
|
|
82
|
+
* clicked the close X). Required when using `open` (controlled mode).
|
|
83
|
+
*/
|
|
84
|
+
onOpenChange?: (open: boolean) => void;
|
|
70
85
|
/**
|
|
71
86
|
* Custom buttons rendered in the widget header next to the close X.
|
|
72
87
|
* Use this for "expand to full page", "settings", or any consumer-defined
|
|
@@ -211,7 +226,7 @@ interface ChatWidgetProps extends ChatWidgetConfig {
|
|
|
211
226
|
*/
|
|
212
227
|
widgetId?: string;
|
|
213
228
|
}
|
|
214
|
-
declare function ChatWidget({ userId, conversationId, initialMessages, className, model, systemPrompt, temperature, theme, features, display, starterPrompts, onClose, headerActions, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
|
|
229
|
+
declare function ChatWidget({ userId, conversationId, initialMessages, className, model, systemPrompt, temperature, theme, features, display, starterPrompts, onClose, headerActions, open, onOpenChange, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
|
|
215
230
|
|
|
216
231
|
interface ChatTheme {
|
|
217
232
|
lightPrimary: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -67,6 +67,21 @@ interface ChatWidgetConfig {
|
|
|
67
67
|
* (e.g. setting their open state, navigating away).
|
|
68
68
|
*/
|
|
69
69
|
onClose?: () => void;
|
|
70
|
+
/**
|
|
71
|
+
* Controlled open state for `popup` layout. When provided, the consumer
|
|
72
|
+
* owns the show/hide lifecycle and the widget will NOT render its own
|
|
73
|
+
* floating toggle button (FAB). The consumer renders whatever trigger
|
|
74
|
+
* they want and toggles `open` themselves.
|
|
75
|
+
*
|
|
76
|
+
* Leave undefined to use the widget's built-in uncontrolled behaviour
|
|
77
|
+
* (FAB appears when closed, click opens, internal state).
|
|
78
|
+
*/
|
|
79
|
+
open?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Called when the widget wants to change its open state (e.g. user
|
|
82
|
+
* clicked the close X). Required when using `open` (controlled mode).
|
|
83
|
+
*/
|
|
84
|
+
onOpenChange?: (open: boolean) => void;
|
|
70
85
|
/**
|
|
71
86
|
* Custom buttons rendered in the widget header next to the close X.
|
|
72
87
|
* Use this for "expand to full page", "settings", or any consumer-defined
|
|
@@ -211,7 +226,7 @@ interface ChatWidgetProps extends ChatWidgetConfig {
|
|
|
211
226
|
*/
|
|
212
227
|
widgetId?: string;
|
|
213
228
|
}
|
|
214
|
-
declare function ChatWidget({ userId, conversationId, initialMessages, className, model, systemPrompt, temperature, theme, features, display, starterPrompts, onClose, headerActions, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
|
|
229
|
+
declare function ChatWidget({ userId, conversationId, initialMessages, className, model, systemPrompt, temperature, theme, features, display, starterPrompts, onClose, headerActions, open, onOpenChange, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
|
|
215
230
|
|
|
216
231
|
interface ChatTheme {
|
|
217
232
|
lightPrimary: string;
|
package/dist/index.js
CHANGED
|
@@ -520,7 +520,11 @@ var PromptInput = ({
|
|
|
520
520
|
"form",
|
|
521
521
|
{
|
|
522
522
|
className: cn(
|
|
523
|
-
|
|
523
|
+
// The form border + horizontal divider colour both come from
|
|
524
|
+
// the styles.src.css `.chat-widget-container form ...` rules so
|
|
525
|
+
// there's a single token (--chat-divider) controlling both.
|
|
526
|
+
// No utility classes for border colour here.
|
|
527
|
+
"w-full overflow-hidden rounded-xl bg-background transition-colors",
|
|
524
528
|
"[&:focus-within]:shadow-none [&:focus]:shadow-none shadow-none",
|
|
525
529
|
className
|
|
526
530
|
),
|
|
@@ -1194,7 +1198,7 @@ var CodeBlock = ({
|
|
|
1194
1198
|
"div",
|
|
1195
1199
|
{
|
|
1196
1200
|
className: cn(
|
|
1197
|
-
"relative w-full overflow-hidden rounded-lg bg-[hsl(var(--chat-text)/0.03)] border border-[
|
|
1201
|
+
"relative w-full overflow-hidden rounded-lg bg-[hsl(var(--chat-text)/0.03)] border border-[var(--chat-divider)]",
|
|
1198
1202
|
className
|
|
1199
1203
|
),
|
|
1200
1204
|
...props,
|
|
@@ -1261,7 +1265,7 @@ var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
|
1261
1265
|
var Tool = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1262
1266
|
Collapsible,
|
|
1263
1267
|
{
|
|
1264
|
-
className: cn("not-prose w-full rounded-md border border-[
|
|
1268
|
+
className: cn("not-prose w-full rounded-md border border-[var(--chat-divider)]", className),
|
|
1265
1269
|
...props
|
|
1266
1270
|
}
|
|
1267
1271
|
);
|
|
@@ -1287,6 +1291,7 @@ var ToolHeader = ({
|
|
|
1287
1291
|
className,
|
|
1288
1292
|
title,
|
|
1289
1293
|
type,
|
|
1294
|
+
toolName,
|
|
1290
1295
|
state,
|
|
1291
1296
|
...props
|
|
1292
1297
|
}) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
@@ -1300,7 +1305,7 @@ var ToolHeader = ({
|
|
|
1300
1305
|
children: [
|
|
1301
1306
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1302
1307
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react9.WrenchIcon, { className: "size-4 text-muted-foreground" }),
|
|
1303
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "font-medium text-sm", children: title ?? type.split("-").slice(1).join("-") }),
|
|
1308
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "font-medium text-sm", children: title ?? toolName ?? type.split("-").slice(1).join("-") }),
|
|
1304
1309
|
getStatusBadge(state)
|
|
1305
1310
|
] }),
|
|
1306
1311
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react9.ChevronDownIcon, { className: "size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" })
|
|
@@ -1379,7 +1384,10 @@ function StarterMessages({
|
|
|
1379
1384
|
onClick: () => onPromptSelect(prompt)
|
|
1380
1385
|
}
|
|
1381
1386
|
),
|
|
1382
|
-
index < prompts.length - 1 &&
|
|
1387
|
+
index < prompts.length - 1 && // 1px-tall element used as a divider — same --chat-divider token
|
|
1388
|
+
// every other separator in the widget uses, so consumers only
|
|
1389
|
+
// need to override one variable to recolour all of them.
|
|
1390
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "h-px mx-3", style: { backgroundColor: "var(--chat-divider)" } })
|
|
1383
1391
|
] }, index))
|
|
1384
1392
|
}
|
|
1385
1393
|
);
|
|
@@ -1452,6 +1460,7 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
|
|
|
1452
1460
|
const [activeTabId, setActiveTabId] = (0, import_react9.useState)("");
|
|
1453
1461
|
const [initialTabCreated, setInitialTabCreated] = (0, import_react9.useState)(false);
|
|
1454
1462
|
const [isInitializing, setIsInitializing] = (0, import_react9.useState)(true);
|
|
1463
|
+
const [isLoadingMessages, setIsLoadingMessages] = (0, import_react9.useState)(false);
|
|
1455
1464
|
const lastSyncedTabId = (0, import_react9.useRef)("");
|
|
1456
1465
|
const hasInitialized = (0, import_react9.useRef)(false);
|
|
1457
1466
|
const { messages, sendMessage, status, setMessages } = (0, import_react11.useChat)({
|
|
@@ -1631,7 +1640,12 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
|
|
|
1631
1640
|
}))
|
|
1632
1641
|
);
|
|
1633
1642
|
setActiveTabId(tabId);
|
|
1634
|
-
|
|
1643
|
+
setIsLoadingMessages(true);
|
|
1644
|
+
try {
|
|
1645
|
+
await loadConversation(tabId);
|
|
1646
|
+
} finally {
|
|
1647
|
+
setIsLoadingMessages(false);
|
|
1648
|
+
}
|
|
1635
1649
|
};
|
|
1636
1650
|
const closeTab = (tabId) => {
|
|
1637
1651
|
if (tabs.length <= 1) return;
|
|
@@ -1699,31 +1713,28 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
|
|
|
1699
1713
|
(0, import_react9.useEffect)(() => {
|
|
1700
1714
|
if (hasInitialized.current) return;
|
|
1701
1715
|
const loadInitialTabs = () => {
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
title: "New Chat",
|
|
1716
|
-
isActive: true
|
|
1717
|
-
};
|
|
1718
|
-
setTabs([currentTab]);
|
|
1719
|
-
setActiveTabId(initialTabId);
|
|
1720
|
-
setInitialTabCreated(true);
|
|
1721
|
-
}
|
|
1722
|
-
} finally {
|
|
1716
|
+
const savedTabs = localStorage.getItem(storageKey("tabs"));
|
|
1717
|
+
const savedActiveTabId = localStorage.getItem(storageKey("active-tab-id"));
|
|
1718
|
+
if (savedTabs && savedTabs !== "[]") {
|
|
1719
|
+
const parsedTabs = JSON.parse(savedTabs);
|
|
1720
|
+
setTabs(parsedTabs);
|
|
1721
|
+
const activeId = savedActiveTabId || parsedTabs[0]?.id;
|
|
1722
|
+
setActiveTabId(activeId);
|
|
1723
|
+
setInitialTabCreated(true);
|
|
1724
|
+
} else if (!initialTabCreated && tabs.length === 0) {
|
|
1725
|
+
const initialTabId = `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
1726
|
+
setTabs([{ id: initialTabId, title: "New Chat", isActive: true }]);
|
|
1727
|
+
setActiveTabId(initialTabId);
|
|
1728
|
+
setInitialTabCreated(true);
|
|
1723
1729
|
setIsInitializing(false);
|
|
1724
1730
|
}
|
|
1725
1731
|
};
|
|
1726
|
-
|
|
1732
|
+
try {
|
|
1733
|
+
loadInitialTabs();
|
|
1734
|
+
} catch (err) {
|
|
1735
|
+
console.error("[chat-widget] init failed, falling back to clean start:", err);
|
|
1736
|
+
setIsInitializing(false);
|
|
1737
|
+
}
|
|
1727
1738
|
hasInitialized.current = true;
|
|
1728
1739
|
}, []);
|
|
1729
1740
|
const hasLoadedInitialMessages = (0, import_react9.useRef)(false);
|
|
@@ -1731,10 +1742,15 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
|
|
|
1731
1742
|
if (hasLoadedInitialMessages.current) return;
|
|
1732
1743
|
if (!config?.userId) return;
|
|
1733
1744
|
if (!activeTabId) return;
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1745
|
+
(async () => {
|
|
1746
|
+
try {
|
|
1747
|
+
await loadConversation(activeTabId);
|
|
1748
|
+
} finally {
|
|
1749
|
+
hasLoadedInitialMessages.current = true;
|
|
1750
|
+
setIsInitializing(false);
|
|
1751
|
+
}
|
|
1752
|
+
})();
|
|
1753
|
+
}, [config?.userId, activeTabId]);
|
|
1738
1754
|
(0, import_react9.useEffect)(() => {
|
|
1739
1755
|
if (isInitializing) return;
|
|
1740
1756
|
if (activeTabId && tabs.length > 0 && activeTabId !== lastSyncedTabId.current) {
|
|
@@ -1854,7 +1870,14 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
|
|
|
1854
1870
|
if (part.type.startsWith("tool-") || part.type === "dynamic-tool") {
|
|
1855
1871
|
const toolPart = part;
|
|
1856
1872
|
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(Tool, { children: [
|
|
1857
|
-
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
1873
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
1874
|
+
ToolHeader,
|
|
1875
|
+
{
|
|
1876
|
+
type: part.type,
|
|
1877
|
+
toolName: toolPart.toolName,
|
|
1878
|
+
state: toolPart.state
|
|
1879
|
+
}
|
|
1880
|
+
),
|
|
1858
1881
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(ToolContent, { children: [
|
|
1859
1882
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolInput, { input: toolPart.input }),
|
|
1860
1883
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolOutput, { output: toolPart.output, errorText: toolPart.errorText })
|
|
@@ -1920,7 +1943,7 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
|
|
|
1920
1943
|
),
|
|
1921
1944
|
children: [
|
|
1922
1945
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-2 px-3 py-2 border-b backdrop-blur-sm relative z-20", style: {
|
|
1923
|
-
borderColor: "var(--chat-
|
|
1946
|
+
borderColor: "var(--chat-divider)",
|
|
1924
1947
|
backgroundColor: "var(--chat-header-bg)"
|
|
1925
1948
|
}, children: [
|
|
1926
1949
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "flex items-center gap-1 flex-1 min-w-0 overflow-x-auto scrollbar-hide py-0.5 scroll-smooth", children: tabs.map((tab, index) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
@@ -2022,10 +2045,10 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
|
|
|
2022
2045
|
),
|
|
2023
2046
|
showHistory && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "absolute right-0 top-full mt-1.5 w-72 rounded-xl shadow-[0_4px_24px_rgba(0,0,0,0.08)] dark:shadow-[0_4px_24px_rgba(0,0,0,0.3)] z-50 animate-in fade-in slide-in-from-top-1 duration-150 overflow-hidden", style: {
|
|
2024
2047
|
backgroundColor: "hsl(var(--chat-background))",
|
|
2025
|
-
border: `1px solid ${"var(--chat-
|
|
2048
|
+
border: `1px solid ${"var(--chat-divider)"}`
|
|
2026
2049
|
}, children: [
|
|
2027
2050
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "p-2.5 border-b", style: {
|
|
2028
|
-
borderColor: "var(--chat-
|
|
2051
|
+
borderColor: "var(--chat-divider)",
|
|
2029
2052
|
backgroundColor: "var(--chat-overlay)"
|
|
2030
2053
|
}, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "relative", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
2031
2054
|
"input",
|
|
@@ -2037,7 +2060,7 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
|
|
|
2037
2060
|
className: "w-full h-7 px-2.5 text-[13px] rounded-lg focus:outline-none transition-all",
|
|
2038
2061
|
style: {
|
|
2039
2062
|
backgroundColor: "hsl(var(--chat-surface-deep))",
|
|
2040
|
-
border: `1px solid ${"var(--chat-
|
|
2063
|
+
border: `1px solid ${"var(--chat-divider)"}`,
|
|
2041
2064
|
color: "hsl(var(--chat-text))"
|
|
2042
2065
|
}
|
|
2043
2066
|
}
|
|
@@ -2134,7 +2157,7 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
|
|
|
2134
2157
|
] }),
|
|
2135
2158
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "px-5 pb-5", children: [
|
|
2136
2159
|
uploadError && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "mb-3 px-4 py-3 bg-red-50 dark:bg-red-900/20 border border-red-200/60 dark:border-red-800/60 rounded-2xl text-sm text-red-700 dark:text-red-400 shadow-sm", children: uploadError }),
|
|
2137
|
-
isInitializing ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "flex items-center justify-center py-8", role: "status", "aria-label": "Loading conversation", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "h-4 w-4 rounded-full border-2 border-current border-t-transparent animate-spin", style: { color: "hsl(var(--chat-text-muted))" } }) }) : messages.length === 0 && status !== "submitted" && config?.starterPrompts && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
2160
|
+
isInitializing || isLoadingMessages ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "flex items-center justify-center py-8", role: "status", "aria-label": "Loading conversation", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "h-4 w-4 rounded-full border-2 border-current border-t-transparent animate-spin", style: { color: "hsl(var(--chat-text-muted))" } }) }) : messages.length === 0 && status !== "submitted" && config?.starterPrompts && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
2138
2161
|
StarterMessages,
|
|
2139
2162
|
{
|
|
2140
2163
|
prompts: config.starterPrompts,
|
|
@@ -2218,15 +2241,26 @@ function ChatWidget({
|
|
|
2218
2241
|
display,
|
|
2219
2242
|
starterPrompts,
|
|
2220
2243
|
onClose,
|
|
2221
|
-
headerActions
|
|
2244
|
+
headerActions,
|
|
2245
|
+
open,
|
|
2246
|
+
onOpenChange
|
|
2222
2247
|
}) {
|
|
2223
2248
|
const layout = display?.layout || "popup";
|
|
2224
|
-
const
|
|
2249
|
+
const isControlled = open !== void 0;
|
|
2250
|
+
const showToggleButton = !isControlled && display?.showToggleButton !== false;
|
|
2225
2251
|
const resizable = layout === "popup" && display?.resizable !== false;
|
|
2226
2252
|
const size = display?.size || "default";
|
|
2227
|
-
const [
|
|
2253
|
+
const [internalIsOpen, setInternalIsOpen] = (0, import_react12.useState)(
|
|
2228
2254
|
layout !== "popup" ? true : display?.defaultOpen || false
|
|
2229
2255
|
);
|
|
2256
|
+
const isOpen = isControlled ? open : internalIsOpen;
|
|
2257
|
+
const setIsOpen = (next) => {
|
|
2258
|
+
if (isControlled) {
|
|
2259
|
+
onOpenChange?.(next);
|
|
2260
|
+
} else {
|
|
2261
|
+
setInternalIsOpen(next);
|
|
2262
|
+
}
|
|
2263
|
+
};
|
|
2230
2264
|
const [isResizing, setIsResizing] = (0, import_react12.useState)(false);
|
|
2231
2265
|
const containerRef = (0, import_react12.useRef)(null);
|
|
2232
2266
|
const customStyles = (0, import_react12.useMemo)(() => {
|