Files
writer-work-flow/apps/web/components/style/RefineView.tsx

114 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { CheckCircle2, X } from "lucide-react";
import { useEffect } from "react";
import { ThinkingIndicator } from "@/components/ThinkingIndicator";
import { Button } from "@/components/ui/Button";
import { Badge } from "@/components/ui/Badge";
import { useRefine } from "@/lib/style/useRefine";
interface RefineViewProps {
projectId: string;
chapterNo: number;
// 选中漂移段的原文(从终稿按段切出;空则提示先编辑终稿)。
segment: string;
// 采纳:把 refined 合入终稿(经审稿页 finalText → 既有 draft 自动保存路径)。
onAdopt: (original: string, refined: string) => void;
onClose: () => void;
}
// 回炉 diffUX §8.3POST .../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="region"
aria-label="回炉对比"
>
<div className="mb-2 flex items-center justify-between">
<h3 className="font-semibold text-ink"></h3>
<Button
type="button"
variant="ghost"
size="icon"
onClick={onClose}
aria-label="关闭"
>
<X className="h-4 w-4" aria-hidden="true" />
</Button>
</div>
{segment.trim().length === 0 ? (
<Badge variant="warning">
稿稿
</Badge>
) : (
<div role="status" aria-live="polite">
{refiner.status === "refining" ? (
<p className="text-info">
<ThinkingIndicator label="回炉中" />
</p>
) : refiner.status === "error" ? (
<p className="text-conflict"></p>
) : refiner.result ? (
<span className="sr-only"></span>
) : null}
</div>
)}
{segment.trim().length > 0 && refiner.result ? (
<div className="mt-2 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
onClick={() => {
if (refiner.result) {
onAdopt(refiner.result.original, refiner.result.refined);
}
}}
variant="primary"
size="sm"
>
<CheckCircle2 className="h-4 w-4" aria-hidden="true" />
</Button>
<Button onClick={onClose} variant="secondary" size="sm">
</Button>
</div>
</div>
) : null}
</div>
);
}