feat: improve app ui and project metadata
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useRefine } from "@/lib/style/useRefine";
|
||||
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;
|
||||
@@ -46,15 +49,17 @@ export function RefineView({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="text-ink-soft hover:text-cinnabar"
|
||||
className="rounded border border-transparent p-1 text-ink-soft hover:border-line hover:text-cinnabar"
|
||||
aria-label="关闭回炉对比"
|
||||
>
|
||||
✕
|
||||
<X className="h-4 w-4" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{segment.trim().length === 0 ? (
|
||||
<p className="text-overdue">该段在终稿中为空,请先在终稿中保留该段正文。</p>
|
||||
<Badge variant="warning">
|
||||
该段在终稿中为空,请先在终稿中保留该段正文。
|
||||
</Badge>
|
||||
) : refiner.status === "refining" ? (
|
||||
<p className="text-info">
|
||||
<ThinkingIndicator label="回炉中" />
|
||||
@@ -76,24 +81,21 @@ export function RefineView({
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
<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"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
>
|
||||
<CheckCircle2 className="h-4 w-4" aria-hidden="true" />
|
||||
采纳合入正文
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="rounded border border-line px-3 py-1.5 text-ink hover:border-cinnabar"
|
||||
>
|
||||
</Button>
|
||||
<Button onClick={onClose} variant="secondary" size="sm">
|
||||
放弃
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { CheckCircle2, CircleDashed, RotateCcw } from "lucide-react";
|
||||
|
||||
import { Badge } from "@/components/ui/Badge";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import type { StyleDriftReport, StyleDriftSegment } from "@/lib/review/sse";
|
||||
|
||||
interface StylePanelProps {
|
||||
@@ -26,7 +30,10 @@ export function StylePanel({ style, incomplete, onRefine }: StylePanelProps) {
|
||||
文风 (style-auditor)
|
||||
</h2>
|
||||
{incomplete ? (
|
||||
<p className="mt-1 text-xs text-overdue">◌ 未完成</p>
|
||||
<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">
|
||||
无文风报告(学文风后第四审才能打分)。
|
||||
@@ -47,7 +54,10 @@ export function StylePanel({ style, incomplete, onRefine }: StylePanelProps) {
|
||||
</p>
|
||||
|
||||
{style.segments.length === 0 ? (
|
||||
<p className="text-pass">✓ 无明显文风漂移</p>
|
||||
<Badge variant="success">
|
||||
<CheckCircle2 className="h-3 w-3" aria-hidden="true" />
|
||||
无明显文风漂移
|
||||
</Badge>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{style.segments.map((seg, i) => (
|
||||
@@ -57,9 +67,9 @@ export function StylePanel({ style, incomplete, onRefine }: StylePanelProps) {
|
||||
data-testid="drift-segment"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-mono text-overdue">
|
||||
<Badge variant="warning" className="font-mono">
|
||||
第 {seg.idx} 段
|
||||
</span>
|
||||
</Badge>
|
||||
<span className="font-mono text-ink-soft">
|
||||
相似 {seg.score}%
|
||||
</span>
|
||||
@@ -68,13 +78,14 @@ export function StylePanel({ style, incomplete, onRefine }: StylePanelProps) {
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mt-1.5 flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
onClick={() => onRefine(seg)}
|
||||
className="rounded bg-cinnabar px-2 py-1 text-panel hover:opacity-90"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
>
|
||||
<RotateCcw className="h-4 w-4" aria-hidden="true" />
|
||||
一键回炉
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -4,6 +4,10 @@ import { useCallback, useState, type ChangeEvent } from "react";
|
||||
|
||||
import { hasUsableSamples, type StyleLearnMode } from "@/lib/style/style";
|
||||
import { useToast } from "@/components/Toast";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { Field } from "@/components/ui/Field";
|
||||
import { StatusNote } from "@/components/ui/StatusNote";
|
||||
import { TextArea } from "@/components/ui/TextArea";
|
||||
|
||||
interface StyleUploadProps {
|
||||
busy: boolean;
|
||||
@@ -61,21 +65,15 @@ export function StyleUpload({
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="style-samples"
|
||||
className="mb-1 block text-sm font-semibold text-ink"
|
||||
>
|
||||
样本正文(空行分段;可粘贴多段)
|
||||
</label>
|
||||
<textarea
|
||||
<Field label="样本正文(空行分段;可粘贴多段)" htmlFor="style-samples">
|
||||
<TextArea
|
||||
id="style-samples"
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
placeholder="粘贴你想学习文风的章节正文…"
|
||||
className="min-h-[30vh] w-full resize-y rounded border border-line bg-panel p-3 font-serif text-[15px] leading-[1.9] text-ink focus:border-cinnabar focus:outline-none"
|
||||
className="min-h-[30vh] bg-panel font-serif text-[15px] leading-[1.9]"
|
||||
/>
|
||||
</div>
|
||||
</Field>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<label className="cursor-pointer rounded border border-line px-3 py-1.5 text-sm text-ink hover:border-cinnabar hover:text-cinnabar">
|
||||
@@ -88,22 +86,17 @@ export function StyleUpload({
|
||||
className="sr-only"
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={submit}
|
||||
disabled={busy}
|
||||
className="rounded bg-cinnabar px-4 py-1.5 text-sm text-panel hover:opacity-90 disabled:opacity-50"
|
||||
>
|
||||
<Button type="button" onClick={submit} disabled={busy} variant="primary">
|
||||
{hasFingerprint ? "重新学习文风" : "学习文风"}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{pollStatus === "polling" ? (
|
||||
<p className="text-xs text-info" aria-live="polite">
|
||||
<StatusNote variant="info" aria-live="polite">
|
||||
提取文风指纹中…({progress}%)
|
||||
</p>
|
||||
</StatusNote>
|
||||
) : pollStatus === "error" ? (
|
||||
<p className="text-xs text-conflict">学文风失败,请稍后重试。</p>
|
||||
<StatusNote variant="danger">学文风失败,请稍后重试。</StatusNote>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user