Skip to content

Commit cf3a6e6

Browse files
authored
feat: commands can now be buttons (#1969)
* feat: commands can now be buttons * fix: light theme hover text color * fix: wrap button commands if no space left
1 parent a599538 commit cf3a6e6

File tree

8 files changed

+85
-5
lines changed

8 files changed

+85
-5
lines changed

backend/chainlit/types.py

+2
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,8 @@ class CommandDict(TypedDict):
257257
description: str
258258
# The lucide icon name
259259
icon: str
260+
# Display the command as a button in the composer
261+
button: Optional[bool]
260262

261263

262264
class FeedbackDict(TypedDict):

cypress/e2e/command/main.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
commands = [
44
{"id": "Picture", "icon": "image", "description": "Use DALL-E"},
5-
{"id": "Search", "icon": "globe", "description": "Find on the web"},
5+
{"id": "Search", "icon": "globe", "description": "Find on the web", "button": True},
66
{
77
"id": "Canvas",
88
"icon": "pen-line",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { cn } from '@/lib/utils';
2+
import { useRecoilValue } from 'recoil';
3+
4+
import { ICommand, commandsState } from '@chainlit/react-client';
5+
6+
import Icon from '@/components/Icon';
7+
import { Button } from '@/components/ui/button';
8+
import {
9+
Tooltip,
10+
TooltipContent,
11+
TooltipProvider,
12+
TooltipTrigger
13+
} from '@/components/ui/tooltip';
14+
15+
interface Props {
16+
disabled?: boolean;
17+
selectedCommandId?: string;
18+
onCommandSelect: (command?: ICommand) => void;
19+
}
20+
21+
export const CommandButtons = ({
22+
disabled = false,
23+
selectedCommandId,
24+
onCommandSelect
25+
}: Props) => {
26+
const commands = useRecoilValue(commandsState);
27+
const commandButtons = commands.filter((c) => !!c.button);
28+
29+
if (!commandButtons.length) return null;
30+
31+
return (
32+
<div className="flex gap-2 ml-1 flex-wrap">
33+
<TooltipProvider>
34+
{commandButtons.map((command) => (
35+
<Tooltip>
36+
<TooltipTrigger asChild>
37+
<Button
38+
key={command.id}
39+
id={`command-${command.id}`}
40+
variant="ghost"
41+
disabled={disabled}
42+
className={cn(
43+
'p-2 h-9 text-[13px] font-medium rounded-full',
44+
selectedCommandId === command.id &&
45+
'border-transparent text-[#08f] hover:text-[#08f] bg-[#DAEEFF] hover:bg-[#BDDCF4] dark:bg-[#2A4A6D] dark:text-[#48AAFF] dark:hover:bg-[#1A416A]'
46+
)}
47+
onClick={() =>
48+
selectedCommandId === command.id
49+
? onCommandSelect(undefined)
50+
: onCommandSelect(command)
51+
}
52+
>
53+
<Icon name={command.icon} className="!h-5 !w-5" />
54+
{command.id}
55+
</Button>
56+
</TooltipTrigger>
57+
<TooltipContent>
58+
<p>{command.description}</p>
59+
</TooltipContent>
60+
</Tooltip>
61+
))}
62+
</TooltipProvider>
63+
</div>
64+
);
65+
};
66+
67+
export default CommandButtons;

frontend/src/components/chat/MessageComposer/CommandButton.tsx frontend/src/components/chat/MessageComposer/CommandPopoverButton.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ interface Props {
2828
onCommandSelect: (command: ICommand) => void;
2929
}
3030

31-
export const CommandButton = ({ disabled = false, onCommandSelect }: Props) => {
31+
export const CommandPopoverButton = ({
32+
disabled = false,
33+
onCommandSelect
34+
}: Props) => {
3235
const commands = useRecoilValue(commandsState);
3336

3437
if (!commands.length) return null;
@@ -89,4 +92,4 @@ export const CommandButton = ({ disabled = false, onCommandSelect }: Props) => {
8992
);
9093
};
9194

92-
export default CommandButton;
95+
export default CommandPopoverButton;

frontend/src/components/chat/MessageComposer/Input.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ const Input = forwardRef<InputMethods, Props>(
159159
// Find existing command span
160160
const existingCommandSpan = content.querySelector('.command-span');
161161

162-
if (selectedCommand) {
162+
if (selectedCommand && !selectedCommand.button) {
163163
// Create new command block
164164
const newCommandBlock = document.createElement('div');
165165
newCommandBlock.className =

frontend/src/components/chat/MessageComposer/index.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import { chatSettingsOpenState } from '@/state/project';
1919
import { IAttachment, attachmentsState } from 'state/chat';
2020

2121
import { Attachments } from './Attachments';
22-
import CommandButton from './CommandButton';
22+
import CommandButtons from './CommandButtons';
23+
import CommandButton from './CommandPopoverButton';
2324
import Input, { InputMethods } from './Input';
2425
import SubmitButton from './SubmitButton';
2526
import UploadButton from './UploadButton';
@@ -181,6 +182,11 @@ export default function MessageComposer({
181182
</Button>
182183
)}
183184
<VoiceButton disabled={disabled} />
185+
<CommandButtons
186+
disabled={disabled}
187+
selectedCommandId={selectedCommand?.id}
188+
onCommandSelect={setSelectedCommand}
189+
/>
184190
</div>
185191
<div className="flex items-center gap-1">
186192
<SubmitButton

frontend/tsconfig.tsbuildinfo

+1
Large diffs are not rendered by default.

libs/react-client/src/types/command.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ export interface ICommand {
22
id: string;
33
icon: string;
44
description: string;
5+
button?: boolean;
56
}

0 commit comments

Comments
 (0)