Compare commits

...

4 Commits

Author SHA1 Message Date
mt
c45bc4d521 style(input): 调整发送按钮为圆形图标样式并更新主题色
- prompt-input.tsx: 发送按钮改为 36x36 圆形,使用 SVG 箭头/方块图标替代文字
- input-box.tsx: 占位区域尺寸同步调整为 36x36
- globals.css: 新增 brand-default/brand-sxwz 品牌 CSS class,新增 ws-interactive-hover token,主题色 #8e47f0 → #150033
2026-06-10 17:52:02 +08:00
mt
9eb494b1b4 feat(brand): 聊天页 sxwz 模式下输入框左移 172px
- ChatPage 接入 useBrand,brand === 'sxwz' 时主容器和输入框 translate-x-[-172px]
- 退出对话回欢迎页时同步关闭 artifacts 面板
2026-06-10 17:51:53 +08:00
mt
0bd9b9bdcb feat(brand): workspace 组件接入品牌文案和 Logo 切换
- layout.tsx: 包裹 BrandProvider + BrandSessionInitializer,SidebarProvider 注入 rootClassName
- welcome.tsx: copy.productLabel 替代硬编码,appLogoSrc 条件渲染 Image/文字
- workspace-header.tsx: 侧边栏折叠时显示品牌缩写,展开时显示 Logo 或 appName
2026-06-10 17:51:46 +08:00
mt
62fd2e6f06 feat(brand): 新增品牌切换系统核心模块
- 定义 Brand 类型、BrandCopy 文案映射、BRAND_COPY 配置
- BrandProvider + useBrand hook 提供 brand/copy/rootClassName
- BrandSessionInitializer 从 URL ?isSxwz= 初始化品牌会话
- sessionStorage 持久化 + URL 参数优先级解析
- parseBrandFromSearchParams 区分为 true/false/无参数三种情况
- 新增 default 品牌 Logo (coxwork.png)
2026-06-10 17:51:34 +08:00
12 changed files with 304 additions and 22 deletions

BIN
frontend/public/coxwork.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -28,6 +28,7 @@ import { MessageList } from "@/components/workspace/messages";
import { ThreadContext } from "@/components/workspace/messages/context"; import { ThreadContext } from "@/components/workspace/messages/context";
import { Tooltip } from "@/components/workspace/tooltip"; import { Tooltip } from "@/components/workspace/tooltip";
import { useSpecificChatMode } from "@/components/workspace/use-chat-mode"; import { useSpecificChatMode } from "@/components/workspace/use-chat-mode";
import { useBrand } from "@/core/brand/provider";
import { Welcome } from "@/components/workspace/welcome"; import { Welcome } from "@/components/workspace/welcome";
import { getAPIClient } from "@/core/api"; import { getAPIClient } from "@/core/api";
import { sanitizeArtifactPaths } from "@/core/artifacts/utils"; import { sanitizeArtifactPaths } from "@/core/artifacts/utils";
@ -48,6 +49,7 @@ import motivationSlogans from "./motivation-slogans.json";
export default function ChatPage() { export default function ChatPage() {
const { t } = useI18n(); const { t } = useI18n();
const { brand } = useBrand();
useSpecificChatMode(); useSpecificChatMode();
const [sloganIndex, setSloganIndex] = useState(0); const [sloganIndex, setSloganIndex] = useState(0);
const [settings, setSettings] = useLocalSettings(); const [settings, setSettings] = useLocalSettings();
@ -345,6 +347,7 @@ export default function ChatPage() {
className={cn( className={cn(
"m-auto flex h-screen min-h-svh overflow-hidden rounded-t-[20px] transition-[width] duration-300 ease-in-out", "m-auto flex h-screen min-h-svh overflow-hidden rounded-t-[20px] transition-[width] duration-300 ease-in-out",
artifactsOpen ? "w-full" : "w-[70%]", artifactsOpen ? "w-full" : "w-[70%]",
brand === "sxwz" && artifactsOpen === false && "translate-x-[-172px]",
)} )}
> >
<div className="relative flex size-full min-h-0 justify-between rounded-t-[20px]"> <div className="relative flex size-full min-h-0 justify-between rounded-t-[20px]">
@ -374,6 +377,7 @@ export default function ChatPage() {
isChatting: false, isChatting: false,
}); });
router.replace(`/workspace/chats/${threadId}?is_chatting=false`) router.replace(`/workspace/chats/${threadId}?is_chatting=false`)
setArtifactsOpen(false);
} }
} }
> >
@ -575,6 +579,7 @@ export default function ChatPage() {
className={cn( className={cn(
"pointer-events-auto relative w-full max-w-[720px]", "pointer-events-auto relative w-full max-w-[720px]",
showWelcomeStyle && "-translate-y-[calc(50vh-96px)]", showWelcomeStyle && "-translate-y-[calc(50vh-96px)]",
brand === "sxwz" && "-translate-x-[172px]"
)} )}
> >
{!(showWelcomeStyle && thread.isThreadLoading) ? ( {!(showWelcomeStyle && thread.isThreadLoading) ? (

View File

@ -14,12 +14,25 @@ import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
import { Toaster } from "@/components/ui/sonner"; import { Toaster } from "@/components/ui/sonner";
import { CommandPalette } from "@/components/workspace/command-palette"; import { CommandPalette } from "@/components/workspace/command-palette";
import { WorkspaceSidebar } from "@/components/workspace/workspace-sidebar"; import { WorkspaceSidebar } from "@/components/workspace/workspace-sidebar";
import { BrandProvider, useBrand } from "@/core/brand/provider";
import { BrandSessionInitializer } from "@/core/brand/provider-client";
import { getLocalSettings, useLocalSettings } from "@/core/settings"; import { getLocalSettings, useLocalSettings } from "@/core/settings";
import { cn } from "@/lib/utils";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
export default function WorkspaceLayout({ export default function WorkspaceLayout({
children, children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<BrandProvider>
<WorkspaceBrandShell>{children}</WorkspaceBrandShell>
</BrandProvider>
);
}
function WorkspaceBrandShell({
children,
}: Readonly<{ children: React.ReactNode }>) { }: Readonly<{ children: React.ReactNode }>) {
const [settings, setSettings] = useLocalSettings(); const [settings, setSettings] = useLocalSettings();
const [open, setOpen] = useState(false); // SSR default: open (matches server render) const [open, setOpen] = useState(false); // SSR default: open (matches server render)
@ -27,6 +40,7 @@ export default function WorkspaceLayout({
const pressedKeysRef = useRef<Set<string>>(new Set()); const pressedKeysRef = useRef<Set<string>>(new Set());
const comboTriggeredRef = useRef(false); const comboTriggeredRef = useRef(false);
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const { rootClassName } = useBrand();
// iframe 技能模式mode=skill时隐藏侧边栏 // iframe 技能模式mode=skill时隐藏侧边栏
const isSkillMode = searchParams.get("mode") === "skill"; const isSkillMode = searchParams.get("mode") === "skill";
@ -110,8 +124,9 @@ export default function WorkspaceLayout({
); );
return ( return (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<BrandSessionInitializer />
<SidebarProvider <SidebarProvider
className="h-screen" className={cn("h-screen", rootClassName)}
open={open} open={open}
onOpenChange={handleOpenChange} onOpenChange={handleOpenChange}
> >

View File

@ -1157,7 +1157,11 @@ export const PromptInputSubmit = ({
? !!disabled ? !!disabled
: disabled || !hasContent || isSubmitted; : disabled || !hasContent || isSubmitted;
let Icon = <ArrowUpIcon className="size-4" />; // let Icon = <ArrowUpIcon className="size-4" />;
let Icon = <svg width="12" height="16" viewBox="0 0 12 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.75 14.75V0.75M0.75 5.75L5.75 0.75L10.75 5.75" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>;
let text: string = t.inputBox.submit; let text: string = t.inputBox.submit;
@ -1165,11 +1169,13 @@ export const PromptInputSubmit = ({
Icon = <Loader2Icon className="size-4 animate-spin" />; Icon = <Loader2Icon className="size-4 animate-spin" />;
text = t.inputBox.submitting; text = t.inputBox.submitting;
} else if (status === "streaming") { } else if (status === "streaming") {
Icon = <SquareIcon className="size-4" />; Icon = <svg className="!w-[12px] !h-[12px]" width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="12" height="12" rx="2" fill="white"/>
</svg>;
text = t.inputBox.stop; text = t.inputBox.stop;
} else if (status === "error") { } else if (status === "error") {
// 没有报错状态先用error状态代替 // 没有报错状态先用error状态代替
Icon = <XIcon className="size-4" />; // Icon = <XIcon className="size-4" />;
// MARK: 这里后端没有返回错误信息,先写死一个文本 // MARK: 这里后端没有返回错误信息,先写死一个文本
text = t.inputBox.submit; text = t.inputBox.submit;
} }
@ -1180,10 +1186,10 @@ export const PromptInputSubmit = ({
aria-label="Submit" aria-label="Submit"
// 被button{bgc:#fff}覆盖了,只能加"!" // 被button{bgc:#fff}覆盖了,只能加"!"
className={cn( className={cn(
"h-[40px] w-[140px] rounded-[10px] border-0 font-bold transition-all", "h-[36px] w-[36px] rounded-[50%] border-0 font-bold transition-all ",
isDisabled isDisabled
? "cursor-not-allowed !bg-gray-200 text-gray-400" ? "cursor-not-allowed !bg-[#15003399] text-gray-400"
: "!bg-[#F0E8FB] text-[#8E47F0] hover:!bg-[#8E47F0] hover:text-[#FFFFFF]", : "!bg-[#150033] text-[#8E47F0] hover:text-[#FFFFFF]",
className, className,
)} )}
size={size} size={size}
@ -1192,8 +1198,8 @@ export const PromptInputSubmit = ({
disabled={isDisabled} disabled={isDisabled}
{...props} {...props}
> >
{/* {children ?? Icon} */} {children ?? Icon}
{text} {/* {text} */}
</InputGroupButton> </InputGroupButton>
</Tooltip> </Tooltip>
); );

View File

@ -151,7 +151,7 @@ function WorkspaceToolButton({
return ( return (
<PromptInputButton <PromptInputButton
className={cn( className={cn(
"group h-full rounded-[10px] p-[10px]! hover:bg-ws-surface-subtle hover:text-ws-interactive-primary", "group h-full rounded-[10px] p-[10px]! hover:bg-ws-interactive-hover hover:text-ws-interactive-primary",
className, className,
)} )}
{...props} {...props}
@ -1050,7 +1050,7 @@ export function InputBox({
</ModelSelector> */} </ModelSelector> */}
<PromptInputTools> <PromptInputTools>
{/* 占位符 */} {/* 占位符 */}
<div className="w-[150px] h-[40px]"></div> <div className="w-[36px] h-[36px]"></div>
</PromptInputTools> </PromptInputTools>
</PromptInputFooter> </PromptInputFooter>
<PromptInputSubmit <PromptInputSubmit

View File

@ -1,8 +1,10 @@
"use client"; "use client";
import Image from "next/image";
import { useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
import { useMemo } from "react"; import { useMemo } from "react";
import { useBrand } from "@/core/brand/provider";
import { useI18n } from "@/core/i18n/hooks"; import { useI18n } from "@/core/i18n/hooks";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@ -16,6 +18,7 @@ export function Welcome({
mode?: "ultra" | "pro" | "thinking" | "flash"; mode?: "ultra" | "pro" | "thinking" | "flash";
}) { }) {
const { t } = useI18n(); const { t } = useI18n();
const { copy } = useBrand();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const isUltra = useMemo(() => mode === "ultra", [mode]); const isUltra = useMemo(() => mode === "ultra", [mode]);
const colors = useMemo(() => { const colors = useMemo(() => {
@ -39,12 +42,37 @@ export function Welcome({
className="flex items-center gap-2" className="flex items-center gap-2"
style={{ fontFamily: '"Microsoft YaHei"' }} style={{ fontFamily: '"Microsoft YaHei"' }}
> >
<AuroraText {/* <AuroraText
className="text-center text-[18px] leading-normal font-normal" className="text-center text-[18px] leading-normal font-normal"
colors={colors} colors={colors}
> >
{t.welcome.greeting} {copy.productLabel}
</AuroraText> </AuroraText> */}
<span className="text-[18px] font-normal text-foreground/70">
{copy.productLabel}
</span>
<span className="text-[18px] font-normal text-foreground/70">
·
</span>
{copy.appLogoSrc ? (
<Image
src={copy.appLogoSrc}
alt={copy.appLogoAlt ?? copy.appName}
width={104}
height={16}
draggable={false}
// className="h-[16px] w-auto"
priority
/>
) : (
<AuroraText
className="text-center text-[18px] leading-normal font-normal"
colors={colors}
>
{copy.appName}
</AuroraText>
)}
</div> </div>
)} )}
</div> </div>
@ -59,7 +87,8 @@ export function Welcome({
)} )}
</div> </div>
) : ( ) : (
<div> </div> // <div> </div>
<></>
)} )}
</div> </div>
); );

View File

@ -1,6 +1,7 @@
"use client"; "use client";
import { MessageSquarePlus } from "lucide-react"; import { MessageSquarePlus } from "lucide-react";
import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { toast } from "sonner"; import { toast } from "sonner";
@ -13,6 +14,7 @@ import {
useSidebar, useSidebar,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import { useThreadChat } from "@/components/workspace/chats"; import { useThreadChat } from "@/components/workspace/chats";
import { useBrand } from "@/core/brand/provider";
import { useI18n } from "@/core/i18n/hooks"; import { useI18n } from "@/core/i18n/hooks";
import { POST_MESSAGE_TYPES, sendToParent } from "@/core/iframe-messages"; import { POST_MESSAGE_TYPES, sendToParent } from "@/core/iframe-messages";
import { env } from "@/env"; import { env } from "@/env";
@ -21,10 +23,14 @@ import { copyToClipboard } from "@/lib/utils";
export function WorkspaceHeader({ className }: { className?: string }) { export function WorkspaceHeader({ className }: { className?: string }) {
const { t } = useI18n(); const { t } = useI18n();
const { copy } = useBrand();
const { state } = useSidebar(); const { state } = useSidebar();
const pathname = usePathname(); const pathname = usePathname();
const { threadId } = useThreadChat(); const { threadId } = useThreadChat();
const threadUrl = threadId ? `/workspace/chats/${threadId}` : ""; const threadUrl = threadId ? `/workspace/chats/${threadId}` : "";
const compactAppName = copy.appName.startsWith("cox")
? `C${copy.appName.charAt(3).toUpperCase()}`
: copy.appName.slice(0, 2).toUpperCase();
const handleCopyThreadId = async () => { const handleCopyThreadId = async () => {
if (!threadId) return; if (!threadId) return;
@ -51,7 +57,7 @@ export function WorkspaceHeader({ className }: { className?: string }) {
{state === "collapsed" ? ( {state === "collapsed" ? (
<div className="group-has-data-[collapsible=icon]/sidebar-wrapper:-translate-y flex w-full cursor-pointer items-center justify-center"> <div className="group-has-data-[collapsible=icon]/sidebar-wrapper:-translate-y flex w-full cursor-pointer items-center justify-center">
<div className="text-primary block pt-1 font-serif group-hover/workspace-header:hidden"> <div className="text-primary block pt-1 font-serif group-hover/workspace-header:hidden">
XC {compactAppName}
</div> </div>
<SidebarTrigger className="hidden pl-2 group-hover/workspace-header:block" /> <SidebarTrigger className="hidden pl-2 group-hover/workspace-header:block" />
</div> </div>
@ -62,9 +68,19 @@ export function WorkspaceHeader({ className }: { className?: string }) {
{t.workspaceHeader.sidebarTitle} {t.workspaceHeader.sidebarTitle}
</Link> </Link>
) : ( ) : (
<div className="text-primary ml-2 cursor-default font-serif"> <div className="text-primary ml-2 flex cursor-default items-center gap-2 font-serif">
{/* TODO: 测试标识 */} {copy.appLogoSrc ? (
XClaw{" "} <Image
src={copy.appLogoSrc}
alt={copy.appLogoAlt ?? copy.appName}
width={104}
height={16}
className="h-4 w-auto"
priority
/>
) : (
copy.appName
)}
<span className="text-sm text-ws-text-subtle-strong">v3.3.0 </span>{" "} <span className="text-sm text-ws-text-subtle-strong">v3.3.0 </span>{" "}
<span <span
className={cn( className={cn(
@ -80,7 +96,6 @@ export function WorkspaceHeader({ className }: { className?: string }) {
> >
id:{threadId ? threadId.slice(0, 5) : "-"} id:{threadId ? threadId.slice(0, 5) : "-"}
</span> </span>
{" "}
{threadId && ( {threadId && (
<a <a
href={threadUrl} href={threadUrl}

View File

@ -0,0 +1,39 @@
import assert from "node:assert/strict";
import test from "node:test";
const {
BRAND_SESSION_STORAGE_KEY,
getBrandRootClassName,
parseBrandFromSearchParams,
resolveBrandSession,
} = await import(new URL("./index.ts", import.meta.url).href);
void test("parseBrandFromSearchParams returns correct brand per param value", () => {
assert.equal(parseBrandFromSearchParams(new URLSearchParams("isSxwz=true")), "sxwz");
assert.equal(parseBrandFromSearchParams(new URLSearchParams("isSxwz=false")), "default");
assert.equal(parseBrandFromSearchParams(new URLSearchParams("")), null);
});
void test("resolveBrandSession falls back to default without url or storage", () => {
assert.equal(resolveBrandSession({ urlBrand: null, storedBrand: null }), "default");
});
void test("resolveBrandSession keeps stored sxwz when later url omits the flag", () => {
assert.equal(resolveBrandSession({ urlBrand: null, storedBrand: "sxwz" }), "sxwz");
});
void test("resolveBrandSession downgrades stored sxwz when url explicitly sets isSxwz=false", () => {
const urlBrand = parseBrandFromSearchParams(new URLSearchParams("isSxwz=false"));
assert.equal(resolveBrandSession({ urlBrand, storedBrand: "sxwz" }), "default");
});
void test("resolveBrandSession upgrades to sxwz when url flag is true", () => {
const urlBrand = parseBrandFromSearchParams(new URLSearchParams("isSxwz=true"));
assert.equal(resolveBrandSession({ urlBrand, storedBrand: "default" }), "sxwz");
});
void test("getBrandRootClassName returns stable workspace hook classes", () => {
assert.equal(getBrandRootClassName("default"), "brand-default");
assert.equal(getBrandRootClassName("sxwz"), "brand-sxwz");
assert.equal(BRAND_SESSION_STORAGE_KEY, "deerflow.brand-session");
});

View File

@ -0,0 +1,76 @@
export const BRAND_SESSION_STORAGE_KEY = "deerflow.brand-session";
export const DEFAULT_BRAND = "default" as const;
const SXWZ_BRAND = "sxwz" as const;
export type Brand = typeof DEFAULT_BRAND | typeof SXWZ_BRAND;
export type BrandCopy = {
productLabel: string;
appName: string;
appLogoSrc?: string;
appLogoAlt?: string;
};
export const BRAND_COPY: Record<Brand, BrandCopy> = {
default: {
productLabel: "轻办公",
appName: "coxworker",
appLogoSrc: "/coxwork.png",
appLogoAlt: "coxworker",
},
sxwz: {
productLabel: "在线教育智能体",
appName: "coxstudy",
},
};
export function isBrand(value: string | null): value is Brand {
return value === DEFAULT_BRAND || value === SXWZ_BRAND;
}
export function parseBrandFromSearchParams(
searchParams: URLSearchParams,
): Brand | null {
const value = searchParams.get("isSxwz");
if (value === "true") return SXWZ_BRAND;
if (value === "false") return DEFAULT_BRAND;
return null;
}
export function resolveBrandSession({
urlBrand,
storedBrand,
}: {
urlBrand: Brand | null;
storedBrand: Brand | null;
}): Brand {
if (urlBrand === SXWZ_BRAND) {
return SXWZ_BRAND;
}
if (urlBrand === DEFAULT_BRAND) {
return DEFAULT_BRAND;
}
if (storedBrand === SXWZ_BRAND) {
return SXWZ_BRAND;
}
return DEFAULT_BRAND;
}
export function getBrandRootClassName(brand: Brand): string {
return brand === SXWZ_BRAND ? "brand-sxwz" : "brand-default";
}
export function readStoredBrand(storage: Pick<Storage, "getItem">): Brand | null {
const value = storage.getItem(BRAND_SESSION_STORAGE_KEY);
return isBrand(value) ? value : null;
}
export function writeStoredBrand(
storage: Pick<Storage, "setItem">,
brand: Brand,
): void {
storage.setItem(BRAND_SESSION_STORAGE_KEY, brand);
}

View File

@ -0,0 +1,34 @@
"use client";
import { useSearchParams } from "next/navigation";
import { useLayoutEffect } from "react";
import { useBrand } from "./provider";
import {
parseBrandFromSearchParams,
readStoredBrand,
resolveBrandSession,
writeStoredBrand,
} from "./index";
export function BrandSessionInitializer() {
const searchParams = useSearchParams();
const { setBrand } = useBrand();
useLayoutEffect(() => {
const storedBrand = readStoredBrand(window.sessionStorage);
const urlBrand = parseBrandFromSearchParams(
new URLSearchParams(searchParams.toString()),
);
const resolvedBrand = resolveBrandSession({
urlBrand,
storedBrand,
});
writeStoredBrand(window.sessionStorage, resolvedBrand);
setBrand(resolvedBrand);
}, [searchParams, setBrand]);
return null;
}

View File

@ -0,0 +1,55 @@
"use client";
import { createContext, useContext, useState, type ReactNode } from "react";
import {
BRAND_COPY,
DEFAULT_BRAND,
getBrandRootClassName,
type Brand,
} from "./index";
type BrandContextValue = {
brand: Brand;
copy: (typeof BRAND_COPY)[Brand];
rootClassName: string;
setBrand: (brand: Brand) => void;
};
const BrandContext = createContext<BrandContextValue | null>(null);
function getInitialBrand(): Brand {
if (typeof window === "undefined") {
return DEFAULT_BRAND;
}
const storedBrand = window.sessionStorage.getItem("deerflow.brand-session");
return storedBrand === "sxwz" ? "sxwz" : DEFAULT_BRAND;
}
export function BrandProvider({ children }: { children: ReactNode }) {
const [brand, setBrand] = useState<Brand>(getInitialBrand);
return (
<BrandContext.Provider
value={{
brand,
copy: BRAND_COPY[brand],
rootClassName: getBrandRootClassName(brand),
setBrand,
}}
>
{children}
</BrandContext.Provider>
);
}
export function useBrand() {
const context = useContext(BrandContext);
if (!context) {
throw new Error("useBrand must be used within BrandProvider");
}
return context;
}

View File

@ -68,6 +68,12 @@
@source inline("bg-{background,muted,primary,secondary,accent}"); @source inline("bg-{background,muted,primary,secondary,accent}");
@source inline("border-{border,input}"); @source inline("border-{border,input}");
.brand-default {
}
.brand-sxwz {
}
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *));
@theme { @theme {
@ -206,6 +212,7 @@
--color-ws-surface-subtle: var(--ws-color-surface-subtle); --color-ws-surface-subtle: var(--ws-color-surface-subtle);
--color-ws-surface-elevated: var(--ws-color-surface-elevated); --color-ws-surface-elevated: var(--ws-color-surface-elevated);
--color-ws-interactive-primary: var(--ws-color-interactive-primary); --color-ws-interactive-primary: var(--ws-color-interactive-primary);
--color-ws-interactive-hover: var(--ws-color-interactive-hover);
--color-ws-line-default: var(--ws-color-line-default); --color-ws-line-default: var(--ws-color-line-default);
--color-ws-text-muted: var(--ws-color-text-muted); --color-ws-text-muted: var(--ws-color-text-muted);
--color-ws-icon-muted: var(--ws-color-icon-muted); --color-ws-icon-muted: var(--ws-color-icon-muted);
@ -311,7 +318,8 @@
--ws-color-fg-primary: #333333; --ws-color-fg-primary: #333333;
--ws-color-surface-subtle: #f9f8fa; --ws-color-surface-subtle: #f9f8fa;
--ws-color-surface-elevated: #fbfafc; --ws-color-surface-elevated: #fbfafc;
--ws-color-interactive-primary: #8e47f0; --ws-color-interactive-hover: #1500331A;
--ws-color-interactive-primary: #150033;
--ws-color-line-default: #e4e7ec; --ws-color-line-default: #e4e7ec;
--ws-color-text-muted: #667085; --ws-color-text-muted: #667085;
--ws-color-icon-muted: #a3a1a1; --ws-color-icon-muted: #a3a1a1;
@ -364,7 +372,7 @@
--ws-color-fg-primary: #f5f5f5; --ws-color-fg-primary: #f5f5f5;
--ws-color-surface-subtle: #1f1f1f; --ws-color-surface-subtle: #1f1f1f;
--ws-color-surface-elevated: #24222a; --ws-color-surface-elevated: #24222a;
--ws-color-interactive-primary: #b987ff; --ws-color-interactive-primary: #150033;
--ws-color-line-default: #3b3f48; --ws-color-line-default: #3b3f48;
--ws-color-text-muted: #98a2b3; --ws-color-text-muted: #98a2b3;
--ws-color-icon-muted: #d0d0d0; --ws-color-icon-muted: #d0d0d0;