Files
image-prompt-studio/components/history-gallery.tsx
2026-04-25 11:26:33 +08:00

184 lines
6.0 KiB
TypeScript
Raw Permalink 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.

import type { ImageHistoryItem, PublicUser } from "@/lib/types";
interface HistoryGalleryProps {
currentUser: PublicUser;
items: ImageHistoryItem[];
}
const dateTimeFormatter = new Intl.DateTimeFormat("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
});
export default function HistoryGallery({
currentUser,
items,
}: HistoryGalleryProps) {
const totalUsers = new Set(items.map((item) => item.userId)).size;
const latestItem = items[0] ?? null;
return (
<div className="history-grid">
<div className="history-overview-grid">
<section className="card history-overview-card">
<div className="section-heading">
<div>
<p className="section-kicker">Archive</p>
<h3>
{currentUser.role === "admin" ? "全站生成历史" : "我的生成历史"}
</h3>
</div>
<span className="subtle-pill">{items.length} </span>
</div>
<div className="history-stat-grid">
<article className="history-stat-card">
<span></span>
<strong>{items.length}</strong>
</article>
<article className="history-stat-card">
<span>{currentUser.role === "admin" ? "涉及用户" : "当前身份"}</span>
<strong>
{currentUser.role === "admin" ? `${totalUsers}` : "普通用户"}
</strong>
</article>
<article className="history-stat-card">
<span></span>
<strong>
{latestItem
? dateTimeFormatter.format(new Date(latestItem.createdAt))
: "暂无记录"}
</strong>
</article>
</div>
</section>
<section className="card history-overview-card history-overview-card-muted">
<div className="section-heading">
<div>
<p className="section-kicker">Access</p>
<h3></h3>
</div>
</div>
<p className="muted block-copy">
{currentUser.role === "admin"
? "管理员可以查看所有用户的历史记录、原始提示词和生成参数,页面会额外标注每张图所属的账号。"
: "普通用户只能查看自己的历史记录和图片内容,接口会按当前登录账号做隔离。"}
</p>
<div className="meta-pills history-info-pills">
<span>Prompt </span>
<span></span>
<span></span>
</div>
</section>
</div>
{items.length === 0 ? (
<section className="card history-empty-card">
<div className="placeholder-orb" />
<h4></h4>
<p>
</p>
</section>
) : (
<section className="history-gallery-list">
{items.map((item) => (
<article key={item.id} className="card history-card">
<div className="history-card-media">
<img src={item.imageUrl} alt={item.fileName} loading="lazy" />
</div>
<div className="history-card-header">
<div>
<h4>{item.fileName}</h4>
<p className="muted">
{dateTimeFormatter.format(new Date(item.createdAt))}
</p>
</div>
{currentUser.role === "admin" ? (
<span className="subtle-pill history-owner-pill">
{item.username}
</span>
) : null}
</div>
<div className="meta-pills history-meta-pills">
<span>{item.model ?? "未知模型"}</span>
<span>{item.size ?? "未知尺寸"}</span>
<span>{item.quality ?? "未知质量"}</span>
</div>
<div className="history-meta-list">
<div>
<span></span>
<strong>{formatOutputFormatLabel(item)}</strong>
</div>
<div>
<span></span>
<strong>{item.background ?? "auto"}</strong>
</div>
<div>
<span></span>
<strong>{formatFileSize(item.fileSize)}</strong>
</div>
</div>
<details className="history-disclosure" open>
<summary></summary>
<p>{item.prompt}</p>
</details>
{item.revisedPrompt ? (
<details className="history-disclosure">
<summary></summary>
<p>{item.revisedPrompt}</p>
</details>
) : null}
<div className="history-actions">
<a
className="ghost-button"
href={item.imageUrl}
target="_blank"
rel="noreferrer"
>
</a>
<a className="primary-button history-download-button" href={item.downloadUrl}>
</a>
</div>
</article>
))}
</section>
)}
</div>
);
}
function formatFileSize(fileSize: number): string {
if (fileSize < 1024) {
return `${fileSize} B`;
}
if (fileSize < 1024 * 1024) {
return `${(fileSize / 1024).toFixed(1)} KB`;
}
return `${(fileSize / (1024 * 1024)).toFixed(2)} MB`;
}
function formatOutputFormatLabel(item: ImageHistoryItem): string {
if (item.outputFormat) {
return item.outputFormat.toUpperCase();
}
return item.mimeType.replace(/^image\//, "").toUpperCase();
}