Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2041c0b07 | ||
|
|
2fa7000ff7 |
No files matched your search
@@ -50,4 +50,6 @@
|
|||||||
# 可选
|
# 可选
|
||||||
# VITE_CANVAS_LANGGRAPH_BASE_URL=
|
# VITE_CANVAS_LANGGRAPH_BASE_URL=
|
||||||
# VITE_STATIC_WEBSITE_ONLY=
|
# VITE_STATIC_WEBSITE_ONLY=
|
||||||
|
# 部署级紧凑导航:true 时固定收起工作台左侧栏,并隐藏换肤与全量/简洁模式切换。
|
||||||
|
# VITE_FORCE_COLLAPSED_SIDEBAR=false
|
||||||
# VITE_GITHUB_OAUTH_TOKEN=
|
# VITE_GITHUB_OAUTH_TOKEN=
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -91,6 +91,10 @@ window.__CM_RUNTIME_CONFIG__ = {
|
|||||||
// 静态站点模式开关;一般部署业务系统时保持 false。
|
// 静态站点模式开关;一般部署业务系统时保持 false。
|
||||||
VITE_STATIC_WEBSITE_ONLY: "false",
|
VITE_STATIC_WEBSITE_ONLY: "false",
|
||||||
|
|
||||||
|
// 紧凑导航开关:true 时固定使用简洁布局,工作台左侧栏收起为菜单图标,
|
||||||
|
// 隐藏系统 Logo、换肤按钮以及“全量/简洁模式”切换。修改后刷新页面即可生效。
|
||||||
|
VITE_FORCE_COLLAPSED_SIDEBAR: "false",
|
||||||
|
|
||||||
// GitHub OAuth Token;不使用 GitHub 相关能力时留空。
|
// GitHub OAuth Token;不使用 GitHub 相关能力时留空。
|
||||||
VITE_GITHUB_OAUTH_TOKEN: "",
|
VITE_GITHUB_OAUTH_TOKEN: "",
|
||||||
};
|
};
|
||||||
@@ -86,7 +86,7 @@ function AppProviders({ children }: { children: React.ReactNode }) {
|
|||||||
attribute="class"
|
attribute="class"
|
||||||
storageKey="strategy-theme"
|
storageKey="strategy-theme"
|
||||||
defaultTheme="light"
|
defaultTheme="light"
|
||||||
themes={["light", "dark", "lightblue"]}
|
themes={["light", "dark", "lightblue", "lightgreen"]}
|
||||||
enableSystem={false}
|
enableSystem={false}
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
forcedTheme={forcedTheme}
|
forcedTheme={forcedTheme}
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ import {
|
|||||||
|
|
||||||
const SIDEBAR_COOKIE_NAME = "sidebar_state";
|
const SIDEBAR_COOKIE_NAME = "sidebar_state";
|
||||||
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
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_MOBILE = "18rem";
|
||||||
const SIDEBAR_WIDTH_ICON = "3rem";
|
const SIDEBAR_WIDTH_ICON = "3rem";
|
||||||
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
|
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { BotIcon, SparklesIcon } from "lucide-react";
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type AgentBrandIconProps = {
|
type AgentBrandIconProps = {
|
||||||
@@ -12,27 +11,15 @@ type AgentBrandIconProps = {
|
|||||||
const SIZE_STYLES = {
|
const SIZE_STYLES = {
|
||||||
nav: {
|
nav: {
|
||||||
frame: "size-[22px] rounded-md",
|
frame: "size-[22px] rounded-md",
|
||||||
bot: "size-3.5",
|
|
||||||
badge: "-right-1 -top-1 size-2.5",
|
|
||||||
sparkle: "size-1.5",
|
|
||||||
},
|
},
|
||||||
sm: {
|
sm: {
|
||||||
frame: "size-10 rounded-full",
|
frame: "size-10 rounded-full",
|
||||||
bot: "size-[18px]",
|
|
||||||
badge: "-right-1 -top-1 size-3.5",
|
|
||||||
sparkle: "size-2",
|
|
||||||
},
|
},
|
||||||
md: {
|
md: {
|
||||||
frame: "size-12 rounded-full",
|
frame: "size-12 rounded-full",
|
||||||
bot: "size-6",
|
|
||||||
badge: "-right-1 -top-1 size-4",
|
|
||||||
sparkle: "size-2.5",
|
|
||||||
},
|
},
|
||||||
lg: {
|
lg: {
|
||||||
frame: "size-16 rounded-full",
|
frame: "size-16 rounded-full",
|
||||||
bot: "size-7",
|
|
||||||
badge: "-right-1 -top-1 size-[18px]",
|
|
||||||
sparkle: "size-3",
|
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@@ -41,9 +28,10 @@ export function AgentBrandIcon({
|
|||||||
size = "md",
|
size = "md",
|
||||||
}: AgentBrandIconProps) {
|
}: AgentBrandIconProps) {
|
||||||
const styles = SIZE_STYLES[size];
|
const styles = SIZE_STYLES[size];
|
||||||
const showBadge = size === "nav";
|
const { theme } = useTheme();
|
||||||
|
const avatarSrc =
|
||||||
|
theme === "light" ? "/agent-avatar-red.png" : "/agent-avatar.png";
|
||||||
|
|
||||||
if (size !== "nav") {
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -55,36 +43,11 @@ export function AgentBrandIcon({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="/agent-avatar.png"
|
src={avatarSrc}
|
||||||
alt=""
|
alt=""
|
||||||
className="size-full object-contain"
|
className="size-full object-contain"
|
||||||
draggable={false}
|
draggable={false}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
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",
|
|
||||||
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>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
@@ -567,7 +567,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
|||||||
{isFromOthers && (
|
{isFromOthers && (
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
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}` : "已发布"}
|
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 && (
|
{(isOwned || isAdmin) && !isBuiltin && agent.published && !agent.is_private && (
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
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="该智能体已对所有用户可见"
|
title="该智能体已对所有用户可见"
|
||||||
>
|
>
|
||||||
<GlobeIcon className="size-3" />
|
<GlobeIcon className="size-3" />
|
||||||
@@ -675,7 +675,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
|||||||
<Badge
|
<Badge
|
||||||
key={`tg:${group}`}
|
key={`tg:${group}`}
|
||||||
variant="outline"
|
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)}
|
{desensitize(group)}
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -687,7 +687,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
|||||||
<Badge
|
<Badge
|
||||||
key={`sk:${skill}`}
|
key={`sk:${skill}`}
|
||||||
variant="secondary"
|
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}
|
title={t.settings.skills.viewDetailAction}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -841,8 +841,8 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
|||||||
size="sm"
|
size="sm"
|
||||||
className={cn(
|
className={cn(
|
||||||
isList
|
isList
|
||||||
? "shrink-0 rounded-xl bg-[#1f63e9] px-4 font-medium text-white hover:bg-[#1857d4]"
|
? "bg-primary hover:bg-primary/90 shrink-0 rounded-xl px-4 font-medium text-primary-foreground"
|
||||||
: "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",
|
: "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}
|
onClick={handleChat}
|
||||||
>
|
>
|
||||||
@@ -857,8 +857,8 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
|||||||
私密
|
私密
|
||||||
</Badge>
|
</Badge>
|
||||||
) : agent.published || isBuiltin ? (
|
) : 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">
|
<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="mr-1.5 size-1.5 rounded-full bg-blue-500" />
|
<span className="bg-primary mr-1.5 size-1.5 rounded-full" />
|
||||||
已发布
|
已发布
|
||||||
</Badge>
|
</Badge>
|
||||||
) : (
|
) : (
|
||||||
@@ -944,15 +944,15 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
|||||||
const cardLayout = (
|
const cardLayout = (
|
||||||
<Card
|
<Card
|
||||||
className={cn(
|
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",
|
"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-blue-300 ring-1 ring-blue-100 dark:border-blue-700 dark:ring-blue-900/40",
|
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">
|
<CardHeader className="min-w-0 gap-0 px-5 pt-5 pb-2">
|
||||||
{agent.featured_order != null ? (
|
{agent.featured_order != null ? (
|
||||||
<>
|
<>
|
||||||
<div className="mb-1 flex h-7 items-center justify-between">
|
<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" />
|
<SparklesIcon className="mr-1 size-3.5 fill-current" />
|
||||||
推荐
|
推荐
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -991,7 +991,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
|||||||
</CardContent>
|
</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">
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
{cardStatusBadge}
|
{cardStatusBadge}
|
||||||
<span
|
<span
|
||||||
@@ -1008,7 +1008,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
|||||||
);
|
);
|
||||||
|
|
||||||
const listLayout = (
|
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" />
|
<AgentBrandIcon className="agent-avatar" size="md" />
|
||||||
<div className="min-w-0 flex-1 overflow-hidden">
|
<div className="min-w-0 flex-1 overflow-hidden">
|
||||||
<div className="flex min-w-0 items-center gap-2">
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
|
|||||||
@@ -271,17 +271,17 @@ export function AgentGallery() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderEmpty = (description: string, showNewButton = false) => (
|
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" />
|
<AgentBrandIcon size="lg" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium dark:text-[#AACEFF]">{t.agents.emptyTitle}</p>
|
<p className="font-medium">{t.agents.emptyTitle}</p>
|
||||||
<p className="text-muted-foreground mt-1 text-sm dark:text-[#55A6FE]">
|
<p className="text-muted-foreground mt-1 text-sm">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{showNewButton && (
|
{showNewButton && (
|
||||||
<Button
|
<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}
|
onClick={handleNewAgent}
|
||||||
>
|
>
|
||||||
<PlusIcon className="mr-1.5 h-4 w-4" />
|
<PlusIcon className="mr-1.5 h-4 w-4" />
|
||||||
@@ -368,20 +368,20 @@ export function AgentGallery() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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 */}
|
{/* 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>
|
<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}
|
{t.agents.title}
|
||||||
</h1>
|
</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 智能体,助力更高效的工作与决策。
|
创建并管理专属的 AI 智能体,助力更高效的工作与决策。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleNewAgent}
|
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]" />
|
<PlusIcon className="mr-2 h-[18px] w-[18px]" />
|
||||||
{t.agents.newAgent}
|
{t.agents.newAgent}
|
||||||
@@ -401,12 +401,12 @@ export function AgentGallery() {
|
|||||||
}}
|
}}
|
||||||
className="flex min-h-0 flex-1 flex-col overflow-hidden"
|
className="flex min-h-0 flex-1 flex-col overflow-hidden"
|
||||||
>
|
>
|
||||||
<div className="shrink-0 px-[38px]">
|
<div className="shrink-0 px-5 sm:px-8">
|
||||||
<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="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">
|
<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
|
<TabsTrigger
|
||||||
value="mine"
|
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>
|
</TabsTrigger>
|
||||||
@@ -415,7 +415,7 @@ export function AgentGallery() {
|
|||||||
key={sq.id}
|
key={sq.id}
|
||||||
value={sq.id}
|
value={sq.id}
|
||||||
title={sq.description || sq.name}
|
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}
|
{sq.name}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
@@ -423,7 +423,7 @@ export function AgentGallery() {
|
|||||||
{privateFeatureAvailable && (
|
{privateFeatureAvailable && (
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
value="private"
|
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" />
|
<LockIcon className="mr-1 h-3.5 w-3.5" />
|
||||||
私密广场
|
私密广场
|
||||||
@@ -433,7 +433,7 @@ export function AgentGallery() {
|
|||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
value="builtin"
|
value="builtin"
|
||||||
title="内置/功能型智能体(仅管理员可见)"
|
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" />
|
<BotIcon className="mr-1 h-3.5 w-3.5" />
|
||||||
内置智能体
|
内置智能体
|
||||||
@@ -441,24 +441,24 @@ export function AgentGallery() {
|
|||||||
)}
|
)}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
{showSearchBar && (
|
{showSearchBar && (
|
||||||
<div className="flex flex-wrap items-center justify-end gap-5 pb-2.5">
|
<div className="flex flex-wrap items-center justify-end gap-3 pb-2">
|
||||||
<div className="relative w-[350px] max-w-full">
|
<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" />
|
<SearchIcon className="pointer-events-none absolute top-1/2 left-3.5 size-[18px] -translate-y-1/2 text-slate-500" />
|
||||||
<Input
|
<Input
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(event) => setSearch(event.target.value)}
|
onChange={(event) => setSearch(event.target.value)}
|
||||||
placeholder="搜索智能体名称或描述"
|
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) && (
|
{(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>
|
</div>
|
||||||
<Select
|
<Select
|
||||||
value={statusFilter}
|
value={statusFilter}
|
||||||
onValueChange={(value) => setStatusFilter(value as AgentStatusFilter)}
|
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 />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent position="popper" side="bottom" sideOffset={4}>
|
<SelectContent position="popper" side="bottom" sideOffset={4}>
|
||||||
@@ -471,7 +471,7 @@ export function AgentGallery() {
|
|||||||
</Select>
|
</Select>
|
||||||
<Select value={sort} onValueChange={(v) => setSort(v as AgentSort)}>
|
<Select value={sort} onValueChange={(v) => setSort(v as AgentSort)}>
|
||||||
<SelectTrigger
|
<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="排序"
|
title="排序"
|
||||||
>
|
>
|
||||||
<ArrowDownUpIcon className="mr-1 size-4 text-slate-500" />
|
<ArrowDownUpIcon className="mr-1 size-4 text-slate-500" />
|
||||||
@@ -506,7 +506,7 @@ export function AgentGallery() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setPublisher(null)}
|
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="点击清除发布者筛选"
|
title="点击清除发布者筛选"
|
||||||
>
|
>
|
||||||
{publisher.label}
|
{publisher.label}
|
||||||
@@ -514,7 +514,7 @@ export function AgentGallery() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</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} 个智能体
|
共 {activeTotal} 个智能体
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -523,7 +523,7 @@ export function AgentGallery() {
|
|||||||
<TabsContent
|
<TabsContent
|
||||||
key={sq.id}
|
key={sq.id}
|
||||||
value={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({
|
{renderListBody({
|
||||||
list: mainAgents,
|
list: mainAgents,
|
||||||
@@ -536,7 +536,7 @@ export function AgentGallery() {
|
|||||||
</TabsContent>
|
</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({
|
{renderListBody({
|
||||||
list: mainAgents,
|
list: mainAgents,
|
||||||
total: mainTotal,
|
total: mainTotal,
|
||||||
@@ -549,7 +549,7 @@ export function AgentGallery() {
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{isAdmin && (
|
{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({
|
{renderListBody({
|
||||||
list: mainAgents,
|
list: mainAgents,
|
||||||
total: mainTotal,
|
total: mainTotal,
|
||||||
@@ -564,7 +564,7 @@ export function AgentGallery() {
|
|||||||
{privateFeatureAvailable && (
|
{privateFeatureAvailable && (
|
||||||
<TabsContent
|
<TabsContent
|
||||||
value="private"
|
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()}
|
{renderPrivateTab()}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { useTheme, type ThemeType } from "@/contexts/ThemeContext";
|
import { useTheme, type ThemeType } from "@/contexts/ThemeContext";
|
||||||
|
import { env } from "@/env";
|
||||||
import { useAppearanceSettings, useLayoutMode } from "@/core/system-settings/hooks";
|
import { useAppearanceSettings, useLayoutMode } from "@/core/system-settings/hooks";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { buildLightAppUrl } from "@/strategy-components/lib/light-app";
|
import { buildLightAppUrl } from "@/strategy-components/lib/light-app";
|
||||||
@@ -26,9 +27,10 @@ import { buildLightAppUrl } from "@/strategy-components/lib/light-app";
|
|||||||
*/
|
*/
|
||||||
export type AppearanceControlTone = "banner-dark" | "banner-light" | "content";
|
export type AppearanceControlTone = "banner-dark" | "banner-light" | "content";
|
||||||
|
|
||||||
// 换肤色板:红色=红白主题(light),蓝色=浅蓝主题(lightblue),深蓝色=深色主题(dark)
|
// 换肤色板:红色=红白主题(light),绿色=WeKnora 绿色主题(lightgreen),蓝色=浅蓝主题(lightblue),深蓝色=深色主题(dark)
|
||||||
export const THEME_SWATCHES: { id: ThemeType; label: string; color: string }[] = [
|
export const THEME_SWATCHES: { id: ThemeType; label: string; color: string }[] = [
|
||||||
{ id: "light", label: "红色", color: "#cf1322" },
|
{ id: "light", label: "红色", color: "#cf1322" },
|
||||||
|
{ id: "lightgreen", label: "绿色", color: "#07c05f" },
|
||||||
{ id: "lightblue", label: "蓝色", color: "#0062d9" },
|
{ id: "lightblue", label: "蓝色", color: "#0062d9" },
|
||||||
{ id: "dark", label: "深蓝色", color: "#002466" },
|
{ id: "dark", label: "深蓝色", color: "#002466" },
|
||||||
];
|
];
|
||||||
@@ -78,6 +80,8 @@ export function CompactShortcutButtons({ tone }: { tone: AppearanceControlTone }
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ThemeSwitcher({ tone }: { tone: AppearanceControlTone }) {
|
export function ThemeSwitcher({ tone }: { tone: AppearanceControlTone }) {
|
||||||
|
if (env.FORCE_COLLAPSED_SIDEBAR) return null;
|
||||||
|
|
||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
@@ -180,6 +184,8 @@ export function LayoutModeSwitcher({
|
|||||||
fallback?: "full" | "compact";
|
fallback?: "full" | "compact";
|
||||||
navigateTo?: string | null;
|
navigateTo?: string | null;
|
||||||
}) {
|
}) {
|
||||||
|
if (env.FORCE_COLLAPSED_SIDEBAR) return null;
|
||||||
|
|
||||||
const [mode, setLayoutMode] = useLayoutMode();
|
const [mode, setLayoutMode] = useLayoutMode();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const current = mode ?? fallback;
|
const current = mode ?? fallback;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import type { MouseEvent } from "react";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
|
|
||||||
import { AgentBrandIcon } from "./agents/agent-brand-icon";
|
import { AgentBrandIcon } from "./agents/agent-brand-icon";
|
||||||
import { RecentChatList } from "./recent-chat-list";
|
import { RecentChatList } from "./recent-chat-list";
|
||||||
@@ -57,12 +58,17 @@ const NAV_ITEMS: NavItem[] = [
|
|||||||
export function CmChatSidebar({
|
export function CmChatSidebar({
|
||||||
onCollapse,
|
onCollapse,
|
||||||
collapsed = false,
|
collapsed = false,
|
||||||
|
forceCollapsed = false,
|
||||||
}: {
|
}: {
|
||||||
onCollapse?: () => void;
|
onCollapse?: () => void;
|
||||||
collapsed?: boolean;
|
collapsed?: boolean;
|
||||||
|
/** Deployment-level compact navigation: retain only accessible menu icons. */
|
||||||
|
forceCollapsed?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const brandLogoSrc = theme === "light" ? "/cm-assistant-logo-red.png" : "/cm-assistant-logo.png";
|
||||||
|
|
||||||
const handleNewChat = (event: MouseEvent<HTMLAnchorElement>) => {
|
const handleNewChat = (event: MouseEvent<HTMLAnchorElement>) => {
|
||||||
if (event.metaKey || event.ctrlKey || event.shiftKey || event.button === 1) return;
|
if (event.metaKey || event.ctrlKey || event.shiftKey || event.button === 1) return;
|
||||||
@@ -73,22 +79,32 @@ export function CmChatSidebar({
|
|||||||
if (collapsed) {
|
if (collapsed) {
|
||||||
return (
|
return (
|
||||||
<aside className="flex h-full min-h-0 w-14 flex-col border-r border-slate-200/80 bg-white text-slate-700 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-200">
|
<aside className="flex h-full min-h-0 w-14 flex-col border-r border-slate-200/80 bg-white text-slate-700 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-200">
|
||||||
|
{!forceCollapsed && (
|
||||||
|
<>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onCollapse}
|
onClick={onCollapse}
|
||||||
aria-label="展开侧边栏"
|
aria-label="展开侧边栏"
|
||||||
title="展开侧边栏"
|
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
|
<img
|
||||||
src="/cm-assistant-logo.png"
|
src={brandLogoSrc}
|
||||||
alt="CM 助手"
|
alt="CM 助手"
|
||||||
className="size-10 object-contain"
|
className="size-10 object-contain"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<div className="mx-2 mt-2 h-px shrink-0 bg-slate-200/80 dark:bg-slate-800" />
|
<div className="mx-2 mt-2 h-px shrink-0 bg-slate-200/80 dark:bg-slate-800" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<nav className="flex shrink-0 flex-col items-center gap-1 px-2 py-3" aria-label="工作台导航">
|
<nav
|
||||||
|
className={cn(
|
||||||
|
"flex shrink-0 flex-col items-center gap-1 px-2 py-3",
|
||||||
|
forceCollapsed && "pt-4",
|
||||||
|
)}
|
||||||
|
aria-label="工作台导航"
|
||||||
|
>
|
||||||
{NAV_ITEMS.map((item) => {
|
{NAV_ITEMS.map((item) => {
|
||||||
const active = item.href === NEW_CHAT_PATH
|
const active = item.href === NEW_CHAT_PATH
|
||||||
? pathname === NEW_CHAT_PATH
|
? pathname === NEW_CHAT_PATH
|
||||||
@@ -105,9 +121,9 @@ export function CmChatSidebar({
|
|||||||
aria-label={item.label}
|
aria-label={item.label}
|
||||||
title={item.label}
|
title={item.label}
|
||||||
className={cn(
|
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
|
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",
|
: "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 +143,7 @@ export function CmChatSidebar({
|
|||||||
href="/page/workspace/settings"
|
href="/page/workspace/settings"
|
||||||
aria-label="设置和更多"
|
aria-label="设置和更多"
|
||||||
title="设置和更多"
|
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} />
|
<Settings className="size-[19px]" strokeWidth={1.8} />
|
||||||
</Link>
|
</Link>
|
||||||
@@ -138,27 +154,27 @@ export function CmChatSidebar({
|
|||||||
|
|
||||||
return (
|
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 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">
|
<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-3.5 outline-none focus-visible:ring-2 focus-visible:ring-blue-500/40">
|
<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
|
<img
|
||||||
src="/cm-assistant-logo.png"
|
src={brandLogoSrc}
|
||||||
alt="CM 助手"
|
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>
|
</Link>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onCollapse}
|
onClick={onCollapse}
|
||||||
aria-label="收起侧边栏"
|
aria-label="收起侧边栏"
|
||||||
title="收起侧边栏"
|
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} />
|
<PanelLeftClose className="size-[18px]" strokeWidth={1.8} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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">
|
<div className="space-y-1">
|
||||||
{NAV_ITEMS.map((item) => {
|
{NAV_ITEMS.map((item) => {
|
||||||
const active = item.href === NEW_CHAT_PATH
|
const active = item.href === NEW_CHAT_PATH
|
||||||
@@ -174,9 +190,9 @@ export function CmChatSidebar({
|
|||||||
onClick={item.href === NEW_CHAT_PATH ? handleNewChat : undefined}
|
onClick={item.href === NEW_CHAT_PATH ? handleNewChat : undefined}
|
||||||
aria-current={active ? "page" : undefined}
|
aria-current={active ? "page" : undefined}
|
||||||
className={cn(
|
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
|
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",
|
: "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}
|
title={prompt.content}
|
||||||
className={cn(
|
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",
|
"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",
|
"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(
|
className={cn(
|
||||||
"text-muted-foreground hover:text-foreground hover:bg-muted/70 h-7 w-7 shrink-0 rounded-full",
|
"text-muted-foreground hover:text-foreground hover:bg-muted/70 h-7 w-7 shrink-0 rounded-full",
|
||||||
compact &&
|
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="管理常用提示词"
|
title="管理常用提示词"
|
||||||
aria-label="管理常用提示词"
|
aria-label="管理常用提示词"
|
||||||
|
|||||||
@@ -637,7 +637,7 @@ export function InputBox({
|
|||||||
title="调整提示词顺序"
|
title="调整提示词顺序"
|
||||||
aria-label="调整提示词顺序"
|
aria-label="调整提示词顺序"
|
||||||
onClick={() => setCompactPromptOrder((current) => current + 1)}
|
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} />
|
<RefreshCwIcon className="size-3.5" strokeWidth={1.8} />
|
||||||
</button>
|
</button>
|
||||||
@@ -650,7 +650,7 @@ export function InputBox({
|
|||||||
type="button"
|
type="button"
|
||||||
data-quick-prompt={item.id}
|
data-quick-prompt={item.id}
|
||||||
title={item.content}
|
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={() => {
|
onClick={() => {
|
||||||
textInput.setInput(item.content);
|
textInput.setInput(item.content);
|
||||||
requestAnimationFrame(() =>
|
requestAnimationFrame(() =>
|
||||||
@@ -748,7 +748,7 @@ export function InputBox({
|
|||||||
: "*:data-[slot='input-group']:rounded-3xl",
|
: "*:data-[slot='input-group']:rounded-3xl",
|
||||||
// 紧凑落地态(CM 参考图):白卡 + 细边 + 轻阴影 + 主色聚焦环(无旋转彩边)
|
// 紧凑落地态(CM 参考图):白卡 + 细边 + 轻阴影 + 主色聚焦环(无旋转彩边)
|
||||||
compact && isNewThread
|
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 提供白卡 + 旋转描边
|
: // landing 态:透明,让外层 gemini-gradient-border 提供白卡 + 旋转描边
|
||||||
// 对话态:常规白卡 + 中性边框 + 轻阴影
|
// 对话态:常规白卡 + 中性边框 + 轻阴影
|
||||||
isNewThread
|
isNewThread
|
||||||
@@ -844,7 +844,7 @@ export function InputBox({
|
|||||||
"gap-1.5! rounded-full px-3! shadow-none",
|
"gap-1.5! rounded-full px-3! shadow-none",
|
||||||
compact && "rounded-lg px-2.5! py-1! text-[11px] font-medium",
|
compact && "rounded-lg px-2.5! py-1! text-[11px] font-medium",
|
||||||
memoryInjectionEnabled
|
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
|
: compact
|
||||||
? "border border-transparent bg-muted/40 text-muted-foreground hover:bg-muted hover:text-foreground"
|
? "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",
|
: "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}
|
onClick={handleMemoryInjectionToggle}
|
||||||
>
|
>
|
||||||
{memoryInjectionEnabled && (
|
{memoryInjectionEnabled && (
|
||||||
<CheckIcon className="size-3 text-blue-700 dark:text-cyan-50" />
|
<CheckIcon className="text-primary size-3" />
|
||||||
)}
|
)}
|
||||||
<BrainCircuitIcon className="size-3" />
|
<BrainCircuitIcon className="size-3" />
|
||||||
<span className="text-xs font-normal">记忆</span>
|
<span className="text-xs font-normal">记忆</span>
|
||||||
@@ -1303,9 +1303,9 @@ export function InputBox({
|
|||||||
</ModelSelector>
|
</ModelSelector>
|
||||||
<PromptInputSubmit
|
<PromptInputSubmit
|
||||||
className={cn(
|
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 &&
|
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}
|
disabled={disabled}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
ExternalLink,
|
ExternalLink,
|
||||||
LayoutGridIcon,
|
LayoutGridIcon,
|
||||||
LayoutIcon,
|
LayoutIcon,
|
||||||
|
LeafIcon,
|
||||||
ListIcon,
|
ListIcon,
|
||||||
MoonIcon,
|
MoonIcon,
|
||||||
PanelLeftIcon,
|
PanelLeftIcon,
|
||||||
@@ -70,7 +71,7 @@ const languageOptions: { value: Locale; label: string }[] = [
|
|||||||
{ value: "zh-CN", label: zhCN.locale.localName },
|
{ value: "zh-CN", label: zhCN.locale.localName },
|
||||||
];
|
];
|
||||||
|
|
||||||
const DEFAULT_SHORTCUT_THEME_MAP = "red=red;blue=blue;dark-blue=dark-blue";
|
const DEFAULT_SHORTCUT_THEME_MAP = "red=red;green=green;blue=blue;dark-blue=dark-blue";
|
||||||
|
|
||||||
function makeShortcutId() {
|
function makeShortcutId() {
|
||||||
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
||||||
@@ -212,13 +213,19 @@ export function AppearanceSettingsPage({ onClose }: { onClose?: () => void }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSetGlobalDefault = async (
|
const handleSetGlobalDefault = async (
|
||||||
value: "light" | "dark" | "lightblue",
|
value: "light" | "dark" | "lightblue" | "lightgreen",
|
||||||
) => {
|
) => {
|
||||||
if (value === globalDefault) return;
|
if (value === globalDefault) return;
|
||||||
try {
|
try {
|
||||||
await updateAppearance.mutateAsync({ default_theme: value });
|
await updateAppearance.mutateAsync({ default_theme: value });
|
||||||
const label =
|
const label =
|
||||||
value === "light" ? "浅色" : value === "dark" ? "深色" : "浅蓝";
|
value === "light"
|
||||||
|
? "浅色"
|
||||||
|
: value === "dark"
|
||||||
|
? "深色"
|
||||||
|
: value === "lightgreen"
|
||||||
|
? "绿色"
|
||||||
|
: "浅蓝";
|
||||||
toast.success(`已将系统默认主题设置为${label}`);
|
toast.success(`已将系统默认主题设置为${label}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(e instanceof Error ? e.message : "保存失败");
|
toast.error(e instanceof Error ? e.message : "保存失败");
|
||||||
@@ -374,6 +381,12 @@ export function AppearanceSettingsPage({ onClose }: { onClose?: () => void }) {
|
|||||||
description: t.settings.appearance.lightblueDescription,
|
description: t.settings.appearance.lightblueDescription,
|
||||||
icon: DropletIcon,
|
icon: DropletIcon,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "lightgreen",
|
||||||
|
label: t.settings.appearance.lightgreen,
|
||||||
|
description: t.settings.appearance.lightgreenDescription,
|
||||||
|
icon: LeafIcon,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
t.settings.appearance.dark,
|
t.settings.appearance.dark,
|
||||||
@@ -382,6 +395,8 @@ export function AppearanceSettingsPage({ onClose }: { onClose?: () => void }) {
|
|||||||
t.settings.appearance.lightDescription,
|
t.settings.appearance.lightDescription,
|
||||||
t.settings.appearance.lightblue,
|
t.settings.appearance.lightblue,
|
||||||
t.settings.appearance.lightblueDescription,
|
t.settings.appearance.lightblueDescription,
|
||||||
|
t.settings.appearance.lightgreen,
|
||||||
|
t.settings.appearance.lightgreenDescription,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -399,7 +414,7 @@ export function AppearanceSettingsPage({ onClose }: { onClose?: () => void }) {
|
|||||||
label={option.label}
|
label={option.label}
|
||||||
description={option.description}
|
description={option.description}
|
||||||
active={currentTheme === option.id}
|
active={currentTheme === option.id}
|
||||||
mode={option.id as "light" | "dark" | "lightblue"}
|
mode={option.id as "light" | "dark" | "lightblue" | "lightgreen"}
|
||||||
onSelect={(value) => setTheme(value)}
|
onSelect={(value) => setTheme(value)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -551,12 +566,24 @@ export function AppearanceSettingsPage({ onClose }: { onClose?: () => void }) {
|
|||||||
<DropletIcon className="mr-1.5 size-4" />
|
<DropletIcon className="mr-1.5 size-4" />
|
||||||
浅蓝
|
浅蓝
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant={globalDefault === "lightgreen" ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => void handleSetGlobalDefault("lightgreen")}
|
||||||
|
disabled={updateAppearance.isPending}
|
||||||
|
>
|
||||||
|
<LeafIcon className="mr-1.5 size-4" />
|
||||||
|
绿色
|
||||||
|
</Button>
|
||||||
<p className="text-muted-foreground ml-2 text-xs">
|
<p className="text-muted-foreground ml-2 text-xs">
|
||||||
当前系统默认:
|
当前系统默认:
|
||||||
{globalDefault === "light"
|
{globalDefault === "light"
|
||||||
? "浅色"
|
? "浅色"
|
||||||
: globalDefault === "dark"
|
: globalDefault === "dark"
|
||||||
? "深色"
|
? "深色"
|
||||||
|
: globalDefault === "lightgreen"
|
||||||
|
? "绿色"
|
||||||
: "浅蓝"}
|
: "浅蓝"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -1074,8 +1101,8 @@ function ThemePreviewCard({
|
|||||||
label: string;
|
label: string;
|
||||||
description: string;
|
description: string;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
mode: "light" | "dark" | "lightblue";
|
mode: "light" | "dark" | "lightblue" | "lightgreen";
|
||||||
onSelect: (mode: "light" | "dark" | "lightblue") => void;
|
onSelect: (mode: "light" | "dark" | "lightblue" | "lightgreen") => void;
|
||||||
}) {
|
}) {
|
||||||
const previewMode = mode;
|
const previewMode = mode;
|
||||||
return (
|
return (
|
||||||
@@ -1107,6 +1134,8 @@ function ThemePreviewCard({
|
|||||||
? "border-neutral-800 bg-neutral-900 text-neutral-200"
|
? "border-neutral-800 bg-neutral-900 text-neutral-200"
|
||||||
: previewMode === "lightblue"
|
: previewMode === "lightblue"
|
||||||
? "border-blue-200 bg-white text-slate-900"
|
? "border-blue-200 bg-white text-slate-900"
|
||||||
|
: previewMode === "lightgreen"
|
||||||
|
? "border-emerald-200 bg-white text-emerald-950"
|
||||||
: "border-slate-200 bg-white text-slate-900",
|
: "border-slate-200 bg-white text-slate-900",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -1118,7 +1147,9 @@ function ThemePreviewCard({
|
|||||||
? "bg-emerald-400"
|
? "bg-emerald-400"
|
||||||
: previewMode === "lightblue"
|
: previewMode === "lightblue"
|
||||||
? "bg-blue-500"
|
? "bg-blue-500"
|
||||||
: "bg-emerald-500",
|
: previewMode === "lightgreen"
|
||||||
|
? "bg-[#07c05f]"
|
||||||
|
: "bg-[#cf1322]",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="h-2 w-10 rounded-full bg-current/20" />
|
<div className="h-2 w-10 rounded-full bg-current/20" />
|
||||||
|
|||||||
@@ -762,7 +762,7 @@ export function SkillRow({
|
|||||||
{isFromOthers && (
|
{isFromOthers && (
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
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={
|
title={
|
||||||
skill.owner_user_id
|
skill.owner_user_id
|
||||||
? t.settings.skills.badgeFromOthersTitle.replace(
|
? t.settings.skills.badgeFromOthersTitle.replace(
|
||||||
@@ -1027,7 +1027,7 @@ export function SkillRow({
|
|||||||
: skill.source === "agent"
|
: skill.source === "agent"
|
||||||
? {
|
? {
|
||||||
Icon: WorkflowIcon,
|
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,
|
Icon: Upload,
|
||||||
@@ -1044,7 +1044,7 @@ export function SkillRow({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isCard ? (
|
{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
|
<div
|
||||||
className="min-w-0 flex-1 cursor-pointer"
|
className="min-w-0 flex-1 cursor-pointer"
|
||||||
onClick={() => setDetailOpen(true)}
|
onClick={() => setDetailOpen(true)}
|
||||||
@@ -1075,10 +1075,10 @@ export function SkillRow({
|
|||||||
</div>
|
</div>
|
||||||
</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="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.65fr)_minmax(150px,0.72fr)_minmax(100px,0.42fr)_minmax(132px,0.58fr)_auto] xl:items-center xl:gap-x-6">
|
<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-4" onClick={() => setDetailOpen(true)}>
|
<div className="flex min-w-0 cursor-pointer items-start gap-3.5" onClick={() => setDetailOpen(true)}>
|
||||||
<div className={cn("flex size-[60px] shrink-0 items-center justify-center rounded-full", iconStyle.className)}>
|
<div className={cn("flex size-14 shrink-0 items-center justify-center rounded-2xl", iconStyle.className)}>
|
||||||
<SkillIcon className="size-7" />
|
<SkillIcon className="size-7" />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1 pt-0.5">
|
<div className="min-w-0 flex-1 pt-0.5">
|
||||||
@@ -1097,7 +1097,7 @@ export function SkillRow({
|
|||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
"mt-2 flex max-w-full items-center gap-1 text-xs text-slate-400",
|
"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" />
|
<UserIcon className="size-3.5 shrink-0" />
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export function UserIdentityBadge({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"bg-muted text-muted-foreground relative flex size-7 shrink-0 items-center justify-center rounded-full",
|
"bg-muted text-muted-foreground relative flex size-7 shrink-0 items-center justify-center rounded-full",
|
||||||
variant === "topbar" &&
|
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")} />
|
<UserRound className={cn("size-3.5", variant === "topbar" && "size-4")} />
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
} from "@/components/ui/breadcrumb";
|
} from "@/components/ui/breadcrumb";
|
||||||
import { useI18n } from "@/core/i18n/hooks";
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
|
import { env } from "@/env";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { ThemeSwitcher } from "@/components/workspace/appearance-controls";
|
import { ThemeSwitcher } from "@/components/workspace/appearance-controls";
|
||||||
import { UserIdentityBadge } from "@/components/workspace/user-identity-badge";
|
import { UserIdentityBadge } from "@/components/workspace/user-identity-badge";
|
||||||
@@ -97,11 +98,15 @@ export function WorkspaceHeader({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 px-4">
|
<div className="flex items-center gap-2 px-4">
|
||||||
{rightActions}
|
{rightActions}
|
||||||
|
{!env.FORCE_COLLAPSED_SIDEBAR && (
|
||||||
|
<>
|
||||||
<ThemeSwitcher tone="content" />
|
<ThemeSwitcher tone="content" />
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="mx-1 h-6 w-px bg-slate-200 dark:bg-slate-700"
|
className="mx-1 h-6 w-px bg-slate-200 dark:bg-slate-700"
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<UserIdentityBadge variant="topbar" />
|
<UserIdentityBadge variant="topbar" />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -12,8 +12,12 @@ import { CmChatSidebar } from "./cm-chat-sidebar";
|
|||||||
export function WorkspaceSidebar({
|
export function WorkspaceSidebar({
|
||||||
className,
|
className,
|
||||||
compactChrome: _compactChrome,
|
compactChrome: _compactChrome,
|
||||||
|
forceCollapsed = false,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Sidebar> & { compactChrome?: boolean }) {
|
}: React.ComponentProps<typeof Sidebar> & {
|
||||||
|
compactChrome?: boolean;
|
||||||
|
forceCollapsed?: boolean;
|
||||||
|
}) {
|
||||||
const { open: isSidebarOpen, toggleSidebar } = useSidebar();
|
const { open: isSidebarOpen, toggleSidebar } = useSidebar();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -36,7 +40,11 @@ export function WorkspaceSidebar({
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
<CmChatSidebar onCollapse={toggleSidebar} collapsed={!isSidebarOpen} />
|
<CmChatSidebar
|
||||||
|
onCollapse={forceCollapsed ? undefined : toggleSidebar}
|
||||||
|
collapsed={!isSidebarOpen}
|
||||||
|
forceCollapsed={forceCollapsed}
|
||||||
|
/>
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { useTheme as useNextTheme } from 'next-themes';
|
|||||||
import { useAppearanceSettings } from '@/core/system-settings/hooks';
|
import { useAppearanceSettings } from '@/core/system-settings/hooks';
|
||||||
|
|
||||||
// 主题类型
|
// 主题类型
|
||||||
export type ThemeType = 'dark' | 'light' | 'lightblue';
|
export type ThemeType = 'dark' | 'light' | 'lightblue' | 'lightgreen';
|
||||||
|
|
||||||
// 主题配置
|
// 主题配置
|
||||||
export interface ThemeConfig {
|
export interface ThemeConfig {
|
||||||
@@ -104,6 +104,28 @@ const themes: Record<ThemeType, ThemeConfig> = {
|
|||||||
overlay: 'black/30',
|
overlay: 'black/30',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
lightgreen: {
|
||||||
|
type: 'lightgreen',
|
||||||
|
name: '绿色主题',
|
||||||
|
colors: {
|
||||||
|
background: 'white',
|
||||||
|
backgroundSecondary: 'emerald-50',
|
||||||
|
text: 'emerald-950',
|
||||||
|
textSecondary: 'emerald-800',
|
||||||
|
primary: 'emerald-600',
|
||||||
|
primaryHover: 'emerald-500',
|
||||||
|
border: 'emerald-200',
|
||||||
|
accent: 'emerald-700',
|
||||||
|
success: 'emerald-600',
|
||||||
|
warning: 'amber-500',
|
||||||
|
error: 'red-600',
|
||||||
|
card: 'white',
|
||||||
|
cardHover: 'emerald-50',
|
||||||
|
input: 'white',
|
||||||
|
inputBorder: 'emerald-200',
|
||||||
|
overlay: 'black/30',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const STORAGE_KEY = 'strategy-theme';
|
const STORAGE_KEY = 'strategy-theme';
|
||||||
@@ -121,6 +143,7 @@ function readPersistedTheme(): ThemeType {
|
|||||||
const saved = localStorage.getItem(STORAGE_KEY);
|
const saved = localStorage.getItem(STORAGE_KEY);
|
||||||
if (saved === 'dark') return 'dark';
|
if (saved === 'dark') return 'dark';
|
||||||
if (saved === 'lightblue') return 'lightblue';
|
if (saved === 'lightblue') return 'lightblue';
|
||||||
|
if (saved === 'lightgreen') return 'lightgreen';
|
||||||
return 'light';
|
return 'light';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +202,8 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {
|
|||||||
current &&
|
current &&
|
||||||
current !== 'light' &&
|
current !== 'light' &&
|
||||||
current !== 'dark' &&
|
current !== 'dark' &&
|
||||||
current !== 'lightblue'
|
current !== 'lightblue' &&
|
||||||
|
current !== 'lightgreen'
|
||||||
) {
|
) {
|
||||||
// Garbage value — wipe it so the boot-sync below can take over.
|
// Garbage value — wipe it so the boot-sync below can take over.
|
||||||
localStorage.removeItem(STORAGE_KEY);
|
localStorage.removeItem(STORAGE_KEY);
|
||||||
@@ -216,6 +240,8 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {
|
|||||||
? 'dark'
|
? 'dark'
|
||||||
: effectiveTheme === 'lightblue'
|
: effectiveTheme === 'lightblue'
|
||||||
? 'lightblue'
|
? 'lightblue'
|
||||||
|
: effectiveTheme === 'lightgreen'
|
||||||
|
? 'lightgreen'
|
||||||
: 'light'
|
: 'light'
|
||||||
: readPersistedTheme();
|
: readPersistedTheme();
|
||||||
|
|
||||||
@@ -234,8 +260,8 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {
|
|||||||
root.removeAttribute('theme-mode');
|
root.removeAttribute('theme-mode');
|
||||||
root.classList.remove('dark');
|
root.classList.remove('dark');
|
||||||
|
|
||||||
if (theme === 'lightblue') {
|
if (theme === 'lightblue' || theme === 'lightgreen') {
|
||||||
root.setAttribute('data-theme', 'lightblue');
|
root.setAttribute('data-theme', theme);
|
||||||
} else {
|
} else {
|
||||||
root.removeAttribute('data-theme');
|
root.removeAttribute('data-theme');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { authStorageGet, authStorageSet } from "@/core/auth/scoped-storage";
|
import { authStorageGet, authStorageSet } from "@/core/auth/scoped-storage";
|
||||||
|
import type { ThemeType } from "@/contexts/ThemeContext";
|
||||||
|
|
||||||
export const EXTERNAL_LOGIN_STORAGE_KEYS = {
|
export const EXTERNAL_LOGIN_STORAGE_KEYS = {
|
||||||
ticket: "login.ticket",
|
ticket: "login.ticket",
|
||||||
@@ -58,6 +59,21 @@ export function readExternalLoginParams(): Required<ExternalLoginParams> {
|
|||||||
* - ``blue`` → 暂无该主题,先用浅色代替(命中 else 分支)
|
* - ``blue`` → 暂无该主题,先用浅色代替(命中 else 分支)
|
||||||
* 其余/缺省同样回退浅色。将来若新增 blue 主题,在此扩展映射即可。
|
* 其余/缺省同样回退浅色。将来若新增 blue 主题,在此扩展映射即可。
|
||||||
*/
|
*/
|
||||||
export function resolveThemeFromQuery(themeParam: string | null | undefined): "dark" | "light" {
|
export function resolveThemeFromQuery(themeParam: string | null | undefined): ThemeType {
|
||||||
return themeParam === "dark-blue" ? "dark" : "light";
|
switch (themeParam?.trim().toLowerCase()) {
|
||||||
|
case "dark":
|
||||||
|
case "dark-blue":
|
||||||
|
case "darkblue":
|
||||||
|
return "dark";
|
||||||
|
case "blue":
|
||||||
|
case "lightblue":
|
||||||
|
case "light-blue":
|
||||||
|
return "lightblue";
|
||||||
|
case "green":
|
||||||
|
case "lightgreen":
|
||||||
|
case "light-green":
|
||||||
|
return "lightgreen";
|
||||||
|
default:
|
||||||
|
return "light";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
|
import type { ThemeType } from "@/contexts/ThemeContext";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 公开页(被第三方 iframe 嵌套)的可配置外观:背景色 + 整体明暗主题。
|
* 公开页(被第三方 iframe 嵌套)的可配置外观:背景色 + 整体明暗主题。
|
||||||
@@ -63,19 +64,25 @@ function readFromSearch(search: string): PublicBackground {
|
|||||||
*/
|
*/
|
||||||
export function normalizePublicTheme(
|
export function normalizePublicTheme(
|
||||||
raw: string | null | undefined,
|
raw: string | null | undefined,
|
||||||
): "dark" | "light" | undefined {
|
): ThemeType | undefined {
|
||||||
const value = raw?.trim().toLowerCase();
|
const value = raw?.trim().toLowerCase();
|
||||||
if (!value) return undefined;
|
if (!value) return undefined;
|
||||||
if (value === "dark" || value === "dark-blue" || value === "深蓝" || value === "深色") {
|
if (value === "dark" || value === "dark-blue" || value === "深蓝" || value === "深色") {
|
||||||
return "dark";
|
return "dark";
|
||||||
}
|
}
|
||||||
|
if (value === "blue" || value === "lightblue" || value === "light-blue" || value === "浅蓝") {
|
||||||
|
return "lightblue";
|
||||||
|
}
|
||||||
|
if (value === "green" || value === "lightgreen" || value === "light-green" || value === "绿色") {
|
||||||
|
return "lightgreen";
|
||||||
|
}
|
||||||
if (value === "light" || value === "red" || value === "红白" || value === "浅色") {
|
if (value === "light" || value === "red" || value === "红白" || value === "浅色") {
|
||||||
return "light";
|
return "light";
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readThemeFromSearch(search: string): "dark" | "light" | undefined {
|
function readThemeFromSearch(search: string): ThemeType | undefined {
|
||||||
const params = new URLSearchParams(search.startsWith("?") ? search : `?${search}`);
|
const params = new URLSearchParams(search.startsWith("?") ? search : `?${search}`);
|
||||||
return normalizePublicTheme(params.get("theme"));
|
return normalizePublicTheme(params.get("theme"));
|
||||||
}
|
}
|
||||||
@@ -86,7 +93,7 @@ function readThemeFromSearch(search: string): "dark" | "light" | undefined {
|
|||||||
* 与 `usePublicBackground` 同样合并两处查询串(hash 路由内优先),
|
* 与 `usePublicBackground` 同样合并两处查询串(hash 路由内优先),
|
||||||
* postMessage 的动态值优先级最高。返回 undefined 表示外部未指定。
|
* postMessage 的动态值优先级最高。返回 undefined 表示外部未指定。
|
||||||
*/
|
*/
|
||||||
export function usePublicEmbedTheme(): "dark" | "light" | undefined {
|
export function usePublicEmbedTheme(): ThemeType | undefined {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
const fromUrl = useMemo(() => {
|
const fromUrl = useMemo(() => {
|
||||||
@@ -97,7 +104,7 @@ export function usePublicEmbedTheme(): "dark" | "light" | undefined {
|
|||||||
return inner ?? outer;
|
return inner ?? outer;
|
||||||
}, [location.search]);
|
}, [location.search]);
|
||||||
|
|
||||||
const [override, setOverride] = useState<"dark" | "light" | undefined>(undefined);
|
const [override, setOverride] = useState<ThemeType | undefined>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function onMessage(event: MessageEvent) {
|
function onMessage(event: MessageEvent) {
|
||||||
|
|||||||
@@ -27,9 +27,11 @@
|
|||||||
* 正常页标签页里既无这些参数也无这份会话 → 所有 `isXxx()` 恒为 false,完全不受影响。
|
* 正常页标签页里既无这些参数也无这份会话 → 所有 `isXxx()` 恒为 false,完全不受影响。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { ThemeType } from "@/contexts/ThemeContext";
|
||||||
|
|
||||||
const EMBED_SESSION_KEY = "embed.session";
|
const EMBED_SESSION_KEY = "embed.session";
|
||||||
|
|
||||||
export type EmbedTheme = "light" | "dark";
|
export type EmbedTheme = ThemeType;
|
||||||
|
|
||||||
/** 本标签页的嵌入会话(按需置位的若干独立开关)。 */
|
/** 本标签页的嵌入会话(按需置位的若干独立开关)。 */
|
||||||
export interface EmbedSession {
|
export interface EmbedSession {
|
||||||
@@ -92,6 +94,8 @@ export function readIsolateParam(search: string): FlagParam {
|
|||||||
export function resolveEmbedThemeMode(raw: string | null | undefined): EmbedTheme {
|
export function resolveEmbedThemeMode(raw: string | null | undefined): EmbedTheme {
|
||||||
const v = (raw ?? "").trim().toLowerCase();
|
const v = (raw ?? "").trim().toLowerCase();
|
||||||
if (v === "dark" || v === "dark-blue" || v === "深色" || v === "深蓝") return "dark";
|
if (v === "dark" || v === "dark-blue" || v === "深色" || v === "深蓝") return "dark";
|
||||||
|
if (v === "blue" || v === "lightblue" || v === "light-blue" || v === "浅蓝") return "lightblue";
|
||||||
|
if (v === "green" || v === "lightgreen" || v === "light-green" || v === "绿色") return "lightgreen";
|
||||||
return "light";
|
return "light";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +112,10 @@ export function readEmbedSession(): EmbedSession | null {
|
|||||||
const p = JSON.parse(raw) as Partial<EmbedSession>;
|
const p = JSON.parse(raw) as Partial<EmbedSession>;
|
||||||
return {
|
return {
|
||||||
embed: p.embed === true,
|
embed: p.embed === true,
|
||||||
theme: p.theme === "dark" ? "dark" : "light",
|
theme:
|
||||||
|
p.theme === "dark" || p.theme === "lightblue" || p.theme === "lightgreen"
|
||||||
|
? p.theme
|
||||||
|
: "light",
|
||||||
hideJump: p.hideJump === true,
|
hideJump: p.hideJump === true,
|
||||||
hideChrome: p.hideChrome === true,
|
hideChrome: p.hideChrome === true,
|
||||||
isolate: p.isolate === true,
|
isolate: p.isolate === true,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { resolveThemeFromQuery } from "@/core/auth/external-login";
|
import { resolveThemeFromQuery } from "@/core/auth/external-login";
|
||||||
|
import type { ThemeType } from "@/contexts/ThemeContext";
|
||||||
|
|
||||||
const DEFAULT_USERNAME = "guest";
|
const DEFAULT_USERNAME = "guest";
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ export function resolveEmbedChatParams(search: string): EmbedChatParams {
|
|||||||
return { sessionId, message, username, theme };
|
return { sessionId, message, username, theme };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveEmbedTheme(themeParam: string): "dark" | "light" {
|
export function resolveEmbedTheme(themeParam: string): ThemeType {
|
||||||
return resolveThemeFromQuery(themeParam || null);
|
return resolveThemeFromQuery(themeParam || null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,8 +51,10 @@ export function resolveEmbedTheme(themeParam: string): "dark" | "light" {
|
|||||||
* 只有明确传 light/红白 才走浅色,其余(dark / dark-blue / 深蓝 / 空)一律深蓝。
|
* 只有明确传 light/红白 才走浅色,其余(dark / dark-blue / 深蓝 / 空)一律深蓝。
|
||||||
* 不复用 `resolveEmbedTheme`(它只认严格的 "dark-blue",传 "dark" 会被判成浅色)。
|
* 不复用 `resolveEmbedTheme`(它只认严格的 "dark-blue",传 "dark" 会被判成浅色)。
|
||||||
*/
|
*/
|
||||||
export function resolveEmbedRoundtableTheme(raw: string): "dark" | "light" {
|
export function resolveEmbedRoundtableTheme(raw: string): ThemeType {
|
||||||
const v = raw.trim().toLowerCase();
|
const v = raw.trim().toLowerCase();
|
||||||
if (v === "light" || v === "red" || v === "红白") return "light";
|
if (v === "light" || v === "red" || v === "红白") return "light";
|
||||||
|
if (v === "blue" || v === "lightblue" || v === "light-blue" || v === "浅蓝") return "lightblue";
|
||||||
|
if (v === "green" || v === "lightgreen" || v === "light-green" || v === "绿色") return "lightgreen";
|
||||||
return "dark";
|
return "dark";
|
||||||
}
|
}
|
||||||
@@ -449,10 +449,12 @@ export const enUS: Translations = {
|
|||||||
light: "Light",
|
light: "Light",
|
||||||
dark: "Dark",
|
dark: "Dark",
|
||||||
lightblue: "Light Blue",
|
lightblue: "Light Blue",
|
||||||
|
lightgreen: "Green",
|
||||||
systemDescription: "Match the operating system preference automatically.",
|
systemDescription: "Match the operating system preference automatically.",
|
||||||
lightDescription: "Bright palette with higher contrast for daytime.",
|
lightDescription: "Bright palette with higher contrast for daytime.",
|
||||||
darkDescription: "Dim palette that reduces glare for focus.",
|
darkDescription: "Dim palette that reduces glare for focus.",
|
||||||
lightblueDescription: "A crisp light palette built around blue.",
|
lightblueDescription: "A crisp light palette built around blue.",
|
||||||
|
lightgreenDescription: "A crisp light palette built around WeKnora green.",
|
||||||
languageTitle: "Language",
|
languageTitle: "Language",
|
||||||
languageDescription: "Switch between languages.",
|
languageDescription: "Switch between languages.",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -384,10 +384,12 @@ export interface Translations {
|
|||||||
light: string;
|
light: string;
|
||||||
dark: string;
|
dark: string;
|
||||||
lightblue: string;
|
lightblue: string;
|
||||||
|
lightgreen: string;
|
||||||
systemDescription: string;
|
systemDescription: string;
|
||||||
lightDescription: string;
|
lightDescription: string;
|
||||||
darkDescription: string;
|
darkDescription: string;
|
||||||
lightblueDescription: string;
|
lightblueDescription: string;
|
||||||
|
lightgreenDescription: string;
|
||||||
languageTitle: string;
|
languageTitle: string;
|
||||||
languageDescription: string;
|
languageDescription: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -430,10 +430,12 @@ export const zhCN: Translations = {
|
|||||||
light: "浅色",
|
light: "浅色",
|
||||||
dark: "深色",
|
dark: "深色",
|
||||||
lightblue: "浅蓝",
|
lightblue: "浅蓝",
|
||||||
|
lightgreen: "绿色",
|
||||||
systemDescription: "自动跟随系统主题。",
|
systemDescription: "自动跟随系统主题。",
|
||||||
lightDescription: "更明亮的配色,适合日间使用。",
|
lightDescription: "更明亮的配色,适合日间使用。",
|
||||||
darkDescription: "更暗的配色,减少眩光方便专注。",
|
darkDescription: "更暗的配色,减少眩光方便专注。",
|
||||||
lightblueDescription: "以蓝色为主色的清爽浅色配色。",
|
lightblueDescription: "以蓝色为主色的清爽浅色配色。",
|
||||||
|
lightgreenDescription: "采用 WeKnora 绿色的清爽浅色配色。",
|
||||||
languageTitle: "语言",
|
languageTitle: "语言",
|
||||||
languageDescription: "在不同语言之间切换。",
|
languageDescription: "在不同语言之间切换。",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ export async function updatePublishSquares(
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ThemeMode = "light" | "dark" | "lightblue";
|
export type ThemeMode = "light" | "dark" | "lightblue" | "lightgreen";
|
||||||
export type ViewMode = "card" | "list";
|
export type ViewMode = "card" | "list";
|
||||||
export type LayoutMode = "full" | "compact";
|
export type LayoutMode = "full" | "compact";
|
||||||
export type CompactShortcutParamType = "login" | "theme";
|
export type CompactShortcutParamType = "login" | "theme";
|
||||||
|
|||||||
@@ -29,5 +29,11 @@ export const env = {
|
|||||||
NEXT_PUBLIC_NOTE_API_BASE_URL: readRuntimeEnv("VITE_NOTE_API_BASE_URL"),
|
NEXT_PUBLIC_NOTE_API_BASE_URL: readRuntimeEnv("VITE_NOTE_API_BASE_URL"),
|
||||||
NEXT_PUBLIC_NOTEBOOK_API_BASE_URL: readRuntimeEnv("VITE_AIPI_BACKENP_URL"),
|
NEXT_PUBLIC_NOTEBOOK_API_BASE_URL: readRuntimeEnv("VITE_AIPI_BACKENP_URL"),
|
||||||
NEXT_PUBLIC_STATIC_WEBSITE_ONLY: readRuntimeEnv("VITE_STATIC_WEBSITE_ONLY"),
|
NEXT_PUBLIC_STATIC_WEBSITE_ONLY: readRuntimeEnv("VITE_STATIC_WEBSITE_ONLY"),
|
||||||
|
/**
|
||||||
|
* Deployment-level compact navigation mode. This is intentionally a runtime
|
||||||
|
* flag so an operations deployment can switch the shell without rebuilding
|
||||||
|
* the frontend bundle.
|
||||||
|
*/
|
||||||
|
FORCE_COLLAPSED_SIDEBAR: readRuntimeFlag("VITE_FORCE_COLLAPSED_SIDEBAR"),
|
||||||
GITHUB_OAUTH_TOKEN: readRuntimeEnv("VITE_GITHUB_OAUTH_TOKEN"),
|
GITHUB_OAUTH_TOKEN: readRuntimeEnv("VITE_GITHUB_OAUTH_TOKEN"),
|
||||||
};
|
};
|
||||||
@@ -488,12 +488,16 @@ export default function ChatPage() {
|
|||||||
{/* 简洁模式无全局顶栏:把换肤 + 界面模式切换搬到页面级顶栏右上角,
|
{/* 简洁模式无全局顶栏:把换肤 + 界面模式切换搬到页面级顶栏右上角,
|
||||||
与全量模式 Header 右上角保持一致(content 配色贴浅底顶栏)。 */}
|
与全量模式 Header 右上角保持一致(content 配色贴浅底顶栏)。 */}
|
||||||
{compactChrome ? (
|
{compactChrome ? (
|
||||||
|
<>
|
||||||
|
{!env.FORCE_COLLAPSED_SIDEBAR && (
|
||||||
<>
|
<>
|
||||||
<ThemeSwitcher tone="content" />
|
<ThemeSwitcher tone="content" />
|
||||||
<div
|
<div
|
||||||
className="bg-border/70 mx-1 h-6 w-px shrink-0"
|
className="bg-border/70 mx-1 h-6 w-px shrink-0"
|
||||||
aria-hidden
|
aria-hidden
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<UserIdentityBadge variant="topbar" />
|
<UserIdentityBadge variant="topbar" />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -19,9 +19,15 @@ export default function LightAppIframeView() {
|
|||||||
|
|
||||||
// 由 useTheme 的 theme 直接换算主题色并显式传入,切主题时 src 立即重建、
|
// 由 useTheme 的 theme 直接换算主题色并显式传入,切主题时 src 立即重建、
|
||||||
// 把新主题色传给被嵌套应用(不依赖 <html> 的 dark class,避免时序读旧值)。
|
// 把新主题色传给被嵌套应用(不依赖 <html> 的 dark class,避免时序读旧值)。
|
||||||
// 深色=dark-blue,浅蓝=blue,其余(红白)=red。
|
// 深色=dark-blue,绿色=green,浅蓝=blue,其余(红白)=red。
|
||||||
const themeColor: AppThemeColor =
|
const themeColor: AppThemeColor =
|
||||||
theme === "dark" ? "dark-blue" : theme === "lightblue" ? "blue" : "red";
|
theme === "dark"
|
||||||
|
? "dark-blue"
|
||||||
|
: theme === "lightgreen"
|
||||||
|
? "green"
|
||||||
|
: theme === "lightblue"
|
||||||
|
? "blue"
|
||||||
|
: "red";
|
||||||
const src = useMemo(
|
const src = useMemo(
|
||||||
() => (app ? buildLightAppUrl(app, undefined, themeColor) : ""),
|
() => (app ? buildLightAppUrl(app, undefined, themeColor) : ""),
|
||||||
[app, themeColor],
|
[app, themeColor],
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import { toast } from "sonner";
|
|||||||
|
|
||||||
import { Pagination } from "@/components/ui/pagination";
|
import { Pagination } from "@/components/ui/pagination";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme, type ThemeType } from "@/contexts/ThemeContext";
|
||||||
import { useLightApps, type LightApp } from "@/strategy-components/api/light-apps";
|
import { useLightApps, type LightApp } from "@/strategy-components/api/light-apps";
|
||||||
import { buildLightAppUrl } from "@/strategy-components/lib/light-app";
|
import { buildLightAppUrl } from "@/strategy-components/lib/light-app";
|
||||||
|
|
||||||
@@ -64,12 +64,46 @@ function hashIndex(str: string, mod: number): number {
|
|||||||
return mod > 0 ? h % mod : 0;
|
return mod > 0 ? h % mod : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLightAccent(theme: ThemeType) {
|
||||||
|
if (theme === "lightgreen") {
|
||||||
|
return {
|
||||||
|
dot: "bg-[#07C05F]",
|
||||||
|
badge: "border-[#07C05F]/15 bg-[#E9F8EC] text-[#049B38]",
|
||||||
|
focus: "focus:border-[#07C05F]/60 focus:ring-[#07C05F]/20",
|
||||||
|
text: "text-[#049B38]",
|
||||||
|
hoverText: "group-hover:text-[#049B38]",
|
||||||
|
link: "text-[#049B38] hover:text-[#038626]",
|
||||||
|
button: "bg-[#07C05F] shadow-[0_1px_3px_rgba(7,192,95,0.2)] hover:bg-[#08DD6E]",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (theme === "lightblue") {
|
||||||
|
return {
|
||||||
|
dot: "bg-[#0062D9]",
|
||||||
|
badge: "border-[#0062D9]/15 bg-[#EAF4FF] text-[#0062D9]",
|
||||||
|
focus: "focus:border-[#0062D9]/60 focus:ring-[#0062D9]/20",
|
||||||
|
text: "text-[#0062D9]",
|
||||||
|
hoverText: "group-hover:text-[#0062D9]",
|
||||||
|
link: "text-[#0062D9] hover:text-[#0056C6]",
|
||||||
|
button: "bg-[#0062D9] shadow-[0_1px_3px_rgba(0,98,217,0.2)] hover:bg-[#0079FE]",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
dot: "bg-[#CF1322]",
|
||||||
|
badge: "border-[#CF1322]/15 bg-[#FFF1F0] text-[#CF1322]",
|
||||||
|
focus: "focus:border-[#CF1322]/60 focus:ring-[#CF1322]/20",
|
||||||
|
text: "text-[#CF1322]",
|
||||||
|
hoverText: "group-hover:text-[#CF1322]",
|
||||||
|
link: "text-[#CF1322] hover:text-[#A8071A]",
|
||||||
|
button: "bg-[#CF1322] shadow-[0_1px_3px_rgba(207,19,34,0.2)] hover:bg-[#A8071A]",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default function LightAppListPage() {
|
export default function LightAppListPage() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [copiedId, setCopiedId] = useState<string | null>(null);
|
const [copiedId, setCopiedId] = useState<string | null>(null);
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const isLightBlue = theme === 'lightblue';
|
const lightAccent = getLightAccent(theme);
|
||||||
|
|
||||||
const { data: apps = [], isLoading } = useLightApps({
|
const { data: apps = [], isLoading } = useLightApps({
|
||||||
appType: "window_open",
|
appType: "window_open",
|
||||||
@@ -118,13 +152,13 @@ export default function LightAppListPage() {
|
|||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<span className="relative flex size-3">
|
<span className="relative flex size-3">
|
||||||
<span className={cn("absolute inline-flex size-full animate-ping rounded-full opacity-75", isLightBlue ? "bg-[#0062D9]" : "bg-[#CF1322]", "dark:bg-[#1677FF]")} />
|
<span className={cn("absolute inline-flex size-full animate-ping rounded-full opacity-75", lightAccent.dot, "dark:bg-[#1677FF]")} />
|
||||||
<span className={cn("relative inline-flex size-3 rounded-full", isLightBlue ? "bg-[#0062D9]" : "bg-[#CF1322]", "dark:bg-[#1677FF]")} />
|
<span className={cn("relative inline-flex size-3 rounded-full", lightAccent.dot, "dark:bg-[#1677FF]")} />
|
||||||
</span>
|
</span>
|
||||||
<h1 className="text-2xl font-bold tracking-tight text-zinc-900 dark:text-[#E8EDF5]">
|
<h1 className="text-2xl font-bold tracking-tight text-zinc-900 dark:text-[#E8EDF5]">
|
||||||
应用列表
|
应用列表
|
||||||
</h1>
|
</h1>
|
||||||
<span className={cn("rounded-[5px] border px-2 py-0.5 font-mono text-xs font-medium dark:border-[#1677FF]/30 dark:bg-[#1677FF]/15 dark:text-[#4096FF]", isLightBlue ? "border-[#0062D9]/15 bg-[#EAF4FF] text-[#0062D9]" : "border-[#CF1322]/15 bg-[#FFF1F0] text-[#CF1322]")}>
|
<span className={cn("rounded-[5px] border px-2 py-0.5 font-mono text-xs font-medium dark:border-[#1677FF]/30 dark:bg-[#1677FF]/15 dark:text-[#4096FF]", lightAccent.badge)}>
|
||||||
导航工作台
|
导航工作台
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -142,7 +176,7 @@ export default function LightAppListPage() {
|
|||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
placeholder="键入应用名称、目标地址、或描述,实时过滤检索…"
|
placeholder="键入应用名称、目标地址、或描述,实时过滤检索…"
|
||||||
className={cn("w-full rounded-[5px] border border-zinc-200 bg-zinc-50/50 py-3 pl-10 pr-10 text-sm transition focus:bg-white focus:outline-none focus:ring-2 dark:border-[#1A2F5A] dark:bg-[#0C1836] dark:text-[#C5CFE0] dark:placeholder-[#4A6491] dark:focus:border-[#1677FF]/60 dark:focus:bg-[#122347] dark:focus:ring-[#1677FF]/25", isLightBlue ? "focus:border-[#0062D9]/60 focus:ring-[#0062D9]/20" : "focus:border-[#CF1322]/60 focus:ring-[#CF1322]/20")}
|
className={cn("w-full rounded-[5px] border border-zinc-200 bg-zinc-50/50 py-3 pl-10 pr-10 text-sm transition focus:bg-white focus:outline-none focus:ring-2 dark:border-[#1A2F5A] dark:bg-[#0C1836] dark:text-[#C5CFE0] dark:placeholder-[#4A6491] dark:focus:border-[#1677FF]/60 dark:focus:bg-[#122347] dark:focus:ring-[#1677FF]/25", lightAccent.focus)}
|
||||||
/>
|
/>
|
||||||
{search && (
|
{search && (
|
||||||
<button
|
<button
|
||||||
@@ -161,13 +195,13 @@ export default function LightAppListPage() {
|
|||||||
<div className="-mt-2 flex items-center justify-between">
|
<div className="-mt-2 flex items-center justify-between">
|
||||||
<div className="text-xs font-medium text-zinc-500 dark:text-[#6E88B0]">
|
<div className="text-xs font-medium text-zinc-500 dark:text-[#6E88B0]">
|
||||||
检索到最终结果:{" "}
|
检索到最终结果:{" "}
|
||||||
<span className={cn("font-bold dark:text-[#4096FF]", isLightBlue ? "text-[#0062D9]" : "text-[#CF1322]")}>{apps.length}</span> 个关联应用
|
<span className={cn("font-bold dark:text-[#4096FF]", lightAccent.text)}>{apps.length}</span> 个关联应用
|
||||||
</div>
|
</div>
|
||||||
{search && (
|
{search && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setSearch("")}
|
onClick={() => setSearch("")}
|
||||||
className={cn("text-xs font-medium underline dark:text-[#4096FF] dark:hover:text-[#1677FF]", isLightBlue ? "text-[#0062D9] hover:text-[#0056C6]" : "text-[#CF1322] hover:text-[#A8071A]")}
|
className={cn("text-xs font-medium underline dark:text-[#4096FF] dark:hover:text-[#1677FF]", lightAccent.link)}
|
||||||
>
|
>
|
||||||
重置过滤条件
|
重置过滤条件
|
||||||
</button>
|
</button>
|
||||||
@@ -239,7 +273,7 @@ function AppCard({
|
|||||||
onOpen: () => void;
|
onOpen: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const isLightBlue = theme === 'lightblue';
|
const lightAccent = getLightAccent(theme);
|
||||||
const Icon = ICON_POOL[hashIndex(app.appId, ICON_POOL.length)];
|
const Icon = ICON_POOL[hashIndex(app.appId, ICON_POOL.length)];
|
||||||
const cardTheme = THEME_POOL[hashIndex(app.appId, THEME_POOL.length)];
|
const cardTheme = THEME_POOL[hashIndex(app.appId, THEME_POOL.length)];
|
||||||
|
|
||||||
@@ -256,7 +290,7 @@ function AppCard({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4 className={cn("mb-2 line-clamp-1 text-base font-bold leading-snug text-zinc-900 transition-colors dark:text-[#E8EDF5] dark:group-hover:text-[#4096FF]", isLightBlue ? "group-hover:text-[#0062D9]" : "group-hover:text-[#CF1322]")}>
|
<h4 className={cn("mb-2 line-clamp-1 text-base font-bold leading-snug text-zinc-900 transition-colors dark:text-[#E8EDF5] dark:group-hover:text-[#4096FF]", lightAccent.hoverText)}>
|
||||||
{app.name}
|
{app.name}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
@@ -270,7 +304,7 @@ function AppCard({
|
|||||||
<div className="mb-4 rounded-[5px] bg-zinc-50/70 p-3 dark:bg-[#0C1836]">
|
<div className="mb-4 rounded-[5px] bg-zinc-50/70 p-3 dark:bg-[#0C1836]">
|
||||||
<div className="mb-1 flex items-center justify-between text-[10px] font-semibold text-zinc-400 dark:text-[#6E88B0]">
|
<div className="mb-1 flex items-center justify-between text-[10px] font-semibold text-zinc-400 dark:text-[#6E88B0]">
|
||||||
<span>拼接后目标路由终端:</span>
|
<span>拼接后目标路由终端:</span>
|
||||||
<span className={cn("text-[9px] font-bold uppercase tracking-wider dark:text-[#4096FF]", isLightBlue ? "text-[#0062D9]" : "text-[#CF1322]")}>
|
<span className={cn("text-[9px] font-bold uppercase tracking-wider dark:text-[#4096FF]", lightAccent.text)}>
|
||||||
自动拼接已激活参
|
自动拼接已激活参
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -312,7 +346,7 @@ function AppCard({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onOpen}
|
onClick={onOpen}
|
||||||
className={cn("flex items-center justify-center gap-1.5 rounded-[5px] px-3 py-2 text-xs font-semibold text-white transition active:scale-[0.98] dark:bg-[#1677FF] dark:shadow-none dark:hover:bg-[#0958D9]", isLightBlue ? "bg-[#0062D9] shadow-[0_1px_3px_rgba(0,98,217,0.2)] hover:bg-[#0079FE]" : "bg-[#CF1322] shadow-[0_1px_3px_rgba(207,19,34,0.2)] hover:bg-[#A8071A]")}
|
className={cn("flex items-center justify-center gap-1.5 rounded-[5px] px-3 py-2 text-xs font-semibold text-white transition active:scale-[0.98] dark:bg-[#1677FF] dark:shadow-none dark:hover:bg-[#0958D9]", lightAccent.button)}
|
||||||
>
|
>
|
||||||
<span>进入应用</span>
|
<span>进入应用</span>
|
||||||
<ArrowUpRight className="size-3.5" />
|
<ArrowUpRight className="size-3.5" />
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const newParam = (): LightAppRouteParam => ({
|
|||||||
paramType: "login",
|
paramType: "login",
|
||||||
source: "access_token",
|
source: "access_token",
|
||||||
customValue: "",
|
customValue: "",
|
||||||
themeValue: "red=red;blue=blue;dark-blue=dark-blue",
|
themeValue: "red=red;green=green;blue=blue;dark-blue=dark-blue",
|
||||||
});
|
});
|
||||||
|
|
||||||
function formFromApp(app: LightApp): FormState {
|
function formFromApp(app: LightApp): FormState {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export default function LightAppNoteQAPage() {
|
|||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const isDark = theme === "dark";
|
const isDark = theme === "dark";
|
||||||
const isLightBlue = theme === 'lightblue';
|
const isLightBlue = theme === 'lightblue';
|
||||||
|
const isLightGreen = theme === "lightgreen";
|
||||||
const [question, setQuestion] = useState("");
|
const [question, setQuestion] = useState("");
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
@@ -112,6 +113,8 @@ export default function LightAppNoteQAPage() {
|
|||||||
"flex h-7 w-7 shrink-0 items-center justify-center rounded-full transition-colors",
|
"flex h-7 w-7 shrink-0 items-center justify-center rounded-full transition-colors",
|
||||||
isDark
|
isDark
|
||||||
? "bg-sky-500 text-white hover:bg-sky-400"
|
? "bg-sky-500 text-white hover:bg-sky-400"
|
||||||
|
: isLightGreen
|
||||||
|
? "bg-[#07C05F] text-white hover:bg-[#08DD6E]"
|
||||||
: isLightBlue
|
: isLightBlue
|
||||||
? "bg-[#0062D9] text-white hover:bg-[#0079FE]"
|
? "bg-[#0062D9] text-white hover:bg-[#0079FE]"
|
||||||
: "bg-[#F5222D] text-white hover:bg-[#CF1322]",
|
: "bg-[#F5222D] text-white hover:bg-[#CF1322]",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import AIWritingPage from '@/open-canvas/pages/AIWritingPage';
|
|||||||
import { getNotebookApiBaseURL } from '@/core/config';
|
import { getNotebookApiBaseURL } from '@/core/config';
|
||||||
import { useTheme, type ThemeType } from '@/contexts/ThemeContext';
|
import { useTheme, type ThemeType } from '@/contexts/ThemeContext';
|
||||||
|
|
||||||
const VALID_THEMES: ThemeType[] = ['dark', 'light', 'lightblue'];
|
const VALID_THEMES: ThemeType[] = ['dark', 'light', 'lightblue', 'lightgreen'];
|
||||||
|
|
||||||
interface NotebookTokenData {
|
interface NotebookTokenData {
|
||||||
doc_ids: number[];
|
doc_ids: number[];
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { useTheme } from "@/contexts/ThemeContext";
|
|||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { useActiveThirdLevel } from "@/core/page-layout/use-sidebar-menu";
|
import { useActiveThirdLevel } from "@/core/page-layout/use-sidebar-menu";
|
||||||
import { useLayoutMode } from "@/core/system-settings/hooks";
|
import { useLayoutMode } from "@/core/system-settings/hooks";
|
||||||
|
import { env } from "@/env";
|
||||||
|
|
||||||
import WorkspaceRoutes from "./WorkspaceRoutes";
|
import WorkspaceRoutes from "./WorkspaceRoutes";
|
||||||
import StrategyRoutes from "./StrategyRoutes";
|
import StrategyRoutes from "./StrategyRoutes";
|
||||||
@@ -27,6 +28,7 @@ import { RoundtableJobNotifier } from "@/roundtable-planning/components/Roundtab
|
|||||||
|
|
||||||
function PageLayout() {
|
function PageLayout() {
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
const forceCollapsedSidebar = env.FORCE_COLLAPSED_SIDEBAR;
|
||||||
const sidebarEnabled = useMemo(() => readSidebarEnabled(), []);
|
const sidebarEnabled = useMemo(() => readSidebarEnabled(), []);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
@@ -42,7 +44,13 @@ function PageLayout() {
|
|||||||
// 登录时置位)这一旧默认。这样无论是否走门户登录,「全量↔简洁」都能真正控制顶栏+侧边栏。
|
// 登录时置位)这一旧默认。这样无论是否走门户登录,「全量↔简洁」都能真正控制顶栏+侧边栏。
|
||||||
const [layoutMode] = useLayoutMode();
|
const [layoutMode] = useLayoutMode();
|
||||||
const chromeByPreference =
|
const chromeByPreference =
|
||||||
layoutMode === "full" ? true : layoutMode === "compact" ? false : sidebarEnabled;
|
forceCollapsedSidebar
|
||||||
|
? false
|
||||||
|
: layoutMode === "full"
|
||||||
|
? true
|
||||||
|
: layoutMode === "compact"
|
||||||
|
? false
|
||||||
|
: sidebarEnabled;
|
||||||
// iframe 嵌入模式(外部系统 `&embed=1`,按标签页 sessionStorage):去掉全局导航栏 + 侧边栏,
|
// iframe 嵌入模式(外部系统 `&embed=1`,按标签页 sessionStorage):去掉全局导航栏 + 侧边栏,
|
||||||
// 只留问答/会商工作区本体。按 location 重算以跟随页内导航;非嵌入标签页恒为 false,不受影响。
|
// 只留问答/会商工作区本体。按 location 重算以跟随页内导航;非嵌入标签页恒为 false,不受影响。
|
||||||
const embedActive = useMemo(
|
const embedActive = useMemo(
|
||||||
@@ -60,7 +68,8 @@ function PageLayout() {
|
|||||||
// 但它常被深链「上一步」从去 chrome 的工作区/新标签打开——那里 chromeByPreference 可能为 false
|
// 但它常被深链「上一步」从去 chrome 的工作区/新标签打开——那里 chromeByPreference 可能为 false
|
||||||
// (界面模式非全量、或会话级 sidebar 未置位),导致整页没有顶栏/侧栏。故对该路由**强制全局 chrome**
|
// (界面模式非全量、或会话级 sidebar 未置位),导致整页没有顶栏/侧栏。故对该路由**强制全局 chrome**
|
||||||
// (仍尊重 embed=1 嵌入态:外部 iframe 套用时仍隐藏)。
|
// (仍尊重 embed=1 嵌入态:外部 iframe 套用时仍隐藏)。
|
||||||
const forceChromeForDashboardAgent = location.pathname.includes("/strategy/dashboard-agent");
|
const forceChromeForDashboardAgent =
|
||||||
|
!forceCollapsedSidebar && location.pathname.includes("/strategy/dashboard-agent");
|
||||||
const isWorkspacePage = location.pathname.startsWith("/page/workspace");
|
const isWorkspacePage = location.pathname.startsWith("/page/workspace");
|
||||||
const pageSidebarAllowed =
|
const pageSidebarAllowed =
|
||||||
(forceChromeForDashboardAgent || chromeByPreference) &&
|
(forceChromeForDashboardAgent || chromeByPreference) &&
|
||||||
@@ -118,6 +127,7 @@ function PageLayout() {
|
|||||||
headerHidden={!pageSidebarAllowed}
|
headerHidden={!pageSidebarAllowed}
|
||||||
compactWorkspaceSidebar={pageSidebarAllowed || isWorkspacePage}
|
compactWorkspaceSidebar={pageSidebarAllowed || isWorkspacePage}
|
||||||
pageSidebarDark={isDark}
|
pageSidebarDark={isDark}
|
||||||
|
forceWorkspaceSidebarCollapsed={forceCollapsedSidebar}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { CSSProperties, ReactNode } from 'react';
|
|||||||
import { Navigate, Route, Routes, useLocation, useParams } from 'react-router-dom';
|
import { Navigate, Route, Routes, useLocation, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { getStoredAuth } from '@/core/auth';
|
import { getStoredAuth } from '@/core/auth';
|
||||||
|
import { env } from '@/env';
|
||||||
|
|
||||||
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar';
|
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar';
|
||||||
import { PromptInputProvider } from '@/components/ai-elements/prompt-input';
|
import { PromptInputProvider } from '@/components/ai-elements/prompt-input';
|
||||||
@@ -79,6 +80,8 @@ function isInIframe(): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function CompactAppearanceActions() {
|
function CompactAppearanceActions() {
|
||||||
|
if (env.FORCE_COLLAPSED_SIDEBAR) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CompactShortcutButtons tone="content" />
|
<CompactShortcutButtons tone="content" />
|
||||||
@@ -99,6 +102,7 @@ export function WorkspaceLayout({
|
|||||||
wrapperOverflowHidden = false,
|
wrapperOverflowHidden = false,
|
||||||
insetOverflowHidden = false,
|
insetOverflowHidden = false,
|
||||||
pageSidebarDark = false,
|
pageSidebarDark = false,
|
||||||
|
forceWorkspaceSidebarCollapsed = false,
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
pageSidebarCollapsed: boolean;
|
pageSidebarCollapsed: boolean;
|
||||||
@@ -111,6 +115,7 @@ export function WorkspaceLayout({
|
|||||||
wrapperOverflowHidden?: boolean;
|
wrapperOverflowHidden?: boolean;
|
||||||
insetOverflowHidden?: boolean;
|
insetOverflowHidden?: boolean;
|
||||||
pageSidebarDark?: boolean;
|
pageSidebarDark?: boolean;
|
||||||
|
forceWorkspaceSidebarCollapsed?: boolean;
|
||||||
}) {
|
}) {
|
||||||
// 全量模式下通用问答(/page/workspace/chats*)的历史侧栏默认展开;其余紧凑页
|
// 全量模式下通用问答(/page/workspace/chats*)的历史侧栏默认展开;其余紧凑页
|
||||||
// (如智能体对话,为聊天区更宽而默认收起)维持原默认。
|
// (如智能体对话,为聊天区更宽而默认收起)维持原默认。
|
||||||
@@ -136,17 +141,24 @@ export function WorkspaceLayout({
|
|||||||
{
|
{
|
||||||
'--page-sidebar-width': pageSidebarWidth,
|
'--page-sidebar-width': pageSidebarWidth,
|
||||||
'--header-height': headerHidden ? '0rem' : '4rem',
|
'--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
|
} as CSSProperties
|
||||||
}
|
}
|
||||||
key={String(compactWorkspaceSidebar)}
|
key={String(compactWorkspaceSidebar)}
|
||||||
data-page-sidebar-collapsed={pageSidebarCollapsed}
|
data-page-sidebar-collapsed={pageSidebarCollapsed}
|
||||||
defaultOpen={
|
defaultOpen={
|
||||||
defaultHistorySidebarOpen ?? true
|
forceWorkspaceSidebarCollapsed ? false : (defaultHistorySidebarOpen ?? true)
|
||||||
}
|
}
|
||||||
|
open={forceWorkspaceSidebarCollapsed ? false : undefined}
|
||||||
|
onOpenChange={forceWorkspaceSidebarCollapsed ? () => undefined : undefined}
|
||||||
>
|
>
|
||||||
{!workspaceSidebarHidden && (
|
{!workspaceSidebarHidden && (
|
||||||
<WorkspaceSidebar compactChrome={compactWorkspaceSidebar} />
|
<WorkspaceSidebar
|
||||||
|
compactChrome={compactWorkspaceSidebar}
|
||||||
|
forceCollapsed={forceWorkspaceSidebarCollapsed}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<SidebarInset
|
<SidebarInset
|
||||||
className={cn('min-w-0', insetOverflowHidden && 'overflow-hidden')}
|
className={cn('min-w-0', insetOverflowHidden && 'overflow-hidden')}
|
||||||
@@ -193,6 +205,7 @@ function NewChatEntryRoute({
|
|||||||
headerHidden: boolean;
|
headerHidden: boolean;
|
||||||
compactWorkspaceSidebar: boolean;
|
compactWorkspaceSidebar: boolean;
|
||||||
pageSidebarDark: boolean;
|
pageSidebarDark: boolean;
|
||||||
|
forceWorkspaceSidebarCollapsed: boolean;
|
||||||
};
|
};
|
||||||
}) {
|
}) {
|
||||||
if (isInIframe()) {
|
if (isInIframe()) {
|
||||||
@@ -214,12 +227,14 @@ export default function WorkspaceRoutes({
|
|||||||
headerHidden = false,
|
headerHidden = false,
|
||||||
compactWorkspaceSidebar = false,
|
compactWorkspaceSidebar = false,
|
||||||
pageSidebarDark = false,
|
pageSidebarDark = false,
|
||||||
|
forceWorkspaceSidebarCollapsed = false,
|
||||||
}: {
|
}: {
|
||||||
pageSidebarCollapsed?: boolean;
|
pageSidebarCollapsed?: boolean;
|
||||||
pageSidebarHidden?: boolean;
|
pageSidebarHidden?: boolean;
|
||||||
headerHidden?: boolean;
|
headerHidden?: boolean;
|
||||||
compactWorkspaceSidebar?: boolean;
|
compactWorkspaceSidebar?: boolean;
|
||||||
pageSidebarDark?: boolean;
|
pageSidebarDark?: boolean;
|
||||||
|
forceWorkspaceSidebarCollapsed?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const layoutProps = {
|
const layoutProps = {
|
||||||
pageSidebarCollapsed,
|
pageSidebarCollapsed,
|
||||||
@@ -227,6 +242,7 @@ export default function WorkspaceRoutes({
|
|||||||
headerHidden,
|
headerHidden,
|
||||||
compactWorkspaceSidebar,
|
compactWorkspaceSidebar,
|
||||||
pageSidebarDark,
|
pageSidebarDark,
|
||||||
|
forceWorkspaceSidebarCollapsed,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -679,6 +679,58 @@
|
|||||||
background-color: #0056C6;
|
background-color: #0056C6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── WeKnora 绿色主题覆盖([data-theme="lightgreen"])──────────────── */
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-primary-btn,
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-dialog-save,
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-switch-on {
|
||||||
|
background-color: #07C05F;
|
||||||
|
}
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-primary-btn,
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-dialog-save {
|
||||||
|
box-shadow: 0 2px 6px rgba(7, 192, 95, 0.2);
|
||||||
|
}
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-primary-btn:hover,
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-dialog-save:hover {
|
||||||
|
background-color: #08DD6E;
|
||||||
|
}
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-primary-btn:active,
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-dialog-save:active {
|
||||||
|
background-color: #049B38;
|
||||||
|
}
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-search-input:focus {
|
||||||
|
border-color: rgba(7, 192, 95, 0.4);
|
||||||
|
box-shadow: 0 0 0 4px rgba(7, 192, 95, 0.1);
|
||||||
|
}
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-view-toggle-btn.is-active,
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-cat-toggle-btn.is-active,
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-count strong,
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-table tr:hover .lam-row-name,
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-param-popover-close,
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-param-key,
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-card:hover .lam-card-title,
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-dialog-close:hover,
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-add-param-btn:hover {
|
||||||
|
color: #049B38;
|
||||||
|
}
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-add-param-btn:hover {
|
||||||
|
border-color: #07C05F;
|
||||||
|
}
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-dialog-icon {
|
||||||
|
background-color: #E9F8EC;
|
||||||
|
color: #049B38;
|
||||||
|
}
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-input:focus,
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .lam-dialog input[type="text"]:focus,
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .t-select.t-is-focused .t-input,
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .t-select.t-is-focused .t-input__wrap {
|
||||||
|
border-color: #07C05F;
|
||||||
|
box-shadow: 0 0 0 2px rgba(7, 192, 95, 0.16);
|
||||||
|
}
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .t-select:hover .t-input,
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page .t-select:hover .t-input__wrap {
|
||||||
|
border-color: rgba(7, 192, 95, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── iframe 菜单排序:拖拽行 + 拖拽手柄 ───────────────────────────────── */
|
/* ── iframe 菜单排序:拖拽行 + 拖拽手柄 ───────────────────────────────── */
|
||||||
.light-app-management-page .lam-reorder-row {
|
.light-app-management-page .lam-reorder-row {
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
|
|||||||
@@ -7,6 +7,11 @@
|
|||||||
background-color: #F9FCFF;
|
background-color: #F9FCFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-theme="lightgreen"] .light-app-management-page,
|
||||||
|
[data-theme="lightgreen"] .light-app-list-page {
|
||||||
|
background-color: #F9FDF9;
|
||||||
|
}
|
||||||
|
|
||||||
.dark .light-app-management-page,
|
.dark .light-app-management-page,
|
||||||
.dark .light-app-list-page {
|
.dark .light-app-list-page {
|
||||||
background-color: var(--color-bg-page);
|
background-color: var(--color-bg-page);
|
||||||
|
|||||||
@@ -39,14 +39,15 @@ export const LOGIN_SOURCE_OPTIONS: { label: string; value: string }[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 我方主题色键。当前启用 red(浅色) / dark-blue(深色);blue 为后续将新增的主题。
|
* 我方主题色键:red(红白)、green(WeKnora 绿色)、blue(浅蓝)、dark-blue(深蓝)。
|
||||||
* 主题参数的映射以这些键为左侧 key(如 `dark-blue=darkBlue`)。
|
* 主题参数的映射以这些键为左侧 key(如 `dark-blue=darkBlue`)。
|
||||||
*/
|
*/
|
||||||
export type AppThemeColor = "red" | "blue" | "dark-blue";
|
export type AppThemeColor = "red" | "green" | "blue" | "dark-blue";
|
||||||
|
|
||||||
/** 我方全部主题色(「应用管理」主题参数逐项配置目标值时遍历;dot 为配色小圆点)。 */
|
/** 我方全部主题色(「应用管理」主题参数逐项配置目标值时遍历;dot 为配色小圆点)。 */
|
||||||
export const APP_THEME_COLORS: { key: AppThemeColor; label: string; dot: string }[] = [
|
export const APP_THEME_COLORS: { key: AppThemeColor; label: string; dot: string }[] = [
|
||||||
{ key: "red", label: "红色", dot: "#F5222D" },
|
{ key: "red", label: "红色", dot: "#F5222D" },
|
||||||
|
{ key: "green", label: "绿色", dot: "#07C05F" },
|
||||||
{ key: "blue", label: "蓝色", dot: "#1677FF" },
|
{ key: "blue", label: "蓝色", dot: "#1677FF" },
|
||||||
{ key: "dark-blue", label: "深蓝色", dot: "#002B5C" },
|
{ key: "dark-blue", label: "深蓝色", dot: "#002B5C" },
|
||||||
];
|
];
|
||||||
@@ -57,18 +58,20 @@ export const APP_THEME_COLORS: { key: AppThemeColor; label: string; dot: string
|
|||||||
*/
|
*/
|
||||||
const THEME_KEY_ALIASES: Record<AppThemeColor, string[]> = {
|
const THEME_KEY_ALIASES: Record<AppThemeColor, string[]> = {
|
||||||
red: ["red", "light"],
|
red: ["red", "light"],
|
||||||
|
green: ["green", "lightgreen", "light-green"],
|
||||||
blue: ["blue"],
|
blue: ["blue"],
|
||||||
"dark-blue": ["dark-blue", "darkblue", "dark"],
|
"dark-blue": ["dark-blue", "darkblue", "dark"],
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 读取我方当前主题色,以 ThemeContext / next-themes 写在 <html> 上的 class 为准
|
* 读取我方当前主题色,以 ThemeContext / next-themes 写在 <html> 上的 class 为准
|
||||||
* (含嵌入场景的强制主题)。深色=dark-blue,浅蓝=blue,其余(浅色/红白)=red。
|
* (含嵌入场景的强制主题)。深色=dark-blue,绿色=green,浅蓝=blue,其余(浅色/红白)=red。
|
||||||
*/
|
*/
|
||||||
export function getCurrentTheme(): AppThemeColor {
|
export function getCurrentTheme(): AppThemeColor {
|
||||||
if (typeof document !== "undefined") {
|
if (typeof document !== "undefined") {
|
||||||
const classList = document.documentElement.classList;
|
const classList = document.documentElement.classList;
|
||||||
if (classList.contains("dark")) return "dark-blue";
|
if (classList.contains("dark")) return "dark-blue";
|
||||||
|
if (classList.contains("lightgreen")) return "green";
|
||||||
if (classList.contains("lightblue")) return "blue";
|
if (classList.contains("lightblue")) return "blue";
|
||||||
}
|
}
|
||||||
return "red";
|
return "red";
|
||||||
|
|||||||
@@ -407,6 +407,65 @@
|
|||||||
--legacy-workspace-brand-rgb: 0 98 217;
|
--legacy-workspace-brand-rgb: 0 98 217;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* WeKnora 绿色主题:沿用 WeKnora 的主色 #07C05F、浅底 #E9F8EC 与深强调色 #049B38。
|
||||||
|
仅覆盖颜色 token,圆角和间距保持 DeerFlow 原有布局。 */
|
||||||
|
.lightgreen {
|
||||||
|
--workspace-brand: #07c05f;
|
||||||
|
--workspace-brand-foreground: #ffffff;
|
||||||
|
--brand-200: #e9f8ec;
|
||||||
|
--brand-900: #07c05f;
|
||||||
|
--brand-950: #049b38;
|
||||||
|
--background: #ffffff;
|
||||||
|
--foreground: #203329;
|
||||||
|
--card: #ffffff;
|
||||||
|
--card-foreground: #203329;
|
||||||
|
--popover: #ffffff;
|
||||||
|
--popover-foreground: #203329;
|
||||||
|
--primary: #07c05f;
|
||||||
|
--primary-foreground: #ffffff;
|
||||||
|
--secondary: #f1fbf4;
|
||||||
|
--secondary-foreground: #049b38;
|
||||||
|
--muted: #f3f8f4;
|
||||||
|
--muted-foreground: #64736a;
|
||||||
|
--accent: #e9f8ec;
|
||||||
|
--accent-foreground: #038626;
|
||||||
|
--destructive: #f5222d;
|
||||||
|
--border: #d8eadc;
|
||||||
|
--input: #cfe5d4;
|
||||||
|
--ring: rgb(7 192 95 / 0.35);
|
||||||
|
--chart-1: #07c05f;
|
||||||
|
--chart-2: #028f72;
|
||||||
|
--chart-3: #4f6680;
|
||||||
|
--chart-4: #e8a72a;
|
||||||
|
--chart-5: #db8d16;
|
||||||
|
--sidebar: #f9fdf9;
|
||||||
|
--sidebar-foreground: #203329;
|
||||||
|
--sidebar-primary: #07c05f;
|
||||||
|
--sidebar-primary-foreground: #ffffff;
|
||||||
|
--sidebar-accent: #e9f8ec;
|
||||||
|
--sidebar-accent-foreground: #038626;
|
||||||
|
--sidebar-border: #e3f0e5;
|
||||||
|
--sidebar-ring: rgb(7 192 95 / 0.4);
|
||||||
|
--legacy-accent-rgb: 233 248 236;
|
||||||
|
--legacy-background-rgb: 255 255 255;
|
||||||
|
--legacy-border-rgb: 216 234 220;
|
||||||
|
--legacy-brand-200-rgb: 233 248 236;
|
||||||
|
--legacy-card-rgb: 255 255 255;
|
||||||
|
--legacy-destructive-rgb: 245 34 45;
|
||||||
|
--legacy-foreground-rgb: 32 51 41;
|
||||||
|
--legacy-input-rgb: 207 229 212;
|
||||||
|
--legacy-muted-rgb: 243 248 244;
|
||||||
|
--legacy-muted-foreground-rgb: 100 115 106;
|
||||||
|
--legacy-primary-rgb: 7 192 95;
|
||||||
|
--legacy-primary-foreground-rgb: 255 255 255;
|
||||||
|
--legacy-ring-rgb: 7 192 95;
|
||||||
|
--legacy-secondary-rgb: 241 251 244;
|
||||||
|
--legacy-sidebar-accent-rgb: 233 248 236;
|
||||||
|
--legacy-sidebar-border-rgb: 227 240 229;
|
||||||
|
--legacy-sidebar-foreground-rgb: 32 51 41;
|
||||||
|
--legacy-workspace-brand-rgb: 7 192 95;
|
||||||
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border outline-ring/50;
|
@apply border-border outline-ring/50;
|
||||||
@@ -1232,6 +1291,44 @@ html.lightblue .tiptap-writing-editor .ProseMirror ::selection {
|
|||||||
background: color-mix(in srgb, #55a6fe 35%, transparent);
|
background: color-mix(in srgb, #55a6fe 35%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── WeKnora 绿色主题:覆盖未使用 token 的历史硬编码颜色 ─────────────── */
|
||||||
|
html.lightgreen .strategy-app-header {
|
||||||
|
background-color: #07c05f;
|
||||||
|
background-image: linear-gradient(105deg, #049b38 0%, #07c05f 48%, #08dd6e 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.lightgreen *::-webkit-scrollbar-thumb {
|
||||||
|
background: linear-gradient(180deg, rgb(9 244 121 / 0.8), rgb(7 192 95 / 0.92))
|
||||||
|
padding-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.lightgreen .workspace-panel {
|
||||||
|
border: 1px solid #d8eadc;
|
||||||
|
box-shadow:
|
||||||
|
0 32px 72px -46px rgb(26 112 68 / 0.3),
|
||||||
|
0 12px 28px -22px rgb(7 192 95 / 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.lightgreen .workspace-user-bubble {
|
||||||
|
border: 1px solid rgb(255 255 255 / 0.14);
|
||||||
|
background: linear-gradient(135deg, #09f479 0%, #07c05f 58%, #049b38 100%);
|
||||||
|
box-shadow:
|
||||||
|
0 22px 40px -28px rgb(7 192 95 / 0.52),
|
||||||
|
0 10px 20px -16px rgb(4 155 56 / 0.38);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.lightgreen .workspace-assistant-card {
|
||||||
|
border: 1px solid #dceee0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.lightgreen .workspace-assistant-card::before {
|
||||||
|
background: linear-gradient(180deg, rgb(9 244 121 / 0.68), rgb(7 192 95 / 0.12));
|
||||||
|
}
|
||||||
|
|
||||||
|
html.lightgreen .tiptap-writing-editor .ProseMirror ::selection {
|
||||||
|
background: color-mix(in srgb, #07c05f 35%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── 来源上传弹窗:A系统基因库的分段单选条(custom-radio) ─────────────── */
|
/* ── 来源上传弹窗:A系统基因库的分段单选条(custom-radio) ─────────────── */
|
||||||
/* 外层容器,模拟 nz-radio-group */
|
/* 外层容器,模拟 nz-radio-group */
|
||||||
.custom-radio-group {
|
.custom-radio-group {
|
||||||
@@ -1290,9 +1387,9 @@ html.lightblue .tiptap-writing-editor .ProseMirror ::selection {
|
|||||||
* agent surfaces on classic rgba()/hex colours instead.
|
* agent surfaces on classic rgba()/hex colours instead.
|
||||||
* -------------------------------------------------------------------------- */
|
* -------------------------------------------------------------------------- */
|
||||||
.agent-avatar {
|
.agent-avatar {
|
||||||
background-color: #edf4ff;
|
background-color: #fff1f0;
|
||||||
color: #1f5fdb;
|
color: #be2323;
|
||||||
box-shadow: inset 0 0 0 1px rgba(59, 130, 246, 0.16);
|
box-shadow: inset 0 0 0 1px rgba(190, 35, 35, 0.16);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .agent-avatar {
|
.dark .agent-avatar {
|
||||||
@@ -1307,6 +1404,12 @@ html.lightblue .tiptap-writing-editor .ProseMirror ::selection {
|
|||||||
box-shadow: inset 0 0 0 1px rgba(0, 98, 217, 0.16);
|
box-shadow: inset 0 0 0 1px rgba(0, 98, 217, 0.16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lightgreen .agent-avatar {
|
||||||
|
background-color: #e9f8ec;
|
||||||
|
color: #049b38;
|
||||||
|
box-shadow: inset 0 0 0 1px rgb(7 192 95 / 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
.agent-sidebar-card--active {
|
.agent-sidebar-card--active {
|
||||||
background-color: #feeded;
|
background-color: #feeded;
|
||||||
border-color: rgba(190, 35, 35, 0.5);
|
border-color: rgba(190, 35, 35, 0.5);
|
||||||
@@ -1322,6 +1425,11 @@ html.lightblue .tiptap-writing-editor .ProseMirror ::selection {
|
|||||||
border-color: rgba(0, 98, 217, 0.5);
|
border-color: rgba(0, 98, 217, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lightgreen .agent-sidebar-card--active {
|
||||||
|
background-color: #e9f8ec;
|
||||||
|
border-color: rgb(7 192 95 / 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
@supports not (color: oklch(1 0 0)) {
|
@supports not (color: oklch(1 0 0)) {
|
||||||
.bg-primary\/5 { background-color: rgba(190, 35, 35, 0.05); }
|
.bg-primary\/5 { background-color: rgba(190, 35, 35, 0.05); }
|
||||||
.bg-primary\/8 { background-color: rgba(190, 35, 35, 0.08); }
|
.bg-primary\/8 { background-color: rgba(190, 35, 35, 0.08); }
|
||||||
@@ -1372,4 +1480,16 @@ html.lightblue .tiptap-writing-editor .ProseMirror ::selection {
|
|||||||
.lightblue .bg-primary\/60 { background-color: rgba(0, 98, 217, 0.60); }
|
.lightblue .bg-primary\/60 { background-color: rgba(0, 98, 217, 0.60); }
|
||||||
.lightblue .bg-primary\/70 { background-color: rgba(0, 98, 217, 0.70); }
|
.lightblue .bg-primary\/70 { background-color: rgba(0, 98, 217, 0.70); }
|
||||||
.lightblue .bg-primary\/90 { background-color: rgba(0, 98, 217, 0.90); }
|
.lightblue .bg-primary\/90 { background-color: rgba(0, 98, 217, 0.90); }
|
||||||
|
|
||||||
|
.lightgreen .bg-primary\/5 { background-color: rgba(7, 192, 95, 0.05); }
|
||||||
|
.lightgreen .bg-primary\/8 { background-color: rgba(7, 192, 95, 0.08); }
|
||||||
|
.lightgreen .bg-primary\/10 { background-color: rgba(7, 192, 95, 0.10); }
|
||||||
|
.lightgreen .bg-primary\/14 { background-color: rgba(7, 192, 95, 0.14); }
|
||||||
|
.lightgreen .bg-primary\/15 { background-color: rgba(7, 192, 95, 0.15); }
|
||||||
|
.lightgreen .bg-primary\/20 { background-color: rgba(7, 192, 95, 0.20); }
|
||||||
|
.lightgreen .bg-primary\/25 { background-color: rgba(7, 192, 95, 0.25); }
|
||||||
|
.lightgreen .bg-primary\/30 { background-color: rgba(7, 192, 95, 0.30); }
|
||||||
|
.lightgreen .bg-primary\/60 { background-color: rgba(7, 192, 95, 0.60); }
|
||||||
|
.lightgreen .bg-primary\/70 { background-color: rgba(7, 192, 95, 0.70); }
|
||||||
|
.lightgreen .bg-primary\/90 { background-color: rgba(7, 192, 95, 0.90); }
|
||||||
}
|
}
|
||||||
@@ -536,7 +536,9 @@ async def update_publish_squares(body: PublishSquaresUpdateRequest, request: Req
|
|||||||
class AppearanceResponse(BaseModel):
|
class AppearanceResponse(BaseModel):
|
||||||
"""Effective system-wide appearance defaults."""
|
"""Effective system-wide appearance defaults."""
|
||||||
|
|
||||||
default_theme: Literal["light", "dark", "lightblue"] = Field(..., description="System default theme — light, dark or lightblue")
|
default_theme: Literal["light", "dark", "lightblue", "lightgreen"] = Field(
|
||||||
|
..., description="System default theme — light, dark, lightblue or lightgreen"
|
||||||
|
)
|
||||||
default_skill_view: Literal["card", "list"] = Field(..., description="System default skill list display mode — card or list")
|
default_skill_view: Literal["card", "list"] = Field(..., description="System default skill list display mode — card or list")
|
||||||
default_agent_view: Literal["card", "list"] = Field(..., description="System default agent list display mode — card or list")
|
default_agent_view: Literal["card", "list"] = Field(..., description="System default agent list display mode — card or list")
|
||||||
default_layout_mode: Literal["full", "compact"] = Field(..., description="System default interface layout — full (sidebar + header) or compact (hidden)")
|
default_layout_mode: Literal["full", "compact"] = Field(..., description="System default interface layout — full (sidebar + header) or compact (hidden)")
|
||||||
@@ -544,7 +546,7 @@ class AppearanceResponse(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class AppearanceUpdateRequest(BaseModel):
|
class AppearanceUpdateRequest(BaseModel):
|
||||||
default_theme: Literal["light", "dark", "lightblue"] | None = Field(default=None)
|
default_theme: Literal["light", "dark", "lightblue", "lightgreen"] | None = Field(default=None)
|
||||||
default_skill_view: Literal["card", "list"] | None = Field(default=None)
|
default_skill_view: Literal["card", "list"] | None = Field(default=None)
|
||||||
default_agent_view: Literal["card", "list"] | None = Field(default=None)
|
default_agent_view: Literal["card", "list"] | None = Field(default=None)
|
||||||
default_layout_mode: Literal["full", "compact"] | None = Field(default=None)
|
default_layout_mode: Literal["full", "compact"] | None = Field(default=None)
|
||||||
|
|||||||
+3
-3
@@ -61,7 +61,7 @@ def _default_shortcut_route_params() -> list[AppearanceRouteParam]:
|
|||||||
paramType="login",
|
paramType="login",
|
||||||
source="access_token",
|
source="access_token",
|
||||||
customValue="",
|
customValue="",
|
||||||
themeValue="red=red;blue=blue;dark-blue=dark-blue",
|
themeValue="red=red;green=green;blue=blue;dark-blue=dark-blue",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -102,9 +102,9 @@ class AppearanceSettings(BaseModel):
|
|||||||
toggle, and any future first-time loads.
|
toggle, and any future first-time loads.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
default_theme: Literal["light", "dark", "lightblue"] = Field(
|
default_theme: Literal["light", "dark", "lightblue", "lightgreen"] = Field(
|
||||||
default="light",
|
default="light",
|
||||||
description="Theme applied for users who haven't picked their own (light / dark / lightblue)",
|
description="Theme applied for users who haven't picked their own (light / dark / lightblue / lightgreen)",
|
||||||
)
|
)
|
||||||
default_skill_view: Literal["card", "list"] = Field(
|
default_skill_view: Literal["card", "list"] = Field(
|
||||||
default="list",
|
default="list",
|
||||||
|
|||||||
Reference in new issue
Block a user