@townco/ui 0.1.83 → 0.1.93

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 (63) hide show
  1. package/dist/core/hooks/use-chat-input.js +13 -6
  2. package/dist/core/hooks/use-chat-messages.d.ts +17 -0
  3. package/dist/core/hooks/use-chat-messages.js +294 -10
  4. package/dist/core/schemas/chat.d.ts +20 -0
  5. package/dist/core/schemas/chat.js +4 -0
  6. package/dist/core/schemas/index.d.ts +1 -0
  7. package/dist/core/schemas/index.js +1 -0
  8. package/dist/core/schemas/source.d.ts +22 -0
  9. package/dist/core/schemas/source.js +45 -0
  10. package/dist/core/store/chat-store.d.ts +4 -0
  11. package/dist/core/store/chat-store.js +54 -0
  12. package/dist/gui/components/Actions.d.ts +15 -0
  13. package/dist/gui/components/Actions.js +22 -0
  14. package/dist/gui/components/ChatInput.d.ts +9 -1
  15. package/dist/gui/components/ChatInput.js +24 -6
  16. package/dist/gui/components/ChatInputCommandMenu.d.ts +1 -0
  17. package/dist/gui/components/ChatInputCommandMenu.js +22 -5
  18. package/dist/gui/components/ChatInputParameters.d.ts +13 -0
  19. package/dist/gui/components/ChatInputParameters.js +67 -0
  20. package/dist/gui/components/ChatLayout.d.ts +2 -0
  21. package/dist/gui/components/ChatLayout.js +183 -61
  22. package/dist/gui/components/ChatPanelTabContent.d.ts +7 -0
  23. package/dist/gui/components/ChatPanelTabContent.js +17 -7
  24. package/dist/gui/components/ChatView.js +105 -15
  25. package/dist/gui/components/CitationChip.d.ts +15 -0
  26. package/dist/gui/components/CitationChip.js +72 -0
  27. package/dist/gui/components/EditableUserMessage.d.ts +18 -0
  28. package/dist/gui/components/EditableUserMessage.js +109 -0
  29. package/dist/gui/components/MessageActions.d.ts +16 -0
  30. package/dist/gui/components/MessageActions.js +97 -0
  31. package/dist/gui/components/MessageContent.js +22 -7
  32. package/dist/gui/components/Response.d.ts +3 -0
  33. package/dist/gui/components/Response.js +30 -3
  34. package/dist/gui/components/Sidebar.js +1 -1
  35. package/dist/gui/components/TodoSubline.js +1 -1
  36. package/dist/gui/components/WorkProgress.js +7 -0
  37. package/dist/gui/components/index.d.ts +6 -1
  38. package/dist/gui/components/index.js +6 -1
  39. package/dist/gui/hooks/index.d.ts +1 -0
  40. package/dist/gui/hooks/index.js +1 -0
  41. package/dist/gui/hooks/use-favicon.d.ts +6 -0
  42. package/dist/gui/hooks/use-favicon.js +47 -0
  43. package/dist/gui/hooks/use-scroll-to-bottom.d.ts +14 -0
  44. package/dist/gui/hooks/use-scroll-to-bottom.js +317 -1
  45. package/dist/gui/index.d.ts +1 -1
  46. package/dist/gui/index.js +1 -1
  47. package/dist/gui/lib/motion.js +6 -6
  48. package/dist/gui/lib/remark-citations.d.ts +28 -0
  49. package/dist/gui/lib/remark-citations.js +70 -0
  50. package/dist/sdk/client/acp-client.d.ts +38 -1
  51. package/dist/sdk/client/acp-client.js +67 -3
  52. package/dist/sdk/schemas/message.d.ts +40 -0
  53. package/dist/sdk/schemas/message.js +20 -0
  54. package/dist/sdk/transports/http.d.ts +24 -1
  55. package/dist/sdk/transports/http.js +189 -1
  56. package/dist/sdk/transports/stdio.d.ts +1 -0
  57. package/dist/sdk/transports/stdio.js +39 -0
  58. package/dist/sdk/transports/types.d.ts +46 -1
  59. package/dist/sdk/transports/websocket.d.ts +1 -0
  60. package/dist/sdk/transports/websocket.js +4 -0
  61. package/dist/tui/components/ChatView.js +3 -4
  62. package/package.json +5 -3
  63. package/src/styles/global.css +71 -0
@@ -403,6 +403,37 @@ export const useChatStore = create((set) => ({
403
403
  };
404
404
  return { messages };
405
405
  }),
406
+ addSourcesToCurrentMessage: (sources) => set((state) => {
407
+ // Find the most recent assistant message
408
+ const lastAssistantIndex = state.messages.findLastIndex((msg) => msg.role === "assistant");
409
+ if (lastAssistantIndex === -1) {
410
+ // No assistant message exists yet - create one with the sources
411
+ logger.debug("No assistant message found, creating one for sources");
412
+ const newMessage = {
413
+ id: `msg_${Date.now()}_assistant`,
414
+ role: "assistant",
415
+ content: "",
416
+ timestamp: new Date().toISOString(),
417
+ isStreaming: false,
418
+ sources,
419
+ };
420
+ return { messages: [...state.messages, newMessage] };
421
+ }
422
+ const messages = [...state.messages];
423
+ const lastAssistantMsg = messages[lastAssistantIndex];
424
+ if (!lastAssistantMsg)
425
+ return state;
426
+ // Add sources to existing message
427
+ messages[lastAssistantIndex] = {
428
+ ...lastAssistantMsg,
429
+ sources: [...(lastAssistantMsg.sources || []), ...sources],
430
+ };
431
+ logger.debug("Added sources to current message", {
432
+ sourcesCount: sources.length,
433
+ totalSources: (lastAssistantMsg.sources?.length || 0) + sources.length,
434
+ });
435
+ return { messages };
436
+ }),
406
437
  updateToolCall: (sessionId, update) => set((state) => {
407
438
  const sessionToolCalls = state.toolCalls[sessionId] || [];
408
439
  const existingIndex = sessionToolCalls.findIndex((tc) => tc.id === update.id);
@@ -441,11 +472,15 @@ export const useChatStore = create((set) => ({
441
472
  attachedFiles: state.input.attachedFiles.filter((_, i) => i !== index),
442
473
  },
443
474
  })),
475
+ setSelectedPromptParameters: (params) => set((state) => ({
476
+ input: { ...state.input, selectedPromptParameters: params },
477
+ })),
444
478
  clearInput: () => set((_state) => ({
445
479
  input: {
446
480
  value: "",
447
481
  isSubmitting: false,
448
482
  attachedFiles: [],
483
+ selectedPromptParameters: undefined,
449
484
  },
450
485
  })),
451
486
  addTokenUsage: (tokenUsage) => set((state) => ({
@@ -494,4 +529,23 @@ export const useChatStore = create((set) => ({
494
529
  })),
495
530
  clearLogs: () => set({ logs: [] }),
496
531
  setActiveTab: (tab) => set({ activeTab: tab }),
532
+ truncateMessagesFrom: (messageIndex) => set((state) => {
533
+ // Truncate messages to keep only those before messageIndex
534
+ const truncatedMessages = state.messages.slice(0, messageIndex);
535
+ // Reset token tracking since context changed
536
+ return {
537
+ messages: truncatedMessages,
538
+ totalBilled: {
539
+ inputTokens: 0,
540
+ outputTokens: 0,
541
+ totalTokens: 0,
542
+ },
543
+ currentContext: {
544
+ inputTokens: 0,
545
+ outputTokens: 0,
546
+ totalTokens: 0,
547
+ },
548
+ latestContextSize: null,
549
+ };
550
+ }),
497
551
  }));
@@ -0,0 +1,15 @@
1
+ import type { ComponentProps } from "react";
2
+ import { Button } from "./Button.js";
3
+ export type ActionsProps = ComponentProps<"div">;
4
+ /**
5
+ * Container for action buttons
6
+ */
7
+ export declare const Actions: ({ className, children, ...props }: import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>) => import("react/jsx-runtime").JSX.Element;
8
+ export type ActionProps = ComponentProps<typeof Button> & {
9
+ tooltip?: string;
10
+ label?: string;
11
+ };
12
+ /**
13
+ * Single action button with optional tooltip
14
+ */
15
+ export declare const Action: ({ tooltip, children, label, className, variant, size, ...props }: ActionProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,22 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from "../lib/utils.js";
4
+ import { Button } from "./Button.js";
5
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./Tooltip.js";
6
+ /**
7
+ * Container for action buttons
8
+ */
9
+ export const Actions = ({ className, children, ...props }) => (_jsx("div", { className: cn("flex items-center gap-1", className), ...props, children: children }));
10
+ /**
11
+ * Single action button with optional tooltip
12
+ */
13
+ export const Action = ({ tooltip, children, label, className, variant = "ghost", size = "sm", ...props }) => {
14
+ const button = (_jsxs(Button, { className: cn("relative size-8 p-1.5 text-muted-foreground hover:text-foreground", className), size: size, type: "button", variant: variant, ...props, children: [children, _jsx("span", { className: "sr-only", children: label || tooltip })
15
+ ] }));
16
+ if (tooltip) {
17
+ return (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [
18
+ _jsx(TooltipTrigger, { asChild: true, children: button }), _jsx(TooltipContent, { children: _jsx("p", { children: tooltip }) })
19
+ ] }) }));
20
+ }
21
+ return button;
22
+ };
@@ -25,6 +25,10 @@ export interface ChatInputRootProps extends Omit<React.FormHTMLAttributes<HTMLFo
25
25
  * Submit handler (legacy prop-based pattern)
26
26
  */
27
27
  onSubmit?: () => void | Promise<void>;
28
+ /**
29
+ * Cancel handler - called when user clicks stop button during streaming
30
+ */
31
+ onCancel?: (() => void | Promise<void>) | undefined;
28
32
  /** Whether input is disabled */
29
33
  disabled?: boolean;
30
34
  /** Whether input is currently submitting */
@@ -51,6 +55,10 @@ export interface ChatInputSubmitProps extends React.ButtonHTMLAttributes<HTMLBut
51
55
  asChild?: boolean;
52
56
  }
53
57
  declare const ChatInputSubmit: React.ForwardRefExoticComponent<ChatInputSubmitProps & React.RefAttributes<HTMLButtonElement>>;
58
+ export interface ChatInputStopProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
59
+ asChild?: boolean;
60
+ }
61
+ declare const ChatInputStop: React.ForwardRefExoticComponent<ChatInputStopProps & React.RefAttributes<HTMLButtonElement>>;
54
62
  export interface ChatInputToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
55
63
  }
56
64
  declare const ChatInputToolbar: React.ForwardRefExoticComponent<ChatInputToolbarProps & React.RefAttributes<HTMLDivElement>>;
@@ -81,4 +89,4 @@ export interface ChatInputCommandMenuProps extends React.HTMLAttributes<HTMLDivE
81
89
  }
82
90
  declare const ChatInputCommandMenu: React.ForwardRefExoticComponent<ChatInputCommandMenuProps & React.RefAttributes<HTMLDivElement>>;
83
91
  export type { CommandMenuItem };
84
- export { ChatInputRoot as Root, ChatInputField as Field, ChatInputSubmit as Submit, ChatInputToolbar as Toolbar, ChatInputActions as Actions, ChatInputAttachment as Attachment, ChatInputVoiceInput as VoiceInput, ChatInputCommandMenu as CommandMenu, };
92
+ export { ChatInputRoot as Root, ChatInputField as Field, ChatInputSubmit as Submit, ChatInputStop as Stop, ChatInputToolbar as Toolbar, ChatInputActions as Actions, ChatInputAttachment as Attachment, ChatInputVoiceInput as VoiceInput, ChatInputCommandMenu as CommandMenu, };
@@ -1,6 +1,6 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Slot } from "@radix-ui/react-slot";
3
- import { Mic, Paperclip, SquareSlash } from "lucide-react";
3
+ import { Mic, Paperclip, Square, SquareSlash } from "lucide-react";
4
4
  import * as React from "react";
5
5
  import { useChatInput as useCoreChatInput } from "../../core/hooks/use-chat-input.js";
6
6
  import { useMessageHistory } from "../../core/hooks/use-message-history.js";
@@ -16,7 +16,7 @@ const useChatInputContext = () => {
16
16
  }
17
17
  return context;
18
18
  };
19
- const ChatInputRoot = React.forwardRef(({ client, startSession, value: valueProp, onChange: onChangeProp, onSubmit: onSubmitProp, disabled = false, isSubmitting: isSubmittingProp, submitOnEnter = true, className, children, ...props }, ref) => {
19
+ const ChatInputRoot = React.forwardRef(({ client, startSession, value: valueProp, onChange: onChangeProp, onSubmit: onSubmitProp, onCancel, disabled = false, isSubmitting: isSubmittingProp, submitOnEnter = true, className, children, ...props }, ref) => {
20
20
  const textareaRef = React.useRef(null);
21
21
  // Provide a dummy startSession function if not provided (for React hooks rule)
22
22
  const dummyStartSession = React.useCallback(async () => Promise.resolve(null), []);
@@ -110,6 +110,7 @@ const ChatInputRoot = React.forwardRef(({ client, startSession, value: valueProp
110
110
  value,
111
111
  onChange,
112
112
  onSubmit,
113
+ onCancel,
113
114
  disabled,
114
115
  isSubmitting,
115
116
  submitOnEnter,
@@ -330,11 +331,25 @@ const ChatInputField = React.forwardRef(({ asChild = false, className, onKeyDown
330
331
  ChatInputField.displayName = "ChatInput.Field";
331
332
  const ChatInputSubmit = React.forwardRef(({ asChild = false, className, disabled: disabledProp, children, ...props }, ref) => {
332
333
  const { value, disabled, isSubmitting } = useChatInputContext();
333
- const isDisabled = disabledProp || disabled || isSubmitting || !value.trim();
334
+ // Hide when streaming (Stop button will be shown instead)
335
+ if (isSubmitting) {
336
+ return null;
337
+ }
338
+ const isDisabled = disabledProp || disabled || !value.trim();
334
339
  const Comp = asChild ? Slot : Button;
335
340
  return (_jsx(Comp, { ref: ref, type: "submit", disabled: isDisabled, variant: !asChild ? "default" : undefined, size: "icon", className: cn(!asChild && "gap-1.5 rounded-full", className), ...props, children: children }));
336
341
  });
337
342
  ChatInputSubmit.displayName = "ChatInput.Submit";
343
+ const ChatInputStop = React.forwardRef(({ asChild = false, className, children, ...props }, ref) => {
344
+ const { onCancel, isSubmitting } = useChatInputContext();
345
+ const Comp = asChild ? Slot : Button;
346
+ // Only show when streaming
347
+ if (!isSubmitting) {
348
+ return null;
349
+ }
350
+ return (_jsx(Comp, { ref: ref, type: "button", onClick: () => onCancel?.(), variant: !asChild ? "default" : undefined, size: "icon", className: cn(!asChild && "gap-1.5 rounded-full", className), ...props, children: children || _jsx(Square, { className: "size-4 fill-current" }) }));
351
+ });
352
+ ChatInputStop.displayName = "ChatInput.Stop";
338
353
  const ChatInputToolbar = React.forwardRef(({ className, children, ...props }, ref) => {
339
354
  return (_jsx("div", { ref: ref, className: cn("flex items-center justify-between p-2", className), ...props, children: children }));
340
355
  });
@@ -425,8 +440,11 @@ const ChatInputVoiceInput = React.forwardRef(({ asChild = false, className, chil
425
440
  });
426
441
  ChatInputVoiceInput.displayName = "ChatInput.VoiceInput";
427
442
  const ChatInputCommandMenu = React.forwardRef(({ commands = [], className, onChange: _, ...props }, ref) => {
428
- const { showCommandMenu, commandMenuQuery, selectedMenuIndex, setSelectedMenuIndex, setMenuItemCount, triggerCounter, onChange, } = useChatInputContext();
429
- return (_jsx(ChatInputCommandMenuComponent, { ref: ref, commands: commands, showCommandMenu: showCommandMenu, commandMenuQuery: commandMenuQuery, selectedMenuIndex: selectedMenuIndex, setSelectedMenuIndex: setSelectedMenuIndex, setMenuItemCount: setMenuItemCount, triggerCounter: triggerCounter, onChange: onChange, className: className, ...props }));
443
+ const { showCommandMenu, commandMenuQuery, selectedMenuIndex, setSelectedMenuIndex, setMenuItemCount, triggerCounter, onChange, setShowCommandMenu, setCommandMenuQuery, } = useChatInputContext();
444
+ return (_jsx(ChatInputCommandMenuComponent, { ref: ref, commands: commands, showCommandMenu: showCommandMenu, commandMenuQuery: commandMenuQuery, selectedMenuIndex: selectedMenuIndex, setSelectedMenuIndex: setSelectedMenuIndex, setMenuItemCount: setMenuItemCount, triggerCounter: triggerCounter, onChange: onChange, onClose: () => {
445
+ setShowCommandMenu(false);
446
+ setCommandMenuQuery("");
447
+ }, className: className, ...props }));
430
448
  });
431
449
  ChatInputCommandMenu.displayName = "ChatInput.CommandMenu";
432
- export { ChatInputRoot as Root, ChatInputField as Field, ChatInputSubmit as Submit, ChatInputToolbar as Toolbar, ChatInputActions as Actions, ChatInputAttachment as Attachment, ChatInputVoiceInput as VoiceInput, ChatInputCommandMenu as CommandMenu, };
450
+ export { ChatInputRoot as Root, ChatInputField as Field, ChatInputSubmit as Submit, ChatInputStop as Stop, ChatInputToolbar as Toolbar, ChatInputActions as Actions, ChatInputAttachment as Attachment, ChatInputVoiceInput as VoiceInput, ChatInputCommandMenu as CommandMenu, };
@@ -16,5 +16,6 @@ export interface ChatInputCommandMenuProps extends Omit<React.HTMLAttributes<HTM
16
16
  setMenuItemCount: (count: number) => void;
17
17
  triggerCounter: number;
18
18
  onChange: (value: string) => void;
19
+ onClose?: () => void;
19
20
  }
20
21
  export declare const ChatInputCommandMenu: React.ForwardRefExoticComponent<ChatInputCommandMenuProps & React.RefAttributes<HTMLDivElement>>;
@@ -4,7 +4,7 @@ import { cn } from "../lib/utils.js";
4
4
  /* -------------------------------------------------------------------------------------------------
5
5
  * ChatInputCommandMenu
6
6
  * -----------------------------------------------------------------------------------------------*/
7
- export const ChatInputCommandMenu = React.forwardRef(({ commands = [], showCommandMenu, commandMenuQuery, selectedMenuIndex, setSelectedMenuIndex, setMenuItemCount, triggerCounter, onChange, className, ...props }, ref) => {
7
+ export const ChatInputCommandMenu = React.forwardRef(({ commands = [], showCommandMenu, commandMenuQuery, selectedMenuIndex, setSelectedMenuIndex, setMenuItemCount, triggerCounter, onChange, onClose, className, ...props }, ref) => {
8
8
  // Fuzzy search implementation
9
9
  const fuzzyMatch = React.useCallback((text, query) => {
10
10
  const lowerText = text.toLowerCase();
@@ -46,19 +46,36 @@ export const ChatInputCommandMenu = React.forwardRef(({ commands = [], showComma
46
46
  React.useEffect(() => {
47
47
  setSelectedMenuIndex(0);
48
48
  }, [setSelectedMenuIndex]);
49
+ // Track the last handled trigger to prevent double-firing
50
+ const lastHandledTrigger = React.useRef(0);
49
51
  // Handle selection when triggered
50
52
  React.useEffect(() => {
51
- if (triggerCounter > 0 && filteredCommands[selectedMenuIndex]) {
53
+ // Only handle if triggerCounter increased and we haven't handled this trigger yet
54
+ if (triggerCounter > 0 &&
55
+ triggerCounter !== lastHandledTrigger.current &&
56
+ filteredCommands[selectedMenuIndex]) {
57
+ lastHandledTrigger.current = triggerCounter;
52
58
  filteredCommands[selectedMenuIndex].onSelect();
53
- // Clear the input after selection
59
+ // Clear the input and close the menu after selection
54
60
  onChange("");
61
+ onClose?.();
55
62
  }
56
- }, [triggerCounter, filteredCommands, selectedMenuIndex, onChange]);
63
+ }, [
64
+ triggerCounter,
65
+ filteredCommands,
66
+ selectedMenuIndex,
67
+ onChange,
68
+ onClose,
69
+ ]);
57
70
  if (!showCommandMenu || filteredCommands.length === 0) {
58
71
  return null;
59
72
  }
60
73
  return (_jsxs("div", { ref: ref, className: cn("absolute bottom-full left-0 z-50 mb-2 w-full max-w-md", "rounded-md border border-border bg-card p-2 shadow-lg", className), ...props, children: [
61
- _jsx("div", { className: "text-caption font-semibold text-muted-foreground px-2 py-1", children: "Commands" }), _jsx("div", { className: "max-h-64 overflow-y-auto", children: filteredCommands.map((command, index) => (_jsxs("button", { type: "button", onClick: () => command.onSelect(), className: cn("w-full rounded-sm px-2 py-2 text-left text-paragraph-sm transition-colors", "flex items-start gap-2", "hover:bg-muted", index === selectedMenuIndex && "bg-muted"), children: [command.icon && (_jsx("span", { className: "shrink-0 mt-0.5", children: command.icon })), _jsxs("div", { className: "flex-1 min-w-0", children: [
74
+ _jsx("div", { className: "text-caption font-semibold text-muted-foreground px-2 py-1", children: "Commands" }), _jsx("div", { className: "max-h-64 overflow-y-auto", children: filteredCommands.map((command, index) => (_jsxs("button", { type: "button", onClick: () => {
75
+ command.onSelect();
76
+ onChange("");
77
+ onClose?.();
78
+ }, className: cn("w-full rounded-sm px-2 py-2 text-left text-paragraph-sm transition-colors", "flex items-start gap-2", "hover:bg-muted", index === selectedMenuIndex && "bg-muted"), children: [command.icon && (_jsx("span", { className: "shrink-0 mt-0.5", children: command.icon })), _jsxs("div", { className: "flex-1 min-w-0", children: [
62
79
  _jsx("div", { className: "font-medium", children: command.label }), command.description && (_jsx("div", { className: "text-caption text-muted-foreground truncate", children: command.description }))] })
63
80
  ] }, command.id))) })
64
81
  ] }));
@@ -0,0 +1,13 @@
1
+ import type { PromptParameter } from "../../sdk/transports/types.js";
2
+ export interface ChatInputParametersProps {
3
+ /** Available prompt parameters from the agent */
4
+ parameters: PromptParameter[];
5
+ /** Optional CSS class name */
6
+ className?: string;
7
+ }
8
+ /**
9
+ * A token-based component for selecting prompt parameters per-message.
10
+ * Shows non-default selections as dismissible chips with dropdown to change.
11
+ */
12
+ export declare function ChatInputParameters({ parameters, className }: ChatInputParametersProps): import("react/jsx-runtime").JSX.Element | null;
13
+ export default ChatInputParameters;
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronDown } from "lucide-react";
3
+ import * as React from "react";
4
+ import { useChatStore } from "../../core/store/chat-store.js";
5
+ import { cn } from "../lib/utils.js";
6
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "./DropdownMenu.js";
7
+ /**
8
+ * A token-based component for selecting prompt parameters per-message.
9
+ * Shows non-default selections as dismissible chips with dropdown to change.
10
+ */
11
+ export function ChatInputParameters({ parameters, className, }) {
12
+ const selectedParams = useChatStore((state) => state.input.selectedPromptParameters);
13
+ const setSelectedPromptParameters = useChatStore((state) => state.setSelectedPromptParameters);
14
+ // Get default values for each parameter
15
+ const getDefaultValue = React.useCallback((param) => {
16
+ return param.defaultOptionId ?? param.options[0]?.id ?? "";
17
+ }, []);
18
+ // Initialize with default values if not set
19
+ React.useEffect(() => {
20
+ if (!selectedParams || Object.keys(selectedParams).length === 0) {
21
+ const defaults = {};
22
+ for (const param of parameters) {
23
+ defaults[param.id] = getDefaultValue(param);
24
+ }
25
+ if (Object.keys(defaults).length > 0) {
26
+ setSelectedPromptParameters(defaults);
27
+ }
28
+ }
29
+ }, [
30
+ parameters,
31
+ selectedParams,
32
+ setSelectedPromptParameters,
33
+ getDefaultValue,
34
+ ]);
35
+ const handleParameterChange = React.useCallback((parameterId, optionId) => {
36
+ const newParams = {
37
+ ...selectedParams,
38
+ [parameterId]: optionId,
39
+ };
40
+ setSelectedPromptParameters(newParams);
41
+ }, [selectedParams, setSelectedPromptParameters]);
42
+ if (parameters.length === 0) {
43
+ return null;
44
+ }
45
+ // Get non-default selections to show as tokens
46
+ const activeSelections = parameters
47
+ .map((param) => {
48
+ const currentValue = selectedParams?.[param.id];
49
+ const defaultValue = getDefaultValue(param);
50
+ const selectedOption = param.options.find((opt) => opt.id === currentValue);
51
+ const isNonDefault = currentValue && currentValue !== defaultValue;
52
+ return {
53
+ param,
54
+ selectedOption,
55
+ isNonDefault,
56
+ };
57
+ })
58
+ .filter((item) => item.selectedOption);
59
+ return (_jsx("div", { className: cn("flex items-center gap-1", className), children: activeSelections.map(({ param, selectedOption, isNonDefault }) => (_jsxs(DropdownMenu, { children: [
60
+ _jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: cn("inline-flex h-10 items-center gap-1.5 rounded-full px-3", "text-sm font-medium transition-colors", "focus:outline-none focus-visible:ring-1 focus-visible:ring-ring", isNonDefault
61
+ ? "bg-accent text-accent-foreground hover:bg-accent/80"
62
+ : "bg-muted/50 text-muted-foreground hover:bg-muted"), children: [
63
+ _jsx("span", { className: "truncate max-w-[140px]", children: selectedOption?.label }), _jsx(ChevronDown, { className: "h-4 w-4 opacity-50" })
64
+ ] }) }), _jsx(DropdownMenuContent, { align: "start", className: "min-w-[160px]", children: param.options.map((option) => (_jsx(DropdownMenuItem, { onClick: () => handleParameterChange(param.id, option.id), className: cn(selectedParams?.[param.id] === option.id && "bg-accent"), children: option.label }, option.id))) })
65
+ ] }, param.id))) }));
66
+ }
67
+ export default ChatInputParameters;
@@ -13,6 +13,8 @@ interface ChatLayoutContextValue {
13
13
  togglePanel: () => void;
14
14
  isDraggingAside: boolean;
15
15
  setIsDraggingAside: (dragging: boolean) => void;
16
+ asideWidth: number;
17
+ setAsideWidth: (width: number) => void;
16
18
  }
17
19
  declare const ChatLayoutContext: React.Context<ChatLayoutContextValue | undefined>;
18
20
  declare const useChatLayoutContext: () => ChatLayoutContextValue;