Update primary color palette (#497)

This commit is contained in:
Jeremy 2024-11-06 19:12:46 +03:00 committed by GitHub
parent b5c165714b
commit d948b99719
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 180 additions and 111 deletions

View file

@ -30,9 +30,9 @@
--card-foreground: 240 10% 3.9%; --card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%; --popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%; --primary: 217 100% 45%;
--primary-foreground: 0 0% 98%; --primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%; --secondary: 213 100% 96%;
--secondary-foreground: 240 5.9% 10%; --secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%; --muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%; --muted-foreground: 240 3.8% 46.1%;
@ -40,7 +40,7 @@
--accent-foreground: 240 5.9% 10%; --accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%; --destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%; --destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%; --border: 214 32% 91%;
--input: 240 5.9% 90%; --input: 240 5.9% 90%;
--ring: 240 10% 3.9%; --ring: 240 10% 3.9%;
--chart-1: 12 76% 61%; --chart-1: 12 76% 61%;
@ -65,8 +65,8 @@
--card-foreground: 0 0% 98%; --card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%; --popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%; --popover-foreground: 0 0% 98%;
--primary: 0 0% 98%; --primary: 208.69 100% 24.18%;
--primary-foreground: 240 5.9% 10%; --primary-foreground: 207 100% 96%;
--secondary: 240 3.7% 15.9%; --secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%; --secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%; --muted: 240 3.7% 15.9%;
@ -142,5 +142,5 @@
} }
.suggestion-highlight { .suggestion-highlight {
@apply bg-blue-100 hover:bg-blue-200 dark:hover:bg-blue-400/50 dark:text-blue-50 dark:bg-blue-400/40; @apply bg-blue-200 hover:bg-blue-300 dark:hover:bg-blue-400/50 dark:text-blue-50 dark:bg-blue-500/40;
} }

View file

@ -29,6 +29,7 @@ import { MultimodalInput } from './multimodal-input';
import { Toolbar } from './toolbar'; import { Toolbar } from './toolbar';
import { useScrollToBottom } from './use-scroll-to-bottom'; import { useScrollToBottom } from './use-scroll-to-bottom';
import { VersionFooter } from './version-footer'; import { VersionFooter } from './version-footer';
import { Button } from '../ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
export interface UICanvas { export interface UICanvas {
title: string; title: string;
@ -254,10 +255,10 @@ export function Canvas({
{!isMobile && ( {!isMobile && (
<motion.div <motion.div
className="relative w-[400px] bg-muted dark:bg-background h-dvh shrink-0" className="relative w-[400px] bg-muted dark:bg-background h-dvh shrink-0"
initial={{ opacity: 0, x: windowWidth - 420, scale: 1 }} initial={{ opacity: 0, x: 10, scale: 1 }}
animate={{ animate={{
opacity: 1, opacity: 1,
x: windowWidth - 400, x: 0,
scale: 1, scale: 1,
transition: { transition: {
delay: 0.2, delay: 0.2,
@ -268,7 +269,7 @@ export function Canvas({
}} }}
exit={{ exit={{
opacity: 0, opacity: 0,
x: windowWidth - 420, x: 0,
scale: 0.95, scale: 0.95,
transition: { delay: 0 }, transition: { delay: 0 },
}} }}
@ -370,7 +371,7 @@ export function Canvas({
} }
: { : {
opacity: 1, opacity: 1,
x: 0, x: 400,
y: 0, y: 0,
height: windowHeight, height: windowHeight,
width: windowWidth ? windowWidth - 400 : 'calc(100dvw-400px)', width: windowWidth ? windowWidth - 400 : 'calc(100dvw-400px)',
@ -396,8 +397,9 @@ export function Canvas({
> >
<div className="p-2 flex flex-row justify-between items-start"> <div className="p-2 flex flex-row justify-between items-start">
<div className="flex flex-row gap-4 items-start"> <div className="flex flex-row gap-4 items-start">
<div <Button
className="cursor-pointer hover:bg-muted dark:hover:bg-zinc-700 p-2 rounded-lg text-muted-foreground" variant="outline"
className="h-fit p-2 dark:hover:bg-zinc-700"
onClick={() => { onClick={() => {
setCanvas((currentCanvas) => ({ setCanvas((currentCanvas) => ({
...currentCanvas, ...currentCanvas,
@ -406,9 +408,9 @@ export function Canvas({
}} }}
> >
<CrossIcon size={18} /> <CrossIcon size={18} />
</div> </Button>
<div className="flex flex-col pt-1"> <div className="flex flex-col">
<div className="font-medium"> <div className="font-medium">
{document?.title ?? canvas.title} {document?.title ?? canvas.title}
</div> </div>
@ -433,58 +435,63 @@ export function Canvas({
<div className="flex flex-row gap-1"> <div className="flex flex-row gap-1">
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger asChild>
<div <Button
className="cursor-pointer hover:bg-muted p-2 rounded-lg text-muted-foreground dark:hover:bg-zinc-700" variant="outline"
className="p-2 h-fit dark:hover:bg-zinc-700"
onClick={() => { onClick={() => {
copyToClipboard(canvas.content); copyToClipboard(canvas.content);
toast.success('Copied to clipboard!'); toast.success('Copied to clipboard!');
}} }}
> >
<CopyIcon size={18} /> <CopyIcon size={18} />
</div> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>Copy to clipboard</TooltipContent> <TooltipContent>Copy to clipboard</TooltipContent>
</Tooltip> </Tooltip>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger asChild>
<div <Button
className="cursor-pointer hover:bg-muted p-2 rounded-lg text-muted-foreground dark:hover:bg-zinc-700" variant="outline"
className="p-2 h-fit dark:hover:bg-zinc-700 !pointer-events-auto"
onClick={() => { onClick={() => {
handleVersionChange('prev'); handleVersionChange('prev');
}} }}
disabled={currentVersionIndex === 0}
> >
<UndoIcon size={18} /> <UndoIcon size={18} />
</div> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>View Previous version</TooltipContent> <TooltipContent>View Previous version</TooltipContent>
</Tooltip> </Tooltip>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger asChild>
<div <Button
className="cursor-pointer hover:bg-muted p-2 rounded-lg text-muted-foreground dark:hover:bg-zinc-700" variant="outline"
className="p-2 h-fit dark:hover:bg-zinc-700 !pointer-events-auto"
onClick={() => { onClick={() => {
handleVersionChange('next'); handleVersionChange('next');
}} }}
disabled={isCurrentVersion}
> >
<RedoIcon size={18} /> <RedoIcon size={18} />
</div> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>View Next version</TooltipContent> <TooltipContent>View Next version</TooltipContent>
</Tooltip> </Tooltip>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger asChild>
<div <Button
className={cx( variant="outline"
'cursor-pointer hover:bg-muted p-2 rounded-lg text-muted-foreground dark:hover:bg-zinc-700', className={cx('p-2 h-fit dark:hover:bg-zinc-700', {
{ 'bg-muted dark:bg-zinc-700': mode === 'diff' } 'bg-muted': mode === 'diff',
)} })}
onClick={() => { onClick={() => {
handleVersionChange('toggle'); handleVersionChange('toggle');
}} }}
> >
<DeltaIcon size={18} /> <DeltaIcon size={18} />
</div> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>View changes</TooltipContent> <TooltipContent>View changes</TooltipContent>
</Tooltip> </Tooltip>

View file

@ -38,7 +38,7 @@ export function ChatHeader({ selectedModelId }: { selectedModelId: string }) {
className="order-1 md:order-2" className="order-1 md:order-2"
/> />
<Button <Button
className="hidden md:flex py-1.5 px-2 h-fit md:h-[34px] order-4 md:ml-auto" className="bg-zinc-900 dark:bg-zinc-100 hover:bg-zinc-800 dark:hover:bg-zinc-200 text-zinc-50 dark:text-zinc-900 hidden md:flex py-1.5 px-2 h-fit md:h-[34px] order-4 md:ml-auto"
asChild asChild
> >
<Link <Link

View file

@ -31,7 +31,7 @@ export function DocumentToolResult({
}: DocumentToolResultProps) { }: DocumentToolResultProps) {
return ( return (
<div <div
className="cursor-pointer border py-2 px-3 rounded-xl w-fit flex flex-row gap-3 items-start" className="bg-background cursor-pointer border py-2 px-3 rounded-xl w-fit flex flex-row gap-3 items-start"
onClick={(event) => { onClick={(event) => {
const rect = event.currentTarget.getBoundingClientRect(); const rect = event.currentTarget.getBoundingClientRect();

View file

@ -836,3 +836,22 @@ export const SparklesIcon = ({ size = 16 }: { size?: number }) => (
></path> ></path>
</svg> </svg>
); );
export const CheckCirclFillIcon = ({ size = 16 }: { size?: number }) => {
return (
<svg
height={size}
strokeLinejoin="round"
viewBox="0 0 16 16"
width={size}
style={{ color: 'currentcolor' }}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8ZM11.5303 6.53033L12.0607 6L11 4.93934L10.4697 5.46967L6.5 9.43934L5.53033 8.46967L5 7.93934L3.93934 9L4.46967 9.53033L5.96967 11.0303C6.26256 11.3232 6.73744 11.3232 7.03033 11.0303L11.5303 6.53033Z"
fill="currentColor"
></path>
</svg>
);
};

View file

@ -32,19 +32,14 @@ export const PreviewMessage = ({
}) => { }) => {
return ( return (
<motion.div <motion.div
className="w-full mx-auto max-w-3xl px-4 group/message " className="w-full mx-auto max-w-3xl px-4 group/message"
initial={{ y: 5, opacity: 0 }} initial={{ y: 5, opacity: 0 }}
animate={{ y: 0, opacity: 1 }} animate={{ y: 0, opacity: 1 }}
data-role={message.role} data-role={message.role}
> >
<div <div
className={cx( className={cx(
'flex gap-4 group-data-[role=user]/message:px-3 w-full group-data-[role=user]/message:w-fit group-data-[role=user]/message:ml-auto group-data-[role=user]/message:max-w-2xl group-data-[role=user]/message:py-2 rounded-xl', 'group-data-[role=user]/message:bg-primary group-data-[role=user]/message:text-primary-foreground flex gap-4 group-data-[role=user]/message:px-3 w-full group-data-[role=user]/message:w-fit group-data-[role=user]/message:ml-auto group-data-[role=user]/message:max-w-2xl group-data-[role=user]/message:py-2 rounded-xl'
{
'group-data-[role=user]/message:bg-muted': !canvas,
'group-data-[role=user]/message:bg-zinc-300 dark:group-data-[role=user]/message:bg-zinc-800':
canvas,
}
)} )}
> >
{message.role === 'assistant' && ( {message.role === 'assistant' && (

View file

@ -1,6 +1,5 @@
'use client'; 'use client';
import { Check, ChevronDown } from 'lucide-react';
import { startTransition, useMemo, useOptimistic, useState } from 'react'; import { startTransition, useMemo, useOptimistic, useState } from 'react';
import { models } from '@/ai/models'; import { models } from '@/ai/models';
@ -14,7 +13,7 @@ import {
} from '@/components/ui/dropdown-menu'; } from '@/components/ui/dropdown-menu';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { ChevronDownIcon } from './icons'; import { CheckCirclFillIcon, ChevronDownIcon } from './icons';
export function ModelSelector({ export function ModelSelector({
selectedModelId, selectedModelId,
@ -57,7 +56,7 @@ export function ModelSelector({
saveModelId(model.id); saveModelId(model.id);
}); });
}} }}
className="gap-4 group/item" className="gap-4 group/item flex flex-row justify-between items-center"
data-active={model.id === optimisticModelId} data-active={model.id === optimisticModelId}
> >
<div className="flex flex-col gap-1 items-start"> <div className="flex flex-col gap-1 items-start">
@ -68,7 +67,9 @@ export function ModelSelector({
</div> </div>
)} )}
</div> </div>
<Check className="size-4 ml-auto opacity-0 group-data-[active=true]/item:opacity-100" /> <div className="text-primary dark:text-primary-foreground opacity-0 group-data-[active=true]/item:opacity-100">
<CheckCirclFillIcon />
</div>
</DropdownMenuItem> </DropdownMenuItem>
))} ))}
</DropdownMenuContent> </DropdownMenuContent>

View file

@ -31,7 +31,7 @@ const suggestedActions = [
{ {
title: 'Help me draft an essay', title: 'Help me draft an essay',
label: 'about Silicon Valley', label: 'about Silicon Valley',
action: 'Help me draft an essay about Silicon Valley', action: 'Help me draft a short essay about Silicon Valley',
}, },
]; ];
@ -283,7 +283,7 @@ export function MultimodalInput({
{isLoading ? ( {isLoading ? (
<Button <Button
className="rounded-full p-1.5 h-fit absolute bottom-2 right-2 m-0.5" className="rounded-full p-1.5 h-fit absolute bottom-2 right-2 m-0.5 border dark:border-zinc-600"
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
stop(); stop();
@ -294,7 +294,7 @@ export function MultimodalInput({
</Button> </Button>
) : ( ) : (
<Button <Button
className="rounded-full p-1.5 h-fit absolute bottom-2 right-2 m-0.5" className="rounded-full p-1.5 h-fit absolute bottom-2 right-2 m-0.5 border dark:border-zinc-600"
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
submitForm(); submitForm();
@ -306,7 +306,7 @@ export function MultimodalInput({
)} )}
<Button <Button
className="rounded-full p-1.5 h-fit absolute bottom-2 right-10 m-0.5 dark:border-zinc-700" className="rounded-full p-1.5 h-fit absolute bottom-2 right-11 m-0.5 dark:border-zinc-700"
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
fileInputRef.current?.click(); fileInputRef.current?.click();

View file

@ -26,6 +26,7 @@ import {
StopIcon, StopIcon,
SummarizeIcon, SummarizeIcon,
} from './icons'; } from './icons';
import { Button } from '../ui/button';
type ToolProps = { type ToolProps = {
type: 'final-polish' | 'request-suggestions' | 'adjust-reading-level'; type: 'final-polish' | 'request-suggestions' | 'adjust-reading-level';
@ -61,12 +62,47 @@ const Tool = ({
} }
}, [selectedTool, type]); }, [selectedTool, type]);
const handleSelect = () => {
if (!isToolbarVisible && setIsToolbarVisible) {
setIsToolbarVisible(true);
return;
}
if (!selectedTool) {
setIsHovered(true);
setSelectedTool(type);
return;
}
if (selectedTool !== type) {
setSelectedTool(type);
} else {
if (type === 'final-polish') {
append({
role: 'user',
content:
'Please add final polish and check for grammar, add section titles for better structure, and ensure everything reads smoothly.',
});
setSelectedTool(null);
} else if (type === 'request-suggestions') {
append({
role: 'user',
content:
'Please add suggestions you have that could improve the writing.',
});
setSelectedTool(null);
}
}
};
return ( return (
<Tooltip open={isHovered && !isAnimating}> <Tooltip open={isHovered && !isAnimating}>
<TooltipTrigger> <TooltipTrigger asChild>
<motion.div <motion.div
className={cx('p-3 rounded-full', { className={cx('p-3 rounded-full', {
'bg-foreground !text-background': selectedTool === type, 'bg-primary !text-primary-foreground': selectedTool === type,
})} })}
onHoverStart={() => { onHoverStart={() => {
setIsHovered(true); setIsHovered(true);
@ -74,6 +110,11 @@ const Tool = ({
onHoverEnd={() => { onHoverEnd={() => {
if (selectedTool !== type) setIsHovered(false); if (selectedTool !== type) setIsHovered(false);
}} }}
onKeyDown={(event) => {
if (event.key === 'Enter') {
handleSelect();
}
}}
initial={{ scale: 1, opacity: 0 }} initial={{ scale: 1, opacity: 0 }}
animate={{ opacity: 1, transition: { delay: 0.1 } }} animate={{ opacity: 1, transition: { delay: 0.1 } }}
whileHover={{ scale: 1.1 }} whileHover={{ scale: 1.1 }}
@ -84,38 +125,7 @@ const Tool = ({
transition: { duration: 0.1 }, transition: { duration: 0.1 },
}} }}
onClick={() => { onClick={() => {
if (!isToolbarVisible && setIsToolbarVisible) { handleSelect();
setIsToolbarVisible(true);
return;
}
if (!selectedTool) {
setIsHovered(true);
setSelectedTool(type);
return;
}
if (selectedTool !== type) {
setSelectedTool(type);
} else {
if (type === 'final-polish') {
append({
role: 'user',
content:
'Please add final polish and check for grammar, add section titles for better structure, and ensure everything reads smoothly.',
});
setSelectedTool(null);
} else if (type === 'request-suggestions') {
append({
role: 'user',
content:
'Please add suggestions you have that could improve the writing.',
});
setSelectedTool(null);
}
}
}} }}
> >
{selectedTool === type ? <ArrowUpIcon /> : icon} {selectedTool === type ? <ArrowUpIcon /> : icon}
@ -192,7 +202,7 @@ const ReadingLevelSelector = ({
className={cx( className={cx(
'absolute bg-background p-3 border rounded-full flex flex-row items-center', 'absolute bg-background p-3 border rounded-full flex flex-row items-center',
{ {
'bg-foreground text-background': currentLevel !== 2, 'bg-primary text-primary-foreground': currentLevel !== 2,
'bg-background text-foreground': currentLevel === 2, 'bg-background text-foreground': currentLevel === 2,
} }
)} )}
@ -241,37 +251,31 @@ const ReadingLevelSelector = ({
); );
}; };
export const Toolbar = ({ export const Tools = ({
isToolbarVisible, isToolbarVisible,
setIsToolbarVisible, selectedTool,
setSelectedTool,
append, append,
isLoading, isAnimating,
stop, setIsToolbarVisible,
setMessages,
}: { }: {
isToolbarVisible: boolean; isToolbarVisible: boolean;
setIsToolbarVisible: Dispatch<SetStateAction<boolean>>; selectedTool: string | null;
isLoading: boolean; setSelectedTool: Dispatch<SetStateAction<string | null>>;
append: ( append: (
message: Message | CreateMessage, message: Message | CreateMessage,
chatRequestOptions?: ChatRequestOptions chatRequestOptions?: ChatRequestOptions
) => Promise<string | null | undefined>; ) => Promise<string | null | undefined>;
stop: () => void; isAnimating: boolean;
setMessages: Dispatch<SetStateAction<Message[]>>; setIsToolbarVisible: Dispatch<SetStateAction<boolean>>;
}) => { }) => {
const toolbarRef = useRef<HTMLDivElement>(null); return (
const timeoutRef = useRef<NodeJS.Timeout>(); <motion.div
className="flex flex-col"
const [selectedTool, setSelectedTool] = useState<string | null>(null); initial={{ opacity: 0, scale: 0.95 }}
const [isAnimating, setIsAnimating] = useState(false); animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
useOnClickOutside(toolbarRef, () => { >
setIsToolbarVisible(false);
setSelectedTool(null);
});
const Tools = (
<>
<AnimatePresence> <AnimatePresence>
{isToolbarVisible && ( {isToolbarVisible && (
<> <>
@ -309,8 +313,38 @@ export const Toolbar = ({
append={append} append={append}
isAnimating={isAnimating} isAnimating={isAnimating}
/> />
</> </motion.div>
); );
};
export const Toolbar = ({
isToolbarVisible,
setIsToolbarVisible,
append,
isLoading,
stop,
setMessages,
}: {
isToolbarVisible: boolean;
setIsToolbarVisible: Dispatch<SetStateAction<boolean>>;
isLoading: boolean;
append: (
message: Message | CreateMessage,
chatRequestOptions?: ChatRequestOptions
) => Promise<string | null | undefined>;
stop: () => void;
setMessages: Dispatch<SetStateAction<Message[]>>;
}) => {
const toolbarRef = useRef<HTMLDivElement>(null);
const timeoutRef = useRef<NodeJS.Timeout>();
const [selectedTool, setSelectedTool] = useState<string | null>(null);
const [isAnimating, setIsAnimating] = useState(false);
useOnClickOutside(toolbarRef, () => {
setIsToolbarVisible(false);
setSelectedTool(null);
});
const startCloseTimer = () => { const startCloseTimer = () => {
if (timeoutRef.current) { if (timeoutRef.current) {
@ -389,7 +423,11 @@ export const Toolbar = ({
ref={toolbarRef} ref={toolbarRef}
> >
{isLoading ? ( {isLoading ? (
<div <motion.div
key="stop-icon"
initial={{ scale: 1 }}
animate={{ scale: 1.4 }}
exit={{ scale: 1 }}
className="p-3" className="p-3"
onClick={() => { onClick={() => {
stop(); stop();
@ -397,15 +435,24 @@ export const Toolbar = ({
}} }}
> >
<StopIcon /> <StopIcon />
</div> </motion.div>
) : selectedTool === 'adjust-reading-level' ? ( ) : selectedTool === 'adjust-reading-level' ? (
<ReadingLevelSelector <ReadingLevelSelector
key="reading-level-selector"
append={append} append={append}
setSelectedTool={setSelectedTool} setSelectedTool={setSelectedTool}
isAnimating={isAnimating} isAnimating={isAnimating}
/> />
) : ( ) : (
Tools <Tools
key="tools"
append={append}
isAnimating={isAnimating}
isToolbarVisible={isToolbarVisible}
selectedTool={selectedTool}
setIsToolbarVisible={setIsToolbarVisible}
setSelectedTool={setSelectedTool}
/>
)} )}
</motion.div> </motion.div>
</TooltipProvider> </TooltipProvider>