feat(workspace): add red theme assets and adapt workspace styles

This commit is contained in:
董宏凯 committed 2026-07-19 17:30:38 +08:00
1 parent 760b88d6f1
commit 2fa7000ff7
13 files changed
+95 -125

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

+3 -1
View File
@@ -27,7 +27,9 @@ import {
const SIDEBAR_COOKIE_NAME = "sidebar_state";
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = "16rem";
// Keep the workbench navigation compact across every theme. The visual theme
// only changes tokens; the information density should remain consistent.
const SIDEBAR_WIDTH = "14rem";
const SIDEBAR_WIDTH_MOBILE = "18rem";
const SIDEBAR_WIDTH_ICON = "3rem";
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
@@ -1,7 +1,6 @@
"use client";
import { BotIcon, SparklesIcon } from "lucide-react";
import { useTheme } from "@/contexts/ThemeContext";
import { cn } from "@/lib/utils";
type AgentBrandIconProps = {
@@ -12,27 +11,15 @@ type AgentBrandIconProps = {
const SIZE_STYLES = {
nav: {
frame: "size-[22px] rounded-md",
bot: "size-3.5",
badge: "-right-1 -top-1 size-2.5",
sparkle: "size-1.5",
},
sm: {
frame: "size-10 rounded-full",
bot: "size-[18px]",
badge: "-right-1 -top-1 size-3.5",
sparkle: "size-2",
},
md: {
frame: "size-12 rounded-full",
bot: "size-6",
badge: "-right-1 -top-1 size-4",
sparkle: "size-2.5",
},
lg: {
frame: "size-16 rounded-full",
bot: "size-7",
badge: "-right-1 -top-1 size-[18px]",
sparkle: "size-3",
},
} as const;
@@ -41,50 +28,26 @@ export function AgentBrandIcon({
size = "md",
}: AgentBrandIconProps) {
const styles = SIZE_STYLES[size];
const showBadge = size === "nav";
if (size !== "nav") {
return (
<span
aria-hidden="true"
data-agent-brand-icon={size}
className={cn(
"relative inline-flex shrink-0 overflow-hidden rounded-full",
styles.frame,
className,
)}
>
<img
src="/agent-avatar.png"
alt=""
className="size-full object-contain"
draggable={false}
/>
</span>
);
}
const { theme } = useTheme();
const avatarSrc =
theme === "light" ? "/agent-avatar-red.png" : "/agent-avatar.png";
return (
<span
aria-hidden="true"
data-agent-brand-icon={size}
className={cn(
"relative inline-flex shrink-0 items-center justify-center border border-blue-100 bg-[radial-gradient(circle_at_35%_30%,#ffffff_0%,#edf4ff_46%,#dce9ff_100%)] text-[#1f5fdb] shadow-[0_8px_22px_rgba(37,99,235,0.13),inset_0_1px_0_rgba(255,255,255,0.9)] dark:border-blue-400/30 dark:bg-blue-500/20 dark:text-blue-300",
"relative inline-flex shrink-0 overflow-hidden rounded-full",
styles.frame,
className,
)}
>
<BotIcon className={styles.bot} strokeWidth={1.9} />
{showBadge && (
<span
className={cn(
"absolute flex items-center justify-center rounded-full bg-gradient-to-br from-[#4b91ff] to-[#1858db] text-white shadow-sm ring-2 ring-white dark:ring-slate-950",
styles.badge,
)}
>
<SparklesIcon className={styles.sparkle} strokeWidth={2.4} />
</span>
)}
<img
src={avatarSrc}
alt=""
className="size-full object-contain"
draggable={false}
/>
</span>
);
}
@@ -567,7 +567,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
{isFromOthers && (
<Badge
variant="outline"
className="border-blue-200 bg-blue-50/60 text-xs text-blue-700 dark:border-blue-800 dark:bg-blue-500/10 dark:text-blue-300"
className="border-primary/20 bg-primary/5 text-primary text-xs dark:border-primary/40 dark:bg-primary/10 dark:text-primary"
title={agent.user_id ? `来自用户 ${agent.user_id}` : "已发布"}
>
来自他人
@@ -576,7 +576,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
{(isOwned || isAdmin) && !isBuiltin && agent.published && !agent.is_private && (
<Badge
variant="outline"
className="border-sky-200 bg-sky-50/60 text-xs text-sky-700 dark:border-sky-800 dark:bg-sky-500/10 dark:text-sky-300"
className="border-primary/20 bg-primary/5 text-primary text-xs dark:border-primary/40 dark:bg-primary/10 dark:text-primary"
title="该智能体已对所有用户可见"
>
<GlobeIcon className="size-3" />
@@ -675,7 +675,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
<Badge
key={`tg:${group}`}
variant="outline"
className="border-0 bg-[#f1f5ff] px-2.5 py-1 text-xs font-normal text-[#2166e8] hover:bg-[#e9f0ff]"
className="border-0 bg-primary/5 text-primary hover:bg-primary/10 px-2.5 py-1 text-xs font-normal"
>
{desensitize(group)}
</Badge>
@@ -687,7 +687,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
<Badge
key={`sk:${skill}`}
variant="secondary"
className="cursor-pointer border-0 bg-[#f1f5ff] px-2.5 py-1 text-xs font-normal text-[#2166e8] hover:bg-[#e9f0ff]"
className="bg-primary/5 text-primary hover:bg-primary/10 cursor-pointer border-0 px-2.5 py-1 text-xs font-normal"
title={t.settings.skills.viewDetailAction}
onClick={(e) => {
e.stopPropagation();
@@ -841,8 +841,8 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
size="sm"
className={cn(
isList
? "shrink-0 rounded-xl bg-[#1f63e9] px-4 font-medium text-white hover:bg-[#1857d4]"
: "h-11 w-full rounded-lg border-[#b9d1ff] bg-white font-medium text-[#135ee8] shadow-none hover:border-[#79a8ff] hover:bg-[#f7faff] hover:text-[#135ee8] dark:border-blue-700 dark:bg-slate-900 dark:text-blue-300",
? "bg-primary hover:bg-primary/90 shrink-0 rounded-xl px-4 font-medium text-primary-foreground"
: "border-primary/35 text-primary hover:border-primary/55 hover:bg-primary/5 h-11 w-full rounded-lg bg-white font-medium shadow-none dark:bg-slate-900 dark:text-primary",
)}
onClick={handleChat}
>
@@ -857,8 +857,8 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
私密
</Badge>
) : agent.published || isBuiltin ? (
<Badge className="border-0 bg-blue-50 px-2.5 py-1 text-xs font-normal text-blue-600 hover:bg-blue-50 dark:bg-blue-500/15 dark:text-blue-300">
<span className="mr-1.5 size-1.5 rounded-full bg-blue-500" />
<Badge className="border-0 bg-primary/5 text-primary hover:bg-primary/5 px-2.5 py-1 text-xs font-normal dark:bg-primary/15 dark:text-primary">
<span className="bg-primary mr-1.5 size-1.5 rounded-full" />
已发布
</Badge>
) : (
@@ -944,15 +944,15 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
const cardLayout = (
<Card
className={cn(
"group flex min-h-[366px] min-w-0 flex-col gap-0 overflow-hidden rounded-2xl border-slate-200/90 bg-white py-0 shadow-[0_5px_18px_rgba(15,23,42,0.035)] transition-[background-color,border-color,box-shadow] duration-200 hover:!border-[#b8d1ff] hover:!bg-[linear-gradient(135deg,rgba(239,246,255,0.92)_0%,rgba(255,255,255,0.98)_48%,#fff_100%)] hover:!ring-1 hover:!ring-[#e2edff] hover:shadow-[0_8px_24px_rgba(37,99,235,0.07)] dark:border-slate-800 dark:bg-slate-900",
agent.featured_order != null && "border-blue-300 ring-1 ring-blue-100 dark:border-blue-700 dark:ring-blue-900/40",
"group flex min-h-[334px] min-w-0 flex-col gap-0 overflow-hidden rounded-2xl border-slate-200/90 bg-white py-0 shadow-[0_5px_18px_rgba(15,23,42,0.035)] transition-[background-color,border-color,box-shadow] duration-200 hover:!border-primary/30 hover:!bg-primary/5 hover:!ring-1 hover:!ring-primary/10 hover:shadow-[0_8px_24px_rgba(15,23,42,0.08)] dark:border-slate-800 dark:bg-slate-900",
agent.featured_order != null && "border-primary/40 ring-1 ring-primary/15 dark:border-primary/45 dark:ring-primary/20",
)}
>
<CardHeader className="min-w-0 gap-0 px-5 pt-5 pb-2">
{agent.featured_order != null ? (
<>
<div className="mb-1 flex h-7 items-center justify-between">
<Badge className="border border-blue-200 bg-blue-50 px-2.5 py-1 text-xs font-medium text-blue-600 hover:bg-blue-50 dark:border-blue-800 dark:bg-blue-500/15 dark:text-blue-300">
<Badge className="border border-primary/20 bg-primary/5 text-primary hover:bg-primary/5 px-2.5 py-1 text-xs font-medium dark:border-primary/40 dark:bg-primary/15 dark:text-primary">
<SparklesIcon className="mr-1 size-3.5 fill-current" />
推荐
</Badge>
@@ -991,7 +991,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
</CardContent>
)}
<CardFooter className="mt-auto flex flex-col items-stretch gap-10 px-5 pt-2 pb-4">
<CardFooter className="mt-auto flex flex-col items-stretch gap-4 px-5 pt-2 pb-4">
<div className="flex min-w-0 items-center gap-3">
{cardStatusBadge}
<span
@@ -1008,7 +1008,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
);
const listLayout = (
<div className="group flex min-w-0 items-center gap-4 rounded-2xl border border-slate-200/90 bg-white p-4 shadow-[0_8px_24px_rgba(15,23,42,0.04)] transition-all hover:border-blue-200 hover:shadow-[0_12px_32px_rgba(37,99,235,0.09)] dark:border-slate-800 dark:bg-slate-900">
<div className="group flex min-w-0 items-center gap-4 rounded-2xl border border-slate-200/90 bg-white p-4 shadow-[0_8px_24px_rgba(15,23,42,0.04)] transition-all hover:border-primary/25 hover:shadow-[0_12px_32px_rgba(15,23,42,0.09)] dark:border-slate-800 dark:bg-slate-900">
<AgentBrandIcon className="agent-avatar" size="md" />
<div className="min-w-0 flex-1 overflow-hidden">
<div className="flex min-w-0 items-center gap-2">
@@ -271,17 +271,17 @@ export function AgentGallery() {
};
const renderEmpty = (description: string, showNewButton = false) => (
<div className="flex h-72 flex-col items-center justify-center gap-3 rounded-2xl border border-dashed border-blue-200 bg-white/70 text-center shadow-[0_12px_36px_rgba(37,99,235,0.05)] dark:border-blue-900 dark:bg-slate-900/70">
<div className="flex h-72 flex-col items-center justify-center gap-3 rounded-2xl border border-dashed border-primary/25 bg-white/70 text-center shadow-[0_12px_36px_rgba(15,23,42,0.05)] dark:border-primary/40 dark:bg-slate-900/70">
<AgentBrandIcon size="lg" />
<div>
<p className="font-medium dark:text-[#AACEFF]">{t.agents.emptyTitle}</p>
<p className="text-muted-foreground mt-1 text-sm dark:text-[#55A6FE]">
<p className="font-medium">{t.agents.emptyTitle}</p>
<p className="text-muted-foreground mt-1 text-sm">
{description}
</p>
</div>
{showNewButton && (
<Button
className="mt-2 rounded-xl bg-[#1f63e9] px-5 shadow-[0_8px_20px_rgba(31,99,233,0.22)] hover:bg-[#1857d4] dark:border-[#2580FF] dark:bg-[#2580FF] dark:text-white dark:hover:border-[#2580FF] dark:hover:bg-[#2580FF]/90 dark:hover:text-white"
className="bg-primary hover:bg-primary/90 mt-2 rounded-xl px-5 text-primary-foreground shadow-[0_8px_20px_rgba(15,23,42,0.16)]"
onClick={handleNewAgent}
>
<PlusIcon className="mr-1.5 h-4 w-4" />
@@ -368,20 +368,20 @@ export function AgentGallery() {
};
return (
<div className="flex size-full min-h-0 flex-col overflow-hidden border-y border-r border-slate-200/80 bg-white dark:border-slate-800 dark:bg-slate-950">
<div className="flex size-full min-h-0 flex-col overflow-hidden bg-white dark:bg-slate-950">
{/* Page header */}
<div className="flex shrink-0 items-start justify-between px-[38px] pt-[45px] pb-[38px]">
<div className="flex shrink-0 items-start justify-between px-5 pt-7 pb-6 sm:px-8">
<div>
<h1 className="text-[28px] leading-none font-semibold tracking-[-0.03em] text-slate-950 dark:text-white">
<h1 className="text-[26px] leading-none font-semibold tracking-[-0.03em] text-slate-950 dark:text-white">
{t.agents.title}
</h1>
<p className="mt-4 text-[15px] text-slate-500 dark:text-slate-400">
<p className="mt-2.5 text-[15px] text-slate-500 dark:text-slate-400">
创建并管理专属的 AI 智能体,助力更高效的工作与决策。
</p>
</div>
<Button
onClick={handleNewAgent}
className="h-12 w-[154px] rounded-lg bg-gradient-to-r from-[#2467ea] to-[#0f54ed] px-0 text-[15px] font-medium shadow-[0_9px_22px_rgba(31,99,233,0.22)] hover:brightness-105"
className="bg-primary hover:bg-primary/90 h-11 w-[148px] rounded-xl px-0 text-[15px] font-medium text-primary-foreground shadow-[0_9px_22px_rgba(15,23,42,0.16)]"
>
<PlusIcon className="mr-2 h-[18px] w-[18px]" />
{t.agents.newAgent}
@@ -401,12 +401,12 @@ export function AgentGallery() {
}}
className="flex min-h-0 flex-1 flex-col overflow-hidden"
>
<div className="shrink-0 px-[38px]">
<div className="flex min-h-14 flex-col gap-3 border-b border-slate-200/90 lg:flex-row lg:items-end lg:justify-between dark:border-slate-800">
<div className="shrink-0 px-5 sm:px-8">
<div className="flex min-h-12 flex-col gap-3 border-b border-slate-200/90 lg:flex-row lg:items-end lg:justify-between dark:border-slate-800">
<TabsList variant="line" className="h-auto w-auto max-w-full flex-wrap justify-start self-start rounded-none border-0 bg-transparent p-0">
<TabsTrigger
value="mine"
className="h-14 rounded-none border-x-0 border-t-0 border-b-2 border-transparent bg-transparent px-3 text-[15px] shadow-none after:hidden data-[state=active]:border-x-0 data-[state=active]:border-t-0 data-[state=active]:!border-b-[#2166e8] data-[state=active]:bg-transparent data-[state=active]:text-[#2166e8] data-[state=active]:!shadow-none"
className="h-12 rounded-none border-x-0 border-t-0 border-b-2 border-transparent bg-transparent px-3 text-[15px] shadow-none after:hidden data-[state=active]:border-x-0 data-[state=active]:border-t-0 data-[state=active]:!border-b-primary data-[state=active]:bg-transparent data-[state=active]:text-primary data-[state=active]:!shadow-none"
>
我的
</TabsTrigger>
@@ -415,7 +415,7 @@ export function AgentGallery() {
key={sq.id}
value={sq.id}
title={sq.description || sq.name}
className="h-14 rounded-none border-x-0 border-t-0 border-b-2 border-transparent bg-transparent px-5 text-[15px] shadow-none after:hidden data-[state=active]:border-x-0 data-[state=active]:border-t-0 data-[state=active]:!border-b-[#2166e8] data-[state=active]:bg-transparent data-[state=active]:text-[#2166e8] data-[state=active]:!shadow-none"
className="h-12 rounded-none border-x-0 border-t-0 border-b-2 border-transparent bg-transparent px-5 text-[15px] shadow-none after:hidden data-[state=active]:border-x-0 data-[state=active]:border-t-0 data-[state=active]:!border-b-primary data-[state=active]:bg-transparent data-[state=active]:text-primary data-[state=active]:!shadow-none"
>
{sq.name}
</TabsTrigger>
@@ -423,7 +423,7 @@ export function AgentGallery() {
{privateFeatureAvailable && (
<TabsTrigger
value="private"
className="h-14 rounded-none border-x-0 border-t-0 border-b-2 border-transparent bg-transparent px-5 text-[15px] shadow-none after:hidden data-[state=active]:border-x-0 data-[state=active]:border-t-0 data-[state=active]:!border-b-[#2166e8] data-[state=active]:bg-transparent data-[state=active]:text-[#2166e8] data-[state=active]:!shadow-none"
className="h-12 rounded-none border-x-0 border-t-0 border-b-2 border-transparent bg-transparent px-5 text-[15px] shadow-none after:hidden data-[state=active]:border-x-0 data-[state=active]:border-t-0 data-[state=active]:!border-b-primary data-[state=active]:bg-transparent data-[state=active]:text-primary data-[state=active]:!shadow-none"
>
<LockIcon className="mr-1 h-3.5 w-3.5" />
私密广场
@@ -433,7 +433,7 @@ export function AgentGallery() {
<TabsTrigger
value="builtin"
title="内置/功能型智能体(仅管理员可见)"
className="h-14 rounded-none border-x-0 border-t-0 border-b-2 border-transparent bg-transparent px-5 text-[15px] shadow-none after:hidden data-[state=active]:border-x-0 data-[state=active]:border-t-0 data-[state=active]:!border-b-[#2166e8] data-[state=active]:bg-transparent data-[state=active]:text-[#2166e8] data-[state=active]:!shadow-none"
className="h-12 rounded-none border-x-0 border-t-0 border-b-2 border-transparent bg-transparent px-5 text-[15px] shadow-none after:hidden data-[state=active]:border-x-0 data-[state=active]:border-t-0 data-[state=active]:!border-b-primary data-[state=active]:bg-transparent data-[state=active]:text-primary data-[state=active]:!shadow-none"
>
<BotIcon className="mr-1 h-3.5 w-3.5" />
内置智能体
@@ -441,24 +441,24 @@ export function AgentGallery() {
)}
</TabsList>
{showSearchBar && (
<div className="flex flex-wrap items-center justify-end gap-5 pb-2.5">
<div className="relative w-[350px] max-w-full">
<div className="flex flex-wrap items-center justify-end gap-3 pb-2">
<div className="relative w-[320px] max-w-full">
<SearchIcon className="pointer-events-none absolute top-1/2 left-3.5 size-[18px] -translate-y-1/2 text-slate-500" />
<Input
value={search}
onChange={(event) => setSearch(event.target.value)}
placeholder="搜索智能体名称或描述"
className="h-12 rounded-xl border-slate-200 bg-white pl-11 text-sm shadow-none placeholder:text-slate-400 focus-visible:border-blue-400 focus-visible:ring-blue-100 dark:border-slate-700 dark:bg-slate-900"
className="h-11 rounded-xl border-slate-200 bg-white pl-11 text-sm shadow-none placeholder:text-slate-400 focus-visible:border-primary/50 focus-visible:ring-primary/15 dark:border-slate-700 dark:bg-slate-900"
/>
{(activeTab === "private" ? isPrivateFetching : mainFetching) && (
<Loader2 className="absolute top-1/2 right-3.5 size-4 -translate-y-1/2 animate-spin text-blue-500" />
<Loader2 className="text-primary absolute top-1/2 right-3.5 size-4 -translate-y-1/2 animate-spin" />
)}
</div>
<Select
value={statusFilter}
onValueChange={(value) => setStatusFilter(value as AgentStatusFilter)}
>
<SelectTrigger className="h-12 w-[156px] rounded-xl border-slate-200 px-4 text-sm shadow-none dark:border-slate-700">
<SelectTrigger className="h-11 w-[150px] rounded-xl border-slate-200 px-4 text-sm shadow-none dark:border-slate-700">
<SelectValue />
</SelectTrigger>
<SelectContent position="popper" side="bottom" sideOffset={4}>
@@ -471,7 +471,7 @@ export function AgentGallery() {
</Select>
<Select value={sort} onValueChange={(v) => setSort(v as AgentSort)}>
<SelectTrigger
className="h-12 w-[156px] rounded-xl border-slate-200 px-4 text-sm shadow-none dark:border-slate-700"
className="h-11 w-[150px] rounded-xl border-slate-200 px-4 text-sm shadow-none dark:border-slate-700"
title="排序"
>
<ArrowDownUpIcon className="mr-1 size-4 text-slate-500" />
@@ -506,7 +506,7 @@ export function AgentGallery() {
<button
type="button"
onClick={() => setPublisher(null)}
className="inline-flex items-center gap-1 rounded-full bg-blue-50 px-3 py-1 text-xs font-medium text-blue-700 transition-colors hover:bg-blue-100 dark:bg-blue-500/15 dark:text-blue-300"
className="bg-primary/5 text-primary hover:bg-primary/10 inline-flex items-center gap-1 rounded-full px-3 py-1 text-xs font-medium transition-colors dark:bg-primary/15 dark:text-primary"
title="点击清除发布者筛选"
>
{publisher.label}
@@ -514,7 +514,7 @@ export function AgentGallery() {
</button>
</div>
)}
<div className="py-5 text-[15px] text-slate-600 dark:text-slate-300">
<div className="py-4 text-[15px] text-slate-600 dark:text-slate-300">
共 {activeTotal} 个智能体
</div>
</div>
@@ -523,7 +523,7 @@ export function AgentGallery() {
<TabsContent
key={sq.id}
value={sq.id}
className="mt-0 flex-1 overflow-y-auto px-[38px] pb-8"
className="mt-0 flex-1 overflow-y-auto px-5 pb-6 sm:px-8"
>
{renderListBody({
list: mainAgents,
@@ -536,7 +536,7 @@ export function AgentGallery() {
</TabsContent>
))}
<TabsContent value="mine" className="mt-0 flex-1 overflow-y-auto px-[38px] pb-8">
<TabsContent value="mine" className="mt-0 flex-1 overflow-y-auto px-5 pb-6 sm:px-8">
{renderListBody({
list: mainAgents,
total: mainTotal,
@@ -549,7 +549,7 @@ export function AgentGallery() {
</TabsContent>
{isAdmin && (
<TabsContent value="builtin" className="mt-0 flex-1 overflow-y-auto px-[38px] pb-8">
<TabsContent value="builtin" className="mt-0 flex-1 overflow-y-auto px-5 pb-6 sm:px-8">
{renderListBody({
list: mainAgents,
total: mainTotal,
@@ -564,7 +564,7 @@ export function AgentGallery() {
{privateFeatureAvailable && (
<TabsContent
value="private"
className="mt-0 flex-1 overflow-y-auto px-[38px] pb-8"
className="mt-0 flex-1 overflow-y-auto px-5 pb-6 sm:px-8"
>
{renderPrivateTab()}
</TabsContent>
@@ -18,6 +18,7 @@ import type { MouseEvent } from "react";
import { useNavigate } from "react-router-dom";
import { cn } from "@/lib/utils";
import { useTheme } from "@/contexts/ThemeContext";
import { AgentBrandIcon } from "./agents/agent-brand-icon";
import { RecentChatList } from "./recent-chat-list";
@@ -63,6 +64,8 @@ export function CmChatSidebar({
}) {
const pathname = usePathname();
const navigate = useNavigate();
const { theme } = useTheme();
const brandLogoSrc = theme === "light" ? "/cm-assistant-logo-red.png" : "/cm-assistant-logo.png";
const handleNewChat = (event: MouseEvent<HTMLAnchorElement>) => {
if (event.metaKey || event.ctrlKey || event.shiftKey || event.button === 1) return;
@@ -78,10 +81,10 @@ export function CmChatSidebar({
onClick={onCollapse}
aria-label="展开侧边栏"
title="展开侧边栏"
className="mx-auto mt-2 flex size-11 shrink-0 items-center justify-center rounded-xl transition-colors hover:bg-slate-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/40 dark:hover:bg-slate-900"
className="mx-auto mt-2 flex size-11 shrink-0 items-center justify-center rounded-xl transition-colors hover:bg-slate-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 dark:hover:bg-slate-900"
>
<img
src="/cm-assistant-logo.png"
src={brandLogoSrc}
alt="CM 助手"
className="size-10 object-contain"
/>
@@ -105,9 +108,9 @@ export function CmChatSidebar({
aria-label={item.label}
title={item.label}
className={cn(
"flex size-10 items-center justify-center rounded-xl transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/40",
"flex size-10 items-center justify-center rounded-xl transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40",
active
? "bg-[#edf4ff] text-[#165ee8] dark:bg-blue-500/15 dark:text-blue-300"
? "bg-primary/10 text-primary dark:bg-primary/15 dark:text-primary"
: "text-slate-600 hover:bg-slate-100 hover:text-slate-950 dark:text-slate-300 dark:hover:bg-slate-900 dark:hover:text-white",
)}
>
@@ -127,7 +130,7 @@ export function CmChatSidebar({
href="/page/workspace/settings"
aria-label="设置和更多"
title="设置和更多"
className="flex size-10 items-center justify-center rounded-xl text-slate-600 transition-colors hover:bg-slate-100 hover:text-slate-950 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/40 dark:text-slate-300 dark:hover:bg-slate-900 dark:hover:text-white"
className="flex size-10 items-center justify-center rounded-xl text-slate-600 transition-colors hover:bg-slate-100 hover:text-slate-950 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 dark:text-slate-300 dark:hover:bg-slate-900 dark:hover:text-white"
>
<Settings className="size-[19px]" strokeWidth={1.8} />
</Link>
@@ -138,27 +141,27 @@ export function CmChatSidebar({
return (
<div className="flex h-full min-h-0 flex-col overflow-hidden border-r border-slate-200/80 bg-white text-slate-900 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-100">
<div className="flex shrink-0 items-center gap-2 px-6 pt-4 pb-3">
<Link href={NEW_CHAT_PATH} className="flex min-w-0 flex-1 items-center gap-3.5 outline-none focus-visible:ring-2 focus-visible:ring-blue-500/40">
<div className="flex shrink-0 items-center gap-1.5 px-4 pt-3.5 pb-2.5">
<Link href={NEW_CHAT_PATH} className="flex min-w-0 flex-1 items-center gap-2.5 outline-none focus-visible:ring-2 focus-visible:ring-primary/40">
<img
src="/cm-assistant-logo.png"
src={brandLogoSrc}
alt="CM 助手"
className="size-10 shrink-0 object-contain"
className="size-9 shrink-0 object-contain"
/>
<span className="truncate text-[20px] font-semibold tracking-[-0.025em]">CM 助手</span>
<span className="truncate text-[19px] font-semibold tracking-[-0.025em]">CM 助手</span>
</Link>
<button
type="button"
onClick={onCollapse}
aria-label="收起侧边栏"
title="收起侧边栏"
className="flex size-8 shrink-0 items-center justify-center rounded-md text-slate-500 transition-colors hover:bg-slate-100 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/40 dark:hover:bg-slate-900 dark:hover:text-white"
className="flex size-8 shrink-0 items-center justify-center rounded-md text-slate-500 transition-colors hover:bg-slate-100 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 dark:hover:bg-slate-900 dark:hover:text-white"
>
<PanelLeftClose className="size-[18px]" strokeWidth={1.8} />
</button>
</div>
<nav className="shrink-0 px-4 pt-1" aria-label="工作台导航">
<nav className="shrink-0 px-3 pt-1" aria-label="工作台导航">
<div className="space-y-1">
{NAV_ITEMS.map((item) => {
const active = item.href === NEW_CHAT_PATH
@@ -174,9 +177,9 @@ export function CmChatSidebar({
onClick={item.href === NEW_CHAT_PATH ? handleNewChat : undefined}
aria-current={active ? "page" : undefined}
className={cn(
"flex h-11 items-center gap-3 rounded-xl px-4 text-[14px] font-medium transition-colors",
"flex h-10 items-center gap-3 rounded-xl px-3 text-[14px] font-medium transition-colors",
active
? "bg-[#edf4ff] text-[#165ee8] dark:bg-blue-500/15 dark:text-blue-300"
? "bg-primary/10 text-primary dark:bg-primary/15 dark:text-primary"
: "text-slate-600 hover:bg-slate-100 hover:text-slate-950 dark:text-slate-300 dark:hover:bg-slate-900 dark:hover:text-white",
)}
>
@@ -119,7 +119,7 @@ export function CustomPromptBar({
title={prompt.content}
className={cn(
"border-border/60 bg-muted/40 text-foreground/80 hover:bg-muted hover:text-foreground hover:border-border max-w-[200px] shrink-0 truncate rounded-full border px-3 py-1 text-xs font-medium shadow-sm transition-colors",
compact && "border-slate-200 bg-white px-3.5 py-1.5 text-[12px] text-slate-600 shadow-none hover:border-blue-200 hover:bg-blue-50/60 hover:text-blue-700",
compact && "border-slate-200 bg-white px-3.5 py-1.5 text-[12px] text-slate-600 shadow-none hover:border-primary/25 hover:bg-primary/5 hover:text-primary",
"dark:border-slate-600/70 dark:bg-slate-700/40 dark:text-slate-200 dark:hover:bg-slate-700",
)}
>
@@ -151,7 +151,7 @@ export function CustomPromptBar({
className={cn(
"text-muted-foreground hover:text-foreground hover:bg-muted/70 h-7 w-7 shrink-0 rounded-full",
compact &&
"h-9 w-9 rounded-xl border border-slate-200 bg-white text-slate-600 shadow-[0_6px_18px_-14px_rgba(15,23,42,0.4)] hover:border-blue-200 hover:bg-blue-50 hover:text-blue-700 dark:border-slate-700 dark:bg-slate-900",
"h-9 w-9 rounded-xl border border-slate-200 bg-white text-slate-600 shadow-[0_6px_18px_-14px_rgba(15,23,42,0.4)] hover:border-primary/25 hover:bg-primary/5 hover:text-primary dark:border-slate-700 dark:bg-slate-900",
)}
title="管理常用提示词"
aria-label="管理常用提示词"
@@ -637,7 +637,7 @@ export function InputBox({
title="调整提示词顺序"
aria-label="调整提示词顺序"
onClick={() => setCompactPromptOrder((current) => current + 1)}
className="flex size-7 items-center justify-center rounded-full text-slate-400 transition-colors hover:bg-slate-100 hover:text-blue-600 focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-blue-500/15 dark:hover:bg-slate-800 dark:hover:text-blue-300"
className="flex size-7 items-center justify-center rounded-full text-slate-400 transition-colors hover:bg-slate-100 hover:text-primary focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-primary/15 dark:hover:bg-slate-800 dark:hover:text-primary"
>
<RefreshCwIcon className="size-3.5" strokeWidth={1.8} />
</button>
@@ -650,7 +650,7 @@ export function InputBox({
type="button"
data-quick-prompt={item.id}
title={item.content}
className="inline-flex min-h-11 max-w-full items-center justify-center rounded-xl border border-[#e5e7eb] bg-white px-4 py-2 text-center text-[14px] font-normal leading-5 text-slate-700 shadow-[0_2px_6px_rgba(15,23,42,0.025)] transition-[background-color,border-color,box-shadow] hover:border-slate-300 hover:bg-slate-50 hover:text-slate-900 hover:shadow-[0_4px_10px_rgba(15,23,42,0.04)] focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-blue-500/15 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:border-slate-600 dark:hover:bg-slate-800"
className="inline-flex min-h-11 max-w-full items-center justify-center rounded-xl border border-[#e5e7eb] bg-white px-4 py-2 text-center text-[14px] font-normal leading-5 text-slate-700 shadow-[0_2px_6px_rgba(15,23,42,0.025)] transition-[background-color,border-color,box-shadow] hover:border-primary/25 hover:bg-primary/5 hover:text-slate-900 hover:shadow-[0_4px_10px_rgba(15,23,42,0.04)] focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-primary/15 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:border-primary/35 dark:hover:bg-primary/10"
onClick={() => {
textInput.setInput(item.content);
requestAnimationFrame(() =>
@@ -748,7 +748,7 @@ export function InputBox({
: "*:data-[slot='input-group']:rounded-3xl",
// 紧凑落地态(CM 参考图):白卡 + 细边 + 轻阴影 + 主色聚焦环(无旋转彩边)
compact && isNewThread
? "overflow-hidden rounded-[24px] border border-slate-200/90 bg-white shadow-[0_18px_50px_-32px_rgba(37,99,235,0.6),0_8px_24px_-20px_rgba(15,23,42,0.35)] transition-all duration-300 focus-within:border-blue-300 focus-within:ring-4 focus-within:ring-blue-500/10 dark:border-slate-700 dark:bg-slate-800"
? "overflow-hidden rounded-[24px] border border-slate-200/90 bg-white shadow-[0_18px_50px_-32px_rgba(15,23,42,0.22),0_8px_24px_-20px_rgba(15,23,42,0.35)] transition-all duration-300 focus-within:border-primary/35 focus-within:ring-4 focus-within:ring-primary/10 dark:border-slate-700 dark:bg-slate-800"
: // landing 态:透明,让外层 gemini-gradient-border 提供白卡 + 旋转描边
// 对话态:常规白卡 + 中性边框 + 轻阴影
isNewThread
@@ -844,7 +844,7 @@ export function InputBox({
"gap-1.5! rounded-full px-3! shadow-none",
compact && "rounded-lg px-2.5! py-1! text-[11px] font-medium",
memoryInjectionEnabled
? "border border-blue-300 bg-blue-50 text-blue-700 ring-1 ring-blue-300/35 hover:bg-blue-100 dark:border-cyan-300/70 dark:bg-cyan-400/20 dark:text-cyan-50 dark:ring-cyan-300/35 dark:shadow-[0_0_0_1px_rgba(34,211,238,0.18),0_0_18px_rgba(34,211,238,0.18)] dark:hover:bg-cyan-400/25"
? "border border-primary/35 bg-primary/5 text-primary ring-1 ring-primary/20 hover:bg-primary/10 dark:border-primary/45 dark:bg-primary/15 dark:text-primary dark:ring-primary/25 dark:hover:bg-primary/20"
: compact
? "border border-transparent bg-muted/40 text-muted-foreground hover:bg-muted hover:text-foreground"
: "border-0 bg-transparent text-muted-foreground hover:bg-muted/70 hover:text-foreground dark:text-slate-400 dark:hover:bg-slate-700/70 dark:hover:text-slate-100",
@@ -852,7 +852,7 @@ export function InputBox({
onClick={handleMemoryInjectionToggle}
>
{memoryInjectionEnabled && (
<CheckIcon className="size-3 text-blue-700 dark:text-cyan-50" />
<CheckIcon className="text-primary size-3" />
)}
<BrainCircuitIcon className="size-3" />
<span className="text-xs font-normal">记忆</span>
@@ -1303,9 +1303,9 @@ export function InputBox({
</ModelSelector>
<PromptInputSubmit
className={cn(
"rounded-full border-border bg-background shadow-sm hover:bg-muted dark:bg-blue-500 dark:border-blue-500 dark:text-white dark:hover:bg-blue-400 dark:shadow-none",
"rounded-full border-border bg-background shadow-sm hover:bg-muted dark:bg-primary dark:border-primary dark:text-primary-foreground dark:hover:bg-primary/90 dark:shadow-none",
compact &&
"size-10 rounded-full border-0 bg-blue-600 text-white shadow-[0_8px_18px_-10px_rgba(37,99,235,0.9)] hover:bg-blue-700 disabled:bg-muted disabled:text-muted-foreground/40 dark:border-0 dark:bg-blue-500 dark:text-white dark:hover:bg-blue-400 dark:disabled:bg-muted",
"bg-primary hover:bg-primary/90 size-10 rounded-full border-0 text-primary-foreground shadow-[0_8px_18px_-10px_rgba(15,23,42,0.34)] disabled:bg-muted disabled:text-muted-foreground/40 dark:border-0 dark:bg-primary dark:text-primary-foreground dark:hover:bg-primary/90 dark:disabled:bg-muted",
)}
disabled={disabled}
variant="outline"
@@ -762,7 +762,7 @@ export function SkillRow({
{isFromOthers && (
<Badge
variant="outline"
className="border-blue-500/30 text-blue-700 dark:text-blue-300 text-xs"
className="border-primary/30 text-primary text-xs dark:text-primary"
title={
skill.owner_user_id
? t.settings.skills.badgeFromOthersTitle.replace(
@@ -1027,7 +1027,7 @@ export function SkillRow({
: skill.source === "agent"
? {
Icon: WorkflowIcon,
className: "bg-[#2166e8] text-white shadow-[0_6px_16px_rgba(37,99,235,0.22)]",
className: "bg-primary text-primary-foreground shadow-[0_6px_16px_rgba(15,23,42,0.18)]",
}
: {
Icon: Upload,
@@ -1044,7 +1044,7 @@ export function SkillRow({
return (
<>
{isCard ? (
<div className="flex h-full min-w-0 flex-col rounded-xl border border-slate-200 bg-white p-5 shadow-[0_5px_18px_rgba(15,23,42,0.035)] transition-[border-color,box-shadow] hover:border-blue-200 hover:shadow-[0_10px_28px_rgba(37,99,235,0.07)] dark:border-slate-800 dark:bg-slate-900 dark:text-white">
<div className="flex h-full min-w-0 flex-col rounded-2xl border border-slate-200 bg-white p-5 shadow-[0_5px_18px_rgba(15,23,42,0.035)] transition-[background-color,border-color,box-shadow] hover:border-primary/25 hover:bg-primary/[0.025] hover:shadow-[0_10px_28px_rgba(15,23,42,0.07)] dark:border-slate-800 dark:bg-slate-900 dark:text-white">
<div
className="min-w-0 flex-1 cursor-pointer"
onClick={() => setDetailOpen(true)}
@@ -1075,10 +1075,10 @@ export function SkillRow({
</div>
</div>
) : (
<div className="group/skill-card w-full rounded-xl border border-slate-200 bg-white px-5 py-4 shadow-[0_5px_18px_rgba(15,23,42,0.03)] transition-[border-color,box-shadow] hover:border-blue-200 hover:shadow-[0_10px_28px_rgba(37,99,235,0.07)] dark:border-slate-800 dark:bg-slate-900 dark:text-white">
<div className="grid min-w-0 grid-cols-1 gap-4 xl:grid-cols-[minmax(0,1.65fr)_minmax(150px,0.72fr)_minmax(100px,0.42fr)_minmax(132px,0.58fr)_auto] xl:items-center xl:gap-x-6">
<div className="flex min-w-0 cursor-pointer items-start gap-4" onClick={() => setDetailOpen(true)}>
<div className={cn("flex size-[60px] shrink-0 items-center justify-center rounded-full", iconStyle.className)}>
<div className="group/skill-card w-full rounded-2xl border border-slate-200 bg-white px-5 py-3.5 shadow-[0_5px_18px_rgba(15,23,42,0.03)] transition-[background-color,border-color,box-shadow] hover:border-primary/25 hover:bg-primary/[0.025] hover:shadow-[0_10px_28px_rgba(15,23,42,0.07)] dark:border-slate-800 dark:bg-slate-900 dark:text-white">
<div className="grid min-w-0 grid-cols-1 gap-4 xl:grid-cols-[minmax(0,1.55fr)_minmax(160px,0.78fr)_minmax(110px,0.42fr)_minmax(142px,0.56fr)_auto] xl:items-center xl:gap-x-5">
<div className="flex min-w-0 cursor-pointer items-start gap-3.5" onClick={() => setDetailOpen(true)}>
<div className={cn("flex size-14 shrink-0 items-center justify-center rounded-2xl", iconStyle.className)}>
<SkillIcon className="size-7" />
</div>
<div className="min-w-0 flex-1 pt-0.5">
@@ -1097,7 +1097,7 @@ export function SkillRow({
}}
className={cn(
"mt-2 flex max-w-full items-center gap-1 text-xs text-slate-400",
skill.owner_user_id && onPublisherClick && "cursor-pointer hover:text-blue-600 hover:underline",
skill.owner_user_id && onPublisherClick && "cursor-pointer hover:text-primary hover:underline",
)}
>
<UserIcon className="size-3.5 shrink-0" />
@@ -45,7 +45,7 @@ export function UserIdentityBadge({
className={cn(
"bg-muted text-muted-foreground relative flex size-7 shrink-0 items-center justify-center rounded-full",
variant === "topbar" &&
"size-8 bg-gradient-to-br from-blue-500 to-indigo-600 text-white shadow-[0_4px_12px_-6px_rgba(37,99,235,0.8)] ring-1 ring-blue-500/10 dark:from-blue-400 dark:to-indigo-500",
"bg-primary text-primary-foreground size-8 shadow-[0_4px_12px_-6px_rgba(15,23,42,0.26)] ring-1 ring-primary/15",
)}
>
<UserRound className={cn("size-3.5", variant === "topbar" && "size-4")} />
+3 -1
View File
@@ -136,7 +136,9 @@ export function WorkspaceLayout({
{
'--page-sidebar-width': pageSidebarWidth,
'--header-height': headerHidden ? '0rem' : '4rem',
'--sidebar-width': '18.5rem',
// Shared by red, blue, and dark workbench themes. Keep this in sync
// with the Sidebar primitive instead of giving one route a wider rail.
'--sidebar-width': '14rem',
} as CSSProperties
}
key={String(compactWorkspaceSidebar)}
+3 -3
View File
@@ -1290,9 +1290,9 @@ html.lightblue .tiptap-writing-editor .ProseMirror ::selection {
* agent surfaces on classic rgba()/hex colours instead.
* -------------------------------------------------------------------------- */
.agent-avatar {
background-color: #edf4ff;
color: #1f5fdb;
box-shadow: inset 0 0 0 1px rgba(59, 130, 246, 0.16);
background-color: #fff1f0;
color: #be2323;
box-shadow: inset 0 0 0 1px rgba(190, 35, 35, 0.16);
}
.dark .agent-avatar {