feat: improve app ui and project metadata

This commit is contained in:
Yaojia Wang
2026-06-28 07:31:20 +02:00
parent 3bd464d400
commit 90a66437d7
86 changed files with 4892 additions and 1108 deletions

View File

@@ -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}

View File

@@ -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>
))}

View File

@@ -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>
);