交互原型大版本

This commit is contained in:
Nostars Developer
2026-04-17 19:30:23 +08:00
parent 10f9c0061a
commit b12e55a776
29 changed files with 4574 additions and 57 deletions

View File

@@ -0,0 +1,110 @@
"use client";
import { useState } from "react";
import type { ImageAsset } from "@/lib/types";
import { getImageUrl } from "@/lib/api";
/**
* 候选评估面板Phase 1 占位)
*
* 对应 INFOLAYER.md 3.3.3 节:同轮生成的一组候选图的并排比较、
* 快速评分、标注差异、挑选定稿。
*/
interface CandidatePanelProps {
candidates: ImageAsset[];
onPick?: (id: string) => void;
onClose?: () => void;
}
export function CandidatePanel({ candidates, onPick, onClose }: CandidatePanelProps) {
const [ratings, setRatings] = useState<Record<string, number>>({});
const [notes, setNotes] = useState<Record<string, string>>({});
if (candidates.length === 0) return null;
return (
<div className="fixed inset-y-0 right-0 w-[min(680px,100vw)] z-40
bg-[var(--bg-secondary)]/95 backdrop-blur-xl
border-l border-[var(--border)] shadow-2xl
flex flex-col animate-in slide-in-from-right">
<div className="flex items-center justify-between px-5 py-3 border-b border-[var(--border)]">
<div className="flex items-center gap-2">
<h2 className="typo-h2"></h2>
<span className="phase-chip">Phase 1</span>
<span className="text-xs text-[var(--text-secondary)]">
{candidates.length}
</span>
</div>
<button
onClick={onClose}
className="w-7 h-7 rounded-lg flex items-center justify-center
text-[var(--text-secondary)] hover:text-[var(--text-primary)]
hover:bg-[var(--bg-tertiary)] cursor-pointer transition-all"
>
</button>
</div>
<div className="flex-1 overflow-y-auto p-5 space-y-4">
<div className="grid grid-cols-2 gap-4">
{candidates.map((c, i) => (
<div key={c.id} className="rounded-xl surface-2 overflow-hidden">
<div className="relative aspect-square bg-[var(--bg-tertiary)]">
{c.url ? (
<img src={getImageUrl(c.url)} alt={`候选 ${i + 1}`} className="w-full h-full object-cover" />
) : (
<div className="w-full h-full flex items-center justify-center text-xs text-[var(--text-secondary)]">
{i + 1}
</div>
)}
<div className="absolute top-2 left-2 px-2 py-0.5 rounded-md
bg-[var(--bg-primary)]/70 backdrop-blur-sm
text-[10px] font-mono text-[var(--text-primary)]">
#{i + 1}
</div>
</div>
<div className="p-2.5">
<div className="flex gap-0.5 mb-1.5">
{[1, 2, 3, 4, 5].map((n) => (
<button
key={n}
onClick={() =>
setRatings((prev) => ({ ...prev, [c.id]: prev[c.id] === n ? 0 : n }))
}
className={`text-base leading-none cursor-pointer transition-colors ${
(ratings[c.id] ?? 0) >= n ? "text-[var(--accent)]" : "text-[var(--border)]"
}`}
>
</button>
))}
</div>
<input
value={notes[c.id] ?? ""}
onChange={(e) => setNotes((prev) => ({ ...prev, [c.id]: e.target.value }))}
placeholder="点评(占位)"
className="w-full px-2 py-1 text-[11px] rounded-md bg-[var(--bg-primary)]
border border-[var(--border)] focus:border-[var(--accent)]/40 outline-none"
/>
<button
onClick={() => onPick?.(c.id)}
className="mt-1.5 w-full text-[11px] py-1 rounded-md
bg-[var(--accent)]/10 text-[var(--accent)] border border-[var(--accent)]/30
hover:bg-[var(--accent)]/20 cursor-pointer transition-all"
>
稿
</button>
</div>
</div>
))}
</div>
<div className="placeholder-card rounded-lg p-3 text-xs text-[var(--text-secondary)]">
<div className="typo-strong text-xs mb-1 text-[var(--text-primary)]"></div>
/ / Phase 2
Agent
</div>
</div>
</div>
);
}