@slipstream-ui/ai 0.4.0 → 0.6.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.
Files changed (51) hide show
  1. package/dist/index.d.mts +51 -4
  2. package/dist/index.d.ts +51 -4
  3. package/dist/index.js +244 -113
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +246 -115
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/manifests/attachment.manifest.json +37 -8
  8. package/dist/manifests/branch.manifest.json +3 -1
  9. package/dist/manifests/chain-thought.manifest.json +1 -1
  10. package/dist/manifests/code-block.manifest.json +3 -1
  11. package/dist/manifests/command-palette.manifest.json +59 -4
  12. package/dist/manifests/confirmation.manifest.json +3 -1
  13. package/dist/manifests/conversation.manifest.json +1 -1
  14. package/dist/manifests/inline-citation.manifest.json +3 -1
  15. package/dist/manifests/message-actions.manifest.json +61 -10
  16. package/dist/manifests/message.manifest.json +13 -1
  17. package/dist/manifests/prompt-input.manifest.json +2 -2
  18. package/dist/manifests/reasoning.manifest.json +19 -1
  19. package/dist/manifests/response.manifest.json +3 -1
  20. package/dist/manifests/source.manifest.json +2 -2
  21. package/dist/manifests/streaming-indicator.manifest.json +1 -1
  22. package/dist/manifests/suggestions.manifest.json +2 -2
  23. package/dist/manifests/tool-call.manifest.json +7 -1
  24. package/package.json +10 -10
  25. package/src/components/code-block/CodeBlock.tsx +91 -5
  26. package/src/components/command-palette/CommandPalette.test.tsx +42 -0
  27. package/src/components/command-palette/CommandPalette.tsx +82 -8
  28. package/src/components/confirmation/Confirmation.tsx +1 -1
  29. package/src/components/conversation/Conversation.tsx +6 -1
  30. package/src/components/message/Message.test.tsx +15 -0
  31. package/src/components/message/Message.tsx +9 -1
  32. package/src/components/reasoning/Reasoning.test.tsx +15 -0
  33. package/src/components/reasoning/Reasoning.tsx +20 -7
  34. package/src/components/tool-call/ToolCall.tsx +24 -8
  35. package/src/manifests/attachment.manifest.json +37 -8
  36. package/src/manifests/branch.manifest.json +3 -1
  37. package/src/manifests/chain-thought.manifest.json +1 -1
  38. package/src/manifests/code-block.manifest.json +3 -1
  39. package/src/manifests/command-palette.manifest.json +59 -4
  40. package/src/manifests/confirmation.manifest.json +3 -1
  41. package/src/manifests/conversation.manifest.json +1 -1
  42. package/src/manifests/inline-citation.manifest.json +3 -1
  43. package/src/manifests/message-actions.manifest.json +61 -10
  44. package/src/manifests/message.manifest.json +13 -1
  45. package/src/manifests/prompt-input.manifest.json +2 -2
  46. package/src/manifests/reasoning.manifest.json +19 -1
  47. package/src/manifests/response.manifest.json +3 -1
  48. package/src/manifests/source.manifest.json +2 -2
  49. package/src/manifests/streaming-indicator.manifest.json +1 -1
  50. package/src/manifests/suggestions.manifest.json +2 -2
  51. package/src/manifests/tool-call.manifest.json +7 -1
package/dist/index.js CHANGED
@@ -85,6 +85,8 @@ var Message = React.forwardRef(
85
85
  avatarFallback,
86
86
  userLabel = "You",
87
87
  assistantLabel = "Assistant",
88
+ formatTime,
89
+ locale,
88
90
  renderContent,
89
91
  isStreaming = false,
90
92
  error,
@@ -138,7 +140,7 @@ var Message = React.forwardRef(
138
140
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex-1 min-w-0", children: [
139
141
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2 mb-1", children: [
140
142
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-sm font-medium", children: displayLabel }),
141
- message.timestamp && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-xs text-muted-foreground", children: new Date(message.timestamp).toLocaleTimeString() })
143
+ message.timestamp && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-xs text-muted-foreground", children: formatTime ? formatTime(new Date(message.timestamp)) : new Date(message.timestamp).toLocaleTimeString(locale) })
142
144
  ] }),
143
145
  error ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { role: "alert", className: "text-sm", children: [
144
146
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-destructive mb-2", children: error }),
@@ -377,11 +379,80 @@ function useIsDark() {
377
379
  }, []);
378
380
  return dark;
379
381
  }
382
+ var LIGHT_CODE_THEME = {
383
+ plain: { color: "#1f2328", backgroundColor: "transparent" },
384
+ styles: [
385
+ { types: ["comment", "prolog", "doctype", "cdata"], style: { color: "#57606a" } },
386
+ { types: ["punctuation", "operator"], style: { color: "#24292f" } },
387
+ { types: ["namespace"], style: { color: "#6f42c1" } },
388
+ { types: ["tag", "deleted", "keyword", "atrule", "important"], style: { color: "#b31d28" } },
389
+ { types: ["attr-name", "variable"], style: { color: "#953800" } },
390
+ { types: ["string", "char", "attr-value", "inserted", "regex"], style: { color: "#0a5c2f" } },
391
+ {
392
+ types: [
393
+ "function",
394
+ "class-name",
395
+ "number",
396
+ "boolean",
397
+ "constant",
398
+ "symbol",
399
+ "property",
400
+ "selector",
401
+ "builtin",
402
+ "entity",
403
+ "url"
404
+ ],
405
+ style: { color: "#0550ae" }
406
+ }
407
+ ]
408
+ };
409
+ var DARK_CODE_THEME = {
410
+ plain: { color: "#e6edf3", backgroundColor: "transparent" },
411
+ styles: [
412
+ { types: ["comment", "prolog", "doctype", "cdata"], style: { color: "#8b949e" } },
413
+ { types: ["punctuation", "operator"], style: { color: "#c9d1d9" } },
414
+ { types: ["namespace"], style: { color: "#d2a8ff" } },
415
+ { types: ["tag", "deleted", "keyword", "atrule", "important"], style: { color: "#ff7b72" } },
416
+ { types: ["attr-name", "variable"], style: { color: "#ffa657" } },
417
+ { types: ["string", "char", "attr-value", "inserted", "regex"], style: { color: "#7ee787" } },
418
+ {
419
+ types: [
420
+ "function",
421
+ "class-name",
422
+ "number",
423
+ "boolean",
424
+ "constant",
425
+ "symbol",
426
+ "property",
427
+ "selector",
428
+ "builtin",
429
+ "entity",
430
+ "url"
431
+ ],
432
+ style: { color: "#79c0ff" }
433
+ }
434
+ ]
435
+ };
380
436
  var CodeBlock = React5.forwardRef(
381
437
  ({ code, language = "tsx", showCopyButton = true, className, ...props }, ref) => {
382
438
  const [copied, setCopied] = React5.useState(false);
383
439
  const isDark = useIsDark();
384
440
  const trimmedCode = code.replace(/\n$/, "");
441
+ const preRef = React5.useRef(null);
442
+ const [scrollable, setScrollable] = React5.useState(false);
443
+ React5.useEffect(() => {
444
+ const element = preRef.current;
445
+ if (!element) return;
446
+ const update = () => setScrollable(element.scrollWidth > element.clientWidth + 1);
447
+ update();
448
+ if (typeof ResizeObserver === "undefined") {
449
+ window.addEventListener("resize", update);
450
+ return () => window.removeEventListener("resize", update);
451
+ }
452
+ const observer = new ResizeObserver(update);
453
+ observer.observe(element);
454
+ return () => observer.disconnect();
455
+ }, [trimmedCode, language]);
385
456
  const handleCopy = async () => {
386
457
  try {
387
458
  await navigator.clipboard.writeText(trimmedCode);
@@ -395,8 +466,8 @@ var CodeBlock = React5.forwardRef(
395
466
  {
396
467
  ref,
397
468
  className: (0, import_core7.cn)(
398
- // bg-background (not muted): Prism light themes assume a white
399
- // surface — on gray the comment/keyword tokens fall below AA.
469
+ // bg-background (not muted): the light syntax palette is tuned for
470
+ // a white surface; on gray the token contrast drops below AA.
400
471
  "relative group rounded-md border border-border bg-background overflow-hidden",
401
472
  className
402
473
  ),
@@ -415,10 +486,13 @@ var CodeBlock = React5.forwardRef(
415
486
  }
416
487
  )
417
488
  ] }),
418
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_prism_react_renderer.Highlight, { theme: isDark ? import_prism_react_renderer.themes.vsDark : import_prism_react_renderer.themes.github, code: trimmedCode, language, children: ({ style, tokens, getLineProps, getTokenProps }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
489
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_prism_react_renderer.Highlight, { theme: isDark ? DARK_CODE_THEME : LIGHT_CODE_THEME, code: trimmedCode, language, children: ({ style, tokens, getLineProps, getTokenProps }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
419
490
  "pre",
420
491
  {
421
- className: "overflow-x-auto p-4 text-xs leading-relaxed m-0",
492
+ ref: preRef,
493
+ tabIndex: scrollable ? 0 : void 0,
494
+ "aria-label": scrollable ? `${language} code, scrollable` : void 0,
495
+ className: "overflow-x-auto p-4 text-xs leading-relaxed m-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
422
496
  style: { ...style, background: "transparent" },
423
497
  children: tokens.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ...getLineProps({ line }), children: line.map((token, key) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { ...getTokenProps({ token }) }, key)) }, i))
424
498
  }
@@ -515,7 +589,7 @@ var import_core9 = require("@slipstream-ui/core");
515
589
  var import_core10 = require("@slipstream-ui/core");
516
590
  var import_jsx_runtime7 = require("react/jsx-runtime");
517
591
  var statusChipClasses = {
518
- pending: "bg-warning/10 text-warning border-warning/20",
592
+ pending: "bg-warning/10 text-warning-ink border-warning/20",
519
593
  approved: "bg-success/10 text-success border-success/20",
520
594
  rejected: "bg-destructive/10 text-destructive border-destructive/20"
521
595
  };
@@ -815,62 +889,71 @@ var Conversation = React10.forwardRef(
815
889
  };
816
890
  const isEmpty = messages.length === 0 && !isLoading;
817
891
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { ref, className: (0, import_core13.cn)("relative h-full w-full", className), ...props, children: [
818
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { ref: scrollRef, className: "flex flex-col gap-4 p-4 overflow-y-auto h-full", children: [
819
- (hasMoreHistory || isLoadingMore) && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
820
- "button",
821
- {
822
- type: "button",
823
- onClick: handleLoadMore,
824
- disabled: isLoadingMore,
825
- className: "mx-auto rounded-full border border-border bg-background px-3 py-1 text-xs text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50",
826
- children: isLoadingMore ? "Loading\u2026" : "Load earlier messages"
827
- }
828
- ),
829
- isEmpty ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 text-center py-16", children: [
830
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { className: "text-lg font-semibold", children: welcomeTitle }),
831
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm text-muted-foreground max-w-sm", children: welcomeDescription }),
832
- welcomeSuggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-2 flex flex-col gap-2 w-full max-w-sm", children: welcomeSuggestions.map((suggestion) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
833
- "button",
834
- {
835
- type: "button",
836
- onClick: () => onWelcomeSelect?.(suggestion),
837
- className: "rounded-md border border-border bg-background px-4 py-2.5 text-sm text-left text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
838
- children: suggestion
839
- },
840
- suggestion
841
- )) })
842
- ] }) : messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
843
- MemoizedMessage,
844
- {
845
- message,
846
- showAvatar: showAvatars,
847
- avatarSrc: message.role === "user" ? userAvatarSrc : assistantAvatarSrc,
848
- avatarFallback: message.role === "user" ? userAvatarFallback : assistantAvatarFallback,
849
- renderContent,
850
- isStreaming: message.id === streamingMessageId,
851
- error: messageErrors?.[message.id],
852
- onRetry: onRetryMessage && messageErrors?.[message.id] ? () => onRetryMessage(message) : void 0
853
- },
854
- message.id
855
- )),
856
- isLoading && !isEmpty && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
857
- MemoizedMessage,
858
- {
859
- message: {
860
- id: "__loading__",
861
- role: "assistant",
862
- content: loadingMessage
863
- },
864
- showAvatar: showAvatars,
865
- avatarSrc: assistantAvatarSrc,
866
- avatarFallback: assistantAvatarFallback,
867
- className: "bg-muted/50",
868
- renderContent: (content) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-muted-foreground animate-pulse motion-reduce:animate-none", children: content })
869
- }
870
- ),
871
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { "aria-live": "polite", className: "sr-only", children: announcement }),
872
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { ref: messagesEndRef })
873
- ] }),
892
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
893
+ "div",
894
+ {
895
+ ref: scrollRef,
896
+ tabIndex: 0,
897
+ "aria-label": "Conversation transcript",
898
+ className: "flex flex-col gap-4 p-4 overflow-y-auto h-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
899
+ children: [
900
+ (hasMoreHistory || isLoadingMore) && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
901
+ "button",
902
+ {
903
+ type: "button",
904
+ onClick: handleLoadMore,
905
+ disabled: isLoadingMore,
906
+ className: "mx-auto rounded-full border border-border bg-background px-3 py-1 text-xs text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50",
907
+ children: isLoadingMore ? "Loading\u2026" : "Load earlier messages"
908
+ }
909
+ ),
910
+ isEmpty ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 text-center py-16", children: [
911
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { className: "text-lg font-semibold", children: welcomeTitle }),
912
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm text-muted-foreground max-w-sm", children: welcomeDescription }),
913
+ welcomeSuggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-2 flex flex-col gap-2 w-full max-w-sm", children: welcomeSuggestions.map((suggestion) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
914
+ "button",
915
+ {
916
+ type: "button",
917
+ onClick: () => onWelcomeSelect?.(suggestion),
918
+ className: "rounded-md border border-border bg-background px-4 py-2.5 text-sm text-left text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
919
+ children: suggestion
920
+ },
921
+ suggestion
922
+ )) })
923
+ ] }) : messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
924
+ MemoizedMessage,
925
+ {
926
+ message,
927
+ showAvatar: showAvatars,
928
+ avatarSrc: message.role === "user" ? userAvatarSrc : assistantAvatarSrc,
929
+ avatarFallback: message.role === "user" ? userAvatarFallback : assistantAvatarFallback,
930
+ renderContent,
931
+ isStreaming: message.id === streamingMessageId,
932
+ error: messageErrors?.[message.id],
933
+ onRetry: onRetryMessage && messageErrors?.[message.id] ? () => onRetryMessage(message) : void 0
934
+ },
935
+ message.id
936
+ )),
937
+ isLoading && !isEmpty && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
938
+ MemoizedMessage,
939
+ {
940
+ message: {
941
+ id: "__loading__",
942
+ role: "assistant",
943
+ content: loadingMessage
944
+ },
945
+ showAvatar: showAvatars,
946
+ avatarSrc: assistantAvatarSrc,
947
+ avatarFallback: assistantAvatarFallback,
948
+ className: "bg-muted/50",
949
+ renderContent: (content) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-muted-foreground animate-pulse motion-reduce:animate-none", children: content })
950
+ }
951
+ ),
952
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { "aria-live": "polite", className: "sr-only", children: announcement }),
953
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { ref: messagesEndRef })
954
+ ]
955
+ }
956
+ ),
874
957
  showDownload && messages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
875
958
  "button",
876
959
  {
@@ -1281,17 +1364,17 @@ var React12 = __toESM(require("react"));
1281
1364
  var import_core16 = require("@slipstream-ui/core");
1282
1365
  var import_jsx_runtime12 = require("react/jsx-runtime");
1283
1366
  var Reasoning = React12.forwardRef(
1284
- ({ steps, className, collapsible = true, defaultExpanded = false, duration, isStreaming = false, ...props }, ref) => {
1367
+ ({ steps, className, collapsible = true, defaultExpanded = false, duration, isStreaming = false, labels, formatTime, locale, ...props }, ref) => {
1285
1368
  const [isExpanded, setIsExpanded] = React12.useState(defaultExpanded);
1286
1369
  const contentId = React12.useId();
1287
1370
  if (steps.length === 0) {
1288
1371
  return null;
1289
1372
  }
1290
1373
  const headerText = [
1291
- "Reasoning",
1292
- `(${steps.length} ${steps.length === 1 ? "step" : "steps"})`,
1374
+ labels?.title ?? "Reasoning",
1375
+ labels?.count ? labels.count(steps.length) : `(${steps.length} ${steps.length === 1 ? "step" : "steps"})`,
1293
1376
  typeof duration === "number" ? `\xB7 ${duration}s` : duration ? `\xB7 ${duration}` : "",
1294
- isStreaming ? "\xB7 thinking\u2026" : ""
1377
+ isStreaming ? `\xB7 ${labels?.thinking ?? "thinking\u2026"}` : ""
1295
1378
  ].filter(Boolean).join(" ");
1296
1379
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { ref, className: (0, import_core16.cn)("border-l-2 border-muted-foreground/30 pl-4", className), ...props, children: [
1297
1380
  collapsible ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
@@ -1302,7 +1385,7 @@ var Reasoning = React12.forwardRef(
1302
1385
  "aria-expanded": isExpanded,
1303
1386
  "aria-controls": contentId,
1304
1387
  children: [
1305
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: (0, import_core16.cn)("transition-transform", isExpanded ? "rotate-90" : ""), children: "\u25B6" }),
1388
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: (0, import_core16.cn)("transition-transform", isExpanded ? "rotate-90" : ""), children: "?" }),
1306
1389
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: headerText }),
1307
1390
  isStreaming && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "flex gap-0.5", "aria-hidden": "true", children: [0, 150, 300].map((delay) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1308
1391
  "span",
@@ -1318,12 +1401,9 @@ var Reasoning = React12.forwardRef(
1318
1401
  (isExpanded || !collapsible) && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { id: contentId, className: "mt-3 space-y-3", children: steps.map((step, index) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex gap-3", children: [
1319
1402
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "w-3 h-3 rounded-full bg-primary/20 border-2 border-primary flex-shrink-0 mt-1" }),
1320
1403
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "text-sm flex-1", children: [
1321
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "font-medium text-foreground mb-1", children: [
1322
- "Step ",
1323
- index + 1
1324
- ] }),
1404
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "font-medium text-foreground mb-1", children: labels?.step?.(index + 1) ?? `Step ${index + 1}` }),
1325
1405
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "text-muted-foreground", children: step.content }),
1326
- step.timestamp && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "text-xs text-muted-foreground mt-1", children: new Date(step.timestamp).toLocaleTimeString() })
1406
+ step.timestamp && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "text-xs text-muted-foreground mt-1", children: formatTime ? formatTime(new Date(step.timestamp)) : new Date(step.timestamp).toLocaleTimeString(locale) })
1327
1407
  ] })
1328
1408
  ] }, step.id)) })
1329
1409
  ] });
@@ -1336,19 +1416,19 @@ var React13 = __toESM(require("react"));
1336
1416
  var import_core17 = require("@slipstream-ui/core");
1337
1417
  var import_jsx_runtime13 = require("react/jsx-runtime");
1338
1418
  var statusChipClasses2 = {
1339
- pending: "bg-warning/10 text-warning border-warning/20",
1340
- running: "bg-info/10 text-info border-info/20",
1419
+ pending: "bg-warning/10 text-warning-ink border-warning/20",
1420
+ running: "bg-info/10 text-info-ink border-info/20",
1341
1421
  completed: "bg-success/10 text-success border-success/20",
1342
1422
  failed: "bg-destructive/10 text-destructive border-destructive/20"
1343
1423
  };
1344
- var statusLabels = {
1424
+ var defaultStatusLabels = {
1345
1425
  pending: "Pending",
1346
1426
  running: "Running",
1347
1427
  completed: "Completed",
1348
1428
  failed: "Failed"
1349
1429
  };
1350
1430
  var ToolCall = React13.forwardRef(
1351
- ({ toolCall, className, showResult = true, ...props }, ref) => {
1431
+ ({ toolCall, className, showResult = true, labels, ...props }, ref) => {
1352
1432
  const serializedArguments = React13.useMemo(
1353
1433
  () => JSON.stringify(toolCall.arguments, null, 2),
1354
1434
  [toolCall.arguments]
@@ -1357,6 +1437,12 @@ var ToolCall = React13.forwardRef(
1357
1437
  if (toolCall.result === void 0) return void 0;
1358
1438
  return typeof toolCall.result === "string" ? toolCall.result : JSON.stringify(toolCall.result, null, 2);
1359
1439
  }, [toolCall.result]);
1440
+ const statusLabels = {
1441
+ pending: labels?.pending ?? defaultStatusLabels.pending,
1442
+ running: labels?.running ?? defaultStatusLabels.running,
1443
+ completed: labels?.completed ?? defaultStatusLabels.completed,
1444
+ failed: labels?.failed ?? defaultStatusLabels.failed
1445
+ };
1360
1446
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1361
1447
  "div",
1362
1448
  {
@@ -1385,15 +1471,15 @@ var ToolCall = React13.forwardRef(
1385
1471
  ] }),
1386
1472
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "space-y-2", children: [
1387
1473
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
1388
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Arguments" }),
1474
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-xs font-medium text-muted-foreground mb-1", children: labels?.arguments ?? "Arguments" }),
1389
1475
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("pre", { className: "text-xs bg-muted p-2 rounded overflow-x-auto", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("code", { children: serializedArguments }) })
1390
1476
  ] }),
1391
1477
  showResult && serializedResult !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
1392
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Result" }),
1478
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-xs font-medium text-muted-foreground mb-1", children: labels?.result ?? "Result" }),
1393
1479
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("pre", { className: "text-xs bg-muted p-2 rounded overflow-x-auto", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("code", { children: serializedResult }) })
1394
1480
  ] }),
1395
1481
  toolCall.status === "failed" && toolCall.error && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { role: "alert", children: [
1396
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-xs font-medium text-destructive mb-1", children: "Error" }),
1482
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-xs font-medium text-destructive mb-1", children: labels?.error ?? "Error" }),
1397
1483
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("pre", { className: "text-xs bg-destructive/10 text-destructive p-2 rounded overflow-x-auto whitespace-pre-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("code", { children: toolCall.error }) })
1398
1484
  ] })
1399
1485
  ] })
@@ -1559,21 +1645,49 @@ function CommandPalette({
1559
1645
  onOpenChange,
1560
1646
  commands,
1561
1647
  onSelect,
1562
- placeholder = "Type a command\u2026"
1648
+ loadCommands,
1649
+ debounceMs = 200,
1650
+ renderCommand,
1651
+ loadingText = "Searching\u2026",
1652
+ placeholder = "Type a command.",
1653
+ dialogLabel = "Command palette",
1654
+ listLabel = "Commands",
1655
+ emptyText = "No matching commands."
1563
1656
  }) {
1564
1657
  const [query, setQuery] = React19.useState("");
1565
1658
  const [activeIndex, setActiveIndex] = React19.useState(0);
1659
+ const [asyncCommands, setAsyncCommands] = React19.useState([]);
1660
+ const [loading, setLoading] = React19.useState(false);
1661
+ const requestId = React19.useRef(0);
1662
+ const asyncSignature = loadCommands ? String(loadCommands.length) : "";
1566
1663
  const inputRef = React19.useRef(null);
1567
1664
  const listboxId = React19.useId();
1568
1665
  const optionId = (index) => `${listboxId}-option-${index}`;
1666
+ React19.useEffect(() => {
1667
+ if (!open || !loadCommands) return;
1668
+ const id = ++requestId.current;
1669
+ setLoading(true);
1670
+ const timer = setTimeout(async () => {
1671
+ try {
1672
+ const result = await loadCommands(query);
1673
+ if (requestId.current === id) setAsyncCommands(result);
1674
+ } catch {
1675
+ if (requestId.current === id) setAsyncCommands([]);
1676
+ } finally {
1677
+ if (requestId.current === id) setLoading(false);
1678
+ }
1679
+ }, debounceMs);
1680
+ return () => clearTimeout(timer);
1681
+ }, [open, query, debounceMs, asyncSignature]);
1569
1682
  const filtered = React19.useMemo(() => {
1683
+ if (loadCommands) return asyncCommands;
1570
1684
  const terms = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
1571
1685
  if (terms.length === 0) return commands;
1572
1686
  return commands.filter((command) => {
1573
1687
  const haystack = `${command.label} ${command.section ?? ""} ${(command.keywords ?? []).join(" ")}`.toLowerCase();
1574
1688
  return terms.every((term) => haystack.includes(term));
1575
1689
  });
1576
- }, [commands, query]);
1690
+ }, [commands, query, asyncCommands, loadCommands]);
1577
1691
  React19.useEffect(() => {
1578
1692
  if (open) {
1579
1693
  setQuery("");
@@ -1625,7 +1739,7 @@ function CommandPalette({
1625
1739
  "aria-controls": listboxId,
1626
1740
  "aria-activedescendant": filtered.length > 0 ? optionId(activeIndex) : void 0,
1627
1741
  "aria-autocomplete": "list",
1628
- "aria-label": "Command palette",
1742
+ "aria-label": dialogLabel,
1629
1743
  placeholder,
1630
1744
  value: query,
1631
1745
  onChange: (event) => {
@@ -1636,37 +1750,54 @@ function CommandPalette({
1636
1750
  className: "w-full border-b border-border bg-transparent px-4 py-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring placeholder:text-muted-foreground"
1637
1751
  }
1638
1752
  ),
1639
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("ul", { id: listboxId, role: "listbox", "aria-label": "Commands", className: "max-h-72 overflow-y-auto py-1", children: [
1640
- filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("li", { role: "presentation", className: "px-4 py-3 text-sm text-muted-foreground", children: "No matching commands." }),
1641
- filtered.map((command, index) => {
1642
- const showSection = command.section && command.section !== lastSection;
1643
- lastSection = command.section ?? lastSection;
1644
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(React19.Fragment, { children: [
1645
- showSection && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("li", { role: "presentation", className: "px-4 pb-1 pt-3 text-xs font-medium uppercase text-muted-foreground", children: command.section }),
1646
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1647
- "li",
1648
- {
1649
- id: optionId(index),
1650
- role: "option",
1651
- "aria-selected": index === activeIndex,
1652
- onMouseEnter: () => setActiveIndex(index),
1653
- onClick: () => {
1654
- onSelect(command);
1655
- onOpenChange(false);
1656
- },
1657
- className: (0, import_core23.cn)(
1658
- "flex cursor-pointer items-center justify-between px-4 py-2 text-sm",
1659
- index === activeIndex ? "bg-primary/10 text-foreground" : "text-muted-foreground"
1660
- ),
1661
- children: [
1662
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { children: command.label }),
1663
- command.shortcut && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "font-mono text-xs", children: command.shortcut })
1664
- ]
1665
- }
1666
- )
1667
- ] }, command.id);
1668
- })
1669
- ] })
1753
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1754
+ "ul",
1755
+ {
1756
+ id: listboxId,
1757
+ role: "listbox",
1758
+ "aria-label": listLabel,
1759
+ "aria-busy": loading || void 0,
1760
+ className: "max-h-72 overflow-y-auto py-1",
1761
+ children: [
1762
+ loading && filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("li", { role: "presentation", className: "px-4 py-3 text-sm text-muted-foreground", children: loadingText }),
1763
+ !loading && filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("li", { role: "presentation", className: "px-4 py-3 text-sm text-muted-foreground", children: emptyText }),
1764
+ filtered.map((command, index) => {
1765
+ const showSection = command.section && command.section !== lastSection;
1766
+ lastSection = command.section ?? lastSection;
1767
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(React19.Fragment, { children: [
1768
+ showSection && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("li", { role: "presentation", className: "px-4 pb-1 pt-3 text-xs font-medium uppercase text-muted-foreground", children: command.section }),
1769
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1770
+ "li",
1771
+ {
1772
+ id: optionId(index),
1773
+ role: "option",
1774
+ "aria-selected": index === activeIndex,
1775
+ onMouseEnter: () => setActiveIndex(index),
1776
+ onClick: () => {
1777
+ onSelect(command);
1778
+ onOpenChange(false);
1779
+ },
1780
+ className: (0, import_core23.cn)(
1781
+ "flex cursor-pointer items-center justify-between px-4 py-2 text-sm",
1782
+ index === activeIndex ? "bg-primary/10 text-foreground" : "text-muted-foreground"
1783
+ ),
1784
+ children: renderCommand ? renderCommand(command, { active: index === activeIndex }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
1785
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "flex min-w-0 items-center gap-2", children: [
1786
+ command.icon && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "shrink-0 [&_svg]:size-4", children: command.icon }),
1787
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "min-w-0", children: [
1788
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "block truncate", children: command.label }),
1789
+ command.description && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "block text-xs text-muted-foreground", children: command.description })
1790
+ ] })
1791
+ ] }),
1792
+ command.shortcut && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "font-mono text-xs", children: command.shortcut })
1793
+ ] })
1794
+ }
1795
+ )
1796
+ ] }, command.id);
1797
+ })
1798
+ ]
1799
+ }
1800
+ )
1670
1801
  ] })
1671
1802
  }
1672
1803
  );