@slipstream-ui/ai 0.4.0 → 0.5.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 (34) hide show
  1. package/dist/index.d.mts +33 -1
  2. package/dist/index.d.ts +33 -1
  3. package/dist/index.js +236 -110
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +238 -112
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/manifests/attachment.manifest.json +2 -0
  8. package/dist/manifests/branch.manifest.json +2 -0
  9. package/dist/manifests/code-block.manifest.json +2 -0
  10. package/dist/manifests/command-palette.manifest.json +37 -3
  11. package/dist/manifests/confirmation.manifest.json +2 -0
  12. package/dist/manifests/inline-citation.manifest.json +2 -0
  13. package/dist/manifests/message-actions.manifest.json +3 -0
  14. package/dist/manifests/reasoning.manifest.json +6 -0
  15. package/dist/manifests/response.manifest.json +2 -0
  16. package/dist/manifests/tool-call.manifest.json +6 -0
  17. package/package.json +3 -3
  18. package/src/components/code-block/CodeBlock.tsx +91 -5
  19. package/src/components/command-palette/CommandPalette.test.tsx +21 -0
  20. package/src/components/command-palette/CommandPalette.tsx +71 -6
  21. package/src/components/confirmation/Confirmation.tsx +1 -1
  22. package/src/components/conversation/Conversation.tsx +6 -1
  23. package/src/components/reasoning/Reasoning.tsx +13 -6
  24. package/src/components/tool-call/ToolCall.tsx +24 -8
  25. package/src/manifests/attachment.manifest.json +2 -0
  26. package/src/manifests/branch.manifest.json +2 -0
  27. package/src/manifests/code-block.manifest.json +2 -0
  28. package/src/manifests/command-palette.manifest.json +37 -3
  29. package/src/manifests/confirmation.manifest.json +2 -0
  30. package/src/manifests/inline-citation.manifest.json +2 -0
  31. package/src/manifests/message-actions.manifest.json +3 -0
  32. package/src/manifests/reasoning.manifest.json +6 -0
  33. package/src/manifests/response.manifest.json +2 -0
  34. package/src/manifests/tool-call.manifest.json +6 -0
package/dist/index.d.mts CHANGED
@@ -409,6 +409,13 @@ type ReasoningProps = {
409
409
  duration?: number | string;
410
410
  /** Marks reasoning as still arriving: animated header state. */
411
411
  isStreaming?: boolean;
412
+ /** Localized header, count and step labels. */
413
+ labels?: {
414
+ title?: string;
415
+ count?: (count: number) => string;
416
+ thinking?: string;
417
+ step?: (index: number) => string;
418
+ };
412
419
  };
413
420
  declare const Reasoning: React.ForwardRefExoticComponent<ReasoningProps & React.RefAttributes<HTMLDivElement>>;
414
421
 
@@ -416,6 +423,16 @@ type ToolCallProps = {
416
423
  toolCall: ToolCall$1;
417
424
  className?: string;
418
425
  showResult?: boolean;
426
+ /** Localized status and section labels. */
427
+ labels?: {
428
+ pending?: string;
429
+ running?: string;
430
+ completed?: string;
431
+ failed?: string;
432
+ arguments?: string;
433
+ result?: string;
434
+ error?: string;
435
+ };
419
436
  };
420
437
  /**
421
438
  * Displays a single tool invocation: name, status chip, serialized
@@ -488,12 +505,27 @@ type Command = {
488
505
  /** Extra lexical search terms. */
489
506
  keywords?: string[];
490
507
  shortcut?: string;
508
+ /** Optional leading icon (rendered by the default row or renderCommand). */
509
+ icon?: React.ReactNode;
510
+ /** Optional secondary line in the default row. */
511
+ description?: string;
491
512
  };
492
513
  type CommandPaletteProps = {
493
514
  open: boolean;
494
515
  onOpenChange: (open: boolean) => void;
516
+ /** Static command set (ignored for filtering when loadCommands is set). */
495
517
  commands: Command[];
496
518
  onSelect: (command: Command) => void;
519
+ /** Async source; called with the current query (debounced). */
520
+ loadCommands?: (query: string) => Promise<Command[]>;
521
+ /** Debounce for loadCommands, in ms. Default 200. */
522
+ debounceMs?: number;
523
+ /** Custom row renderer (icons, type badges, metadata). */
524
+ renderCommand?: (command: Command, state: {
525
+ active: boolean;
526
+ }) => React.ReactNode;
527
+ /** Shown while async commands load. */
528
+ loadingText?: string;
497
529
  placeholder?: string;
498
530
  };
499
531
  /**
@@ -501,7 +533,7 @@ type CommandPaletteProps = {
501
533
  * keyboard navigation, lexical filtering over label/keywords, and
502
534
  * Escape-to-close. No focus leaves the input while navigating.
503
535
  */
504
- declare function CommandPalette({ open, onOpenChange, commands, onSelect, placeholder, }: CommandPaletteProps): React.JSX.Element | null;
536
+ declare function CommandPalette({ open, onOpenChange, commands, onSelect, loadCommands, debounceMs, renderCommand, loadingText, placeholder, }: CommandPaletteProps): React.JSX.Element | null;
505
537
 
506
538
  /**
507
539
  * Structural types mirroring the parts of the Vercel AI SDK's chat state the
package/dist/index.d.ts CHANGED
@@ -409,6 +409,13 @@ type ReasoningProps = {
409
409
  duration?: number | string;
410
410
  /** Marks reasoning as still arriving: animated header state. */
411
411
  isStreaming?: boolean;
412
+ /** Localized header, count and step labels. */
413
+ labels?: {
414
+ title?: string;
415
+ count?: (count: number) => string;
416
+ thinking?: string;
417
+ step?: (index: number) => string;
418
+ };
412
419
  };
413
420
  declare const Reasoning: React.ForwardRefExoticComponent<ReasoningProps & React.RefAttributes<HTMLDivElement>>;
414
421
 
@@ -416,6 +423,16 @@ type ToolCallProps = {
416
423
  toolCall: ToolCall$1;
417
424
  className?: string;
418
425
  showResult?: boolean;
426
+ /** Localized status and section labels. */
427
+ labels?: {
428
+ pending?: string;
429
+ running?: string;
430
+ completed?: string;
431
+ failed?: string;
432
+ arguments?: string;
433
+ result?: string;
434
+ error?: string;
435
+ };
419
436
  };
420
437
  /**
421
438
  * Displays a single tool invocation: name, status chip, serialized
@@ -488,12 +505,27 @@ type Command = {
488
505
  /** Extra lexical search terms. */
489
506
  keywords?: string[];
490
507
  shortcut?: string;
508
+ /** Optional leading icon (rendered by the default row or renderCommand). */
509
+ icon?: React.ReactNode;
510
+ /** Optional secondary line in the default row. */
511
+ description?: string;
491
512
  };
492
513
  type CommandPaletteProps = {
493
514
  open: boolean;
494
515
  onOpenChange: (open: boolean) => void;
516
+ /** Static command set (ignored for filtering when loadCommands is set). */
495
517
  commands: Command[];
496
518
  onSelect: (command: Command) => void;
519
+ /** Async source; called with the current query (debounced). */
520
+ loadCommands?: (query: string) => Promise<Command[]>;
521
+ /** Debounce for loadCommands, in ms. Default 200. */
522
+ debounceMs?: number;
523
+ /** Custom row renderer (icons, type badges, metadata). */
524
+ renderCommand?: (command: Command, state: {
525
+ active: boolean;
526
+ }) => React.ReactNode;
527
+ /** Shown while async commands load. */
528
+ loadingText?: string;
497
529
  placeholder?: string;
498
530
  };
499
531
  /**
@@ -501,7 +533,7 @@ type CommandPaletteProps = {
501
533
  * keyboard navigation, lexical filtering over label/keywords, and
502
534
  * Escape-to-close. No focus leaves the input while navigating.
503
535
  */
504
- declare function CommandPalette({ open, onOpenChange, commands, onSelect, placeholder, }: CommandPaletteProps): React.JSX.Element | null;
536
+ declare function CommandPalette({ open, onOpenChange, commands, onSelect, loadCommands, debounceMs, renderCommand, loadingText, placeholder, }: CommandPaletteProps): React.JSX.Element | null;
505
537
 
506
538
  /**
507
539
  * Structural types mirroring the parts of the Vercel AI SDK's chat state the
package/dist/index.js CHANGED
@@ -377,11 +377,80 @@ function useIsDark() {
377
377
  }, []);
378
378
  return dark;
379
379
  }
380
+ var LIGHT_CODE_THEME = {
381
+ plain: { color: "#1f2328", backgroundColor: "transparent" },
382
+ styles: [
383
+ { types: ["comment", "prolog", "doctype", "cdata"], style: { color: "#57606a" } },
384
+ { types: ["punctuation", "operator"], style: { color: "#24292f" } },
385
+ { types: ["namespace"], style: { color: "#6f42c1" } },
386
+ { types: ["tag", "deleted", "keyword", "atrule", "important"], style: { color: "#b31d28" } },
387
+ { types: ["attr-name", "variable"], style: { color: "#953800" } },
388
+ { types: ["string", "char", "attr-value", "inserted", "regex"], style: { color: "#0a5c2f" } },
389
+ {
390
+ types: [
391
+ "function",
392
+ "class-name",
393
+ "number",
394
+ "boolean",
395
+ "constant",
396
+ "symbol",
397
+ "property",
398
+ "selector",
399
+ "builtin",
400
+ "entity",
401
+ "url"
402
+ ],
403
+ style: { color: "#0550ae" }
404
+ }
405
+ ]
406
+ };
407
+ var DARK_CODE_THEME = {
408
+ plain: { color: "#e6edf3", backgroundColor: "transparent" },
409
+ styles: [
410
+ { types: ["comment", "prolog", "doctype", "cdata"], style: { color: "#8b949e" } },
411
+ { types: ["punctuation", "operator"], style: { color: "#c9d1d9" } },
412
+ { types: ["namespace"], style: { color: "#d2a8ff" } },
413
+ { types: ["tag", "deleted", "keyword", "atrule", "important"], style: { color: "#ff7b72" } },
414
+ { types: ["attr-name", "variable"], style: { color: "#ffa657" } },
415
+ { types: ["string", "char", "attr-value", "inserted", "regex"], style: { color: "#7ee787" } },
416
+ {
417
+ types: [
418
+ "function",
419
+ "class-name",
420
+ "number",
421
+ "boolean",
422
+ "constant",
423
+ "symbol",
424
+ "property",
425
+ "selector",
426
+ "builtin",
427
+ "entity",
428
+ "url"
429
+ ],
430
+ style: { color: "#79c0ff" }
431
+ }
432
+ ]
433
+ };
380
434
  var CodeBlock = React5.forwardRef(
381
435
  ({ code, language = "tsx", showCopyButton = true, className, ...props }, ref) => {
382
436
  const [copied, setCopied] = React5.useState(false);
383
437
  const isDark = useIsDark();
384
438
  const trimmedCode = code.replace(/\n$/, "");
439
+ const preRef = React5.useRef(null);
440
+ const [scrollable, setScrollable] = React5.useState(false);
441
+ React5.useEffect(() => {
442
+ const element = preRef.current;
443
+ if (!element) return;
444
+ const update = () => setScrollable(element.scrollWidth > element.clientWidth + 1);
445
+ update();
446
+ if (typeof ResizeObserver === "undefined") {
447
+ window.addEventListener("resize", update);
448
+ return () => window.removeEventListener("resize", update);
449
+ }
450
+ const observer = new ResizeObserver(update);
451
+ observer.observe(element);
452
+ return () => observer.disconnect();
453
+ }, [trimmedCode, language]);
385
454
  const handleCopy = async () => {
386
455
  try {
387
456
  await navigator.clipboard.writeText(trimmedCode);
@@ -395,8 +464,8 @@ var CodeBlock = React5.forwardRef(
395
464
  {
396
465
  ref,
397
466
  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.
467
+ // bg-background (not muted): the light syntax palette is tuned for
468
+ // a white surface; on gray the token contrast drops below AA.
400
469
  "relative group rounded-md border border-border bg-background overflow-hidden",
401
470
  className
402
471
  ),
@@ -415,10 +484,13 @@ var CodeBlock = React5.forwardRef(
415
484
  }
416
485
  )
417
486
  ] }),
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)(
487
+ /* @__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
488
  "pre",
420
489
  {
421
- className: "overflow-x-auto p-4 text-xs leading-relaxed m-0",
490
+ ref: preRef,
491
+ tabIndex: scrollable ? 0 : void 0,
492
+ "aria-label": scrollable ? `${language} code, scrollable` : void 0,
493
+ 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
494
  style: { ...style, background: "transparent" },
423
495
  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
496
  }
@@ -515,7 +587,7 @@ var import_core9 = require("@slipstream-ui/core");
515
587
  var import_core10 = require("@slipstream-ui/core");
516
588
  var import_jsx_runtime7 = require("react/jsx-runtime");
517
589
  var statusChipClasses = {
518
- pending: "bg-warning/10 text-warning border-warning/20",
590
+ pending: "bg-warning/10 text-warning-ink border-warning/20",
519
591
  approved: "bg-success/10 text-success border-success/20",
520
592
  rejected: "bg-destructive/10 text-destructive border-destructive/20"
521
593
  };
@@ -815,62 +887,71 @@ var Conversation = React10.forwardRef(
815
887
  };
816
888
  const isEmpty = messages.length === 0 && !isLoading;
817
889
  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
- ] }),
890
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
891
+ "div",
892
+ {
893
+ ref: scrollRef,
894
+ tabIndex: 0,
895
+ "aria-label": "Conversation transcript",
896
+ 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",
897
+ children: [
898
+ (hasMoreHistory || isLoadingMore) && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
899
+ "button",
900
+ {
901
+ type: "button",
902
+ onClick: handleLoadMore,
903
+ disabled: isLoadingMore,
904
+ 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",
905
+ children: isLoadingMore ? "Loading\u2026" : "Load earlier messages"
906
+ }
907
+ ),
908
+ 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: [
909
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { className: "text-lg font-semibold", children: welcomeTitle }),
910
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm text-muted-foreground max-w-sm", children: welcomeDescription }),
911
+ 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)(
912
+ "button",
913
+ {
914
+ type: "button",
915
+ onClick: () => onWelcomeSelect?.(suggestion),
916
+ 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",
917
+ children: suggestion
918
+ },
919
+ suggestion
920
+ )) })
921
+ ] }) : messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
922
+ MemoizedMessage,
923
+ {
924
+ message,
925
+ showAvatar: showAvatars,
926
+ avatarSrc: message.role === "user" ? userAvatarSrc : assistantAvatarSrc,
927
+ avatarFallback: message.role === "user" ? userAvatarFallback : assistantAvatarFallback,
928
+ renderContent,
929
+ isStreaming: message.id === streamingMessageId,
930
+ error: messageErrors?.[message.id],
931
+ onRetry: onRetryMessage && messageErrors?.[message.id] ? () => onRetryMessage(message) : void 0
932
+ },
933
+ message.id
934
+ )),
935
+ isLoading && !isEmpty && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
936
+ MemoizedMessage,
937
+ {
938
+ message: {
939
+ id: "__loading__",
940
+ role: "assistant",
941
+ content: loadingMessage
942
+ },
943
+ showAvatar: showAvatars,
944
+ avatarSrc: assistantAvatarSrc,
945
+ avatarFallback: assistantAvatarFallback,
946
+ className: "bg-muted/50",
947
+ renderContent: (content) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-muted-foreground animate-pulse motion-reduce:animate-none", children: content })
948
+ }
949
+ ),
950
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { "aria-live": "polite", className: "sr-only", children: announcement }),
951
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { ref: messagesEndRef })
952
+ ]
953
+ }
954
+ ),
874
955
  showDownload && messages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
875
956
  "button",
876
957
  {
@@ -1281,17 +1362,17 @@ var React12 = __toESM(require("react"));
1281
1362
  var import_core16 = require("@slipstream-ui/core");
1282
1363
  var import_jsx_runtime12 = require("react/jsx-runtime");
1283
1364
  var Reasoning = React12.forwardRef(
1284
- ({ steps, className, collapsible = true, defaultExpanded = false, duration, isStreaming = false, ...props }, ref) => {
1365
+ ({ steps, className, collapsible = true, defaultExpanded = false, duration, isStreaming = false, labels, ...props }, ref) => {
1285
1366
  const [isExpanded, setIsExpanded] = React12.useState(defaultExpanded);
1286
1367
  const contentId = React12.useId();
1287
1368
  if (steps.length === 0) {
1288
1369
  return null;
1289
1370
  }
1290
1371
  const headerText = [
1291
- "Reasoning",
1292
- `(${steps.length} ${steps.length === 1 ? "step" : "steps"})`,
1372
+ labels?.title ?? "Reasoning",
1373
+ labels?.count ? labels.count(steps.length) : `(${steps.length} ${steps.length === 1 ? "step" : "steps"})`,
1293
1374
  typeof duration === "number" ? `\xB7 ${duration}s` : duration ? `\xB7 ${duration}` : "",
1294
- isStreaming ? "\xB7 thinking\u2026" : ""
1375
+ isStreaming ? `\xB7 ${labels?.thinking ?? "thinking\u2026"}` : ""
1295
1376
  ].filter(Boolean).join(" ");
1296
1377
  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
1378
  collapsible ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
@@ -1302,7 +1383,7 @@ var Reasoning = React12.forwardRef(
1302
1383
  "aria-expanded": isExpanded,
1303
1384
  "aria-controls": contentId,
1304
1385
  children: [
1305
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: (0, import_core16.cn)("transition-transform", isExpanded ? "rotate-90" : ""), children: "\u25B6" }),
1386
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: (0, import_core16.cn)("transition-transform", isExpanded ? "rotate-90" : ""), children: "?" }),
1306
1387
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: headerText }),
1307
1388
  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
1389
  "span",
@@ -1318,10 +1399,7 @@ var Reasoning = React12.forwardRef(
1318
1399
  (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
1400
  /* @__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
1401
  /* @__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
- ] }),
1402
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "font-medium text-foreground mb-1", children: labels?.step?.(index + 1) ?? `Step ${index + 1}` }),
1325
1403
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "text-muted-foreground", children: step.content }),
1326
1404
  step.timestamp && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "text-xs text-muted-foreground mt-1", children: new Date(step.timestamp).toLocaleTimeString() })
1327
1405
  ] })
@@ -1336,19 +1414,19 @@ var React13 = __toESM(require("react"));
1336
1414
  var import_core17 = require("@slipstream-ui/core");
1337
1415
  var import_jsx_runtime13 = require("react/jsx-runtime");
1338
1416
  var statusChipClasses2 = {
1339
- pending: "bg-warning/10 text-warning border-warning/20",
1340
- running: "bg-info/10 text-info border-info/20",
1417
+ pending: "bg-warning/10 text-warning-ink border-warning/20",
1418
+ running: "bg-info/10 text-info-ink border-info/20",
1341
1419
  completed: "bg-success/10 text-success border-success/20",
1342
1420
  failed: "bg-destructive/10 text-destructive border-destructive/20"
1343
1421
  };
1344
- var statusLabels = {
1422
+ var defaultStatusLabels = {
1345
1423
  pending: "Pending",
1346
1424
  running: "Running",
1347
1425
  completed: "Completed",
1348
1426
  failed: "Failed"
1349
1427
  };
1350
1428
  var ToolCall = React13.forwardRef(
1351
- ({ toolCall, className, showResult = true, ...props }, ref) => {
1429
+ ({ toolCall, className, showResult = true, labels, ...props }, ref) => {
1352
1430
  const serializedArguments = React13.useMemo(
1353
1431
  () => JSON.stringify(toolCall.arguments, null, 2),
1354
1432
  [toolCall.arguments]
@@ -1357,6 +1435,12 @@ var ToolCall = React13.forwardRef(
1357
1435
  if (toolCall.result === void 0) return void 0;
1358
1436
  return typeof toolCall.result === "string" ? toolCall.result : JSON.stringify(toolCall.result, null, 2);
1359
1437
  }, [toolCall.result]);
1438
+ const statusLabels = {
1439
+ pending: labels?.pending ?? defaultStatusLabels.pending,
1440
+ running: labels?.running ?? defaultStatusLabels.running,
1441
+ completed: labels?.completed ?? defaultStatusLabels.completed,
1442
+ failed: labels?.failed ?? defaultStatusLabels.failed
1443
+ };
1360
1444
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1361
1445
  "div",
1362
1446
  {
@@ -1385,15 +1469,15 @@ var ToolCall = React13.forwardRef(
1385
1469
  ] }),
1386
1470
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "space-y-2", children: [
1387
1471
  /* @__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" }),
1472
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-xs font-medium text-muted-foreground mb-1", children: labels?.arguments ?? "Arguments" }),
1389
1473
  /* @__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
1474
  ] }),
1391
1475
  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" }),
1476
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-xs font-medium text-muted-foreground mb-1", children: labels?.result ?? "Result" }),
1393
1477
  /* @__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
1478
  ] }),
1395
1479
  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" }),
1480
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-xs font-medium text-destructive mb-1", children: labels?.error ?? "Error" }),
1397
1481
  /* @__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
1482
  ] })
1399
1483
  ] })
@@ -1559,21 +1643,46 @@ function CommandPalette({
1559
1643
  onOpenChange,
1560
1644
  commands,
1561
1645
  onSelect,
1562
- placeholder = "Type a command\u2026"
1646
+ loadCommands,
1647
+ debounceMs = 200,
1648
+ renderCommand,
1649
+ loadingText = "Searching\u2026",
1650
+ placeholder = "Type a command."
1563
1651
  }) {
1564
1652
  const [query, setQuery] = React19.useState("");
1565
1653
  const [activeIndex, setActiveIndex] = React19.useState(0);
1654
+ const [asyncCommands, setAsyncCommands] = React19.useState([]);
1655
+ const [loading, setLoading] = React19.useState(false);
1656
+ const requestId = React19.useRef(0);
1657
+ const asyncSignature = loadCommands ? String(loadCommands.length) : "";
1566
1658
  const inputRef = React19.useRef(null);
1567
1659
  const listboxId = React19.useId();
1568
1660
  const optionId = (index) => `${listboxId}-option-${index}`;
1661
+ React19.useEffect(() => {
1662
+ if (!open || !loadCommands) return;
1663
+ const id = ++requestId.current;
1664
+ setLoading(true);
1665
+ const timer = setTimeout(async () => {
1666
+ try {
1667
+ const result = await loadCommands(query);
1668
+ if (requestId.current === id) setAsyncCommands(result);
1669
+ } catch {
1670
+ if (requestId.current === id) setAsyncCommands([]);
1671
+ } finally {
1672
+ if (requestId.current === id) setLoading(false);
1673
+ }
1674
+ }, debounceMs);
1675
+ return () => clearTimeout(timer);
1676
+ }, [open, query, debounceMs, asyncSignature]);
1569
1677
  const filtered = React19.useMemo(() => {
1678
+ if (loadCommands) return asyncCommands;
1570
1679
  const terms = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
1571
1680
  if (terms.length === 0) return commands;
1572
1681
  return commands.filter((command) => {
1573
1682
  const haystack = `${command.label} ${command.section ?? ""} ${(command.keywords ?? []).join(" ")}`.toLowerCase();
1574
1683
  return terms.every((term) => haystack.includes(term));
1575
1684
  });
1576
- }, [commands, query]);
1685
+ }, [commands, query, asyncCommands, loadCommands]);
1577
1686
  React19.useEffect(() => {
1578
1687
  if (open) {
1579
1688
  setQuery("");
@@ -1636,37 +1745,54 @@ function CommandPalette({
1636
1745
  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
1746
  }
1638
1747
  ),
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
- ] })
1748
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1749
+ "ul",
1750
+ {
1751
+ id: listboxId,
1752
+ role: "listbox",
1753
+ "aria-label": "Commands",
1754
+ "aria-busy": loading || void 0,
1755
+ className: "max-h-72 overflow-y-auto py-1",
1756
+ children: [
1757
+ 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 }),
1758
+ !loading && 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." }),
1759
+ filtered.map((command, index) => {
1760
+ const showSection = command.section && command.section !== lastSection;
1761
+ lastSection = command.section ?? lastSection;
1762
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(React19.Fragment, { children: [
1763
+ 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 }),
1764
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1765
+ "li",
1766
+ {
1767
+ id: optionId(index),
1768
+ role: "option",
1769
+ "aria-selected": index === activeIndex,
1770
+ onMouseEnter: () => setActiveIndex(index),
1771
+ onClick: () => {
1772
+ onSelect(command);
1773
+ onOpenChange(false);
1774
+ },
1775
+ className: (0, import_core23.cn)(
1776
+ "flex cursor-pointer items-center justify-between px-4 py-2 text-sm",
1777
+ index === activeIndex ? "bg-primary/10 text-foreground" : "text-muted-foreground"
1778
+ ),
1779
+ children: renderCommand ? renderCommand(command, { active: index === activeIndex }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
1780
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "flex min-w-0 items-center gap-2", children: [
1781
+ command.icon && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "shrink-0 [&_svg]:size-4", children: command.icon }),
1782
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "min-w-0", children: [
1783
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "block truncate", children: command.label }),
1784
+ command.description && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "block text-xs text-muted-foreground", children: command.description })
1785
+ ] })
1786
+ ] }),
1787
+ command.shortcut && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "font-mono text-xs", children: command.shortcut })
1788
+ ] })
1789
+ }
1790
+ )
1791
+ ] }, command.id);
1792
+ })
1793
+ ]
1794
+ }
1795
+ )
1670
1796
  ] })
1671
1797
  }
1672
1798
  );