代码修改

This commit is contained in:
董宏凯 committed 2026-07-22 15:43:06 +08:00
1 parent b93b82fcaf
commit 055523bf33
28 files changed
+483 -92

No files matched your search

+2
View File
@@ -50,4 +50,6 @@
# 可选
# VITE_CANVAS_LANGGRAPH_BASE_URL=
# VITE_STATIC_WEBSITE_ONLY=
# 部署级紧凑导航:true 时固定收起工作台左侧栏,并隐藏换肤与全量/简洁模式切换。
# VITE_FORCE_COLLAPSED_SIDEBAR=false
# VITE_GITHUB_OAUTH_TOKEN=
+7
View File
@@ -19,12 +19,15 @@ window.__CM_RUNTIME_CONFIG__ = {
VITE_BRAND_LOGO_LIGHT_URL: "brand-assets/brand-logo-light.png",
// 浅蓝主题的顶栏系统 Logo 图片地址。
VITE_BRAND_LOGO_LIGHTBLUE_URL: "brand-assets/brand-logo-lightblue.png",
// 绿色主题可单独替换 Logo;默认复用浅色 Logo。
VITE_BRAND_LOGO_LIGHTGREEN_URL: "brand-assets/brand-logo-light.png",
// 深色主题的顶栏系统 Logo 图片地址。
VITE_BRAND_LOGO_DARK_URL: "brand-assets/brand-logo-dark.png",
// 红白主题的欢迎页 Logo 图片地址:为空时显示原有 CM 文字和渐变背景。
VITE_BRAND_WELCOME_LIGHT_URL: "",
// 浅蓝主题的欢迎页 Logo 图片地址:为空时显示原有 CM 文字和渐变背景。
VITE_BRAND_WELCOME_LIGHTBLUE_URL: "",
VITE_BRAND_WELCOME_LIGHTGREEN_URL: "",
// 深色主题的欢迎页 Logo 图片地址:为空时显示原有 CM 文字和渐变背景。
VITE_BRAND_WELCOME_DARK_URL: "",
@@ -112,6 +115,10 @@ window.__CM_RUNTIME_CONFIG__ = {
// 静态站点模式开关;一般部署业务系统时保持 false。
VITE_STATIC_WEBSITE_ONLY: "false",
// 紧凑导航开关:true 时固定使用简洁布局,工作台左侧栏收起为菜单图标,
// 隐藏系统 Logo、换肤按钮以及“全量/简洁模式”切换。修改后刷新页面即可生效。
VITE_FORCE_COLLAPSED_SIDEBAR: "false",
// GitHub OAuth Token;不使用 GitHub 相关能力时留空。
VITE_GITHUB_OAUTH_TOKEN: "",
};
+1 -1
View File
@@ -86,7 +86,7 @@ function AppProviders({ children }: { children: React.ReactNode }) {
attribute="class"
storageKey="strategy-theme"
defaultTheme="light"
themes={["light", "dark", "lightblue"]}
themes={["light", "dark", "lightblue", "lightgreen"]}
enableSystem={false}
disableTransitionOnChange
forcedTheme={forcedTheme}
+17 -13
View File
@@ -57,18 +57,21 @@ function useSidebarSafe() {
return React.useContext(SidebarContext);
}
function SidebarProvider({
defaultOpen = true,
open: openProp,
onOpenChange: setOpenProp,
className,
function SidebarProvider({
defaultOpen = true,
open: openProp,
onOpenChange: setOpenProp,
disableToggle = false,
className,
style,
children,
...props
}: React.ComponentProps<"div"> & {
defaultOpen?: boolean;
open?: boolean;
onOpenChange?: (open: boolean) => void;
}: React.ComponentProps<"div"> & {
defaultOpen?: boolean;
open?: boolean;
onOpenChange?: (open: boolean) => void;
/** Keep a controlled deployment-level rail from being reopened by hotkeys. */
disableToggle?: boolean;
}) {
const isMobile = useIsMobile();
const [openMobile, setOpenMobile] = React.useState(false);
@@ -92,10 +95,11 @@ function SidebarProvider({
[setOpenProp, open],
);
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
}, [isMobile, setOpen, setOpenMobile]);
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
if (disableToggle) return;
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
}, [disableToggle, isMobile, setOpen, setOpenMobile]);
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
@@ -15,6 +15,7 @@ import {
} from "@/components/ui/dropdown-menu";
import { useTheme, type ThemeType } from "@/contexts/ThemeContext";
import { useAppearanceSettings, useLayoutMode } from "@/core/system-settings/hooks";
import { env } from "@/env";
import { cn } from "@/lib/utils";
import { buildLightAppUrl } from "@/strategy-components/lib/light-app";
@@ -29,6 +30,7 @@ export type AppearanceControlTone = "banner-dark" | "banner-light" | "content";
// 换肤色板:红色=红白主题(light),蓝色=浅蓝主题(lightblue),深蓝色=深色主题(dark)
export const THEME_SWATCHES: { id: ThemeType; label: string; color: string }[] = [
{ id: "light", label: "红色", color: "#cf1322" },
{ id: "lightgreen", label: "绿色", color: "#07c05f" },
{ id: "lightblue", label: "蓝色", color: "#0062d9" },
{ id: "dark", label: "深蓝色", color: "#002466" },
];
@@ -95,6 +97,8 @@ export function ThemeSwitcher({ tone }: { tone: AppearanceControlTone }) {
useEffect(() => () => cancelClose(), []);
if (env.FORCE_COLLAPSED_SIDEBAR) return null;
return (
<div
className="relative"
@@ -185,6 +189,8 @@ export function LayoutModeSwitcher({
const current = mode ?? fallback;
const TriggerIcon = current === "full" ? PanelLeftIcon : LayoutIcon;
if (env.FORCE_COLLAPSED_SIDEBAR) return null;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -7,6 +7,7 @@ import {
ExternalLink,
LayoutGridIcon,
LayoutIcon,
LeafIcon,
ListIcon,
MoonIcon,
PanelLeftIcon,
@@ -70,7 +71,7 @@ const languageOptions: { value: Locale; label: string }[] = [
{ 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() {
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
@@ -211,14 +212,20 @@ export function AppearanceSettingsPage({ onClose }: { onClose?: () => void }) {
}
};
const handleSetGlobalDefault = async (
value: "light" | "dark" | "lightblue",
const handleSetGlobalDefault = async (
value: "light" | "dark" | "lightblue" | "lightgreen",
) => {
if (value === globalDefault) return;
try {
await updateAppearance.mutateAsync({ default_theme: value });
const label =
value === "light" ? "浅色" : value === "dark" ? "深色" : "浅蓝";
const label =
value === "light"
? "浅色"
: value === "dark"
? "深色"
: value === "lightgreen"
? "绿色"
: "浅蓝";
toast.success(`已将系统默认主题设置为${label}`);
} catch (e) {
toast.error(e instanceof Error ? e.message : "保存失败");
@@ -368,20 +375,28 @@ export function AppearanceSettingsPage({ onClose }: { onClose?: () => void }) {
description: t.settings.appearance.darkDescription,
icon: MoonIcon,
},
{
id: "lightblue",
label: t.settings.appearance.lightblue,
description: t.settings.appearance.lightblueDescription,
icon: DropletIcon,
},
{
id: "lightblue",
label: t.settings.appearance.lightblue,
description: t.settings.appearance.lightblueDescription,
icon: DropletIcon,
},
{
id: "lightgreen",
label: t.settings.appearance.lightgreen,
description: t.settings.appearance.lightgreenDescription,
icon: LeafIcon,
},
],
[
t.settings.appearance.dark,
t.settings.appearance.darkDescription,
t.settings.appearance.light,
t.settings.appearance.lightDescription,
t.settings.appearance.lightblue,
t.settings.appearance.lightblueDescription,
t.settings.appearance.lightblue,
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}
description={option.description}
active={currentTheme === option.id}
mode={option.id as "light" | "dark" | "lightblue"}
mode={option.id as "light" | "dark" | "lightblue" | "lightgreen"}
onSelect={(value) => setTheme(value)}
/>
))}
@@ -541,23 +556,35 @@ export function AppearanceSettingsPage({ onClose }: { onClose?: () => void }) {
<MoonIcon className="mr-1.5 size-4" />
深色
</Button>
<Button
type="button"
variant={globalDefault === "lightblue" ? "default" : "outline"}
<Button
type="button"
variant={globalDefault === "lightblue" ? "default" : "outline"}
size="sm"
onClick={() => void handleSetGlobalDefault("lightblue")}
disabled={updateAppearance.isPending}
>
<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">
当前系统默认:
{globalDefault === "light"
? "浅色"
: globalDefault === "dark"
? "深色"
: "浅蓝"}
: globalDefault === "dark"
? "深色"
: globalDefault === "lightgreen"
? "绿色"
: "浅蓝"}
</p>
</div>
</SettingsSection>
@@ -1074,8 +1101,8 @@ function ThemePreviewCard({
label: string;
description: string;
active: boolean;
mode: "light" | "dark" | "lightblue";
onSelect: (mode: "light" | "dark" | "lightblue") => void;
mode: "light" | "dark" | "lightblue" | "lightgreen";
onSelect: (mode: "light" | "dark" | "lightblue" | "lightgreen") => void;
}) {
const previewMode = mode;
return (
@@ -1104,10 +1131,12 @@ function ThemePreviewCard({
className={cn(
"relative overflow-hidden rounded-md border text-xs transition-colors",
previewMode === "dark"
? "border-neutral-800 bg-neutral-900 text-neutral-200"
: previewMode === "lightblue"
? "border-blue-200 bg-white text-slate-900"
: "border-slate-200 bg-white text-slate-900",
? "border-neutral-800 bg-neutral-900 text-neutral-200"
: previewMode === "lightblue"
? "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",
)}
>
<div className="border-border/50 flex items-center gap-2 border-b px-3 py-2">
@@ -1115,10 +1144,12 @@ function ThemePreviewCard({
className={cn(
"h-2 w-2 rounded-full",
previewMode === "dark"
? "bg-emerald-400"
: previewMode === "lightblue"
? "bg-blue-500"
: "bg-emerald-500",
? "bg-emerald-400"
: previewMode === "lightblue"
? "bg-blue-500"
: previewMode === "lightgreen"
? "bg-[#07c05f]"
: "bg-[#cf1322]",
)}
/>
<div className="h-2 w-10 rounded-full bg-current/20" />
@@ -20,11 +20,15 @@ import { WorkspaceHeader } from "./workspace-header";
import { WorkspaceNavChatList } from "./workspace-nav-chat-list";
import { WorkspaceNavMenu } from "./workspace-nav-menu";
export function WorkspaceSidebar({
compactChrome = false,
className,
...props
}: React.ComponentProps<typeof Sidebar> & { compactChrome?: boolean }) {
export function WorkspaceSidebar({
compactChrome = false,
forceCollapsed = false,
className,
...props
}: React.ComponentProps<typeof Sidebar> & {
compactChrome?: boolean;
forceCollapsed?: boolean;
}) {
const { open: isSidebarOpen } = useSidebar();
const { theme } = useTheme();
const pathname = usePathname();
@@ -32,8 +36,26 @@ export function WorkspaceSidebar({
// 通用问答(全量模式)左栏:新建回答 → 常用智能体 → 聊天记录(需求 0616 #1)。
// 仅在通用问答(/page/workspace/chats*)下启用三块式布局;点击常用智能体后是
// 「原地嵌入」(停留在 /chats/new?agent=...,仅右侧问答区换成智能体对话),左侧栏不变。
const isGeneralQa = pathname.startsWith("/page/workspace/chats");
const isGeneralQa = pathname.startsWith("/page/workspace/chats");
if (forceCollapsed) {
return (
<Sidebar
variant="sidebar"
collapsible="icon"
className={cn(
"bg-sidebar [&_[data-slot=sidebar-inner]]:bg-sidebar",
className,
)}
{...props}
>
<SidebarContent>
<WorkspaceNavChatList />
</SidebarContent>
</Sidebar>
);
}
// 紧凑模式:用「外层定宽容器 + transition-[width]」做展开/收起过渡动画。
// collapsible="none" 的 Sidebar 是普通 in-flow 定宽块(非 absolute,故不受本布局对
// sidebar-container 的 absolute/left-0 覆盖影响),外层 overflow-hidden 把它从
+30 -4
View File
@@ -10,7 +10,7 @@ import { useTheme as useNextTheme } from 'next-themes';
import { useAppearanceSettings } from '@/core/system-settings/hooks';
// 主题类型
export type ThemeType = 'dark' | 'light' | 'lightblue';
export type ThemeType = 'dark' | 'light' | 'lightblue' | 'lightgreen';
// 主题配置
export interface ThemeConfig {
@@ -104,6 +104,28 @@ const themes: Record<ThemeType, ThemeConfig> = {
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';
@@ -121,6 +143,7 @@ function readPersistedTheme(): ThemeType {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved === 'dark') return 'dark';
if (saved === 'lightblue') return 'lightblue';
if (saved === 'lightgreen') return 'lightgreen';
return 'light';
}
@@ -179,7 +202,8 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {
current &&
current !== 'light' &&
current !== 'dark' &&
current !== 'lightblue'
current !== 'lightblue' &&
current !== 'lightgreen'
) {
// Garbage value — wipe it so the boot-sync below can take over.
localStorage.removeItem(STORAGE_KEY);
@@ -216,6 +240,8 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {
? 'dark'
: effectiveTheme === 'lightblue'
? 'lightblue'
: effectiveTheme === 'lightgreen'
? 'lightgreen'
: 'light'
: readPersistedTheme();
@@ -234,8 +260,8 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {
root.removeAttribute('theme-mode');
root.classList.remove('dark');
if (theme === 'lightblue') {
root.setAttribute('data-theme', 'lightblue');
if (theme === 'lightblue' || theme === 'lightgreen') {
root.setAttribute('data-theme', theme);
} else {
root.removeAttribute('data-theme');
}
@@ -3,23 +3,26 @@ import fallbackLogoLight from "@/assets/img/brand-logo-light.png";
import fallbackLogoLightblue from "@/assets/img/brand-logo-lightblue.png";
import { readRuntimeEnv } from "@/env";
export type BrandTheme = "light" | "lightblue" | "dark";
export type BrandTheme = "light" | "lightblue" | "lightgreen" | "dark";
const headerFallbackAssets: Record<BrandTheme, string> = {
light: fallbackLogoLight,
lightblue: fallbackLogoLightblue,
lightgreen: fallbackLogoLight,
dark: fallbackLogoDark,
};
const headerRuntimeKeys: Record<BrandTheme, string> = {
light: "VITE_BRAND_LOGO_LIGHT_URL",
lightblue: "VITE_BRAND_LOGO_LIGHTBLUE_URL",
lightgreen: "VITE_BRAND_LOGO_LIGHTGREEN_URL",
dark: "VITE_BRAND_LOGO_DARK_URL",
};
const welcomeRuntimeKeys: Record<BrandTheme, string> = {
light: "VITE_BRAND_WELCOME_LIGHT_URL",
lightblue: "VITE_BRAND_WELCOME_LIGHTBLUE_URL",
lightgreen: "VITE_BRAND_WELCOME_LIGHTGREEN_URL",
dark: "VITE_BRAND_WELCOME_DARK_URL",
};
+4 -2
View File
@@ -448,11 +448,13 @@ export const enUS: Translations = {
system: "System",
light: "Light",
dark: "Dark",
lightblue: "Light Blue",
lightblue: "Light Blue",
lightgreen: "Green",
systemDescription: "Match the operating system preference automatically.",
lightDescription: "Bright palette with higher contrast for daytime.",
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",
languageDescription: "Switch between languages.",
},
+4 -2
View File
@@ -383,11 +383,13 @@ export interface Translations {
system: string;
light: string;
dark: string;
lightblue: string;
lightblue: string;
lightgreen: string;
systemDescription: string;
lightDescription: string;
darkDescription: string;
lightblueDescription: string;
lightblueDescription: string;
lightgreenDescription: string;
languageTitle: string;
languageDescription: string;
};
+4 -2
View File
@@ -429,11 +429,13 @@ export const zhCN: Translations = {
system: "系统",
light: "浅色",
dark: "深色",
lightblue: "浅蓝",
lightblue: "浅蓝",
lightgreen: "绿色",
systemDescription: "自动跟随系统主题。",
lightDescription: "更明亮的配色,适合日间使用。",
darkDescription: "更暗的配色,减少眩光方便专注。",
lightblueDescription: "以蓝色为主色的清爽浅色配色。",
lightblueDescription: "以蓝色为主色的清爽浅色配色。",
lightgreenDescription: "采用 WeKnora 绿色的清爽浅色配色。",
languageTitle: "语言",
languageDescription: "在不同语言之间切换。",
},
+1 -1
View File
@@ -276,7 +276,7 @@ export async function updatePublishSquares(
return data;
}
export type ThemeMode = "light" | "dark" | "lightblue";
export type ThemeMode = "light" | "dark" | "lightblue" | "lightgreen";
export type ViewMode = "card" | "list";
export type LayoutMode = "full" | "compact";
export type CompactShortcutParamType = "login" | "theme";
+5
View File
@@ -29,5 +29,10 @@ export const env = {
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_STATIC_WEBSITE_ONLY: readRuntimeEnv("VITE_STATIC_WEBSITE_ONLY"),
/**
* Deployment-level compact navigation mode. This is intentionally a runtime
* flag so operations can switch the shell without rebuilding the frontend.
*/
FORCE_COLLAPSED_SIDEBAR: readRuntimeFlag("VITE_FORCE_COLLAPSED_SIDEBAR"),
GITHUB_OAUTH_TOKEN: readRuntimeEnv("VITE_GITHUB_OAUTH_TOKEN"),
};
@@ -21,7 +21,13 @@ export default function LightAppIframeView() {
// 把新主题色传给被嵌套应用(不依赖 <html> 的 dark class,避免时序读旧值)。
// 深色=dark-blue,浅蓝=blue,其余(红白)=red。
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(
() => (app ? buildLightAppUrl(app, undefined, themeColor) : ""),
[app, themeColor],
+46 -12
View File
@@ -34,7 +34,7 @@ import { toast } from "sonner";
import { Pagination } from "@/components/ui/pagination";
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 { buildLightAppUrl } from "@/strategy-components/lib/light-app";
@@ -64,12 +64,46 @@ function hashIndex(str: string, mod: number): number {
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() {
const [search, setSearch] = useState("");
const [page, setPage] = useState(1);
const [copiedId, setCopiedId] = useState<string | null>(null);
const { theme } = useTheme();
const isLightBlue = theme === 'lightblue';
const lightAccent = getLightAccent(theme);
const { data: apps = [], isLoading } = useLightApps({
appType: "window_open",
@@ -118,13 +152,13 @@ export default function LightAppListPage() {
<div className="space-y-1">
<div className="flex items-center gap-2.5">
<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("relative inline-flex size-3 rounded-full", 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", lightAccent.dot, "dark:bg-[#1677FF]")} />
</span>
<h1 className="text-2xl font-bold tracking-tight text-zinc-900 dark:text-[#E8EDF5]">
应用列表
</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>
</div>
@@ -142,7 +176,7 @@ export default function LightAppListPage() {
value={search}
onChange={(e) => setSearch(e.target.value)}
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 && (
<button
@@ -161,13 +195,13 @@ export default function LightAppListPage() {
<div className="-mt-2 flex items-center justify-between">
<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>
{search && (
<button
type="button"
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>
@@ -239,7 +273,7 @@ function AppCard({
onOpen: () => void;
}) {
const { theme } = useTheme();
const isLightBlue = theme === 'lightblue';
const lightAccent = getLightAccent(theme);
const Icon = ICON_POOL[hashIndex(app.appId, ICON_POOL.length)];
const cardTheme = THEME_POOL[hashIndex(app.appId, THEME_POOL.length)];
@@ -256,7 +290,7 @@ function AppCard({
</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}
</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-1 flex items-center justify-between text-[10px] font-semibold text-zinc-400 dark:text-[#6E88B0]">
<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>
</div>
@@ -312,7 +346,7 @@ function AppCard({
<button
type="button"
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>
<ArrowUpRight className="size-3.5" />
@@ -93,7 +93,7 @@ const newParam = (): LightAppRouteParam => ({
paramType: "login",
source: "access_token",
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 {
@@ -10,6 +10,7 @@ export default function LightAppNoteQAPage() {
const { theme } = useTheme();
const isDark = theme === "dark";
const isLightBlue = theme === 'lightblue';
const isLightGreen = theme === "lightgreen";
const [question, setQuestion] = useState("");
const handleSubmit = () => {
@@ -112,7 +113,9 @@ export default function LightAppNoteQAPage() {
"flex h-7 w-7 shrink-0 items-center justify-center rounded-full transition-colors",
isDark
? "bg-sky-500 text-white hover:bg-sky-400"
: isLightBlue
: isLightGreen
? "bg-[#07C05F] text-white hover:bg-[#08DD6E]"
: isLightBlue
? "bg-[#0062D9] text-white hover:bg-[#0079FE]"
: "bg-[#F5222D] text-white hover:bg-[#CF1322]",
)}
@@ -4,7 +4,7 @@ import AIWritingPage from '@/open-canvas/pages/AIWritingPage';
import { getNotebookApiBaseURL } from '@/core/config';
import { useTheme, type ThemeType } from '@/contexts/ThemeContext';
const VALID_THEMES: ThemeType[] = ['dark', 'light', 'lightblue'];
const VALID_THEMES: ThemeType[] = ['dark', 'light', 'lightblue', 'lightgreen'];
interface NotebookTokenData {
doc_ids: number[];
+12 -2
View File
@@ -16,6 +16,7 @@ import { useTheme } from "@/contexts/ThemeContext";
import { useLocation } from "react-router-dom";
import { useActiveThirdLevel } from "@/core/page-layout/use-sidebar-menu";
import { useLayoutMode } from "@/core/system-settings/hooks";
import { env } from "@/env";
import WorkspaceRoutes from "./WorkspaceRoutes";
import StrategyRoutes from "./StrategyRoutes";
@@ -27,6 +28,7 @@ import { RoundtableJobNotifier } from "@/roundtable-planning/components/Roundtab
function PageLayout() {
const [collapsed, setCollapsed] = useState(false);
const forceCollapsedSidebar = env.FORCE_COLLAPSED_SIDEBAR;
const sidebarEnabled = useMemo(() => readSidebarEnabled(), []);
const location = useLocation();
const { theme } = useTheme();
@@ -42,7 +44,13 @@ function PageLayout() {
// 登录时置位)这一旧默认。这样无论是否走门户登录,「全量↔简洁」都能真正控制顶栏+侧边栏。
const [layoutMode] = useLayoutMode();
const chromeByPreference =
layoutMode === "full" ? true : layoutMode === "compact" ? false : sidebarEnabled;
forceCollapsedSidebar
? false
: layoutMode === "full"
? true
: layoutMode === "compact"
? false
: sidebarEnabled;
// iframe 嵌入模式(外部系统 `&embed=1`,按标签页 sessionStorage):去掉全局导航栏 + 侧边栏,
// 只留问答/会商工作区本体。按 location 重算以跟随页内导航;非嵌入标签页恒为 false,不受影响。
const embedActive = useMemo(
@@ -60,7 +68,8 @@ function PageLayout() {
// 但它常被深链「上一步」从去 chrome 的工作区/新标签打开——那里 chromeByPreference 可能为 false
// (界面模式非全量、或会话级 sidebar 未置位),导致整页没有顶栏/侧栏。故对该路由**强制全局 chrome**
// (仍尊重 embed=1 嵌入态:外部 iframe 套用时仍隐藏)。
const forceChromeForDashboardAgent = location.pathname.includes("/strategy/dashboard-agent");
const forceChromeForDashboardAgent =
!forceCollapsedSidebar && location.pathname.includes("/strategy/dashboard-agent");
const pageSidebarAllowed =
(forceChromeForDashboardAgent || chromeByPreference) &&
!isChatsIframeWorkspacePath(location.pathname) &&
@@ -114,6 +123,7 @@ function PageLayout() {
headerHidden={!pageSidebarAllowed}
compactWorkspaceSidebar={pageSidebarAllowed}
pageSidebarDark={isDark}
forceWorkspaceSidebarCollapsed={forceCollapsedSidebar}
/>
}
/>
+17 -2
View File
@@ -122,6 +122,7 @@ export function WorkspaceLayout({
wrapperOverflowHidden = false,
insetOverflowHidden = false,
pageSidebarDark = false,
forceWorkspaceSidebarCollapsed = false,
}: {
children: ReactNode;
pageSidebarCollapsed: boolean;
@@ -134,6 +135,8 @@ export function WorkspaceLayout({
wrapperOverflowHidden?: boolean;
insetOverflowHidden?: boolean;
pageSidebarDark?: boolean;
/** Deployment-level compact mode: keep only the icon menu rail. */
forceWorkspaceSidebarCollapsed?: boolean;
}) {
// 全量模式下通用问答(/page/workspace/chats*)的历史侧栏默认展开;其余紧凑页
// (如智能体对话,为聊天区更宽而默认收起)维持原默认。
@@ -169,11 +172,19 @@ export function WorkspaceLayout({
key={String(compactWorkspaceSidebar)}
data-page-sidebar-collapsed={pageSidebarCollapsed}
defaultOpen={
defaultHistorySidebarOpen ?? (isGeneralQa || !compactWorkspaceSidebar)
forceWorkspaceSidebarCollapsed
? false
: (defaultHistorySidebarOpen ?? (isGeneralQa || !compactWorkspaceSidebar))
}
open={forceWorkspaceSidebarCollapsed ? false : undefined}
onOpenChange={forceWorkspaceSidebarCollapsed ? () => undefined : undefined}
disableToggle={forceWorkspaceSidebarCollapsed}
>
{!workspaceSidebarHidden && (
<WorkspaceSidebar compactChrome={compactWorkspaceSidebar} />
<WorkspaceSidebar
compactChrome={compactWorkspaceSidebar}
forceCollapsed={forceWorkspaceSidebarCollapsed}
/>
)}
<SidebarInset
className={cn('min-w-0', insetOverflowHidden && 'overflow-hidden')}
@@ -227,6 +238,7 @@ function NewChatEntryRoute({
headerHidden: boolean;
compactWorkspaceSidebar: boolean;
pageSidebarDark: boolean;
forceWorkspaceSidebarCollapsed: boolean;
};
}) {
if (isInIframe()) {
@@ -248,12 +260,14 @@ export default function WorkspaceRoutes({
headerHidden = false,
compactWorkspaceSidebar = false,
pageSidebarDark = false,
forceWorkspaceSidebarCollapsed = false,
}: {
pageSidebarCollapsed?: boolean;
pageSidebarHidden?: boolean;
headerHidden?: boolean;
compactWorkspaceSidebar?: boolean;
pageSidebarDark?: boolean;
forceWorkspaceSidebarCollapsed?: boolean;
}) {
const layoutProps = {
pageSidebarCollapsed,
@@ -261,6 +275,7 @@ export default function WorkspaceRoutes({
headerHidden,
compactWorkspaceSidebar,
pageSidebarDark,
forceWorkspaceSidebarCollapsed,
};
return (
@@ -689,6 +689,58 @@
background-color: #0056C6;
}
/* WeKnora green theme. */
[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 菜单排序:拖拽行 + 拖拽手柄 ───────────────────────────────── */
.light-app-management-page .lam-reorder-row {
cursor: grab;
@@ -7,6 +7,11 @@
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-list-page {
background-color: var(--color-bg-page);
@@ -42,10 +42,11 @@ export const LOGIN_SOURCE_OPTIONS: { label: string; value: string }[] = [
* 我方主题色键。当前启用 red(浅色) / dark-blue(深色);blue 为后续将新增的主题。
* 主题参数的映射以这些键为左侧 key(如 `dark-blue=darkBlue`)。
*/
export type AppThemeColor = "red" | "blue" | "dark-blue";
export type AppThemeColor = "red" | "green" | "blue" | "dark-blue";
/** 我方全部主题色(「应用管理」主题参数逐项配置目标值时遍历;dot 为配色小圆点)。 */
export const APP_THEME_COLORS: { key: AppThemeColor; label: string; dot: string }[] = [
{ key: "green", label: "绿色", dot: "#07C05F" },
{ key: "red", label: "红色", dot: "#F5222D" },
{ key: "blue", label: "蓝色", dot: "#1677FF" },
{ key: "dark-blue", label: "深蓝色", dot: "#002B5C" },
@@ -57,6 +58,7 @@ export const APP_THEME_COLORS: { key: AppThemeColor; label: string; dot: string
*/
const THEME_KEY_ALIASES: Record<AppThemeColor, string[]> = {
red: ["red", "light"],
green: ["green", "lightgreen"],
blue: ["blue"],
"dark-blue": ["dark-blue", "darkblue", "dark"],
};
@@ -69,6 +71,7 @@ export function getCurrentTheme(): AppThemeColor {
if (typeof document !== "undefined") {
const classList = document.documentElement.classList;
if (classList.contains("dark")) return "dark-blue";
if (classList.contains("lightgreen")) return "green";
if (classList.contains("lightblue")) return "blue";
}
return "red";
+118
View File
@@ -407,6 +407,64 @@
--legacy-workspace-brand-rgb: 0 98 217;
}
/* WeKnora 绿色主题:沿用主色 #07C05F、浅底 #E9F8EC 与深强调色 #049B38。 */
.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 {
* {
@apply border-border outline-ring/50;
@@ -1248,6 +1306,43 @@ html.lightblue .tiptap-writing-editor .ProseMirror ::selection {
background: color-mix(in srgb, #55a6fe 35%, transparent);
}
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) ─────────────── */
/* 外层容器,模拟 nz-radio-group */
.custom-radio-group {
@@ -1323,6 +1418,12 @@ html.lightblue .tiptap-writing-editor .ProseMirror ::selection {
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 {
background-color: #feeded;
border-color: rgba(190, 35, 35, 0.5);
@@ -1338,6 +1439,11 @@ html.lightblue .tiptap-writing-editor .ProseMirror ::selection {
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)) {
.bg-primary\/5 { background-color: rgba(190, 35, 35, 0.05); }
.bg-primary\/8 { background-color: rgba(190, 35, 35, 0.08); }
@@ -1388,4 +1494,16 @@ html.lightblue .tiptap-writing-editor .ProseMirror ::selection {
.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\/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); }
}
+31
View File
@@ -195,6 +195,37 @@
--td-bg-color-secondarycontainer-active: #eaf4ff;
}
/* WeKnora green: selected by ThemeContext through data-theme="lightgreen". */
:root[data-theme="lightgreen"] {
--td-brand-color-1: #e9f8ec;
--td-brand-color-2: #d5f4df;
--td-brand-color-3: #c0efcf;
--td-brand-color-4: #a5e9bc;
--td-brand-color-5: #8ce0af;
--td-brand-color-6: #09f479;
--td-brand-color-7: #07c05f;
--td-brand-color-8: #06b04d;
--td-brand-color-9: #049b38;
--td-brand-color-10: #038626;
--td-brand-color: #07c05f;
--td-brand-color-hover: #08dd6e;
--td-brand-color-focus: #e9f8ec;
--td-brand-color-active: #06b04d;
--td-brand-color-disabled: #8ce0af;
--td-brand-color-light: #e9f8ec;
--td-brand-color-light-hover: #d5f4df;
--td-text-color-primary: #203329;
--td-text-color-secondary: #64736a;
--td-text-color-brand: #07c05f;
--td-text-color-link: #049b38;
--td-bg-color-container-hover: #f9fdf9;
--td-bg-color-container-active: #e9f8ec;
--td-bg-color-secondarycontainer: #f1fbf4;
--td-bg-color-secondarycontainer-hover: #e9f8ec;
--td-bg-color-secondarycontainer-active: #d5f4df;
}
/* Deep blue: selected by ThemeContext through .dark + theme-mode="dark". */
:root.dark,
:root[theme-mode="dark"] {
@@ -536,7 +536,9 @@ async def update_publish_squares(body: PublishSquaresUpdateRequest, request: Req
class AppearanceResponse(BaseModel):
"""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_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)")
@@ -544,7 +546,7 @@ class AppearanceResponse(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_agent_view: Literal["card", "list"] | None = Field(default=None)
default_layout_mode: Literal["full", "compact"] | None = Field(default=None)
@@ -575,7 +577,7 @@ def _normalize_compact_shortcut_buttons(
key=key,
paramType="theme",
themeValue=(param.themeValue or "").strip()
or "red=red;blue=blue;dark-blue=dark-blue",
or "red=red;green=green;blue=blue;dark-blue=dark-blue",
)
)
continue
@@ -61,7 +61,7 @@ def _default_shortcut_route_params() -> list[AppearanceRouteParam]:
paramType="login",
source="access_token",
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.
"""
default_theme: Literal["light", "dark", "lightblue"] = Field(
default_theme: Literal["light", "dark", "lightblue", "lightgreen"] = Field(
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="list",