Compare commits
No commits in common. "c45bc4d5219d280f36bd8e07f881b78e3a3d0564" and "63563ce6a3d0b97ace843a83b42c2ff4b7d1dd9c" have entirely different histories.
c45bc4d521
...
63563ce6a3
Binary file not shown.
|
Before Width: | Height: | Size: 6.4 KiB |
@ -28,7 +28,6 @@ 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";
|
||||||
@ -49,7 +48,6 @@ 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();
|
||||||
@ -347,7 +345,6 @@ 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]">
|
||||||
@ -377,7 +374,6 @@ 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -579,7 +575,6 @@ 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) ? (
|
||||||
|
|||||||
@ -14,25 +14,12 @@ 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)
|
||||||
@ -40,7 +27,6 @@ function WorkspaceBrandShell({
|
|||||||
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";
|
||||||
@ -124,9 +110,8 @@ function WorkspaceBrandShell({
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<BrandSessionInitializer />
|
|
||||||
<SidebarProvider
|
<SidebarProvider
|
||||||
className={cn("h-screen", rootClassName)}
|
className="h-screen"
|
||||||
open={open}
|
open={open}
|
||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1157,11 +1157,7 @@ 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;
|
||||||
|
|
||||||
@ -1169,13 +1165,11 @@ 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 = <svg className="!w-[12px] !h-[12px]" width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
Icon = <SquareIcon className="size-4" />;
|
||||||
<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;
|
||||||
}
|
}
|
||||||
@ -1186,10 +1180,10 @@ export const PromptInputSubmit = ({
|
|||||||
aria-label="Submit"
|
aria-label="Submit"
|
||||||
// 被button{bgc:#fff}覆盖了,只能加"!"
|
// 被button{bgc:#fff}覆盖了,只能加"!"
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-[36px] w-[36px] rounded-[50%] border-0 font-bold transition-all ",
|
"h-[40px] w-[140px] rounded-[10px] border-0 font-bold transition-all",
|
||||||
isDisabled
|
isDisabled
|
||||||
? "cursor-not-allowed !bg-[#15003399] text-gray-400"
|
? "cursor-not-allowed !bg-gray-200 text-gray-400"
|
||||||
: "!bg-[#150033] text-[#8E47F0] hover:text-[#FFFFFF]",
|
: "!bg-[#F0E8FB] text-[#8E47F0] hover:!bg-[#8E47F0] hover:text-[#FFFFFF]",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
size={size}
|
size={size}
|
||||||
@ -1198,8 +1192,8 @@ export const PromptInputSubmit = ({
|
|||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children ?? Icon}
|
{/* {children ?? Icon} */}
|
||||||
{/* {text} */}
|
{text}
|
||||||
</InputGroupButton>
|
</InputGroupButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -151,7 +151,7 @@ function WorkspaceToolButton({
|
|||||||
return (
|
return (
|
||||||
<PromptInputButton
|
<PromptInputButton
|
||||||
className={cn(
|
className={cn(
|
||||||
"group h-full rounded-[10px] p-[10px]! hover:bg-ws-interactive-hover hover:text-ws-interactive-primary",
|
"group h-full rounded-[10px] p-[10px]! hover:bg-ws-surface-subtle hover:text-ws-interactive-primary",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -1050,7 +1050,7 @@ export function InputBox({
|
|||||||
</ModelSelector> */}
|
</ModelSelector> */}
|
||||||
<PromptInputTools>
|
<PromptInputTools>
|
||||||
{/* 占位符 */}
|
{/* 占位符 */}
|
||||||
<div className="w-[36px] h-[36px]"></div>
|
<div className="w-[150px] h-[40px]"></div>
|
||||||
</PromptInputTools>
|
</PromptInputTools>
|
||||||
</PromptInputFooter>
|
</PromptInputFooter>
|
||||||
<PromptInputSubmit
|
<PromptInputSubmit
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
"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";
|
||||||
|
|
||||||
@ -18,7 +16,6 @@ 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(() => {
|
||||||
@ -42,37 +39,12 @@ export function Welcome({
|
|||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
style={{ fontFamily: '"Microsoft YaHei"' }}
|
style={{ fontFamily: '"Microsoft YaHei"' }}
|
||||||
>
|
>
|
||||||
{/* <AuroraText
|
|
||||||
className="text-center text-[18px] leading-normal font-normal"
|
|
||||||
colors={colors}
|
|
||||||
>
|
|
||||||
{copy.productLabel}
|
|
||||||
</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
|
<AuroraText
|
||||||
className="text-center text-[18px] leading-normal font-normal"
|
className="text-center text-[18px] leading-normal font-normal"
|
||||||
colors={colors}
|
colors={colors}
|
||||||
>
|
>
|
||||||
{copy.appName}
|
{t.welcome.greeting}
|
||||||
</AuroraText>
|
</AuroraText>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -87,8 +59,7 @@ export function Welcome({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
// <div> </div>
|
<div> </div>
|
||||||
<></>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
"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";
|
||||||
@ -14,7 +13,6 @@ 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";
|
||||||
@ -23,14 +21,10 @@ 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;
|
||||||
@ -57,7 +51,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">
|
||||||
{compactAppName}
|
XC
|
||||||
</div>
|
</div>
|
||||||
<SidebarTrigger className="hidden pl-2 group-hover/workspace-header:block" />
|
<SidebarTrigger className="hidden pl-2 group-hover/workspace-header:block" />
|
||||||
</div>
|
</div>
|
||||||
@ -68,19 +62,9 @@ export function WorkspaceHeader({ className }: { className?: string }) {
|
|||||||
{t.workspaceHeader.sidebarTitle}
|
{t.workspaceHeader.sidebarTitle}
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-primary ml-2 flex cursor-default items-center gap-2 font-serif">
|
<div className="text-primary ml-2 cursor-default font-serif">
|
||||||
{copy.appLogoSrc ? (
|
{/* TODO: 测试标识 */}
|
||||||
<Image
|
XClaw{" "}
|
||||||
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(
|
||||||
@ -96,6 +80,7 @@ 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}
|
||||||
|
|||||||
@ -1,39 +0,0 @@
|
|||||||
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");
|
|
||||||
});
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
"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;
|
|
||||||
}
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
"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;
|
|
||||||
}
|
|
||||||
@ -68,12 +68,6 @@
|
|||||||
@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 {
|
||||||
@ -212,7 +206,6 @@
|
|||||||
--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);
|
||||||
@ -318,8 +311,7 @@
|
|||||||
--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-hover: #1500331A;
|
--ws-color-interactive-primary: #8e47f0;
|
||||||
--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;
|
||||||
@ -372,7 +364,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: #150033;
|
--ws-color-interactive-primary: #b987ff;
|
||||||
--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;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user