Restore Ultracite + fix sidebar (#1233)
This commit is contained in:
parent
8fbfc253fa
commit
947ed094a6
177 changed files with 6908 additions and 8306 deletions
|
|
@ -1,11 +1,13 @@
|
|||
'use client';
|
||||
import cx from 'classnames';
|
||||
"use client";
|
||||
import type { UseChatHelpers } from "@ai-sdk/react";
|
||||
import cx from "classnames";
|
||||
import {
|
||||
AnimatePresence,
|
||||
motion,
|
||||
useMotionValue,
|
||||
useTransform,
|
||||
} from 'framer-motion';
|
||||
} from "framer-motion";
|
||||
import { nanoid } from "nanoid";
|
||||
import {
|
||||
type Dispatch,
|
||||
memo,
|
||||
|
|
@ -14,21 +16,18 @@ import {
|
|||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useOnClickOutside } from 'usehooks-ts';
|
||||
import { nanoid } from 'nanoid';
|
||||
} from "react";
|
||||
import { useOnClickOutside } from "usehooks-ts";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
|
||||
import { ArrowUpIcon, StopIcon, SummarizeIcon } from './icons';
|
||||
import { artifactDefinitions, type ArtifactKind } from './artifact';
|
||||
import type { ArtifactToolbarItem } from './create-artifact';
|
||||
import type { UseChatHelpers } from '@ai-sdk/react';
|
||||
import type { ChatMessage } from '@/lib/types';
|
||||
} from "@/components/ui/tooltip";
|
||||
import type { ChatMessage } from "@/lib/types";
|
||||
import { type ArtifactKind, artifactDefinitions } from "./artifact";
|
||||
import type { ArtifactToolbarItem } from "./create-artifact";
|
||||
import { ArrowUpIcon, StopIcon, SummarizeIcon } from "./icons";
|
||||
|
||||
type ToolProps = {
|
||||
description: string;
|
||||
|
|
@ -38,11 +37,11 @@ type ToolProps = {
|
|||
isToolbarVisible?: boolean;
|
||||
setIsToolbarVisible?: Dispatch<SetStateAction<boolean>>;
|
||||
isAnimating: boolean;
|
||||
sendMessage: UseChatHelpers<ChatMessage>['sendMessage'];
|
||||
sendMessage: UseChatHelpers<ChatMessage>["sendMessage"];
|
||||
onClick: ({
|
||||
sendMessage,
|
||||
}: {
|
||||
sendMessage: UseChatHelpers<ChatMessage>['sendMessage'];
|
||||
sendMessage: UseChatHelpers<ChatMessage>["sendMessage"];
|
||||
}) => void;
|
||||
};
|
||||
|
||||
|
|
@ -89,40 +88,42 @@ const Tool = ({
|
|||
<Tooltip open={isHovered && !isAnimating}>
|
||||
<TooltipTrigger asChild>
|
||||
<motion.div
|
||||
className={cx('rounded-full p-3', {
|
||||
'bg-primary text-primary-foreground!': selectedTool === description,
|
||||
})}
|
||||
onHoverStart={() => {
|
||||
setIsHovered(true);
|
||||
}}
|
||||
onHoverEnd={() => {
|
||||
if (selectedTool !== description) setIsHovered(false);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
handleSelect();
|
||||
}
|
||||
}}
|
||||
initial={{ scale: 1, opacity: 0 }}
|
||||
animate={{ opacity: 1, transition: { delay: 0.1 } }}
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className={cx("rounded-full p-3", {
|
||||
"bg-primary text-primary-foreground!": selectedTool === description,
|
||||
})}
|
||||
exit={{
|
||||
scale: 0.9,
|
||||
opacity: 0,
|
||||
transition: { duration: 0.1 },
|
||||
}}
|
||||
initial={{ scale: 1, opacity: 0 }}
|
||||
onClick={() => {
|
||||
handleSelect();
|
||||
}}
|
||||
onHoverEnd={() => {
|
||||
if (selectedTool !== description) {
|
||||
setIsHovered(false);
|
||||
}
|
||||
}}
|
||||
onHoverStart={() => {
|
||||
setIsHovered(true);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
handleSelect();
|
||||
}
|
||||
}}
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
{selectedTool === description ? <ArrowUpIcon /> : icon}
|
||||
</motion.div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
className="rounded-2xl bg-foreground p-3 px-4 text-background"
|
||||
side="left"
|
||||
sideOffset={16}
|
||||
className="rounded-2xl bg-foreground p-3 px-4 text-background"
|
||||
>
|
||||
{description}
|
||||
</TooltipContent>
|
||||
|
|
@ -130,7 +131,7 @@ const Tool = ({
|
|||
);
|
||||
};
|
||||
|
||||
const randomArr = [...Array(6)].map((x) => nanoid(5));
|
||||
const randomArr = [...new Array(6)].map((_x) => nanoid(5));
|
||||
|
||||
const ReadingLevelSelector = ({
|
||||
setSelectedTool,
|
||||
|
|
@ -139,15 +140,15 @@ const ReadingLevelSelector = ({
|
|||
}: {
|
||||
setSelectedTool: Dispatch<SetStateAction<string | null>>;
|
||||
isAnimating: boolean;
|
||||
sendMessage: UseChatHelpers<ChatMessage>['sendMessage'];
|
||||
sendMessage: UseChatHelpers<ChatMessage>["sendMessage"];
|
||||
}) => {
|
||||
const LEVELS = [
|
||||
'Elementary',
|
||||
'Middle School',
|
||||
'Keep current level',
|
||||
'High School',
|
||||
'College',
|
||||
'Graduate',
|
||||
"Elementary",
|
||||
"Middle School",
|
||||
"Keep current level",
|
||||
"High School",
|
||||
"College",
|
||||
"Graduate",
|
||||
];
|
||||
|
||||
const y = useMotionValue(-40 * 2);
|
||||
|
|
@ -159,7 +160,7 @@ const ReadingLevelSelector = ({
|
|||
useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = yToLevel.on('change', (latest) => {
|
||||
const unsubscribe = yToLevel.on("change", (latest) => {
|
||||
const level = Math.min(5, Math.max(0, Math.round(Math.abs(latest))));
|
||||
setCurrentLevel(level);
|
||||
});
|
||||
|
|
@ -171,11 +172,11 @@ const ReadingLevelSelector = ({
|
|||
<div className="relative flex flex-col items-center justify-end">
|
||||
{randomArr.map((id) => (
|
||||
<motion.div
|
||||
key={id}
|
||||
className="flex size-[40px] flex-row items-center justify-center"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className="flex size-[40px] flex-row items-center justify-center"
|
||||
exit={{ opacity: 0 }}
|
||||
initial={{ opacity: 0 }}
|
||||
key={id}
|
||||
transition={{ delay: 0.1 }}
|
||||
>
|
||||
<div className="size-2 rounded-full bg-muted-foreground/40" />
|
||||
|
|
@ -187,37 +188,23 @@ const ReadingLevelSelector = ({
|
|||
<TooltipTrigger asChild>
|
||||
<motion.div
|
||||
className={cx(
|
||||
'absolute flex flex-row items-center rounded-full border bg-background p-3',
|
||||
"absolute flex flex-row items-center rounded-full border bg-background p-3",
|
||||
{
|
||||
'bg-primary text-primary-foreground': currentLevel !== 2,
|
||||
'bg-background text-foreground': currentLevel === 2,
|
||||
},
|
||||
"bg-primary text-primary-foreground": currentLevel !== 2,
|
||||
"bg-background text-foreground": currentLevel === 2,
|
||||
}
|
||||
)}
|
||||
style={{ y }}
|
||||
drag="y"
|
||||
dragConstraints={{ top: -dragConstraints, bottom: 0 }}
|
||||
dragElastic={0}
|
||||
dragMomentum={false}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
transition={{ duration: 0.1 }}
|
||||
dragConstraints={{ top: -dragConstraints, bottom: 0 }}
|
||||
onDragStart={() => {
|
||||
setHasUserSelectedLevel(false);
|
||||
}}
|
||||
onDragEnd={() => {
|
||||
if (currentLevel === 2) {
|
||||
setSelectedTool(null);
|
||||
} else {
|
||||
setHasUserSelectedLevel(true);
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
if (currentLevel !== 2 && hasUserSelectedLevel) {
|
||||
sendMessage({
|
||||
role: 'user',
|
||||
role: "user",
|
||||
parts: [
|
||||
{
|
||||
type: 'text',
|
||||
type: "text",
|
||||
text: `Please adjust the reading level to ${LEVELS[currentLevel]} level.`,
|
||||
},
|
||||
],
|
||||
|
|
@ -226,14 +213,28 @@ const ReadingLevelSelector = ({
|
|||
setSelectedTool(null);
|
||||
}
|
||||
}}
|
||||
onDragEnd={() => {
|
||||
if (currentLevel === 2) {
|
||||
setSelectedTool(null);
|
||||
} else {
|
||||
setHasUserSelectedLevel(true);
|
||||
}
|
||||
}}
|
||||
onDragStart={() => {
|
||||
setHasUserSelectedLevel(false);
|
||||
}}
|
||||
style={{ y }}
|
||||
transition={{ duration: 0.1 }}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
{currentLevel === 2 ? <SummarizeIcon /> : <ArrowUpIcon />}
|
||||
</motion.div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
className="rounded-2xl bg-foreground p-3 px-4 text-background text-sm"
|
||||
side="left"
|
||||
sideOffset={16}
|
||||
className="rounded-2xl bg-foreground p-3 px-4 text-background text-sm"
|
||||
>
|
||||
{LEVELS[currentLevel]}
|
||||
</TooltipContent>
|
||||
|
|
@ -255,32 +256,32 @@ export const Tools = ({
|
|||
isToolbarVisible: boolean;
|
||||
selectedTool: string | null;
|
||||
setSelectedTool: Dispatch<SetStateAction<string | null>>;
|
||||
sendMessage: UseChatHelpers<ChatMessage>['sendMessage'];
|
||||
sendMessage: UseChatHelpers<ChatMessage>["sendMessage"];
|
||||
isAnimating: boolean;
|
||||
setIsToolbarVisible: Dispatch<SetStateAction<boolean>>;
|
||||
tools: Array<ArtifactToolbarItem>;
|
||||
tools: ArtifactToolbarItem[];
|
||||
}) => {
|
||||
const [primaryTool, ...secondaryTools] = tools;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="flex flex-col gap-1.5"
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="flex flex-col gap-1.5"
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
>
|
||||
<AnimatePresence>
|
||||
{isToolbarVisible &&
|
||||
secondaryTools.map((secondaryTool) => (
|
||||
<Tool
|
||||
key={secondaryTool.description}
|
||||
description={secondaryTool.description}
|
||||
icon={secondaryTool.icon}
|
||||
selectedTool={selectedTool}
|
||||
setSelectedTool={setSelectedTool}
|
||||
sendMessage={sendMessage}
|
||||
isAnimating={isAnimating}
|
||||
key={secondaryTool.description}
|
||||
onClick={secondaryTool.onClick}
|
||||
selectedTool={selectedTool}
|
||||
sendMessage={sendMessage}
|
||||
setSelectedTool={setSelectedTool}
|
||||
/>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
|
|
@ -288,13 +289,13 @@ export const Tools = ({
|
|||
<Tool
|
||||
description={primaryTool.description}
|
||||
icon={primaryTool.icon}
|
||||
selectedTool={selectedTool}
|
||||
setSelectedTool={setSelectedTool}
|
||||
isToolbarVisible={isToolbarVisible}
|
||||
setIsToolbarVisible={setIsToolbarVisible}
|
||||
sendMessage={sendMessage}
|
||||
isAnimating={isAnimating}
|
||||
isToolbarVisible={isToolbarVisible}
|
||||
onClick={primaryTool.onClick}
|
||||
selectedTool={selectedTool}
|
||||
sendMessage={sendMessage}
|
||||
setIsToolbarVisible={setIsToolbarVisible}
|
||||
setSelectedTool={setSelectedTool}
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
|
|
@ -311,10 +312,10 @@ const PureToolbar = ({
|
|||
}: {
|
||||
isToolbarVisible: boolean;
|
||||
setIsToolbarVisible: Dispatch<SetStateAction<boolean>>;
|
||||
status: UseChatHelpers<ChatMessage>['status'];
|
||||
sendMessage: UseChatHelpers<ChatMessage>['sendMessage'];
|
||||
stop: UseChatHelpers<ChatMessage>['stop'];
|
||||
setMessages: UseChatHelpers<ChatMessage>['setMessages'];
|
||||
status: UseChatHelpers<ChatMessage>["status"];
|
||||
sendMessage: UseChatHelpers<ChatMessage>["sendMessage"];
|
||||
stop: UseChatHelpers<ChatMessage>["stop"];
|
||||
setMessages: UseChatHelpers<ChatMessage>["setMessages"];
|
||||
artifactKind: ArtifactKind;
|
||||
}) => {
|
||||
const toolbarRef = useRef<HTMLDivElement>(null);
|
||||
|
|
@ -354,17 +355,17 @@ const PureToolbar = ({
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'streaming') {
|
||||
if (status === "streaming") {
|
||||
setIsToolbarVisible(false);
|
||||
}
|
||||
}, [status, setIsToolbarVisible]);
|
||||
|
||||
const artifactDefinition = artifactDefinitions.find(
|
||||
(definition) => definition.kind === artifactKind,
|
||||
(definition) => definition.kind === artifactKind
|
||||
);
|
||||
|
||||
if (!artifactDefinition) {
|
||||
throw new Error('Artifact definition not found!');
|
||||
throw new Error("Artifact definition not found!");
|
||||
}
|
||||
|
||||
const toolsByArtifactKind = artifactDefinition.toolbar;
|
||||
|
|
@ -376,11 +377,9 @@ const PureToolbar = ({
|
|||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<motion.div
|
||||
className="absolute right-6 bottom-6 flex cursor-pointer flex-col justify-end rounded-full border bg-background p-1.5 shadow-lg"
|
||||
initial={{ opacity: 0, y: -20, scale: 1 }}
|
||||
animate={
|
||||
isToolbarVisible
|
||||
? selectedTool === 'adjust-reading-level'
|
||||
? selectedTool === "adjust-reading-level"
|
||||
? {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
|
|
@ -397,34 +396,40 @@ const PureToolbar = ({
|
|||
}
|
||||
: { opacity: 1, y: 0, height: 54, transition: { delay: 0 } }
|
||||
}
|
||||
className="absolute right-6 bottom-6 flex cursor-pointer flex-col justify-end rounded-full border bg-background p-1.5 shadow-lg"
|
||||
exit={{ opacity: 0, y: -20, transition: { duration: 0.1 } }}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
|
||||
onHoverStart={() => {
|
||||
if (status === 'streaming') return;
|
||||
|
||||
cancelCloseTimer();
|
||||
setIsToolbarVisible(true);
|
||||
}}
|
||||
onHoverEnd={() => {
|
||||
if (status === 'streaming') return;
|
||||
|
||||
startCloseTimer();
|
||||
initial={{ opacity: 0, y: -20, scale: 1 }}
|
||||
onAnimationComplete={() => {
|
||||
setIsAnimating(false);
|
||||
}}
|
||||
onAnimationStart={() => {
|
||||
setIsAnimating(true);
|
||||
}}
|
||||
onAnimationComplete={() => {
|
||||
setIsAnimating(false);
|
||||
onHoverEnd={() => {
|
||||
if (status === "streaming") {
|
||||
return;
|
||||
}
|
||||
|
||||
startCloseTimer();
|
||||
}}
|
||||
onHoverStart={() => {
|
||||
if (status === "streaming") {
|
||||
return;
|
||||
}
|
||||
|
||||
cancelCloseTimer();
|
||||
setIsToolbarVisible(true);
|
||||
}}
|
||||
ref={toolbarRef}
|
||||
transition={{ type: "spring", stiffness: 300, damping: 25 }}
|
||||
>
|
||||
{status === 'streaming' ? (
|
||||
{status === "streaming" ? (
|
||||
<motion.div
|
||||
key="stop-icon"
|
||||
initial={{ scale: 1 }}
|
||||
animate={{ scale: 1.4 }}
|
||||
exit={{ scale: 1 }}
|
||||
className="p-3"
|
||||
exit={{ scale: 1 }}
|
||||
initial={{ scale: 1 }}
|
||||
key="stop-icon"
|
||||
onClick={() => {
|
||||
stop();
|
||||
setMessages((messages) => messages);
|
||||
|
|
@ -432,20 +437,20 @@ const PureToolbar = ({
|
|||
>
|
||||
<StopIcon />
|
||||
</motion.div>
|
||||
) : selectedTool === 'adjust-reading-level' ? (
|
||||
) : selectedTool === "adjust-reading-level" ? (
|
||||
<ReadingLevelSelector
|
||||
isAnimating={isAnimating}
|
||||
key="reading-level-selector"
|
||||
sendMessage={sendMessage}
|
||||
setSelectedTool={setSelectedTool}
|
||||
isAnimating={isAnimating}
|
||||
/>
|
||||
) : (
|
||||
<Tools
|
||||
key="tools"
|
||||
sendMessage={sendMessage}
|
||||
isAnimating={isAnimating}
|
||||
isToolbarVisible={isToolbarVisible}
|
||||
key="tools"
|
||||
selectedTool={selectedTool}
|
||||
sendMessage={sendMessage}
|
||||
setIsToolbarVisible={setIsToolbarVisible}
|
||||
setSelectedTool={setSelectedTool}
|
||||
tools={toolsByArtifactKind}
|
||||
|
|
@ -457,9 +462,15 @@ const PureToolbar = ({
|
|||
};
|
||||
|
||||
export const Toolbar = memo(PureToolbar, (prevProps, nextProps) => {
|
||||
if (prevProps.status !== nextProps.status) return false;
|
||||
if (prevProps.isToolbarVisible !== nextProps.isToolbarVisible) return false;
|
||||
if (prevProps.artifactKind !== nextProps.artifactKind) return false;
|
||||
if (prevProps.status !== nextProps.status) {
|
||||
return false;
|
||||
}
|
||||
if (prevProps.isToolbarVisible !== nextProps.isToolbarVisible) {
|
||||
return false;
|
||||
}
|
||||
if (prevProps.artifactKind !== nextProps.artifactKind) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue