@nextclaw/agent-chat-ui 0.2.3 → 0.2.5

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.ts CHANGED
@@ -1,5 +1,6 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { RefObject } from 'react';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
3
4
 
4
5
  type ChatTexts = {
5
6
  slashLoadingLabel: string;
@@ -191,7 +192,15 @@ type ChatMessageListProps = {
191
192
  className?: string;
192
193
  };
193
194
 
194
- declare function ChatInputBar(props: ChatInputBarProps): react_jsx_runtime.JSX.Element;
195
+ type ChatInputBarHandle = {
196
+ insertFileToken: (tokenKey: string, label: string) => void;
197
+ insertFileTokens: (tokens: Array<{
198
+ tokenKey: string;
199
+ label: string;
200
+ }>) => void;
201
+ focusComposer: () => void;
202
+ };
203
+ declare const ChatInputBar: react.ForwardRefExoticComponent<ChatInputBarProps & react.RefAttributes<ChatInputBarHandle>>;
195
204
 
196
205
  declare function ChatMessageList(props: ChatMessageListProps): react_jsx_runtime.JSX.Element;
197
206
 
@@ -255,4 +264,4 @@ declare function resolveChatComposerSlashTrigger(nodes: ChatComposerNode[], sele
255
264
  end: number;
256
265
  } | null;
257
266
 
258
- export { type ChatComposerNode, type ChatComposerSelection, type ChatComposerTextNode, type ChatComposerTokenKind, type ChatComposerTokenNode, type ChatInlineHint, ChatInputBar, type ChatInputBarActionsProps, type ChatInputBarProps, type ChatInputBarToolbarProps, ChatMessageList, type ChatMessageListProps, type ChatMessagePartViewModel, type ChatMessageRole, type ChatMessageTexts, type ChatMessageViewModel, type ChatSelectedItem, type ChatSkillPickerOption, type ChatSkillPickerProps, type ChatSlashItem, type ChatSlashMenuProps, type ChatTexts, type ChatToolPartViewModel, type ChatToolbarAccessory, type ChatToolbarAccessoryIcon, type ChatToolbarIcon, type ChatToolbarSelect, type ChatToolbarSelectOption, copyText, createChatComposerNodesFromText, createChatComposerTextNode, createChatComposerTokenNode, createEmptyChatComposerNodes, extractChatComposerTokenKeys, normalizeChatComposerNodes, removeChatComposerTokenNodes, replaceChatComposerRange, resolveChatComposerSlashTrigger, serializeChatComposerDocument, serializeChatComposerPlainText, useActiveItemScroll, useCopyFeedback, useElementWidth, useStickyBottomScroll };
267
+ export { type ChatComposerNode, type ChatComposerSelection, type ChatComposerTextNode, type ChatComposerTokenKind, type ChatComposerTokenNode, type ChatInlineHint, ChatInputBar, type ChatInputBarActionsProps, type ChatInputBarHandle, type ChatInputBarProps, type ChatInputBarToolbarProps, ChatMessageList, type ChatMessageListProps, type ChatMessagePartViewModel, type ChatMessageRole, type ChatMessageTexts, type ChatMessageViewModel, type ChatSelectedItem, type ChatSkillPickerOption, type ChatSkillPickerProps, type ChatSlashItem, type ChatSlashMenuProps, type ChatTexts, type ChatToolPartViewModel, type ChatToolbarAccessory, type ChatToolbarAccessoryIcon, type ChatToolbarIcon, type ChatToolbarSelect, type ChatToolbarSelectOption, copyText, createChatComposerNodesFromText, createChatComposerTextNode, createChatComposerTokenNode, createEmptyChatComposerNodes, extractChatComposerTokenKeys, normalizeChatComposerNodes, removeChatComposerTokenNodes, replaceChatComposerRange, resolveChatComposerSlashTrigger, serializeChatComposerDocument, serializeChatComposerPlainText, useActiveItemScroll, useCopyFeedback, useElementWidth, useStickyBottomScroll };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/components/chat/ui/chat-input-bar/chat-input-bar.tsx
2
- import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef4, useState as useState4 } from "react";
2
+ import { forwardRef as forwardRef7, useEffect as useEffect4, useImperativeHandle as useImperativeHandle2, useMemo as useMemo3, useRef as useRef4, useState as useState4 } from "react";
3
3
 
4
4
  // src/components/chat/ui/chat-input-bar/chat-slash-menu.tsx
5
5
  import { useMemo, useRef as useRef2 } from "react";
@@ -1251,32 +1251,53 @@ var ChatComposerSurfaceRenderer = class {
1251
1251
  element.dataset.composerTokenKind = node.tokenKind;
1252
1252
  element.dataset.composerTokenKey = node.tokenKey;
1253
1253
  element.dataset.composerLabel = node.label;
1254
- element.className = [
1254
+ element.title = node.label;
1255
+ element.className = this.buildTokenClassName(node.tokenKind, isSelected);
1256
+ element.append(this.createTokenIcon(node.tokenKind));
1257
+ const label = document.createElement("span");
1258
+ label.className = node.tokenKind === "file" ? "min-w-0 flex-1 truncate text-[12px] font-medium text-slate-700" : "truncate";
1259
+ label.textContent = node.label;
1260
+ element.append(label);
1261
+ return element;
1262
+ };
1263
+ this.buildTokenClassName = (tokenKind, isSelected) => {
1264
+ if (tokenKind === "file") {
1265
+ return [
1266
+ "mx-[2px]",
1267
+ "inline-flex",
1268
+ "h-7",
1269
+ "max-w-[min(100%,17rem)]",
1270
+ "items-center",
1271
+ "gap-1.5",
1272
+ "rounded-lg",
1273
+ "border",
1274
+ "px-2",
1275
+ "align-baseline",
1276
+ "transition-[border-color,background-color,box-shadow,color]",
1277
+ "duration-150",
1278
+ isSelected ? "border-slate-300 bg-slate-100 text-slate-800 shadow-[0_0_0_2px_rgba(148,163,184,0.14)]" : "border-slate-200/80 bg-slate-50 text-slate-700"
1279
+ ].join(" ");
1280
+ }
1281
+ return [
1255
1282
  "mx-[2px]",
1256
1283
  "inline-flex",
1257
- "h-6",
1284
+ "h-7",
1258
1285
  "max-w-full",
1259
1286
  "items-center",
1260
- "gap-1",
1261
- "rounded-md",
1287
+ "gap-1.5",
1288
+ "rounded-lg",
1262
1289
  "border",
1263
- "px-1.5",
1290
+ "px-2",
1264
1291
  "align-baseline",
1265
1292
  "text-[11px]",
1266
1293
  "font-medium",
1267
1294
  "transition",
1268
1295
  isSelected ? "border-primary/30 bg-primary/18 text-primary" : "border-primary/12 bg-primary/8 text-primary"
1269
1296
  ].join(" ");
1270
- element.append(this.createTokenIcon(node.tokenKind));
1271
- const label = document.createElement("span");
1272
- label.className = "truncate";
1273
- label.textContent = node.label;
1274
- element.append(label);
1275
- return element;
1276
1297
  };
1277
1298
  this.createTokenIcon = (tokenKind) => {
1278
1299
  const wrapper = document.createElement("span");
1279
- wrapper.className = "inline-flex h-3 w-3 shrink-0 items-center justify-center text-primary/70";
1300
+ wrapper.className = tokenKind === "file" ? "inline-flex h-4.5 w-4.5 shrink-0 items-center justify-center rounded-md bg-white text-slate-500 ring-1 ring-black/5" : "inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center text-primary/70";
1280
1301
  wrapper.append(tokenKind === "file" ? this.createFileIcon() : this.createSkillIcon());
1281
1302
  return wrapper;
1282
1303
  };
@@ -1290,10 +1311,9 @@ var ChatComposerSurfaceRenderer = class {
1290
1311
  };
1291
1312
  this.createFileIcon = () => {
1292
1313
  return this.createSvgIcon([
1293
- { tag: "path", attrs: { d: "M5.25 2.75h4.5L13 6v7.25A1.75 1.75 0 0 1 11.25 15h-6.5A1.75 1.75 0 0 1 3 13.25v-8.75A1.75 1.75 0 0 1 4.75 2.75Z" } },
1294
- { tag: "path", attrs: { d: "M9.75 2.75V6H13" } },
1295
- { tag: "path", attrs: { d: "M5.75 8.75h4.5" } },
1296
- { tag: "path", attrs: { d: "M5.75 10.75h4.5" } }
1314
+ { tag: "path", attrs: { d: "M3.25 4.25A1.5 1.5 0 0 1 4.75 2.75h6.5a1.5 1.5 0 0 1 1.5 1.5v7.5a1.5 1.5 0 0 1-1.5 1.5h-6.5a1.5 1.5 0 0 1-1.5-1.5v-7.5Z" } },
1315
+ { tag: "path", attrs: { d: "m4.75 10 2.25-2.5 1.75 1.75 1.25-1.25 2 2" } },
1316
+ { tag: "path", attrs: { d: "M9.75 6.25h.01" } }
1297
1317
  ]);
1298
1318
  };
1299
1319
  this.createSvgIcon = (children) => {
@@ -1463,8 +1483,8 @@ var ChatComposerViewController = class {
1463
1483
  commitSnapshot(this.controller.insertText(text));
1464
1484
  };
1465
1485
  this.handleBlur = (params) => {
1466
- const { setSelectedRange, onSlashQueryChange, onSlashOpenChange } = params;
1467
- setSelectedRange(null);
1486
+ const { clearSelectedRange, onSlashQueryChange, onSlashOpenChange } = params;
1487
+ clearSelectedRange();
1468
1488
  onSlashQueryChange?.(null);
1469
1489
  onSlashOpenChange(false);
1470
1490
  };
@@ -1510,6 +1530,20 @@ var ChatComposerRuntime = class {
1510
1530
  },
1511
1531
  insertFileToken: (tokenKey, label) => {
1512
1532
  this.commitSnapshot(this.controller.insertFileToken(tokenKey, label));
1533
+ this.focusComposerSoon();
1534
+ },
1535
+ insertFileTokens: (tokens) => {
1536
+ let nextSnapshot = null;
1537
+ for (const token of tokens) {
1538
+ nextSnapshot = this.controller.insertFileToken(token.tokenKey, token.label);
1539
+ }
1540
+ if (nextSnapshot) {
1541
+ this.commitSnapshot(nextSnapshot);
1542
+ this.focusComposerSoon();
1543
+ }
1544
+ },
1545
+ focusComposer: () => {
1546
+ this.focusComposer();
1513
1547
  },
1514
1548
  syncSelectedSkills: (nextKeys, options) => {
1515
1549
  this.viewController.syncSelectedSkills(nextKeys, options, this.commitSnapshot);
@@ -1603,7 +1637,7 @@ var ChatComposerRuntime = class {
1603
1637
  const config = this.requireConfig();
1604
1638
  this.isComposing = false;
1605
1639
  this.viewController.handleBlur({
1606
- setSelectedRange: this.setSelectedRange,
1640
+ clearSelectedRange: this.clearSelectedRange,
1607
1641
  onSlashQueryChange: config.onSlashQueryChange,
1608
1642
  onSlashOpenChange: config.onSlashOpenChange
1609
1643
  });
@@ -1613,6 +1647,10 @@ var ChatComposerRuntime = class {
1613
1647
  this.selection = selection;
1614
1648
  this.requestRender();
1615
1649
  };
1650
+ this.clearSelectedRange = () => {
1651
+ this.selectedRange = null;
1652
+ this.requestRender();
1653
+ };
1616
1654
  this.commitSnapshot = (nextSnapshot) => {
1617
1655
  const config = this.requireConfig();
1618
1656
  this.selection = nextSnapshot.selection;
@@ -1633,6 +1671,33 @@ var ChatComposerRuntime = class {
1633
1671
  this.requestRender = () => {
1634
1672
  this.requireConfig().requestRender();
1635
1673
  };
1674
+ this.focusComposerSoon = () => {
1675
+ if (typeof requestAnimationFrame === "function") {
1676
+ requestAnimationFrame(() => this.focusComposer());
1677
+ return;
1678
+ }
1679
+ this.focusComposer();
1680
+ };
1681
+ this.focusComposer = () => {
1682
+ if (!this.rootElement) {
1683
+ return;
1684
+ }
1685
+ const targetSelection = this.selection;
1686
+ this.rootElement.focus();
1687
+ const restoreSelection2 = () => {
1688
+ if (!this.rootElement) {
1689
+ return;
1690
+ }
1691
+ this.selection = targetSelection;
1692
+ this.selectedRange = targetSelection;
1693
+ this.viewController.restoreSelectionIfFocused(this.rootElement, targetSelection);
1694
+ };
1695
+ if (typeof requestAnimationFrame === "function") {
1696
+ requestAnimationFrame(restoreSelection2);
1697
+ return;
1698
+ }
1699
+ restoreSelection2();
1700
+ };
1636
1701
  this.requireConfig = () => {
1637
1702
  if (!this.config) {
1638
1703
  throw new Error("ChatComposerRuntime is not configured.");
@@ -1744,7 +1809,7 @@ function InputBarHint({ hint }) {
1744
1809
  ) : null
1745
1810
  ] }) });
1746
1811
  }
1747
- function ChatInputBar(props) {
1812
+ var ChatInputBar = forwardRef7(function ChatInputBar2(props, ref) {
1748
1813
  const composerRef = useRef4(null);
1749
1814
  const [slashQuery, setSlashQuery] = useState4(null);
1750
1815
  const [activeSlashIndex, setActiveSlashIndex] = useState4(0);
@@ -1784,6 +1849,11 @@ function ChatInputBar(props) {
1784
1849
  }
1785
1850
  };
1786
1851
  }, [props.toolbar]);
1852
+ useImperativeHandle2(ref, () => ({
1853
+ insertFileToken: (tokenKey, label) => composerRef.current?.insertFileToken(tokenKey, label),
1854
+ insertFileTokens: (tokens) => composerRef.current?.insertFileTokens(tokens),
1855
+ focusComposer: () => composerRef.current?.focusComposer()
1856
+ }), []);
1787
1857
  return /* @__PURE__ */ jsx11("div", { className: "border-t border-gray-200/80 bg-white p-4", children: /* @__PURE__ */ jsx11("div", { className: "mx-auto w-full max-w-[min(1120px,100%)]", children: /* @__PURE__ */ jsxs6("div", { className: "overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-card", children: [
1788
1858
  /* @__PURE__ */ jsxs6("div", { className: "relative", children: [
1789
1859
  /* @__PURE__ */ jsx11(
@@ -1838,7 +1908,8 @@ function ChatInputBar(props) {
1838
1908
  /* @__PURE__ */ jsx11(InputBarHint, { hint: props.hint }),
1839
1909
  /* @__PURE__ */ jsx11(ChatInputBarToolbar, { ...toolbar })
1840
1910
  ] }) }) });
1841
- }
1911
+ });
1912
+ ChatInputBar.displayName = "ChatInputBar";
1842
1913
 
1843
1914
  // src/components/chat/ui/chat-message-list/chat-message-avatar.tsx
1844
1915
  import { Bot, User, Wrench } from "lucide-react";
@@ -2119,10 +2190,30 @@ function ChatMessageMarkdown(props) {
2119
2190
  import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
2120
2191
  function ChatMessageFile({ file }) {
2121
2192
  if (file.isImage && file.dataUrl) {
2122
- return /* @__PURE__ */ jsxs8("figure", { className: "overflow-hidden rounded-2xl border border-black/8 bg-black/6", children: [
2123
- /* @__PURE__ */ jsx15("img", { src: file.dataUrl, alt: file.label, className: "block max-h-80 w-full object-contain" }),
2124
- /* @__PURE__ */ jsx15("figcaption", { className: "border-t border-black/8 px-3 py-2 text-xs opacity-80", children: file.label })
2125
- ] });
2193
+ return /* @__PURE__ */ jsx15(
2194
+ "img",
2195
+ {
2196
+ src: file.dataUrl,
2197
+ alt: file.label,
2198
+ className: "block max-h-80 max-w-full rounded-2xl object-contain"
2199
+ }
2200
+ );
2201
+ }
2202
+ if (file.dataUrl) {
2203
+ return /* @__PURE__ */ jsxs8(
2204
+ "a",
2205
+ {
2206
+ href: file.dataUrl,
2207
+ download: file.label,
2208
+ target: "_blank",
2209
+ rel: "noreferrer",
2210
+ className: "block rounded-2xl border border-black/8 bg-black/6 px-3 py-2 text-sm transition hover:bg-black/8",
2211
+ children: [
2212
+ /* @__PURE__ */ jsx15("div", { className: "font-medium", children: file.label }),
2213
+ /* @__PURE__ */ jsx15("div", { className: "text-xs opacity-75", children: file.mimeType })
2214
+ ]
2215
+ }
2216
+ );
2126
2217
  }
2127
2218
  return /* @__PURE__ */ jsxs8("div", { className: "rounded-2xl border border-black/8 bg-black/6 px-3 py-2 text-sm", children: [
2128
2219
  /* @__PURE__ */ jsx15("div", { className: "font-medium", children: file.label }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/agent-chat-ui",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "private": false,
5
5
  "description": "Reusable Nextclaw agent chat UI primitives and default skin.",
6
6
  "type": "module",