refactor: enhance chat and UI elements with animations, tooltips, and improved styling; streamline sidebar, navbar, and chat-page interactions in top bar
This commit is contained in:
parent
303865438f
commit
db92ab230b
5 changed files with 114 additions and 27 deletions
|
|
@ -4,6 +4,7 @@ import {
|
|||
createRootRoute,
|
||||
useRouterState,
|
||||
} from "@tanstack/react-router";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import { Suspense } from "react";
|
||||
import { AppProvider } from "../provider";
|
||||
|
||||
|
|
@ -20,9 +21,19 @@ function RootLayout() {
|
|||
return (
|
||||
<AppProvider>
|
||||
{!hideNavbar && <Navbar />}
|
||||
<Suspense fallback={null}>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
<AnimatePresence initial={false}>
|
||||
<motion.div
|
||||
key={pathname}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
className="flex-1"
|
||||
>
|
||||
<Suspense fallback={null}>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</AppProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ import {
|
|||
} from "@/components/ui/hover-card";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
AiChat02Icon,
|
||||
Analytics01Icon,
|
||||
ArrowRight01Icon,
|
||||
Book03Icon,
|
||||
PackageIcon,
|
||||
ZapIcon,
|
||||
} from "@hugeicons/core-free-icons";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
|
|
@ -16,9 +19,9 @@ import { useState } from "react";
|
|||
|
||||
const NAV_ITEMS = [
|
||||
{ label: "Studio", href: "/studio", icon: ZapIcon, enabled: true },
|
||||
{ label: "Evaluate", href: "/evaluate", enabled: false },
|
||||
{ label: "Export", href: "/export", enabled: true },
|
||||
{ label: "Chat", href: "/chat", enabled: true },
|
||||
{ label: "Evaluate", href: "/evaluate", icon: Analytics01Icon, enabled: false },
|
||||
{ label: "Export", href: "/export", icon: PackageIcon, enabled: true },
|
||||
{ label: "Chat", href: "/chat", icon: AiChat02Icon, enabled: true },
|
||||
];
|
||||
|
||||
export function Navbar() {
|
||||
|
|
@ -37,11 +40,11 @@ export function Navbar() {
|
|||
<motion.img
|
||||
src="https://unsloth.ai/cgi/image/unsloth_sticker_no_shadow_ldN4V4iydw00qSIIWDCUv.png?width=96&quality=80&format=auto"
|
||||
alt="unsloth"
|
||||
className="size-8"
|
||||
className="size-10"
|
||||
animate={{ rotate: logoHovered ? 360 : 0 }}
|
||||
transition={{ duration: 0.5, ease: [0.165, 0.84, 0.44, 1] }}
|
||||
/>
|
||||
<span className="text-xl font-bold tracking-tight font-heading">
|
||||
<span className="text-2xl font-bold tracking-wide font-heading">
|
||||
unsloth
|
||||
</span>
|
||||
<AnimatePresence>
|
||||
|
|
@ -67,7 +70,7 @@ export function Navbar() {
|
|||
return (
|
||||
<span
|
||||
key={item.href}
|
||||
className="rounded-full px-4 py-1.5 text-sm font-medium text-muted-foreground/40 cursor-not-allowed"
|
||||
className="relative rounded-full px-4 py-1.5 text-sm font-medium text-muted-foreground/40 cursor-not-allowed"
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
|
|
@ -78,19 +81,44 @@ export function Navbar() {
|
|||
key={item.href}
|
||||
to={item.href}
|
||||
className={cn(
|
||||
"rounded-full px-4 py-1.5 text-sm font-medium transition-colors",
|
||||
"relative rounded-full px-4 py-1.5 text-sm font-medium transition-colors",
|
||||
active
|
||||
? "bg-foreground text-background"
|
||||
? "text-background"
|
||||
: "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{"icon" in item && item.icon && (
|
||||
<HugeiconsIcon
|
||||
icon={item.icon}
|
||||
className="size-3.5 inline-block mr-1 -mt-px fill-current"
|
||||
{active && (
|
||||
<motion.span
|
||||
layoutId="nav-pill"
|
||||
className="absolute inset-0 rounded-full bg-foreground"
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 500,
|
||||
damping: 35,
|
||||
mass: 0.5,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{item.label}
|
||||
<span className="relative z-10 flex items-center gap-1.5">
|
||||
<AnimatePresence mode="popLayout">
|
||||
{active && item.icon && (
|
||||
<motion.span
|
||||
key={item.href}
|
||||
initial={{ width: 0, opacity: 0 }}
|
||||
animate={{ width: "auto", opacity: 1 }}
|
||||
exit={{ width: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.2, ease: [0.165, 0.84, 0.44, 1] }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<HugeiconsIcon
|
||||
icon={item.icon}
|
||||
className="size-3.5 -mt-px fill-current"
|
||||
/>
|
||||
</motion.span>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{item.label}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,23 @@ import {
|
|||
ModelSelector,
|
||||
} from "@/components/assistant-ui/model-selector";
|
||||
import { Thread } from "@/components/assistant-ui/thread";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
useSidebar,
|
||||
} from "@/components/ui/sidebar";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Settings04Icon } from "@hugeicons/core-free-icons";
|
||||
import {
|
||||
ColumnInsertIcon,
|
||||
PencilEdit02Icon,
|
||||
Settings04Icon,
|
||||
} from "@hugeicons/core-free-icons";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import {
|
||||
type CSSProperties,
|
||||
|
|
@ -176,7 +186,7 @@ function InlineSidebar({
|
|||
<aside
|
||||
data-sidebar="sidebar"
|
||||
className={cn(
|
||||
"bg-sidebar text-sidebar-foreground h-full overflow-hidden transition-[width] duration-200 ease-linear",
|
||||
"bg-sidebar text-sidebar-foreground h-full overflow-hidden rounded-2xl corner-squircle transition-[width] duration-200 ease-linear",
|
||||
!collapsed && (side === "left" ? "border-r border-0 border-sidebar-border" : "border-l border-0 border-sidebar-border"),
|
||||
collapsed ? "w-0" : "w-(--sidebar-width)",
|
||||
)}
|
||||
|
|
@ -189,6 +199,34 @@ function InlineSidebar({
|
|||
);
|
||||
}
|
||||
|
||||
function TopBarActions({
|
||||
onNewThread,
|
||||
onNewCompare,
|
||||
}: { onNewThread: () => void; onNewCompare: () => void }) {
|
||||
const { state } = useSidebar();
|
||||
if (state !== "collapsed") return null;
|
||||
return (
|
||||
<>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon-sm" onClick={onNewThread}>
|
||||
<HugeiconsIcon icon={PencilEdit02Icon} strokeWidth={2} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">New Chat</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon-sm" onClick={onNewCompare}>
|
||||
<HugeiconsIcon icon={ColumnInsertIcon} strokeWidth={2} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">Compare</TooltipContent>
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChatPage(): ReactElement {
|
||||
const [view, setView] = useState<ChatView>({ mode: "single" });
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
|
|
@ -204,6 +242,14 @@ export function ChatPage(): ReactElement {
|
|||
() => setInferenceParams((p) => ({ ...p, checkpoint: "" })),
|
||||
[],
|
||||
);
|
||||
const handleNewThread = useCallback(
|
||||
() => setView({ mode: "single" }),
|
||||
[],
|
||||
);
|
||||
const handleNewCompare = useCallback(
|
||||
() => setView({ mode: "compare", pairId: crypto.randomUUID() }),
|
||||
[],
|
||||
);
|
||||
|
||||
const models =
|
||||
inferenceParams.inferenceEngine === "llama-cpp"
|
||||
|
|
@ -212,7 +258,7 @@ export function ChatPage(): ReactElement {
|
|||
|
||||
return (
|
||||
<SidebarProvider
|
||||
defaultOpen={false}
|
||||
defaultOpen={true}
|
||||
className="!min-h-0 h-[calc(100vh-4rem)] max-w-7xl mx-auto px-4"
|
||||
style={{ "--sidebar-width": "14rem", "--sidebar-width-icon": "3rem" } as CSSProperties}
|
||||
>
|
||||
|
|
@ -220,17 +266,19 @@ export function ChatPage(): ReactElement {
|
|||
<ThreadSidebar
|
||||
view={view}
|
||||
onSelect={setView}
|
||||
onNewThread={() => setView({ mode: "single" })}
|
||||
onNewCompare={() =>
|
||||
setView({ mode: "compare", pairId: crypto.randomUUID() })
|
||||
}
|
||||
onNewThread={handleNewThread}
|
||||
onNewCompare={handleNewCompare}
|
||||
/>
|
||||
</InlineSidebar>
|
||||
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
||||
<div className="flex h-11 shrink-0 items-center px-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="flex items-center gap-1">
|
||||
<SidebarTrigger />
|
||||
<TopBarActions
|
||||
onNewThread={handleNewThread}
|
||||
onNewCompare={handleNewCompare}
|
||||
/>
|
||||
<ModelSelector
|
||||
models={models}
|
||||
value={inferenceParams.checkpoint}
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ export function ChatSettingsPanel({
|
|||
|
||||
return (
|
||||
<aside
|
||||
className={`shrink-0 h-full overflow-hidden bg-sidebar transition-[width] duration-200 ease-linear ${open ? "w-[17rem] border-sidebar-border" : "w-0"}`}
|
||||
className={`shrink-0 h-full overflow-hidden bg-sidebar rounded-2xl corner-squircle transition-[width] duration-200 ease-linear ${open ? "w-[17rem] border-sidebar-border" : "w-0"}`}
|
||||
>
|
||||
<div className="flex h-full w-[17rem] flex-col">
|
||||
<div className="flex items-center gap-2 px-3 py-2">
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
--chart-4: oklch(0.6926 0.1112 346.5775);
|
||||
--chart-5: oklch(0.7497 0.1003 85.0057);
|
||||
--radius: 1.2rem;
|
||||
--sidebar: oklch(0.9845 0.005 165.0106);
|
||||
--sidebar: oklch(0.975 0 0);
|
||||
--sidebar-foreground: oklch(0.1281 0.0179 169.2764);
|
||||
--sidebar-primary: oklch(0.6929 0.1396 166.5513);
|
||||
--sidebar-primary-foreground: oklch(1 0 0);
|
||||
|
|
@ -118,7 +118,7 @@
|
|||
--chart-3: oklch(0.7554 0.1285 197.339);
|
||||
--chart-4: oklch(0.7503 0.1199 346.7805);
|
||||
--chart-5: oklch(0.799 0.1196 84.6633);
|
||||
--sidebar: oklch(0.0853 0.0204 156.6743);
|
||||
--sidebar: oklch(0.15 0 0);
|
||||
--sidebar-foreground: oklch(0.9808 0.0059 170.4505);
|
||||
--sidebar-primary: oklch(0.6929 0.1396 166.5513);
|
||||
--sidebar-primary-foreground: oklch(1 0 0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue