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:
@@ -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-5):font-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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user