Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature - UI for AI Chat #587

Merged
merged 8 commits into from
Oct 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions app/src/routes/editor/EditPanel/AITab/ChatHistory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { Button } from '@/components/ui/button';
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { TooltipProvider, Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { ChatBubbleIcon, CounterClockwiseClockIcon, Cross1Icon } from '@radix-ui/react-icons';
import { TooltipArrow } from '@radix-ui/react-tooltip';
import clsx from 'clsx';
import { useState } from 'react';

const exampleHistory = [
{
timeLabel: 'Today',
chats: [
{
id: '1',
text: 'When @button.tsx is clicked, make the map card appear and have an active background',
},
{
id: '2',
text: 'Another example chat for more to see how the chats look',
},
{
id: '3',
text: 'Another example chat for more to see how the chats look',
},
],
},
{
timeLabel: '3d ago',
chats: [
{
id: '1',
text: 'Another example chat for more to see how the chats look',
},
{
id: '2',
text: 'Another example chat for more to see how the chats look',
},
{
id: '3',
text: 'Another example chat for more to see how the chats look',
},
{
id: '4',
text: 'Another example chat for more to see how the chats look',
},
{
id: '5',
text: 'Another example chat for more to see how the chats look',
},
],
},
{
timeLabel: '5d ago',
chats: [
{
id: '1',
text: 'Another example chat for more to see how the chats look',
},
],
},
];

const ChatHistory = () => {
const [isHistoryOpen, setIsHistoryOpen] = useState(false);

return (
<Popover open={isHistoryOpen} onOpenChange={setIsHistoryOpen}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<PopoverTrigger asChild>
<Button
variant={'ghost'}
size={'icon'}
className={clsx('p-2 w-fit h-fit', {
'bg-background-secondary text-primary': isHistoryOpen,
})}
>
<CounterClockwiseClockIcon />
</Button>
</PopoverTrigger>
</TooltipTrigger>
<TooltipContent side="bottom">
<p>Chat History</p>
<TooltipArrow className="fill-foreground" />
</TooltipContent>
</Tooltip>
</TooltipProvider>

<PopoverAnchor className="absolute -left-2 top-0" />
<PopoverContent side="left" align="start" className="rounded-xl p-0">
<div className="flex flex-col">
<div className="border-b">
<div className="flex flex-row justify-between items-center p-1 h-fit text-xs text-foreground-tertiary">
<span className="px-2">Chat History</span>
<Button
variant={'ghost'}
size={'icon'}
className="p-2 w-fit hover:bg-transparent"
onClick={() => setIsHistoryOpen(false)}
>
<Cross1Icon />
</Button>
</div>
</div>
<div className="flex flex-col gap-2 p-2 text-foreground-tertiary">
{exampleHistory.map((history) => (
<div className="flex flex-col gap-1" key={history.timeLabel}>
<span className="text-[0.7rem] px-2">{history.timeLabel}</span>
<div className="flex flex-col">
{history.chats.map((chat) => (
<div
className="flex flex-row w-full p-2 gap-2 items-center rounded-md hover:bg-background-onlook active:bg-background-brand active:text-foreground cursor-pointer select-none"
key={chat.id}
>
<ChatBubbleIcon className="flex-none" />
<span className="text-xs truncate">{chat.text}</span>
</div>
))}
</div>
</div>
))}
</div>
</div>
</PopoverContent>
</Popover>
);
};

export default ChatHistory;
74 changes: 74 additions & 0 deletions app/src/routes/editor/EditPanel/AITab/ChatPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { FileIcon, ImageIcon } from '@radix-ui/react-icons';
import React from 'react';

const fileIcons: { [key: string]: React.ComponentType } = {
file: FileIcon,
image: ImageIcon,
};

const exampleChat = {
messages: [
{
id: '1',
agent: 'user',
text: [
'When @button.tsx is clicked, make the map card appear and have an active background',
],
files: [
{ type: 'file', name: 'map_card.tsx' },
{ type: 'image', name: 'example.jpg' },
],
},
{
id: '2',
agent: 'AI',
text: [
'I opened map-card.tsx and button.tsx adding in the correct classes.',
'You need to add X that will let Y happen. To make sure Y is functional, be sure to test Z.',
'Let me know what you think!',
],
},
],
};

const ChatPanel = () => {
return (
<>
{exampleChat.messages.map((message) =>
message.agent === 'AI' ? (
<div
className="flex w-full flex-col gap-6 p-4 text-small content-start"
key={message.id}
>
{message.text.map((text) => (
<p key={text}>{text}</p>
))}
</div>
) : (
<div className="w-full flex flex-row justify-end px-2" key={message.id}>
<div className="flex flex-col ml-8 p-2 rounded-lg shadow-sm rounded-br-none border-[0.5px] bg-background-primary">
<div className="flex flex-row gap-3 text-micro mb-1.5 text-foreground-secondary">
{message.files?.map((file) => (
<span
className="flex flex-row gap-1 items-center"
key={file.name}
>
{React.createElement(fileIcons[file.type])}
<span>{file.name}</span>
</span>
))}
</div>
<div className="text-small">
{message.text.map((text) => (
<p key={text}>{text}</p>
))}
</div>
</div>
</div>
),
)}
</>
);
};

export default ChatPanel;
51 changes: 51 additions & 0 deletions app/src/routes/editor/EditPanel/AITab/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { ArrowRightIcon, FilePlusIcon, ImageIcon } from '@radix-ui/react-icons';
import ChatPanel from './ChatPanel';

const AITab = () => {
return (
<div className="w-full h-[calc(100vh-8.25rem)] flex flex-col justify-end">
<ChatPanel />
<div className="flex w-full text-foreground-tertiary pt-4 px-4 border-t text-small">
<Textarea
placeholder="Ask follow up questions or provide more context..."
className="p-0 border-0 shadow-none rounded-none caret-[#FA003C] selection:bg-[#FA003C]/30 selection:text-[#FA003C] text-foreground-primary placeholder:text-foreground-primary/50"
rows={1}
style={{ resize: 'none' }}
onInput={(e) => {
e.currentTarget.style.height = 'auto'; // Reset height
e.currentTarget.style.height = `${e.currentTarget.scrollHeight}px`;
}}
/>
</div>
<div className="flex flex-row w-full justify-between pt-5 pb-4 px-4">
<div className="flex flex-row justify-start gap-1.5">
<Button
variant={'outline'}
className="w-fit h-fit py-0.5 px-2.5 text-foreground-tertiary"
>
<ImageIcon className="mr-2" />
<span className="text-smallPlus">Image</span>
</Button>
<Button
variant={'outline'}
className="w-fit h-fit py-0.5 px-2.5 text-foreground-tertiary"
>
<FilePlusIcon className="mr-2" />
<span className="text-smallPlus">File Reference</span>
</Button>
</div>
<Button
size={'icon'}
variant={'secondary'}
className="text-smallPlus w-fit h-full py-0.5 px-2.5 text-primary"
>
<ArrowRightIcon />
</Button>
</div>
</div>
);
};

export default AITab;
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ import {
StyleType,
} from '@/lib/editor/styles/models';
import { observer } from 'mobx-react-lite';
import BorderInput from './inputs/compound/BorderInput';
import DisplayInput from './inputs/compound/DisplayInput';
import NestedInputs from './inputs/compound/NestedInputs';
import AutoLayoutInput from './inputs/single/AutoLayoutInput';
import ColorInput from './inputs/single/ColorInput';
import NumberUnitInput from './inputs/single/NumberUnitInput';
import SelectInput from './inputs/single/SelectInput';
import TagDetails from './inputs/single/TagDetails';
import TailwindInput from './inputs/single/TailwindInput';
import TextInput from './inputs/single/TextInput';
import BorderInput from './compound/BorderInput';
import DisplayInput from './compound/DisplayInput';
import NestedInputs from './compound/NestedInputs';
import AutoLayoutInput from './single/AutoLayoutInput';
import ColorInput from './single/ColorInput';
import NumberUnitInput from './single/NumberUnitInput';
import SelectInput from './single/SelectInput';
import TagDetails from './single/TagDetails';
import TailwindInput from './single/TailwindInput';
import TextInput from './single/TextInput';

export const STYLE_GROUP_MAPPING: Record<StyleGroupKey, BaseStyle[]> = {
[StyleGroupKey.Position]: PositionGroup,
Expand Down
Loading