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

100 lines
3.5 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, CircleDashed, RotateCcw } from "lucide-react";
import { Badge } from "@/components/ui/Badge";
import { Button } from "@/components/ui/Button";
import { cardClass } from "@/lib/ui/variants";
import type { StyleDriftReport, StyleDriftSegment } from "@/lib/review/sse";
interface StylePanelProps {
style: StyleDriftReport | null;
incomplete: boolean;
// 选中段做回炉(打开 RefineView
onRefine: (segment: StyleDriftSegment) => void;
}
// 整体相似度的四分圆字符档(◔◑◕●),按 score 映射UX §6.4 ③)。
const QUARTER_CHARS = ["○", "◔", "◑", "◕", "●"] as const;
function quarterChar(score: number): string {
const clamped = Math.min(100, Math.max(0, score));
const idx = Math.round((clamped / 100) * (QUARTER_CHARS.length - 1));
return QUARTER_CHARS[idx] ?? QUARTER_CHARS[0];
}
// 文风审区UX §6.4 ③):整体相似度 ◔% + 漂移段列表,每段可一键回炉。只读建议。
export function StylePanel({ style, incomplete, onRefine }: StylePanelProps) {
return (
<section className="mb-4">
<h2 className="text-xs font-semibold uppercase tracking-wide text-ink-soft">
(style-auditor)
</h2>
{incomplete ? (
<Badge variant="warning" className="mt-1">
<CircleDashed className="h-3 w-3" aria-hidden="true" />
</Badge>
) : style === null ? (
<p className="mt-1 text-xs text-ink-soft">
</p>
) : (
<div className="mt-2 space-y-2 text-xs">
<p
className="flex items-center gap-1.5"
aria-label={`整体文风相似度 ${style.score}%`}
role="img"
>
<span className="text-lg leading-none text-cinnabar" aria-hidden="true">
{quarterChar(style.score)}
</span>
<span className="text-ink">
<span className="font-mono">{style.score}%</span>
</span>
</p>
{style.segments.length === 0 ? (
<Badge variant="success">
<CheckCircle2 className="h-3 w-3" aria-hidden="true" />
</Badge>
) : (
<ul className="space-y-2">
{style.segments.map((seg, i) => (
<li
key={i}
className={cardClass("p-2")}
data-testid="drift-segment"
>
<div className="flex items-center gap-2">
<Badge variant="warning" className="font-mono">
{seg.idx}
</Badge>
<span className="font-mono text-ink-soft">
{seg.score}%
</span>
{seg.label ? (
<span className="text-ink-soft">{seg.label}</span>
) : null}
</div>
<div className="mt-1.5 flex gap-2">
<Button
onClick={() => onRefine(seg)}
variant="primary"
size="sm"
>
<RotateCcw className="h-4 w-4" aria-hidden="true" />
</Button>
</div>
</li>
))}
</ul>
)}
</div>
)}
</section>
);
}