@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.mjs CHANGED
@@ -490,7 +490,11 @@ var PromptInput = ({
490
490
  "form",
491
491
  {
492
492
  className: cn(
493
- "w-full divide-y divide-[hsl(var(--chat-text)/0.1)] focus-within:divide-ring overflow-hidden rounded-xl border border-[hsl(var(--chat-text)/0.1)] bg-background focus-within:border-ring transition-colors",
493
+ // The form border + horizontal divider colour both come from
494
+ // the styles.src.css `.chat-widget-container form ...` rules so
495
+ // there's a single token (--chat-divider) controlling both.
496
+ // No utility classes for border colour here.
497
+ "w-full overflow-hidden rounded-xl bg-background transition-colors",
494
498
  "[&:focus-within]:shadow-none [&:focus]:shadow-none shadow-none",
495
499
  className
496
500
  ),
@@ -1174,7 +1178,7 @@ var CodeBlock = ({
1174
1178
  "div",
1175
1179
  {
1176
1180
  className: cn(
1177
- "relative w-full overflow-hidden rounded-lg bg-[hsl(var(--chat-text)/0.03)] border border-[hsl(var(--chat-text)/0.1)]",
1181
+ "relative w-full overflow-hidden rounded-lg bg-[hsl(var(--chat-text)/0.03)] border border-[var(--chat-divider)]",
1178
1182
  className
1179
1183
  ),
1180
1184
  ...props,
@@ -1241,7 +1245,7 @@ import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
1241
1245
  var Tool = ({ className, ...props }) => /* @__PURE__ */ jsx17(
1242
1246
  Collapsible,
1243
1247
  {
1244
- className: cn("not-prose w-full rounded-md border border-[hsl(var(--chat-text)/0.1)]", className),
1248
+ className: cn("not-prose w-full rounded-md border border-[var(--chat-divider)]", className),
1245
1249
  ...props
1246
1250
  }
1247
1251
  );
@@ -1267,6 +1271,7 @@ var ToolHeader = ({
1267
1271
  className,
1268
1272
  title,
1269
1273
  type,
1274
+ toolName,
1270
1275
  state,
1271
1276
  ...props
1272
1277
  }) => /* @__PURE__ */ jsxs10(
@@ -1280,7 +1285,7 @@ var ToolHeader = ({
1280
1285
  children: [
1281
1286
  /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
1282
1287
  /* @__PURE__ */ jsx17(WrenchIcon, { className: "size-4 text-muted-foreground" }),
1283
- /* @__PURE__ */ jsx17("span", { className: "font-medium text-sm", children: title ?? type.split("-").slice(1).join("-") }),
1288
+ /* @__PURE__ */ jsx17("span", { className: "font-medium text-sm", children: title ?? toolName ?? type.split("-").slice(1).join("-") }),
1284
1289
  getStatusBadge(state)
1285
1290
  ] }),
1286
1291
  /* @__PURE__ */ jsx17(ChevronDownIcon4, { className: "size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" })
@@ -1359,7 +1364,10 @@ function StarterMessages({
1359
1364
  onClick: () => onPromptSelect(prompt)
1360
1365
  }
1361
1366
  ),
1362
- index < prompts.length - 1 && /* @__PURE__ */ jsx18("div", { className: "h-px bg-[hsl(var(--chat-text)/0.08)] mx-3" })
1367
+ index < prompts.length - 1 && // 1px-tall element used as a divider — same --chat-divider token
1368
+ // every other separator in the widget uses, so consumers only
1369
+ // need to override one variable to recolour all of them.
1370
+ /* @__PURE__ */ jsx18("div", { className: "h-px mx-3", style: { backgroundColor: "var(--chat-divider)" } })
1363
1371
  ] }, index))
1364
1372
  }
1365
1373
  );
@@ -1432,6 +1440,7 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
1432
1440
  const [activeTabId, setActiveTabId] = useState4("");
1433
1441
  const [initialTabCreated, setInitialTabCreated] = useState4(false);
1434
1442
  const [isInitializing, setIsInitializing] = useState4(true);
1443
+ const [isLoadingMessages, setIsLoadingMessages] = useState4(false);
1435
1444
  const lastSyncedTabId = useRef3("");
1436
1445
  const hasInitialized = useRef3(false);
1437
1446
  const { messages, sendMessage, status, setMessages } = useChat({
@@ -1611,7 +1620,12 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
1611
1620
  }))
1612
1621
  );
1613
1622
  setActiveTabId(tabId);
1614
- await loadConversation(tabId);
1623
+ setIsLoadingMessages(true);
1624
+ try {
1625
+ await loadConversation(tabId);
1626
+ } finally {
1627
+ setIsLoadingMessages(false);
1628
+ }
1615
1629
  };
1616
1630
  const closeTab = (tabId) => {
1617
1631
  if (tabs.length <= 1) return;
@@ -1679,31 +1693,28 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
1679
1693
  useEffect4(() => {
1680
1694
  if (hasInitialized.current) return;
1681
1695
  const loadInitialTabs = () => {
1682
- try {
1683
- const savedTabs = localStorage.getItem(storageKey("tabs"));
1684
- const savedActiveTabId = localStorage.getItem(storageKey("active-tab-id"));
1685
- if (savedTabs && savedTabs !== "[]") {
1686
- const parsedTabs = JSON.parse(savedTabs);
1687
- setTabs(parsedTabs);
1688
- const activeId = savedActiveTabId || parsedTabs[0]?.id;
1689
- setActiveTabId(activeId);
1690
- setInitialTabCreated(true);
1691
- } else if (!initialTabCreated && tabs.length === 0) {
1692
- const initialTabId = `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1693
- const currentTab = {
1694
- id: initialTabId,
1695
- title: "New Chat",
1696
- isActive: true
1697
- };
1698
- setTabs([currentTab]);
1699
- setActiveTabId(initialTabId);
1700
- setInitialTabCreated(true);
1701
- }
1702
- } finally {
1696
+ const savedTabs = localStorage.getItem(storageKey("tabs"));
1697
+ const savedActiveTabId = localStorage.getItem(storageKey("active-tab-id"));
1698
+ if (savedTabs && savedTabs !== "[]") {
1699
+ const parsedTabs = JSON.parse(savedTabs);
1700
+ setTabs(parsedTabs);
1701
+ const activeId = savedActiveTabId || parsedTabs[0]?.id;
1702
+ setActiveTabId(activeId);
1703
+ setInitialTabCreated(true);
1704
+ } else if (!initialTabCreated && tabs.length === 0) {
1705
+ const initialTabId = `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1706
+ setTabs([{ id: initialTabId, title: "New Chat", isActive: true }]);
1707
+ setActiveTabId(initialTabId);
1708
+ setInitialTabCreated(true);
1703
1709
  setIsInitializing(false);
1704
1710
  }
1705
1711
  };
1706
- loadInitialTabs();
1712
+ try {
1713
+ loadInitialTabs();
1714
+ } catch (err) {
1715
+ console.error("[chat-widget] init failed, falling back to clean start:", err);
1716
+ setIsInitializing(false);
1717
+ }
1707
1718
  hasInitialized.current = true;
1708
1719
  }, []);
1709
1720
  const hasLoadedInitialMessages = useRef3(false);
@@ -1711,10 +1722,15 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
1711
1722
  if (hasLoadedInitialMessages.current) return;
1712
1723
  if (!config?.userId) return;
1713
1724
  if (!activeTabId) return;
1714
- if (isInitializing) return;
1715
- loadConversation(activeTabId);
1716
- hasLoadedInitialMessages.current = true;
1717
- }, [config?.userId, activeTabId, isInitializing]);
1725
+ (async () => {
1726
+ try {
1727
+ await loadConversation(activeTabId);
1728
+ } finally {
1729
+ hasLoadedInitialMessages.current = true;
1730
+ setIsInitializing(false);
1731
+ }
1732
+ })();
1733
+ }, [config?.userId, activeTabId]);
1718
1734
  useEffect4(() => {
1719
1735
  if (isInitializing) return;
1720
1736
  if (activeTabId && tabs.length > 0 && activeTabId !== lastSyncedTabId.current) {
@@ -1834,7 +1850,14 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
1834
1850
  if (part.type.startsWith("tool-") || part.type === "dynamic-tool") {
1835
1851
  const toolPart = part;
1836
1852
  return /* @__PURE__ */ jsxs12(Tool, { children: [
1837
- /* @__PURE__ */ jsx20(ToolHeader, { type: part.type, state: toolPart.state }),
1853
+ /* @__PURE__ */ jsx20(
1854
+ ToolHeader,
1855
+ {
1856
+ type: part.type,
1857
+ toolName: toolPart.toolName,
1858
+ state: toolPart.state
1859
+ }
1860
+ ),
1838
1861
  /* @__PURE__ */ jsxs12(ToolContent, { children: [
1839
1862
  /* @__PURE__ */ jsx20(ToolInput, { input: toolPart.input }),
1840
1863
  /* @__PURE__ */ jsx20(ToolOutput, { output: toolPart.output, errorText: toolPart.errorText })
@@ -1900,7 +1923,7 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
1900
1923
  ),
1901
1924
  children: [
1902
1925
  /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2 px-3 py-2 border-b backdrop-blur-sm relative z-20", style: {
1903
- borderColor: "var(--chat-border-soft)",
1926
+ borderColor: "var(--chat-divider)",
1904
1927
  backgroundColor: "var(--chat-header-bg)"
1905
1928
  }, children: [
1906
1929
  /* @__PURE__ */ jsx20("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__ */ jsxs12(
@@ -2002,10 +2025,10 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2002
2025
  ),
2003
2026
  showHistory && /* @__PURE__ */ jsxs12("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: {
2004
2027
  backgroundColor: "hsl(var(--chat-background))",
2005
- border: `1px solid ${"var(--chat-border-medium)"}`
2028
+ border: `1px solid ${"var(--chat-divider)"}`
2006
2029
  }, children: [
2007
2030
  /* @__PURE__ */ jsx20("div", { className: "p-2.5 border-b", style: {
2008
- borderColor: "var(--chat-border-soft)",
2031
+ borderColor: "var(--chat-divider)",
2009
2032
  backgroundColor: "var(--chat-overlay)"
2010
2033
  }, children: /* @__PURE__ */ jsx20("div", { className: "relative", children: /* @__PURE__ */ jsx20(
2011
2034
  "input",
@@ -2017,7 +2040,7 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2017
2040
  className: "w-full h-7 px-2.5 text-[13px] rounded-lg focus:outline-none transition-all",
2018
2041
  style: {
2019
2042
  backgroundColor: "hsl(var(--chat-surface-deep))",
2020
- border: `1px solid ${"var(--chat-border-medium)"}`,
2043
+ border: `1px solid ${"var(--chat-divider)"}`,
2021
2044
  color: "hsl(var(--chat-text))"
2022
2045
  }
2023
2046
  }
@@ -2114,7 +2137,7 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2114
2137
  ] }),
2115
2138
  /* @__PURE__ */ jsxs12("div", { className: "px-5 pb-5", children: [
2116
2139
  uploadError && /* @__PURE__ */ jsx20("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 }),
2117
- isInitializing ? /* @__PURE__ */ jsx20("div", { className: "flex items-center justify-center py-8", role: "status", "aria-label": "Loading conversation", children: /* @__PURE__ */ jsx20("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__ */ jsx20(
2140
+ isInitializing || isLoadingMessages ? /* @__PURE__ */ jsx20("div", { className: "flex items-center justify-center py-8", role: "status", "aria-label": "Loading conversation", children: /* @__PURE__ */ jsx20("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__ */ jsx20(
2118
2141
  StarterMessages,
2119
2142
  {
2120
2143
  prompts: config.starterPrompts,
@@ -2198,15 +2221,26 @@ function ChatWidget({
2198
2221
  display,
2199
2222
  starterPrompts,
2200
2223
  onClose,
2201
- headerActions
2224
+ headerActions,
2225
+ open,
2226
+ onOpenChange
2202
2227
  }) {
2203
2228
  const layout = display?.layout || "popup";
2204
- const showToggleButton = display?.showToggleButton !== false;
2229
+ const isControlled = open !== void 0;
2230
+ const showToggleButton = !isControlled && display?.showToggleButton !== false;
2205
2231
  const resizable = layout === "popup" && display?.resizable !== false;
2206
2232
  const size = display?.size || "default";
2207
- const [isOpen, setIsOpen] = useState5(
2233
+ const [internalIsOpen, setInternalIsOpen] = useState5(
2208
2234
  layout !== "popup" ? true : display?.defaultOpen || false
2209
2235
  );
2236
+ const isOpen = isControlled ? open : internalIsOpen;
2237
+ const setIsOpen = (next) => {
2238
+ if (isControlled) {
2239
+ onOpenChange?.(next);
2240
+ } else {
2241
+ setInternalIsOpen(next);
2242
+ }
2243
+ };
2210
2244
  const [isResizing, setIsResizing] = useState5(false);
2211
2245
  const containerRef = useRef4(null);
2212
2246
  const customStyles = useMemo3(() => {