feat(ui): 工作台精修 + 拆分(P3-3..7)

- Workbench.tsx 937→467 行,抽出 AiToolbar/WorkbenchToolbar/MobileContextBar
- P3-3 AI 工具条分组留白 + 眉题 + 主 CTA 凸显
- P3-4 专注写作模式(useFocusMode hook+vitest,隐藏侧栏加宽正文)+ 左双栏面色区分
- P3-5 右栏本章参考小标题去衬线(font-sans) + 空态引导(无标题保 h2→h3 大纲)
- P3-6 底栏信息/操作/状态三区 + TOC 当前章高亮;TOC 状态点因无每章状态数据跳过
- P3-7 展示层流式跟随滚动(不触碰 SSE 逻辑,尊重 reduced-motion)
- AppShell 加可选 hideNav(加法式,默认 false)
This commit is contained in:
Yaojia Wang
2026-07-11 08:04:56 +02:00
parent 67e30a6863
commit 5674158707
9 changed files with 787 additions and 549 deletions

View File

@@ -1,10 +1,19 @@
"use client";
import { Minus, Pin, PinOff, Plus, RotateCcw, Undo2, X } from "lucide-react";
import type { ReactNode } from "react";
import {
Minus,
Pin,
PinOff,
Plus,
RotateCcw,
Sparkles,
Undo2,
X,
} from "lucide-react";
import { ThinkingIndicator } from "@/components/ThinkingIndicator";
import { Button } from "@/components/ui/Button";
import { SectionHeader } from "@/components/ui/SectionHeader";
import { StatusNote } from "@/components/ui/StatusNote";
import { buttonClass } from "@/lib/ui/variants";
import {
@@ -35,6 +44,25 @@ export function ChapterAssistant({ projectId, chapterNo }: ChapterAssistantProps
);
}
// 窄栏小标题P3-5font-sans + eyebrow 字号/正字距(对齐 Eyebrow 眉题的视觉),
// 但保留 <h3> 语义(不降级为 <p>,守住标题大纲 a11y。可带右侧行内控件如步进器
function PanelLabel({
children,
action,
}: {
children: ReactNode;
action?: ReactNode;
}) {
return (
<div className="flex items-start justify-between gap-3">
<h3 className="font-sans text-eyebrow uppercase tracking-wide text-muted-soft">
{children}
</h3>
{action ? <div className="shrink-0">{action}</div> : null}
</div>
);
}
export function AssistantContent({ projectId, chapterNo }: ChapterAssistantProps) {
const injection = useInjection(projectId, chapterNo);
const data = injection.data;
@@ -43,21 +71,17 @@ export function AssistantContent({ projectId, chapterNo }: ChapterAssistantProps
return (
<div className="px-4">
<SectionHeader
title="本章参考"
action={
injection.saving ? (
<ThinkingIndicator
label="保存中"
className="text-xs text-ink-soft"
/>
) : null
}
/>
<div className="flex items-center justify-between gap-2">
<h2 className="font-sans text-title-md font-medium text-ink">
</h2>
{injection.saving ? (
<ThinkingIndicator label="保存中" className="text-xs text-ink-soft" />
) : null}
</div>
<section className="mt-4">
<SectionHeader
title="AI 写这章会参考"
<section className="mt-5">
<PanelLabel
action={
data ? (
<RecentStepper
@@ -67,10 +91,12 @@ export function AssistantContent({ projectId, chapterNo }: ChapterAssistantProps
/>
) : null
}
/>
>
AI
</PanelLabel>
{injection.loading ? (
<p className="mt-2 text-xs text-ink-soft"></p>
<p className="mt-2 text-caption text-ink-soft"></p>
) : !data ? (
// 首次加载失败data 仍为空 → 满屏错误框 + 重试。
<StatusNote variant="danger" className="mt-2 text-xs" title="参考信息暂不可用">
@@ -113,11 +139,15 @@ export function AssistantContent({ projectId, chapterNo }: ChapterAssistantProps
) : null}
{selected.length === 0 ? (
<p className="mt-2 rounded border border-dashed border-line bg-bg p-3 text-xs text-ink-soft">
AI /
</p>
// 空态占位(不加标题,避免在 h3 子小节内注入 h2 破坏标题大纲)。
<div className="mt-2 flex flex-col items-center gap-2 py-4 text-center">
<Sparkles className="h-5 w-5 text-muted-soft" aria-hidden="true" />
<p className="max-w-xs text-caption leading-6 text-ink-soft">
AI /
</p>
</div>
) : (
<ul className="mt-2 space-y-2">
<ul className="mt-3 space-y-2">
{selected.map((entity) => (
<EntityRow
key={`${entity.kind}:${entity.name}`}
@@ -137,9 +167,9 @@ export function AssistantContent({ projectId, chapterNo }: ChapterAssistantProps
)}
{excluded.length > 0 ? (
<div className="mt-3">
<SectionHeader title="已排除(本章不参考)" />
<ul className="mt-1 space-y-1">
<div className="mt-4">
<PanelLabel></PanelLabel>
<ul className="mt-2 space-y-1">
{excluded.map((ref) => (
<ExcludedRow
key={`${ref.kind}:${ref.name}`}
@@ -153,11 +183,11 @@ export function AssistantContent({ projectId, chapterNo }: ChapterAssistantProps
) : null}
</section>
<section className="mt-6">
<SectionHeader
title="写完后检查"
description="写完这章AI 会把一致性、伏笔、文风、节奏各查一遍。到左栏或底栏的「审稿」逐条过。"
/>
<section className="mt-6 border-t border-line-soft pt-5">
<PanelLabel></PanelLabel>
<p className="mt-1.5 text-caption leading-6 text-ink-soft">
AI 稿
</p>
</section>
</div>
);