@townco/ui 0.1.82 → 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.
- package/dist/core/hooks/use-chat-input.js +13 -6
- package/dist/core/hooks/use-chat-messages.d.ts +17 -0
- package/dist/core/hooks/use-chat-messages.js +294 -10
- package/dist/core/schemas/chat.d.ts +20 -0
- package/dist/core/schemas/chat.js +4 -0
- package/dist/core/schemas/index.d.ts +1 -0
- package/dist/core/schemas/index.js +1 -0
- package/dist/core/schemas/source.d.ts +22 -0
- package/dist/core/schemas/source.js +45 -0
- package/dist/core/store/chat-store.d.ts +4 -0
- package/dist/core/store/chat-store.js +54 -0
- package/dist/gui/components/Actions.d.ts +15 -0
- package/dist/gui/components/Actions.js +22 -0
- package/dist/gui/components/ChatInput.d.ts +9 -1
- package/dist/gui/components/ChatInput.js +24 -6
- package/dist/gui/components/ChatInputCommandMenu.d.ts +1 -0
- package/dist/gui/components/ChatInputCommandMenu.js +22 -5
- package/dist/gui/components/ChatInputParameters.d.ts +13 -0
- package/dist/gui/components/ChatInputParameters.js +67 -0
- package/dist/gui/components/ChatLayout.d.ts +2 -0
- package/dist/gui/components/ChatLayout.js +183 -61
- package/dist/gui/components/ChatPanelTabContent.d.ts +7 -0
- package/dist/gui/components/ChatPanelTabContent.js +17 -7
- package/dist/gui/components/ChatView.js +105 -15
- package/dist/gui/components/CitationChip.d.ts +15 -0
- package/dist/gui/components/CitationChip.js +72 -0
- package/dist/gui/components/EditableUserMessage.d.ts +18 -0
- package/dist/gui/components/EditableUserMessage.js +109 -0
- package/dist/gui/components/MessageActions.d.ts +16 -0
- package/dist/gui/components/MessageActions.js +97 -0
- package/dist/gui/components/MessageContent.js +22 -7
- package/dist/gui/components/Response.d.ts +3 -0
- package/dist/gui/components/Response.js +30 -3
- package/dist/gui/components/Sidebar.js +1 -1
- package/dist/gui/components/TodoSubline.js +1 -1
- package/dist/gui/components/WorkProgress.js +7 -0
- package/dist/gui/components/index.d.ts +6 -1
- package/dist/gui/components/index.js +6 -1
- package/dist/gui/hooks/index.d.ts +1 -0
- package/dist/gui/hooks/index.js +1 -0
- package/dist/gui/hooks/use-favicon.d.ts +6 -0
- package/dist/gui/hooks/use-favicon.js +47 -0
- package/dist/gui/hooks/use-scroll-to-bottom.d.ts +14 -0
- package/dist/gui/hooks/use-scroll-to-bottom.js +317 -1
- package/dist/gui/index.d.ts +1 -1
- package/dist/gui/index.js +1 -1
- package/dist/gui/lib/motion.js +6 -6
- package/dist/gui/lib/remark-citations.d.ts +28 -0
- package/dist/gui/lib/remark-citations.js +70 -0
- package/dist/sdk/client/acp-client.d.ts +38 -1
- package/dist/sdk/client/acp-client.js +67 -3
- package/dist/sdk/schemas/message.d.ts +40 -0
- package/dist/sdk/schemas/message.js +20 -0
- package/dist/sdk/transports/http.d.ts +24 -1
- package/dist/sdk/transports/http.js +189 -1
- package/dist/sdk/transports/stdio.d.ts +1 -0
- package/dist/sdk/transports/stdio.js +39 -0
- package/dist/sdk/transports/types.d.ts +46 -1
- package/dist/sdk/transports/websocket.d.ts +1 -0
- package/dist/sdk/transports/websocket.js +4 -0
- package/dist/tui/components/ChatView.js +3 -4
- package/package.json +5 -3
- 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
|
-
|
|
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,
|
|
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
|
|
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
|
-
}, [
|
|
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: () =>
|
|
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;
|