feat(brand): workspace 组件接入品牌文案和 Logo 切换
- layout.tsx: 包裹 BrandProvider + BrandSessionInitializer,SidebarProvider 注入 rootClassName - welcome.tsx: copy.productLabel 替代硬编码,appLogoSrc 条件渲染 Image/文字 - workspace-header.tsx: 侧边栏折叠时显示品牌缩写,展开时显示 Logo 或 appName
This commit is contained in:
parent
62fd2e6f06
commit
0bd9b9bdcb
@ -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}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -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
|
||||||
|
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}
|
||||||
>
|
>
|
||||||
{t.welcome.greeting}
|
{copy.appName}
|
||||||
</AuroraText>
|
</AuroraText>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -59,7 +87,8 @@ export function Welcome({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div> </div>
|
// <div> </div>
|
||||||
|
<></>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user