@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.mjs CHANGED
@@ -14,6 +14,8 @@ var Message = React.forwardRef(
14
14
  avatarFallback,
15
15
  userLabel = "You",
16
16
  assistantLabel = "Assistant",
17
+ formatTime,
18
+ locale,
17
19
  renderContent,
18
20
  isStreaming = false,
19
21
  error,
@@ -67,7 +69,7 @@ var Message = React.forwardRef(
67
69
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
68
70
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
69
71
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: displayLabel }),
70
- message.timestamp && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: new Date(message.timestamp).toLocaleTimeString() })
72
+ message.timestamp && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: formatTime ? formatTime(new Date(message.timestamp)) : new Date(message.timestamp).toLocaleTimeString(locale) })
71
73
  ] }),
72
74
  error ? /* @__PURE__ */ jsxs("div", { role: "alert", className: "text-sm", children: [
73
75
  /* @__PURE__ */ jsx("div", { className: "text-destructive mb-2", children: error }),
@@ -292,7 +294,7 @@ import { cn as cn6 } from "@slipstream-ui/core";
292
294
 
293
295
  // src/components/code-block/CodeBlock.tsx
294
296
  import * as React5 from "react";
295
- import { Highlight, themes } from "prism-react-renderer";
297
+ import { Highlight } from "prism-react-renderer";
296
298
  import { cn as cn5 } from "@slipstream-ui/core";
297
299
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
298
300
  function useIsDark() {
@@ -306,11 +308,80 @@ function useIsDark() {
306
308
  }, []);
307
309
  return dark;
308
310
  }
311
+ var LIGHT_CODE_THEME = {
312
+ plain: { color: "#1f2328", backgroundColor: "transparent" },
313
+ styles: [
314
+ { types: ["comment", "prolog", "doctype", "cdata"], style: { color: "#57606a" } },
315
+ { types: ["punctuation", "operator"], style: { color: "#24292f" } },
316
+ { types: ["namespace"], style: { color: "#6f42c1" } },
317
+ { types: ["tag", "deleted", "keyword", "atrule", "important"], style: { color: "#b31d28" } },
318
+ { types: ["attr-name", "variable"], style: { color: "#953800" } },
319
+ { types: ["string", "char", "attr-value", "inserted", "regex"], style: { color: "#0a5c2f" } },
320
+ {
321
+ types: [
322
+ "function",
323
+ "class-name",
324
+ "number",
325
+ "boolean",
326
+ "constant",
327
+ "symbol",
328
+ "property",
329
+ "selector",
330
+ "builtin",
331
+ "entity",
332
+ "url"
333
+ ],
334
+ style: { color: "#0550ae" }
335
+ }
336
+ ]
337
+ };
338
+ var DARK_CODE_THEME = {
339
+ plain: { color: "#e6edf3", backgroundColor: "transparent" },
340
+ styles: [
341
+ { types: ["comment", "prolog", "doctype", "cdata"], style: { color: "#8b949e" } },
342
+ { types: ["punctuation", "operator"], style: { color: "#c9d1d9" } },
343
+ { types: ["namespace"], style: { color: "#d2a8ff" } },
344
+ { types: ["tag", "deleted", "keyword", "atrule", "important"], style: { color: "#ff7b72" } },
345
+ { types: ["attr-name", "variable"], style: { color: "#ffa657" } },
346
+ { types: ["string", "char", "attr-value", "inserted", "regex"], style: { color: "#7ee787" } },
347
+ {
348
+ types: [
349
+ "function",
350
+ "class-name",
351
+ "number",
352
+ "boolean",
353
+ "constant",
354
+ "symbol",
355
+ "property",
356
+ "selector",
357
+ "builtin",
358
+ "entity",
359
+ "url"
360
+ ],
361
+ style: { color: "#79c0ff" }
362
+ }
363
+ ]
364
+ };
309
365
  var CodeBlock = React5.forwardRef(
310
366
  ({ code, language = "tsx", showCopyButton = true, className, ...props }, ref) => {
311
367
  const [copied, setCopied] = React5.useState(false);
312
368
  const isDark = useIsDark();
313
369
  const trimmedCode = code.replace(/\n$/, "");
370
+ const preRef = React5.useRef(null);
371
+ const [scrollable, setScrollable] = React5.useState(false);
372
+ React5.useEffect(() => {
373
+ const element = preRef.current;
374
+ if (!element) return;
375
+ const update = () => setScrollable(element.scrollWidth > element.clientWidth + 1);
376
+ update();
377
+ if (typeof ResizeObserver === "undefined") {
378
+ window.addEventListener("resize", update);
379
+ return () => window.removeEventListener("resize", update);
380
+ }
381
+ const observer = new ResizeObserver(update);
382
+ observer.observe(element);
383
+ return () => observer.disconnect();
384
+ }, [trimmedCode, language]);
314
385
  const handleCopy = async () => {
315
386
  try {
316
387
  await navigator.clipboard.writeText(trimmedCode);
@@ -324,8 +395,8 @@ var CodeBlock = React5.forwardRef(
324
395
  {
325
396
  ref,
326
397
  className: cn5(
327
- // bg-background (not muted): Prism light themes assume a white
328
- // surface — on gray the comment/keyword tokens fall below AA.
398
+ // bg-background (not muted): the light syntax palette is tuned for
399
+ // a white surface; on gray the token contrast drops below AA.
329
400
  "relative group rounded-md border border-border bg-background overflow-hidden",
330
401
  className
331
402
  ),
@@ -344,10 +415,13 @@ var CodeBlock = React5.forwardRef(
344
415
  }
345
416
  )
346
417
  ] }),
347
- /* @__PURE__ */ jsx5(Highlight, { theme: isDark ? themes.vsDark : themes.github, code: trimmedCode, language, children: ({ style, tokens, getLineProps, getTokenProps }) => /* @__PURE__ */ jsx5(
418
+ /* @__PURE__ */ jsx5(Highlight, { theme: isDark ? DARK_CODE_THEME : LIGHT_CODE_THEME, code: trimmedCode, language, children: ({ style, tokens, getLineProps, getTokenProps }) => /* @__PURE__ */ jsx5(
348
419
  "pre",
349
420
  {
350
- className: "overflow-x-auto p-4 text-xs leading-relaxed m-0",
421
+ ref: preRef,
422
+ tabIndex: scrollable ? 0 : void 0,
423
+ "aria-label": scrollable ? `${language} code, scrollable` : void 0,
424
+ className: "overflow-x-auto p-4 text-xs leading-relaxed m-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
351
425
  style: { ...style, background: "transparent" },
352
426
  children: tokens.map((line, i) => /* @__PURE__ */ jsx5("div", { ...getLineProps({ line }), children: line.map((token, key) => /* @__PURE__ */ jsx5("span", { ...getTokenProps({ token }) }, key)) }, i))
353
427
  }
@@ -444,7 +518,7 @@ import { Button as Button2 } from "@slipstream-ui/core";
444
518
  import { cn as cn7 } from "@slipstream-ui/core";
445
519
  import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
446
520
  var statusChipClasses = {
447
- pending: "bg-warning/10 text-warning border-warning/20",
521
+ pending: "bg-warning/10 text-warning-ink border-warning/20",
448
522
  approved: "bg-success/10 text-success border-success/20",
449
523
  rejected: "bg-destructive/10 text-destructive border-destructive/20"
450
524
  };
@@ -744,62 +818,71 @@ var Conversation = React10.forwardRef(
744
818
  };
745
819
  const isEmpty = messages.length === 0 && !isLoading;
746
820
  return /* @__PURE__ */ jsxs8("div", { ref, className: cn10("relative h-full w-full", className), ...props, children: [
747
- /* @__PURE__ */ jsxs8("div", { ref: scrollRef, className: "flex flex-col gap-4 p-4 overflow-y-auto h-full", children: [
748
- (hasMoreHistory || isLoadingMore) && /* @__PURE__ */ jsx10(
749
- "button",
750
- {
751
- type: "button",
752
- onClick: handleLoadMore,
753
- disabled: isLoadingMore,
754
- 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",
755
- children: isLoadingMore ? "Loading\u2026" : "Load earlier messages"
756
- }
757
- ),
758
- isEmpty ? /* @__PURE__ */ jsxs8("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 text-center py-16", children: [
759
- /* @__PURE__ */ jsx10("h3", { className: "text-lg font-semibold", children: welcomeTitle }),
760
- /* @__PURE__ */ jsx10("p", { className: "text-sm text-muted-foreground max-w-sm", children: welcomeDescription }),
761
- welcomeSuggestions.length > 0 && /* @__PURE__ */ jsx10("div", { className: "mt-2 flex flex-col gap-2 w-full max-w-sm", children: welcomeSuggestions.map((suggestion) => /* @__PURE__ */ jsx10(
762
- "button",
763
- {
764
- type: "button",
765
- onClick: () => onWelcomeSelect?.(suggestion),
766
- 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",
767
- children: suggestion
768
- },
769
- suggestion
770
- )) })
771
- ] }) : messages.map((message) => /* @__PURE__ */ jsx10(
772
- MemoizedMessage,
773
- {
774
- message,
775
- showAvatar: showAvatars,
776
- avatarSrc: message.role === "user" ? userAvatarSrc : assistantAvatarSrc,
777
- avatarFallback: message.role === "user" ? userAvatarFallback : assistantAvatarFallback,
778
- renderContent,
779
- isStreaming: message.id === streamingMessageId,
780
- error: messageErrors?.[message.id],
781
- onRetry: onRetryMessage && messageErrors?.[message.id] ? () => onRetryMessage(message) : void 0
782
- },
783
- message.id
784
- )),
785
- isLoading && !isEmpty && /* @__PURE__ */ jsx10(
786
- MemoizedMessage,
787
- {
788
- message: {
789
- id: "__loading__",
790
- role: "assistant",
791
- content: loadingMessage
792
- },
793
- showAvatar: showAvatars,
794
- avatarSrc: assistantAvatarSrc,
795
- avatarFallback: assistantAvatarFallback,
796
- className: "bg-muted/50",
797
- renderContent: (content) => /* @__PURE__ */ jsx10("span", { className: "text-muted-foreground animate-pulse motion-reduce:animate-none", children: content })
798
- }
799
- ),
800
- /* @__PURE__ */ jsx10("div", { "aria-live": "polite", className: "sr-only", children: announcement }),
801
- /* @__PURE__ */ jsx10("div", { ref: messagesEndRef })
802
- ] }),
821
+ /* @__PURE__ */ jsxs8(
822
+ "div",
823
+ {
824
+ ref: scrollRef,
825
+ tabIndex: 0,
826
+ "aria-label": "Conversation transcript",
827
+ 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",
828
+ children: [
829
+ (hasMoreHistory || isLoadingMore) && /* @__PURE__ */ jsx10(
830
+ "button",
831
+ {
832
+ type: "button",
833
+ onClick: handleLoadMore,
834
+ disabled: isLoadingMore,
835
+ 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",
836
+ children: isLoadingMore ? "Loading\u2026" : "Load earlier messages"
837
+ }
838
+ ),
839
+ isEmpty ? /* @__PURE__ */ jsxs8("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 text-center py-16", children: [
840
+ /* @__PURE__ */ jsx10("h3", { className: "text-lg font-semibold", children: welcomeTitle }),
841
+ /* @__PURE__ */ jsx10("p", { className: "text-sm text-muted-foreground max-w-sm", children: welcomeDescription }),
842
+ welcomeSuggestions.length > 0 && /* @__PURE__ */ jsx10("div", { className: "mt-2 flex flex-col gap-2 w-full max-w-sm", children: welcomeSuggestions.map((suggestion) => /* @__PURE__ */ jsx10(
843
+ "button",
844
+ {
845
+ type: "button",
846
+ onClick: () => onWelcomeSelect?.(suggestion),
847
+ 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",
848
+ children: suggestion
849
+ },
850
+ suggestion
851
+ )) })
852
+ ] }) : messages.map((message) => /* @__PURE__ */ jsx10(
853
+ MemoizedMessage,
854
+ {
855
+ message,
856
+ showAvatar: showAvatars,
857
+ avatarSrc: message.role === "user" ? userAvatarSrc : assistantAvatarSrc,
858
+ avatarFallback: message.role === "user" ? userAvatarFallback : assistantAvatarFallback,
859
+ renderContent,
860
+ isStreaming: message.id === streamingMessageId,
861
+ error: messageErrors?.[message.id],
862
+ onRetry: onRetryMessage && messageErrors?.[message.id] ? () => onRetryMessage(message) : void 0
863
+ },
864
+ message.id
865
+ )),
866
+ isLoading && !isEmpty && /* @__PURE__ */ jsx10(
867
+ MemoizedMessage,
868
+ {
869
+ message: {
870
+ id: "__loading__",
871
+ role: "assistant",
872
+ content: loadingMessage
873
+ },
874
+ showAvatar: showAvatars,
875
+ avatarSrc: assistantAvatarSrc,
876
+ avatarFallback: assistantAvatarFallback,
877
+ className: "bg-muted/50",
878
+ renderContent: (content) => /* @__PURE__ */ jsx10("span", { className: "text-muted-foreground animate-pulse motion-reduce:animate-none", children: content })
879
+ }
880
+ ),
881
+ /* @__PURE__ */ jsx10("div", { "aria-live": "polite", className: "sr-only", children: announcement }),
882
+ /* @__PURE__ */ jsx10("div", { ref: messagesEndRef })
883
+ ]
884
+ }
885
+ ),
803
886
  showDownload && messages.length > 0 && /* @__PURE__ */ jsx10(
804
887
  "button",
805
888
  {
@@ -1210,17 +1293,17 @@ import * as React12 from "react";
1210
1293
  import { cn as cn12 } from "@slipstream-ui/core";
1211
1294
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1212
1295
  var Reasoning = React12.forwardRef(
1213
- ({ steps, className, collapsible = true, defaultExpanded = false, duration, isStreaming = false, ...props }, ref) => {
1296
+ ({ steps, className, collapsible = true, defaultExpanded = false, duration, isStreaming = false, labels, formatTime, locale, ...props }, ref) => {
1214
1297
  const [isExpanded, setIsExpanded] = React12.useState(defaultExpanded);
1215
1298
  const contentId = React12.useId();
1216
1299
  if (steps.length === 0) {
1217
1300
  return null;
1218
1301
  }
1219
1302
  const headerText = [
1220
- "Reasoning",
1221
- `(${steps.length} ${steps.length === 1 ? "step" : "steps"})`,
1303
+ labels?.title ?? "Reasoning",
1304
+ labels?.count ? labels.count(steps.length) : `(${steps.length} ${steps.length === 1 ? "step" : "steps"})`,
1222
1305
  typeof duration === "number" ? `\xB7 ${duration}s` : duration ? `\xB7 ${duration}` : "",
1223
- isStreaming ? "\xB7 thinking\u2026" : ""
1306
+ isStreaming ? `\xB7 ${labels?.thinking ?? "thinking\u2026"}` : ""
1224
1307
  ].filter(Boolean).join(" ");
1225
1308
  return /* @__PURE__ */ jsxs10("div", { ref, className: cn12("border-l-2 border-muted-foreground/30 pl-4", className), ...props, children: [
1226
1309
  collapsible ? /* @__PURE__ */ jsxs10(
@@ -1231,7 +1314,7 @@ var Reasoning = React12.forwardRef(
1231
1314
  "aria-expanded": isExpanded,
1232
1315
  "aria-controls": contentId,
1233
1316
  children: [
1234
- /* @__PURE__ */ jsx12("span", { className: cn12("transition-transform", isExpanded ? "rotate-90" : ""), children: "\u25B6" }),
1317
+ /* @__PURE__ */ jsx12("span", { className: cn12("transition-transform", isExpanded ? "rotate-90" : ""), children: "?" }),
1235
1318
  /* @__PURE__ */ jsx12("span", { children: headerText }),
1236
1319
  isStreaming && /* @__PURE__ */ jsx12("span", { className: "flex gap-0.5", "aria-hidden": "true", children: [0, 150, 300].map((delay) => /* @__PURE__ */ jsx12(
1237
1320
  "span",
@@ -1247,12 +1330,9 @@ var Reasoning = React12.forwardRef(
1247
1330
  (isExpanded || !collapsible) && /* @__PURE__ */ jsx12("div", { id: contentId, className: "mt-3 space-y-3", children: steps.map((step, index) => /* @__PURE__ */ jsxs10("div", { className: "flex gap-3", children: [
1248
1331
  /* @__PURE__ */ jsx12("div", { className: "w-3 h-3 rounded-full bg-primary/20 border-2 border-primary flex-shrink-0 mt-1" }),
1249
1332
  /* @__PURE__ */ jsxs10("div", { className: "text-sm flex-1", children: [
1250
- /* @__PURE__ */ jsxs10("div", { className: "font-medium text-foreground mb-1", children: [
1251
- "Step ",
1252
- index + 1
1253
- ] }),
1333
+ /* @__PURE__ */ jsx12("div", { className: "font-medium text-foreground mb-1", children: labels?.step?.(index + 1) ?? `Step ${index + 1}` }),
1254
1334
  /* @__PURE__ */ jsx12("div", { className: "text-muted-foreground", children: step.content }),
1255
- step.timestamp && /* @__PURE__ */ jsx12("div", { className: "text-xs text-muted-foreground mt-1", children: new Date(step.timestamp).toLocaleTimeString() })
1335
+ step.timestamp && /* @__PURE__ */ jsx12("div", { className: "text-xs text-muted-foreground mt-1", children: formatTime ? formatTime(new Date(step.timestamp)) : new Date(step.timestamp).toLocaleTimeString(locale) })
1256
1336
  ] })
1257
1337
  ] }, step.id)) })
1258
1338
  ] });
@@ -1265,19 +1345,19 @@ import * as React13 from "react";
1265
1345
  import { cn as cn13 } from "@slipstream-ui/core";
1266
1346
  import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1267
1347
  var statusChipClasses2 = {
1268
- pending: "bg-warning/10 text-warning border-warning/20",
1269
- running: "bg-info/10 text-info border-info/20",
1348
+ pending: "bg-warning/10 text-warning-ink border-warning/20",
1349
+ running: "bg-info/10 text-info-ink border-info/20",
1270
1350
  completed: "bg-success/10 text-success border-success/20",
1271
1351
  failed: "bg-destructive/10 text-destructive border-destructive/20"
1272
1352
  };
1273
- var statusLabels = {
1353
+ var defaultStatusLabels = {
1274
1354
  pending: "Pending",
1275
1355
  running: "Running",
1276
1356
  completed: "Completed",
1277
1357
  failed: "Failed"
1278
1358
  };
1279
1359
  var ToolCall = React13.forwardRef(
1280
- ({ toolCall, className, showResult = true, ...props }, ref) => {
1360
+ ({ toolCall, className, showResult = true, labels, ...props }, ref) => {
1281
1361
  const serializedArguments = React13.useMemo(
1282
1362
  () => JSON.stringify(toolCall.arguments, null, 2),
1283
1363
  [toolCall.arguments]
@@ -1286,6 +1366,12 @@ var ToolCall = React13.forwardRef(
1286
1366
  if (toolCall.result === void 0) return void 0;
1287
1367
  return typeof toolCall.result === "string" ? toolCall.result : JSON.stringify(toolCall.result, null, 2);
1288
1368
  }, [toolCall.result]);
1369
+ const statusLabels = {
1370
+ pending: labels?.pending ?? defaultStatusLabels.pending,
1371
+ running: labels?.running ?? defaultStatusLabels.running,
1372
+ completed: labels?.completed ?? defaultStatusLabels.completed,
1373
+ failed: labels?.failed ?? defaultStatusLabels.failed
1374
+ };
1289
1375
  return /* @__PURE__ */ jsxs11(
1290
1376
  "div",
1291
1377
  {
@@ -1314,15 +1400,15 @@ var ToolCall = React13.forwardRef(
1314
1400
  ] }),
1315
1401
  /* @__PURE__ */ jsxs11("div", { className: "space-y-2", children: [
1316
1402
  /* @__PURE__ */ jsxs11("div", { children: [
1317
- /* @__PURE__ */ jsx13("div", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Arguments" }),
1403
+ /* @__PURE__ */ jsx13("div", { className: "text-xs font-medium text-muted-foreground mb-1", children: labels?.arguments ?? "Arguments" }),
1318
1404
  /* @__PURE__ */ jsx13("pre", { className: "text-xs bg-muted p-2 rounded overflow-x-auto", children: /* @__PURE__ */ jsx13("code", { children: serializedArguments }) })
1319
1405
  ] }),
1320
1406
  showResult && serializedResult !== void 0 && /* @__PURE__ */ jsxs11("div", { children: [
1321
- /* @__PURE__ */ jsx13("div", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Result" }),
1407
+ /* @__PURE__ */ jsx13("div", { className: "text-xs font-medium text-muted-foreground mb-1", children: labels?.result ?? "Result" }),
1322
1408
  /* @__PURE__ */ jsx13("pre", { className: "text-xs bg-muted p-2 rounded overflow-x-auto", children: /* @__PURE__ */ jsx13("code", { children: serializedResult }) })
1323
1409
  ] }),
1324
1410
  toolCall.status === "failed" && toolCall.error && /* @__PURE__ */ jsxs11("div", { role: "alert", children: [
1325
- /* @__PURE__ */ jsx13("div", { className: "text-xs font-medium text-destructive mb-1", children: "Error" }),
1411
+ /* @__PURE__ */ jsx13("div", { className: "text-xs font-medium text-destructive mb-1", children: labels?.error ?? "Error" }),
1326
1412
  /* @__PURE__ */ jsx13("pre", { className: "text-xs bg-destructive/10 text-destructive p-2 rounded overflow-x-auto whitespace-pre-wrap", children: /* @__PURE__ */ jsx13("code", { children: toolCall.error }) })
1327
1413
  ] })
1328
1414
  ] })
@@ -1482,27 +1568,55 @@ StreamingIndicator.displayName = "StreamingIndicator";
1482
1568
  // src/components/command-palette/CommandPalette.tsx
1483
1569
  import * as React19 from "react";
1484
1570
  import { cn as cn19 } from "@slipstream-ui/core";
1485
- import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
1571
+ import { Fragment as Fragment5, jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
1486
1572
  function CommandPalette({
1487
1573
  open,
1488
1574
  onOpenChange,
1489
1575
  commands,
1490
1576
  onSelect,
1491
- placeholder = "Type a command\u2026"
1577
+ loadCommands,
1578
+ debounceMs = 200,
1579
+ renderCommand,
1580
+ loadingText = "Searching\u2026",
1581
+ placeholder = "Type a command.",
1582
+ dialogLabel = "Command palette",
1583
+ listLabel = "Commands",
1584
+ emptyText = "No matching commands."
1492
1585
  }) {
1493
1586
  const [query, setQuery] = React19.useState("");
1494
1587
  const [activeIndex, setActiveIndex] = React19.useState(0);
1588
+ const [asyncCommands, setAsyncCommands] = React19.useState([]);
1589
+ const [loading, setLoading] = React19.useState(false);
1590
+ const requestId = React19.useRef(0);
1591
+ const asyncSignature = loadCommands ? String(loadCommands.length) : "";
1495
1592
  const inputRef = React19.useRef(null);
1496
1593
  const listboxId = React19.useId();
1497
1594
  const optionId = (index) => `${listboxId}-option-${index}`;
1595
+ React19.useEffect(() => {
1596
+ if (!open || !loadCommands) return;
1597
+ const id = ++requestId.current;
1598
+ setLoading(true);
1599
+ const timer = setTimeout(async () => {
1600
+ try {
1601
+ const result = await loadCommands(query);
1602
+ if (requestId.current === id) setAsyncCommands(result);
1603
+ } catch {
1604
+ if (requestId.current === id) setAsyncCommands([]);
1605
+ } finally {
1606
+ if (requestId.current === id) setLoading(false);
1607
+ }
1608
+ }, debounceMs);
1609
+ return () => clearTimeout(timer);
1610
+ }, [open, query, debounceMs, asyncSignature]);
1498
1611
  const filtered = React19.useMemo(() => {
1612
+ if (loadCommands) return asyncCommands;
1499
1613
  const terms = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
1500
1614
  if (terms.length === 0) return commands;
1501
1615
  return commands.filter((command) => {
1502
1616
  const haystack = `${command.label} ${command.section ?? ""} ${(command.keywords ?? []).join(" ")}`.toLowerCase();
1503
1617
  return terms.every((term) => haystack.includes(term));
1504
1618
  });
1505
- }, [commands, query]);
1619
+ }, [commands, query, asyncCommands, loadCommands]);
1506
1620
  React19.useEffect(() => {
1507
1621
  if (open) {
1508
1622
  setQuery("");
@@ -1554,7 +1668,7 @@ function CommandPalette({
1554
1668
  "aria-controls": listboxId,
1555
1669
  "aria-activedescendant": filtered.length > 0 ? optionId(activeIndex) : void 0,
1556
1670
  "aria-autocomplete": "list",
1557
- "aria-label": "Command palette",
1671
+ "aria-label": dialogLabel,
1558
1672
  placeholder,
1559
1673
  value: query,
1560
1674
  onChange: (event) => {
@@ -1565,37 +1679,54 @@ function CommandPalette({
1565
1679
  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"
1566
1680
  }
1567
1681
  ),
1568
- /* @__PURE__ */ jsxs16("ul", { id: listboxId, role: "listbox", "aria-label": "Commands", className: "max-h-72 overflow-y-auto py-1", children: [
1569
- filtered.length === 0 && /* @__PURE__ */ jsx19("li", { role: "presentation", className: "px-4 py-3 text-sm text-muted-foreground", children: "No matching commands." }),
1570
- filtered.map((command, index) => {
1571
- const showSection = command.section && command.section !== lastSection;
1572
- lastSection = command.section ?? lastSection;
1573
- return /* @__PURE__ */ jsxs16(React19.Fragment, { children: [
1574
- showSection && /* @__PURE__ */ jsx19("li", { role: "presentation", className: "px-4 pb-1 pt-3 text-xs font-medium uppercase text-muted-foreground", children: command.section }),
1575
- /* @__PURE__ */ jsxs16(
1576
- "li",
1577
- {
1578
- id: optionId(index),
1579
- role: "option",
1580
- "aria-selected": index === activeIndex,
1581
- onMouseEnter: () => setActiveIndex(index),
1582
- onClick: () => {
1583
- onSelect(command);
1584
- onOpenChange(false);
1585
- },
1586
- className: cn19(
1587
- "flex cursor-pointer items-center justify-between px-4 py-2 text-sm",
1588
- index === activeIndex ? "bg-primary/10 text-foreground" : "text-muted-foreground"
1589
- ),
1590
- children: [
1591
- /* @__PURE__ */ jsx19("span", { children: command.label }),
1592
- command.shortcut && /* @__PURE__ */ jsx19("span", { className: "font-mono text-xs", children: command.shortcut })
1593
- ]
1594
- }
1595
- )
1596
- ] }, command.id);
1597
- })
1598
- ] })
1682
+ /* @__PURE__ */ jsxs16(
1683
+ "ul",
1684
+ {
1685
+ id: listboxId,
1686
+ role: "listbox",
1687
+ "aria-label": listLabel,
1688
+ "aria-busy": loading || void 0,
1689
+ className: "max-h-72 overflow-y-auto py-1",
1690
+ children: [
1691
+ loading && filtered.length === 0 && /* @__PURE__ */ jsx19("li", { role: "presentation", className: "px-4 py-3 text-sm text-muted-foreground", children: loadingText }),
1692
+ !loading && filtered.length === 0 && /* @__PURE__ */ jsx19("li", { role: "presentation", className: "px-4 py-3 text-sm text-muted-foreground", children: emptyText }),
1693
+ filtered.map((command, index) => {
1694
+ const showSection = command.section && command.section !== lastSection;
1695
+ lastSection = command.section ?? lastSection;
1696
+ return /* @__PURE__ */ jsxs16(React19.Fragment, { children: [
1697
+ showSection && /* @__PURE__ */ jsx19("li", { role: "presentation", className: "px-4 pb-1 pt-3 text-xs font-medium uppercase text-muted-foreground", children: command.section }),
1698
+ /* @__PURE__ */ jsx19(
1699
+ "li",
1700
+ {
1701
+ id: optionId(index),
1702
+ role: "option",
1703
+ "aria-selected": index === activeIndex,
1704
+ onMouseEnter: () => setActiveIndex(index),
1705
+ onClick: () => {
1706
+ onSelect(command);
1707
+ onOpenChange(false);
1708
+ },
1709
+ className: cn19(
1710
+ "flex cursor-pointer items-center justify-between px-4 py-2 text-sm",
1711
+ index === activeIndex ? "bg-primary/10 text-foreground" : "text-muted-foreground"
1712
+ ),
1713
+ children: renderCommand ? renderCommand(command, { active: index === activeIndex }) : /* @__PURE__ */ jsxs16(Fragment5, { children: [
1714
+ /* @__PURE__ */ jsxs16("span", { className: "flex min-w-0 items-center gap-2", children: [
1715
+ command.icon && /* @__PURE__ */ jsx19("span", { className: "shrink-0 [&_svg]:size-4", children: command.icon }),
1716
+ /* @__PURE__ */ jsxs16("span", { className: "min-w-0", children: [
1717
+ /* @__PURE__ */ jsx19("span", { className: "block truncate", children: command.label }),
1718
+ command.description && /* @__PURE__ */ jsx19("span", { className: "block text-xs text-muted-foreground", children: command.description })
1719
+ ] })
1720
+ ] }),
1721
+ command.shortcut && /* @__PURE__ */ jsx19("span", { className: "font-mono text-xs", children: command.shortcut })
1722
+ ] })
1723
+ }
1724
+ )
1725
+ ] }, command.id);
1726
+ })
1727
+ ]
1728
+ }
1729
+ )
1599
1730
  ] })
1600
1731
  }
1601
1732
  );