@nextclaw/agent-chat-ui 0.2.4 → 0.2.6

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
@@ -23,15 +23,15 @@ type ChatSelectedItem = {
23
23
  key: string;
24
24
  label: string;
25
25
  };
26
- type ChatComposerTokenKind = 'skill' | 'file';
26
+ type ChatComposerTokenKind = "skill" | "file";
27
27
  type ChatComposerTextNode = {
28
28
  id: string;
29
- type: 'text';
29
+ type: "text";
30
30
  text: string;
31
31
  };
32
32
  type ChatComposerTokenNode = {
33
33
  id: string;
34
- type: 'token';
34
+ type: "token";
35
35
  tokenKind: ChatComposerTokenKind;
36
36
  tokenKey: string;
37
37
  label: string;
@@ -41,8 +41,8 @@ type ChatComposerSelection = {
41
41
  start: number;
42
42
  end: number;
43
43
  };
44
- type ChatToolbarIcon = 'sparkles' | 'brain';
45
- type ChatToolbarAccessoryIcon = ChatToolbarIcon | 'paperclip';
44
+ type ChatToolbarIcon = "sparkles" | "brain";
45
+ type ChatToolbarAccessoryIcon = ChatToolbarIcon | "paperclip";
46
46
  type ChatToolbarSelectOption = {
47
47
  value: string;
48
48
  label: string;
@@ -106,7 +106,7 @@ type ChatInputBarToolbarProps = {
106
106
  actions: ChatInputBarActionsProps;
107
107
  };
108
108
  type ChatInlineHint = {
109
- tone: 'neutral' | 'warning';
109
+ tone: "neutral" | "warning";
110
110
  loading?: boolean;
111
111
  text?: string;
112
112
  actionLabel?: string;
@@ -118,7 +118,7 @@ type ChatSlashMenuProps = {
118
118
  items: ChatSlashItem[];
119
119
  activeIndex: number;
120
120
  activeItem: ChatSlashItem | null;
121
- texts: Pick<ChatTexts, 'slashLoadingLabel' | 'slashSectionLabel' | 'slashEmptyLabel' | 'slashHintLabel' | 'slashSkillHintLabel'>;
121
+ texts: Pick<ChatTexts, "slashLoadingLabel" | "slashSectionLabel" | "slashEmptyLabel" | "slashHintLabel" | "slashSkillHintLabel">;
122
122
  onSelectItem: (item: ChatSlashItem) => void;
123
123
  onOpenChange: (open: boolean) => void;
124
124
  onSetActiveIndex: (index: number) => void;
@@ -132,41 +132,47 @@ type ChatInputBarProps = {
132
132
  onFilesAdd?: (files: File[]) => Promise<void> | void;
133
133
  onSlashQueryChange?: (query: string | null) => void;
134
134
  };
135
- slashMenu: Pick<ChatSlashMenuProps, 'isLoading' | 'items' | 'texts'>;
135
+ slashMenu: Pick<ChatSlashMenuProps, "isLoading" | "items" | "texts">;
136
136
  hint?: ChatInlineHint | null;
137
137
  toolbar: ChatInputBarToolbarProps;
138
138
  };
139
- type ChatMessageRole = 'user' | 'assistant' | 'tool' | 'system' | 'message';
139
+ type ChatMessageRole = "user" | "assistant" | "tool" | "system" | "message";
140
140
  type ChatToolPartViewModel = {
141
- kind: 'call' | 'result';
141
+ kind: "call" | "result";
142
142
  toolName: string;
143
143
  summary?: string;
144
144
  output?: string;
145
145
  hasResult: boolean;
146
+ statusTone: "running" | "success" | "error" | "cancelled";
147
+ statusLabel: string;
146
148
  titleLabel: string;
149
+ inputLabel: string;
147
150
  outputLabel: string;
148
151
  emptyLabel: string;
152
+ callIdLabel: string;
153
+ callId?: string;
149
154
  };
150
155
  type ChatMessagePartViewModel = {
151
- type: 'markdown';
156
+ type: "markdown";
152
157
  text: string;
153
158
  } | {
154
- type: 'reasoning';
159
+ type: "reasoning";
155
160
  text: string;
156
161
  label: string;
157
162
  } | {
158
- type: 'tool-card';
163
+ type: "tool-card";
159
164
  card: ChatToolPartViewModel;
160
165
  } | {
161
- type: 'file';
166
+ type: "file";
162
167
  file: {
163
168
  label: string;
164
169
  mimeType: string;
165
170
  dataUrl?: string;
171
+ sizeBytes?: number;
166
172
  isImage: boolean;
167
173
  };
168
174
  } | {
169
- type: 'unknown';
175
+ type: "unknown";
170
176
  label: string;
171
177
  rawType: string;
172
178
  text?: string;
package/dist/index.js CHANGED
@@ -1255,30 +1255,9 @@ var ChatComposerSurfaceRenderer = class {
1255
1255
  element.className = this.buildTokenClassName(node.tokenKind, isSelected);
1256
1256
  element.append(this.createTokenIcon(node.tokenKind));
1257
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";
1258
+ label.className = node.tokenKind === "file" ? "min-w-0 flex-1 truncate text-[12px] font-medium text-slate-700" : "truncate";
1259
1259
  label.textContent = node.label;
1260
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
1261
  return element;
1283
1262
  };
1284
1263
  this.buildTokenClassName = (tokenKind, isSelected) => {
@@ -1286,19 +1265,17 @@ var ChatComposerSurfaceRenderer = class {
1286
1265
  return [
1287
1266
  "mx-[2px]",
1288
1267
  "inline-flex",
1289
- "h-8",
1290
- "max-w-[min(100%,19rem)]",
1268
+ "h-7",
1269
+ "max-w-[min(100%,17rem)]",
1291
1270
  "items-center",
1292
- "gap-2",
1293
- "rounded-xl",
1271
+ "gap-1.5",
1272
+ "rounded-lg",
1294
1273
  "border",
1295
1274
  "px-2",
1296
- "pr-2.5",
1297
1275
  "align-baseline",
1298
- "shadow-[0_1px_2px_rgba(15,23,42,0.06)]",
1299
1276
  "transition-[border-color,background-color,box-shadow,color]",
1300
1277
  "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"
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"
1302
1279
  ].join(" ");
1303
1280
  }
1304
1281
  return [
@@ -1320,7 +1297,7 @@ var ChatComposerSurfaceRenderer = class {
1320
1297
  };
1321
1298
  this.createTokenIcon = (tokenKind) => {
1322
1299
  const wrapper = document.createElement("span");
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";
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";
1324
1301
  wrapper.append(tokenKind === "file" ? this.createFileIcon() : this.createSkillIcon());
1325
1302
  return wrapper;
1326
1303
  };
@@ -1339,10 +1316,6 @@ var ChatComposerSurfaceRenderer = class {
1339
1316
  { tag: "path", attrs: { d: "M9.75 6.25h.01" } }
1340
1317
  ]);
1341
1318
  };
1342
- this.resolveFileBadgeLabel = (label) => {
1343
- const match = /\.([a-z0-9]+)$/i.exec(label.trim());
1344
- return match?.[1]?.slice(0, 4).toUpperCase() || "IMG";
1345
- };
1346
1319
  this.createSvgIcon = (children) => {
1347
1320
  const svg = document.createElementNS(SVG_NAMESPACE, "svg");
1348
1321
  svg.setAttribute("viewBox", "0 0 16 16");
@@ -1709,8 +1682,21 @@ var ChatComposerRuntime = class {
1709
1682
  if (!this.rootElement) {
1710
1683
  return;
1711
1684
  }
1685
+ const targetSelection = this.selection;
1712
1686
  this.rootElement.focus();
1713
- this.viewController.restoreSelectionIfFocused(this.rootElement, this.selection);
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();
1714
1700
  };
1715
1701
  this.requireConfig = () => {
1716
1702
  if (!this.config) {
@@ -2204,10 +2190,30 @@ function ChatMessageMarkdown(props) {
2204
2190
  import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
2205
2191
  function ChatMessageFile({ file }) {
2206
2192
  if (file.isImage && file.dataUrl) {
2207
- return /* @__PURE__ */ jsxs8("figure", { className: "overflow-hidden rounded-2xl border border-black/8 bg-black/6", children: [
2208
- /* @__PURE__ */ jsx15("img", { src: file.dataUrl, alt: file.label, className: "block max-h-80 w-full object-contain" }),
2209
- /* @__PURE__ */ jsx15("figcaption", { className: "border-t border-black/8 px-3 py-2 text-xs opacity-80", children: file.label })
2210
- ] });
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
+ );
2211
2217
  }
2212
2218
  return /* @__PURE__ */ jsxs8("div", { className: "rounded-2xl border border-black/8 bg-black/6 px-3 py-2 text-sm", children: [
2213
2219
  /* @__PURE__ */ jsx15("div", { className: "font-medium", children: file.label }),
@@ -2234,9 +2240,24 @@ function ChatReasoningBlock(props) {
2234
2240
  }
2235
2241
 
2236
2242
  // src/components/chat/ui/chat-message-list/chat-tool-card.tsx
2237
- import { Clock3, FileSearch, Globe, Search as Search2, SendHorizontal, Terminal, Wrench as Wrench2 } from "lucide-react";
2243
+ import { AlertCircle, CheckCircle2, CircleSlash, Clock3, FileSearch, Globe, Loader2, Search as Search2, SendHorizontal, Terminal, Wrench as Wrench2 } from "lucide-react";
2238
2244
  import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
2239
2245
  var TOOL_OUTPUT_PREVIEW_MAX = 220;
2246
+ var TOOL_CALL_ID_PREVIEW_MAX = 18;
2247
+ var STATUS_STYLES = {
2248
+ running: {
2249
+ text: "text-amber-700/80"
2250
+ },
2251
+ success: {
2252
+ text: "text-amber-700/80"
2253
+ },
2254
+ error: {
2255
+ text: "text-amber-700/80"
2256
+ },
2257
+ cancelled: {
2258
+ text: "text-amber-700/80"
2259
+ }
2260
+ };
2240
2261
  function renderToolIcon(toolName) {
2241
2262
  const lowered = toolName.toLowerCase();
2242
2263
  if (lowered.includes("exec") || lowered.includes("shell") || lowered.includes("command")) {
@@ -2259,18 +2280,50 @@ function renderToolIcon(toolName) {
2259
2280
  }
2260
2281
  return /* @__PURE__ */ jsx17(Wrench2, { className: "h-3.5 w-3.5" });
2261
2282
  }
2283
+ function truncateMiddle(value, maxLength = TOOL_CALL_ID_PREVIEW_MAX) {
2284
+ if (value.length <= maxLength) {
2285
+ return value;
2286
+ }
2287
+ const head = Math.ceil((maxLength - 1) / 2);
2288
+ const tail = Math.floor((maxLength - 1) / 2);
2289
+ return `${value.slice(0, head)}\u2026${value.slice(value.length - tail)}`;
2290
+ }
2291
+ function renderStatusMeta(card) {
2292
+ const style = STATUS_STYLES[card.statusTone];
2293
+ if (card.statusTone === "running") {
2294
+ return /* @__PURE__ */ jsxs10("span", { className: cn("inline-flex items-center gap-1 text-[11px] font-medium leading-none", style.text), children: [
2295
+ /* @__PURE__ */ jsx17(Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
2296
+ card.statusLabel
2297
+ ] });
2298
+ }
2299
+ const icon = card.statusTone === "success" ? /* @__PURE__ */ jsx17(CheckCircle2, { className: "h-3.5 w-3.5" }) : card.statusTone === "error" ? /* @__PURE__ */ jsx17(AlertCircle, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx17(CircleSlash, { className: "h-3.5 w-3.5" });
2300
+ return /* @__PURE__ */ jsxs10("span", { className: cn("inline-flex items-center gap-1 text-[11px] font-medium leading-none", style.text), children: [
2301
+ icon,
2302
+ card.statusTone === "success" ? null : card.statusLabel
2303
+ ] });
2304
+ }
2262
2305
  function ChatToolCard({ card }) {
2263
2306
  const output = card.output?.trim() ?? "";
2264
2307
  const showDetails = output.length > TOOL_OUTPUT_PREVIEW_MAX || output.includes("\n");
2265
2308
  const preview = showDetails ? `${output.slice(0, TOOL_OUTPUT_PREVIEW_MAX)}...` : output;
2266
2309
  const showOutputSection = card.kind === "result" || card.hasResult;
2310
+ const statusStyle = STATUS_STYLES[card.statusTone];
2267
2311
  return /* @__PURE__ */ jsxs10("div", { className: "rounded-xl border border-amber-200/80 bg-amber-50/60 px-3 py-2.5", children: [
2268
2312
  /* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap items-center gap-2 text-xs font-semibold text-amber-800", children: [
2269
2313
  renderToolIcon(card.toolName),
2270
2314
  /* @__PURE__ */ jsx17("span", { children: card.titleLabel }),
2271
- /* @__PURE__ */ jsx17("span", { className: "font-mono text-[11px] text-amber-900/80", children: card.toolName })
2315
+ /* @__PURE__ */ jsx17("span", { className: "font-mono text-[11px] text-amber-900/80", children: card.toolName }),
2316
+ renderStatusMeta(card)
2272
2317
  ] }),
2273
- card.summary ? /* @__PURE__ */ jsx17("div", { className: "mt-1 break-words font-mono text-[11px] text-amber-800/90", children: card.summary }) : null,
2318
+ card.summary ? /* @__PURE__ */ jsxs10("div", { className: "mt-1", children: [
2319
+ /* @__PURE__ */ jsx17("div", { className: "text-[10px] text-amber-700/75", children: card.inputLabel }),
2320
+ /* @__PURE__ */ jsx17("div", { className: "break-words font-mono text-[11px] text-amber-800/90", children: card.summary })
2321
+ ] }) : null,
2322
+ card.callId ? /* @__PURE__ */ jsxs10("div", { className: cn("mt-1 text-[10px]", statusStyle.text), children: [
2323
+ /* @__PURE__ */ jsx17("span", { children: card.callIdLabel }),
2324
+ /* @__PURE__ */ jsx17("span", { children: ": " }),
2325
+ /* @__PURE__ */ jsx17("span", { className: "font-mono", children: truncateMiddle(card.callId) })
2326
+ ] }) : null,
2274
2327
  showOutputSection ? /* @__PURE__ */ jsx17("div", { className: "mt-2", children: !output ? /* @__PURE__ */ jsx17("div", { className: "text-[11px] text-amber-700/80", children: card.emptyLabel }) : showDetails ? /* @__PURE__ */ jsxs10("details", { className: "group", children: [
2275
2328
  /* @__PURE__ */ jsx17("summary", { className: "cursor-pointer text-[11px] text-amber-700", children: card.outputLabel }),
2276
2329
  /* @__PURE__ */ jsx17("pre", { className: "mt-2 whitespace-pre-wrap break-words rounded-lg border border-amber-200 bg-amber-100/40 p-2 text-[11px] text-amber-900", children: output })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/agent-chat-ui",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "private": false,
5
5
  "description": "Reusable Nextclaw agent chat UI primitives and default skin.",
6
6
  "type": "module",