@nextclaw/agent-chat-ui 0.2.3 → 0.2.4

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,76 @@ 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-800" : "truncate";
1259
+ label.textContent = node.label;
1260
+ element.append(label);
1261
+ if (node.tokenKind === "file") {
1262
+ const badge = document.createElement("span");
1263
+ badge.className = [
1264
+ "hidden",
1265
+ "shrink-0",
1266
+ "rounded-md",
1267
+ "border",
1268
+ "border-sky-100",
1269
+ "bg-sky-50",
1270
+ "px-1.5",
1271
+ "py-0.5",
1272
+ "text-[9px]",
1273
+ "font-semibold",
1274
+ "uppercase",
1275
+ "tracking-[0.12em]",
1276
+ "text-sky-700",
1277
+ "sm:inline-flex"
1278
+ ].join(" ");
1279
+ badge.textContent = this.resolveFileBadgeLabel(node.label);
1280
+ element.append(badge);
1281
+ }
1282
+ return element;
1283
+ };
1284
+ this.buildTokenClassName = (tokenKind, isSelected) => {
1285
+ if (tokenKind === "file") {
1286
+ return [
1287
+ "mx-[2px]",
1288
+ "inline-flex",
1289
+ "h-8",
1290
+ "max-w-[min(100%,19rem)]",
1291
+ "items-center",
1292
+ "gap-2",
1293
+ "rounded-xl",
1294
+ "border",
1295
+ "px-2",
1296
+ "pr-2.5",
1297
+ "align-baseline",
1298
+ "shadow-[0_1px_2px_rgba(15,23,42,0.06)]",
1299
+ "transition-[border-color,background-color,box-shadow,color]",
1300
+ "duration-150",
1301
+ isSelected ? "border-sky-300 bg-sky-50 text-slate-900 shadow-[0_0_0_3px_rgba(14,165,233,0.14)]" : "border-slate-200 bg-[linear-gradient(180deg,rgba(255,255,255,1),rgba(248,250,252,0.98))] text-slate-700"
1302
+ ].join(" ");
1303
+ }
1304
+ return [
1255
1305
  "mx-[2px]",
1256
1306
  "inline-flex",
1257
- "h-6",
1307
+ "h-7",
1258
1308
  "max-w-full",
1259
1309
  "items-center",
1260
- "gap-1",
1261
- "rounded-md",
1310
+ "gap-1.5",
1311
+ "rounded-lg",
1262
1312
  "border",
1263
- "px-1.5",
1313
+ "px-2",
1264
1314
  "align-baseline",
1265
1315
  "text-[11px]",
1266
1316
  "font-medium",
1267
1317
  "transition",
1268
1318
  isSelected ? "border-primary/30 bg-primary/18 text-primary" : "border-primary/12 bg-primary/8 text-primary"
1269
1319
  ].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
1320
  };
1277
1321
  this.createTokenIcon = (tokenKind) => {
1278
1322
  const wrapper = document.createElement("span");
1279
- wrapper.className = "inline-flex h-3 w-3 shrink-0 items-center justify-center text-primary/70";
1323
+ wrapper.className = tokenKind === "file" ? "inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-lg bg-sky-100 text-sky-700" : "inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center text-primary/70";
1280
1324
  wrapper.append(tokenKind === "file" ? this.createFileIcon() : this.createSkillIcon());
1281
1325
  return wrapper;
1282
1326
  };
@@ -1290,12 +1334,15 @@ var ChatComposerSurfaceRenderer = class {
1290
1334
  };
1291
1335
  this.createFileIcon = () => {
1292
1336
  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" } }
1337
+ { 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" } },
1338
+ { tag: "path", attrs: { d: "m4.75 10 2.25-2.5 1.75 1.75 1.25-1.25 2 2" } },
1339
+ { tag: "path", attrs: { d: "M9.75 6.25h.01" } }
1297
1340
  ]);
1298
1341
  };
1342
+ this.resolveFileBadgeLabel = (label) => {
1343
+ const match = /\.([a-z0-9]+)$/i.exec(label.trim());
1344
+ return match?.[1]?.slice(0, 4).toUpperCase() || "IMG";
1345
+ };
1299
1346
  this.createSvgIcon = (children) => {
1300
1347
  const svg = document.createElementNS(SVG_NAMESPACE, "svg");
1301
1348
  svg.setAttribute("viewBox", "0 0 16 16");
@@ -1463,8 +1510,8 @@ var ChatComposerViewController = class {
1463
1510
  commitSnapshot(this.controller.insertText(text));
1464
1511
  };
1465
1512
  this.handleBlur = (params) => {
1466
- const { setSelectedRange, onSlashQueryChange, onSlashOpenChange } = params;
1467
- setSelectedRange(null);
1513
+ const { clearSelectedRange, onSlashQueryChange, onSlashOpenChange } = params;
1514
+ clearSelectedRange();
1468
1515
  onSlashQueryChange?.(null);
1469
1516
  onSlashOpenChange(false);
1470
1517
  };
@@ -1510,6 +1557,20 @@ var ChatComposerRuntime = class {
1510
1557
  },
1511
1558
  insertFileToken: (tokenKey, label) => {
1512
1559
  this.commitSnapshot(this.controller.insertFileToken(tokenKey, label));
1560
+ this.focusComposerSoon();
1561
+ },
1562
+ insertFileTokens: (tokens) => {
1563
+ let nextSnapshot = null;
1564
+ for (const token of tokens) {
1565
+ nextSnapshot = this.controller.insertFileToken(token.tokenKey, token.label);
1566
+ }
1567
+ if (nextSnapshot) {
1568
+ this.commitSnapshot(nextSnapshot);
1569
+ this.focusComposerSoon();
1570
+ }
1571
+ },
1572
+ focusComposer: () => {
1573
+ this.focusComposer();
1513
1574
  },
1514
1575
  syncSelectedSkills: (nextKeys, options) => {
1515
1576
  this.viewController.syncSelectedSkills(nextKeys, options, this.commitSnapshot);
@@ -1603,7 +1664,7 @@ var ChatComposerRuntime = class {
1603
1664
  const config = this.requireConfig();
1604
1665
  this.isComposing = false;
1605
1666
  this.viewController.handleBlur({
1606
- setSelectedRange: this.setSelectedRange,
1667
+ clearSelectedRange: this.clearSelectedRange,
1607
1668
  onSlashQueryChange: config.onSlashQueryChange,
1608
1669
  onSlashOpenChange: config.onSlashOpenChange
1609
1670
  });
@@ -1613,6 +1674,10 @@ var ChatComposerRuntime = class {
1613
1674
  this.selection = selection;
1614
1675
  this.requestRender();
1615
1676
  };
1677
+ this.clearSelectedRange = () => {
1678
+ this.selectedRange = null;
1679
+ this.requestRender();
1680
+ };
1616
1681
  this.commitSnapshot = (nextSnapshot) => {
1617
1682
  const config = this.requireConfig();
1618
1683
  this.selection = nextSnapshot.selection;
@@ -1633,6 +1698,20 @@ var ChatComposerRuntime = class {
1633
1698
  this.requestRender = () => {
1634
1699
  this.requireConfig().requestRender();
1635
1700
  };
1701
+ this.focusComposerSoon = () => {
1702
+ if (typeof requestAnimationFrame === "function") {
1703
+ requestAnimationFrame(() => this.focusComposer());
1704
+ return;
1705
+ }
1706
+ this.focusComposer();
1707
+ };
1708
+ this.focusComposer = () => {
1709
+ if (!this.rootElement) {
1710
+ return;
1711
+ }
1712
+ this.rootElement.focus();
1713
+ this.viewController.restoreSelectionIfFocused(this.rootElement, this.selection);
1714
+ };
1636
1715
  this.requireConfig = () => {
1637
1716
  if (!this.config) {
1638
1717
  throw new Error("ChatComposerRuntime is not configured.");
@@ -1744,7 +1823,7 @@ function InputBarHint({ hint }) {
1744
1823
  ) : null
1745
1824
  ] }) });
1746
1825
  }
1747
- function ChatInputBar(props) {
1826
+ var ChatInputBar = forwardRef7(function ChatInputBar2(props, ref) {
1748
1827
  const composerRef = useRef4(null);
1749
1828
  const [slashQuery, setSlashQuery] = useState4(null);
1750
1829
  const [activeSlashIndex, setActiveSlashIndex] = useState4(0);
@@ -1784,6 +1863,11 @@ function ChatInputBar(props) {
1784
1863
  }
1785
1864
  };
1786
1865
  }, [props.toolbar]);
1866
+ useImperativeHandle2(ref, () => ({
1867
+ insertFileToken: (tokenKey, label) => composerRef.current?.insertFileToken(tokenKey, label),
1868
+ insertFileTokens: (tokens) => composerRef.current?.insertFileTokens(tokens),
1869
+ focusComposer: () => composerRef.current?.focusComposer()
1870
+ }), []);
1787
1871
  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
1872
  /* @__PURE__ */ jsxs6("div", { className: "relative", children: [
1789
1873
  /* @__PURE__ */ jsx11(
@@ -1838,7 +1922,8 @@ function ChatInputBar(props) {
1838
1922
  /* @__PURE__ */ jsx11(InputBarHint, { hint: props.hint }),
1839
1923
  /* @__PURE__ */ jsx11(ChatInputBarToolbar, { ...toolbar })
1840
1924
  ] }) }) });
1841
- }
1925
+ });
1926
+ ChatInputBar.displayName = "ChatInputBar";
1842
1927
 
1843
1928
  // src/components/chat/ui/chat-message-list/chat-message-avatar.tsx
1844
1929
  import { Bot, User, Wrench } from "lucide-react";
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.4",
4
4
  "private": false,
5
5
  "description": "Reusable Nextclaw agent chat UI primitives and default skin.",
6
6
  "type": "module",