From 61a6931bb170b8b269ac6ddfdc36b9dcf79e4391 Mon Sep 17 00:00:00 2001 From: Yaojia Wang Date: Fri, 10 Jul 2026 17:43:46 +0200 Subject: [PATCH] =?UTF-8?q?refactor(frontend):=20=E5=88=A0=E9=A1=B6?= =?UTF-8?q?=E9=83=A8=E3=80=8CAI=20=E5=B7=A5=E5=85=B7=E6=9D=A1=E3=80=8D?= =?UTF-8?q?=EF=BC=8C--chrome=20=E5=BD=92=E4=BD=8D=204rem=EF=BC=88=E5=86=99?= =?UTF-8?q?=E4=BD=9C=E5=8F=B0=20UX=20P0-3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 顶栏「AI 工具条」5 项全是重复左栏的导航链接,且让「写本章」同时是导航 Link 与生成动作两种相反含义。删掉后导航只归左栏,同时消灭最严重的三重复 + 假「AI」标签 + 「写本章」双重含义,并省下 48px(--chrome 7rem→4rem)。 死代码 AiToolbar/AiToolbarMoreMenu/ai-tools 全仓仅 AppShell 引用,一并移除。 --- apps/web/components/AiToolbar.tsx | 93 ---------------------- apps/web/components/AiToolbarMoreMenu.tsx | 95 ----------------------- apps/web/components/AppShell.tsx | 6 +- apps/web/lib/nav/ai-tools.test.ts | 44 ----------- apps/web/lib/nav/ai-tools.ts | 35 --------- 5 files changed, 1 insertion(+), 272 deletions(-) delete mode 100644 apps/web/components/AiToolbar.tsx delete mode 100644 apps/web/components/AiToolbarMoreMenu.tsx delete mode 100644 apps/web/lib/nav/ai-tools.test.ts delete mode 100644 apps/web/lib/nav/ai-tools.ts diff --git a/apps/web/components/AiToolbar.tsx b/apps/web/components/AiToolbar.tsx deleted file mode 100644 index fc42561..0000000 --- a/apps/web/components/AiToolbar.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import Link from "next/link"; -import { - BookOpen, - ClipboardCheck, - ListTree, - PenLine, - Sparkles, - type LucideIcon, -} from "lucide-react"; - -import { AiToolbarMoreMenu } from "@/components/AiToolbarMoreMenu"; -import { - aiToolItems, - primaryAiToolItems, - secondaryAiToolItems, -} from "@/lib/nav/ai-tools"; -import type { ActiveNav } from "@/lib/nav/items"; -import { buttonClass } from "@/lib/ui/variants"; - -interface AiToolbarProps { - projectId: string; - activeNav?: ActiveNav; -} - -// T4-a · 项目内页常驻 AI 工具条(顶栏下,UX §3/§5)。 -// 桌面展示完整工具条;窄屏只保留写本章/审稿,把低频入口收进更多菜单。 -export function AiToolbar({ projectId, activeNav }: AiToolbarProps) { - const primaryItems = primaryAiToolItems(projectId); - const secondaryItems = secondaryAiToolItems(projectId); - return ( - - ); -} - -function toolIcon(key: ActiveNav): LucideIcon { - if (key === "write") return PenLine; - if (key === "review") return ClipboardCheck; - if (key === "outline") return ListTree; - if (key === "codex") return BookOpen; - return Sparkles; -} - -function toolClassName(isPrimary: boolean, isActive: boolean): string { - if (isActive) { - return buttonClass({ variant: "outline", size: "sm" }); - } - if (isPrimary) { - return buttonClass({ variant: "primary", size: "sm" }); - } - return buttonClass({ variant: "secondary", size: "sm" }); -} diff --git a/apps/web/components/AiToolbarMoreMenu.tsx b/apps/web/components/AiToolbarMoreMenu.tsx deleted file mode 100644 index 80843ee..0000000 --- a/apps/web/components/AiToolbarMoreMenu.tsx +++ /dev/null @@ -1,95 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { MoreHorizontal } from "lucide-react"; -import { useEffect, useId, useRef, useState } from "react"; - -import type { AiToolItem } from "@/lib/nav/ai-tools"; -import type { ActiveNav } from "@/lib/nav/items"; -import { buttonClass, cn, focusRing } from "@/lib/ui/variants"; - -interface AiToolbarMoreMenuProps { - items: AiToolItem[]; - activeNav?: ActiveNav; -} - -export function AiToolbarMoreMenu({ - items, - activeNav, -}: AiToolbarMoreMenuProps) { - const [open, setOpen] = useState(false); - const menuId = useId(); - const rootRef = useRef(null); - const buttonRef = useRef(null); - - useEffect(() => { - if (!open) return; - - const onKeyDown = (event: KeyboardEvent): void => { - if (event.key === "Escape") { - setOpen(false); - buttonRef.current?.focus(); - } - }; - - const onPointerDown = (event: PointerEvent): void => { - const target = event.target; - if (target instanceof Node && !rootRef.current?.contains(target)) { - setOpen(false); - } - }; - - window.addEventListener("keydown", onKeyDown); - window.addEventListener("pointerdown", onPointerDown); - return () => { - window.removeEventListener("keydown", onKeyDown); - window.removeEventListener("pointerdown", onPointerDown); - }; - }, [open]); - - return ( -
- - {open ? ( - // 诚实的 Tab-only 折叠面板:普通导航链接,不冒充 ARIA menu(无 roving tabindex/方向键)。 - - ) : null} -
- ); -} diff --git a/apps/web/components/AppShell.tsx b/apps/web/components/AppShell.tsx index 81ef7cb..c7e6f80 100644 --- a/apps/web/components/AppShell.tsx +++ b/apps/web/components/AppShell.tsx @@ -4,7 +4,6 @@ import { Settings } from "lucide-react"; import type { ActiveNav } from "@/lib/nav/items"; import { buttonClass, focusRing } from "@/lib/ui/variants"; -import { AiToolbar } from "./AiToolbar"; import { CommandSearchButton } from "./command/CommandPaletteMount"; import { LeftNav } from "./LeftNav"; import { NavDrawer } from "./NavDrawer"; @@ -32,7 +31,7 @@ export function AppShell({ return (
- {projectId ? ( - - ) : null}
{ - it("returns 5 items", () => { - expect(aiToolItems("p1")).toHaveLength(5); - }); - - it("leads with 写本章 (primary, key write)", () => { - const [first] = aiToolItems("p1"); - expect(first?.label).toBe("写本章"); - expect(first?.primary).toBe(true); - expect(first?.key).toBe("write"); - }); - - it("scopes every href under the project path", () => { - for (const item of aiToolItems("abc")) { - expect(item.href.startsWith("/projects/abc/")).toBe(true); - } - }); - - it("maps 审稿 to the review page and key", () => { - const review = aiToolItems("p1").find((item) => item.label === "审稿"); - expect(review?.href).toBe("/projects/p1/review"); - expect(review?.key).toBe("review"); - }); - - it("keeps high-frequency tools outside the mobile more menu", () => { - expect(primaryAiToolItems("p1").map((item) => item.key)).toEqual([ - "write", - "review", - ]); - expect(secondaryAiToolItems("p1").map((item) => item.key)).toEqual([ - "outline", - "codex", - "toolbox", - ]); - }); -}); diff --git a/apps/web/lib/nav/ai-tools.ts b/apps/web/lib/nav/ai-tools.ts deleted file mode 100644 index 64b628f..0000000 --- a/apps/web/lib/nav/ai-tools.ts +++ /dev/null @@ -1,35 +0,0 @@ -// T4-a · 顶部 AI 工具条数据(纯逻辑,UX §3/§5)。 -// 把高频 AI 动作(写本章/审稿/大纲/设定库/工具箱)做成常驻一排,根治「命令面板太隐蔽」。 -// 只露已实现能力;新生成器(创作工具箱 B 组)落地后并入工具箱入口。 -// 组件只渲染;条目与激活键集中在此,复用 ActiveNav 高亮规则。 - -import type { ActiveNav } from "./items"; - -export interface AiToolItem { - href: string; - label: string; - // 高亮键(与 AppShell activeNav 比对)。 - key: ActiveNav; - // 主动作(写本章)朱砂强调,其余次级。 - primary?: boolean; -} - -// 项目内页 AI 工具条条目(按已实现能力露出)。 -export function aiToolItems(projectId: string): AiToolItem[] { - const base = `/projects/${projectId}`; - return [ - { href: `${base}/write`, label: "写本章", key: "write", primary: true }, - { href: `${base}/review`, label: "审稿", key: "review" }, - { href: `${base}/outline`, label: "大纲", key: "outline" }, - { href: `${base}/codex`, label: "设定库", key: "codex" }, - { href: `${base}/toolbox`, label: "工具箱", key: "toolbox" }, - ]; -} - -export function primaryAiToolItems(projectId: string): AiToolItem[] { - return aiToolItems(projectId).slice(0, 2); -} - -export function secondaryAiToolItems(projectId: string): AiToolItem[] { - return aiToolItems(projectId).slice(2); -}