M4(文风): style-auditor 双轨(提取指纹/漂移第四审)+ jobs 长任务框架(zombie reaper) + 回炉 refine + GET /style read-back。 M5(生成+扩展): worldbuilder/character-gen(入库 continuity 409 gate + partition_writes 白名单 + schema→JSONB 形变); 网关多 provider 回退链/熔断/能力降级(Anthropic/Gemini 适配器);Skill registry + 表权限沙箱 + 规则; 前端 角色生成器/世界观/Codex/规则页/技能库/⌘K 命令面板。 K1(Kimi Code 订阅接入): OAuth device-flow(kimi-code)+ 静态 Console key(kimi-code-key)两路径; coding 端点 KimiCLI 伪造头(实测 UA allow-list 门禁,缺则 403)+ JSON 模式结构化(thinking ⊥ tool_choice)。 本地联调修复: CORS 中间件;assemble 注入 premise+「写第N章」指令(修空 prompt 400); GET /outline·/draft read-back + 大纲/工作台/审稿页重载;写页 client/server 常量边界 + notFound 健壮化; 字数 toLocaleString locale 水合;审稿页终稿从已存草稿 seed(修 accept 422)。 门禁: backend ruff/mypy(157)/alembic 无漂移/pytest 451 · frontend lint/tsc/vitest/build。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
101 lines
3.2 KiB
TypeScript
101 lines
3.2 KiB
TypeScript
"use client";
|
||
|
||
import { useRefine } from "@/lib/style/useRefine";
|
||
import { useEffect } from "react";
|
||
|
||
interface RefineViewProps {
|
||
projectId: string;
|
||
chapterNo: number;
|
||
// 选中漂移段的原文(从终稿按段切出;空则提示先编辑终稿)。
|
||
segment: string;
|
||
// 采纳:把 refined 合入终稿(经审稿页 finalText → 既有 draft 自动保存路径)。
|
||
onAdopt: (original: string, refined: string) => void;
|
||
onClose: () => void;
|
||
}
|
||
|
||
// 回炉 diff(UX §8.3):POST .../refine → 展示原段/重写段对比 → 采纳合入正文。
|
||
// 不另写库(不变量#3);采纳经既有 draft 自动保存合入。
|
||
export function RefineView({
|
||
projectId,
|
||
chapterNo,
|
||
segment,
|
||
onAdopt,
|
||
onClose,
|
||
}: RefineViewProps) {
|
||
const refiner = useRefine();
|
||
|
||
// 进入即触发回炉(段非空)。
|
||
useEffect(() => {
|
||
if (segment.trim().length > 0) {
|
||
void refiner.refine(projectId, chapterNo, segment);
|
||
}
|
||
// 仅在段变化时触发。
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [segment, projectId, chapterNo]);
|
||
|
||
return (
|
||
<div
|
||
className="rounded border border-cinnabar bg-panel p-3 text-xs"
|
||
role="dialog"
|
||
aria-label="回炉对比"
|
||
>
|
||
<div className="mb-2 flex items-center justify-between">
|
||
<h3 className="font-semibold text-ink">回炉对比</h3>
|
||
<button
|
||
type="button"
|
||
onClick={onClose}
|
||
className="text-ink-soft hover:text-cinnabar"
|
||
aria-label="关闭回炉对比"
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
|
||
{segment.trim().length === 0 ? (
|
||
<p className="text-overdue">该段在终稿中为空,请先在终稿中保留该段正文。</p>
|
||
) : refiner.status === "refining" ? (
|
||
<p className="text-info" aria-live="polite">
|
||
回炉中…
|
||
</p>
|
||
) : refiner.status === "error" ? (
|
||
<p className="text-conflict">回炉失败,请稍后重试。</p>
|
||
) : refiner.result ? (
|
||
<div className="space-y-2">
|
||
<div>
|
||
<p className="mb-1 text-ink-soft">原段</p>
|
||
<p className="whitespace-pre-wrap rounded border border-line bg-bg p-2 text-ink-soft line-through">
|
||
{refiner.result.original}
|
||
</p>
|
||
</div>
|
||
<div>
|
||
<p className="mb-1 text-cinnabar">重写段</p>
|
||
<p className="whitespace-pre-wrap rounded border border-cinnabar bg-bg p-2 text-ink">
|
||
{refiner.result.refined}
|
||
</p>
|
||
</div>
|
||
<div className="flex gap-2">
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
if (refiner.result) {
|
||
onAdopt(refiner.result.original, refiner.result.refined);
|
||
}
|
||
}}
|
||
className="rounded bg-cinnabar px-3 py-1.5 text-panel hover:opacity-90"
|
||
>
|
||
采纳合入正文
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={onClose}
|
||
className="rounded border border-line px-3 py-1.5 text-ink hover:border-cinnabar"
|
||
>
|
||
放弃
|
||
</button>
|
||
</div>
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
);
|
||
}
|