feat(web): 审稿改稿闭环——一键采纳/AI改写/正文高亮定位编辑/伏笔确认/草稿自动保存/章节目录
- 冲突「采纳改法」:有补丁瞬时改入终稿并选中;无补丁但可定位则采纳时调 AI(refiner)重写该段套入;定位不到提示手改 - 正文改为行内高亮叠层编辑器(与页面同底色、随内容增高、无滚动条),点冲突卡/锚点整页滚到并选中+闪烁 - 仅对可在正文定位的冲突显示「跳转」/锚点;locate 支持从 where 引号抽原文,杜绝定位失败刷屏 - 伏笔建议卡:新埋一键登记(预填表单)/ 疑似回收一键标记 CLOSED(复用现有端点) - 审稿页草稿自动保存(防抖 PUT /draft),与「验收本章」解耦,刷新不丢 - 写作路由带章号 ?chapter=N + 验收成功面板/大纲页「写下一章」入口;写作目录从大纲列出全部章节 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,10 @@ interface ConflictCardProps {
|
||||
missing: boolean;
|
||||
// R1:命中段内联预览(无段号/越界时为 null,回退到「跳转」按钮)。
|
||||
snippet: string | null;
|
||||
// 该冲突能否在正文里定位(决定是否显示「跳转」——定位不到就别给会失败的入口)。
|
||||
locatable: boolean;
|
||||
// 正在 AI 改写(采纳无补丁冲突时):禁用按钮 + 显示进度,避免重复点。
|
||||
rewriting: boolean;
|
||||
onVerdict: (index: number, verdict: Verdict) => void;
|
||||
onNote: (index: number, note: string) => void;
|
||||
onJump: (index: number) => void;
|
||||
@@ -37,6 +41,8 @@ export function ConflictCard({
|
||||
draft,
|
||||
missing,
|
||||
snippet,
|
||||
locatable,
|
||||
rewriting,
|
||||
onVerdict,
|
||||
onNote,
|
||||
onJump,
|
||||
@@ -63,6 +69,18 @@ export function ConflictCard({
|
||||
<p className="flex-1 text-sm text-ink">{conflict.suggestion}</p>
|
||||
</div>
|
||||
|
||||
{conflict.original && conflict.replacement ? (
|
||||
<div className="mt-2 rounded border border-pass/30 bg-bg/40 px-3 py-1.5 text-xs leading-relaxed">
|
||||
<span className="mr-1 text-ink-soft">改法:</span>
|
||||
<span className="text-conflict line-through">{conflict.original}</span>
|
||||
<span className="mx-1 text-ink-soft" aria-hidden="true">
|
||||
→
|
||||
</span>
|
||||
<span className="text-pass">{conflict.replacement}</span>
|
||||
<span className="ml-2 text-ink-soft">(点「采纳改法」改入终稿)</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{snippet ? (
|
||||
<blockquote className="mt-2 border-l-2 border-conflict/40 bg-bg/50 py-1 pl-3 text-xs leading-relaxed text-ink-soft">
|
||||
<span className="mr-1 text-conflict" aria-hidden="true">
|
||||
@@ -73,7 +91,7 @@ export function ConflictCard({
|
||||
) : null}
|
||||
|
||||
<div className="mt-2 flex flex-wrap items-center gap-2 pl-1 text-xs text-ink-soft">
|
||||
{conflict.where ? (
|
||||
{conflict.where && locatable ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onJump(index)}
|
||||
@@ -101,8 +119,9 @@ export function ConflictCard({
|
||||
key={opt.value}
|
||||
type="button"
|
||||
aria-pressed={active}
|
||||
disabled={rewriting}
|
||||
onClick={() => onVerdict(index, opt.value)}
|
||||
className={`rounded px-3 py-1 text-xs ${verdictClass(
|
||||
className={`rounded px-3 py-1 text-xs disabled:cursor-not-allowed disabled:opacity-40 ${verdictClass(
|
||||
active,
|
||||
opt.primary,
|
||||
)}`}
|
||||
@@ -111,7 +130,9 @@ export function ConflictCard({
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
{resolved ? (
|
||||
{rewriting ? (
|
||||
<span className="text-xs text-info">✦ AI 改写中…</span>
|
||||
) : resolved ? (
|
||||
<span className="text-xs text-pass" aria-hidden="true">
|
||||
✓ 已裁决
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user