"use client"; import type { ReactNode } from "react"; import Link from "next/link"; import { ArrowUpRight, Minus, Pin, PinOff, Plus, RotateCcw, Sparkles, Undo2, X, } from "lucide-react"; import { ThinkingIndicator } from "@/components/ThinkingIndicator"; import { Button } from "@/components/ui/Button"; import { StatusNote } from "@/components/ui/StatusNote"; import { ChapterForeshadowList } from "@/components/workbench/ChapterForeshadowList"; import { chapterForeshadowTotal } from "@/lib/foreshadow/chapterForeshadow"; import { useChapterForeshadow } from "@/lib/foreshadow/useChapterForeshadow"; import { buttonClass } from "@/lib/ui/variants"; import { isPinned, kindLabel, reasonLabel, RECENT_N_MAX, RECENT_N_MIN, type InjectionEntity, type InjectionEntityRef, } from "@/lib/workbench/injection"; import { useInjection } from "@/lib/workbench/useInjection"; interface ChapterAssistantProps { projectId: string; chapterNo: number; } // 右栏「本章助手」(UX §6.3)。 // 「本章注入(透明)」读 B0 端点,列出确定性选中的设定/角色 + 入选理由徽标, // 并让作者 📌置顶 / ✕排除 / 调近 N 章(可控版)——「看到的=写章用的」信任牌(不变量 #6)。 // 桌面 aside 包裹;移动端经 Workbench 抽屉复用 AssistantContent。 export function ChapterAssistant({ projectId, chapterNo }: ChapterAssistantProps) { return ( ); } // 窄栏小标题(P3-5):font-sans + eyebrow 字号/正字距(对齐 Eyebrow 眉题的视觉), // 但保留

语义(不降级为

,守住标题大纲 a11y)。可带右侧行内控件(如步进器)。 function PanelLabel({ children, action, }: { children: ReactNode; action?: ReactNode; }) { return (

{children}

{action ?
{action}
: null}
); } export function AssistantContent({ projectId, chapterNo }: ChapterAssistantProps) { const injection = useInjection(projectId, chapterNo); const data = injection.data; const selected = data?.selected ?? []; const excluded = data?.excluded ?? []; return (

本章参考

{injection.saving ? ( ) : null}
) : null } > AI 写这章会参考 {injection.loading ? (

加载参考信息…

) : !data ? ( // 首次加载失败:data 仍为空 → 满屏错误框 + 重试。

请检查后端连接后重试。

) : ( <> {injection.saveError ? ( // 保存失败:悬浮提示,不替换列表;可关闭 + 重试上一次覆盖。
) : null} {selected.length === 0 ? ( // 空态占位(不加标题,避免在 h3 子小节内注入 h2 破坏标题大纲)。
) : (
    {selected.map((entity) => ( ))}
)} )} {excluded.length > 0 ? (
已排除(本章不参考)
    {excluded.map((ref) => ( ))}
) : null}
写完后检查

写完这章,AI 会把一致性、伏笔、文风、节奏各查一遍。到左栏或底栏的「审稿」逐条过。

); } // 「本章伏笔」主动提醒:按当前章号把伏笔分可回收 / 待回收 / 已逾期三类, // 让作者在写这章时就想起该回收哪条、哪条已逾期。只读,编辑走「伏笔看板」。 function ChapterForeshadowSection({ projectId, chapterNo }: ChapterAssistantProps) { const { groups, status, reload } = useChapterForeshadow(projectId, chapterNo); const boardHref = `/projects/${projectId}/foreshadow`; const isEmpty = chapterForeshadowTotal(groups) === 0; return (
按已验收进度} > 本章伏笔 {status === "loading" ? (

加载伏笔…

) : status === "error" ? ( ) : isEmpty ? (

这一章暂无待回收或已逾期的伏笔。

) : (
)} 去伏笔看板
); } interface EntityRowProps { entity: InjectionEntity; pinned: boolean; disabled: boolean; onTogglePin: (ref: InjectionEntityRef) => void; onExclude: (ref: InjectionEntityRef) => void; } function EntityRow({ entity, pinned, disabled, onTogglePin, onExclude }: EntityRowProps) { const ref: InjectionEntityRef = { kind: entity.kind, name: entity.name }; return (
  • {kindLabel(entity.kind)} {entity.name}
    {(entity.reasons ?? []).map((reason) => ( {reasonLabel(reason)} ))}
  • ); } interface ExcludedRowProps { refItem: InjectionEntityRef; disabled: boolean; onRestore: (ref: InjectionEntityRef) => void; } function ExcludedRow({ refItem, disabled, onRestore }: ExcludedRowProps) { return (
  • {kindLabel(refItem.kind)} {refItem.name}
  • ); } interface RecentStepperProps { value: number; disabled: boolean; onChange: (n: number) => void; } // 「关联近 N 章」步进器:覆盖近况摘要回看章数(钳在 [MIN, MAX])。 function RecentStepper({ value, disabled, onChange }: RecentStepperProps) { return (
    {value}
    ); }