feat: 完成源晶权限与经验系统并优化 me/admin 交互

这个提交包含在:
cryptocommuniums-afk
2026-02-23 20:02:46 +08:00
父节点 2b6def2560
当前提交 43cbd38bac
修改 104 个文件,包含 13348 行新增776 行删除

查看文件

@@ -3,10 +3,12 @@
import Link from "next/link";
import { useEffect, useMemo, useState } from "react";
import { HintTip } from "@/components/hint-tip";
import { apiFetch } from "@/lib/api";
import { readToken } from "@/lib/auth";
import { useI18nText } from "@/lib/i18n";
import { Activity, HardDrive, Play, RefreshCw, Server, FileText, CheckCircle, XCircle, Clock } from "lucide-react";
import { formatUnixDateTime } from "@/lib/time";
import { Activity, HardDrive, Play, RefreshCw, Server } from "lucide-react";
type ImportJob = {
id: number;
@@ -55,8 +57,7 @@ type MeProfile = {
};
function fmtTs(v: number | null | undefined): string {
if (!v) return "-";
return new Date(v * 1000).toLocaleString();
return formatUnixDateTime(v);
}
type ImportJobOptions = {
@@ -78,6 +79,23 @@ function parseOptions(raw: string): ImportJobOptions | null {
}
}
function statusToneClass(raw: string): string {
const value = raw.toLowerCase();
if (value.includes("queue") || value === "queued" || value === "pending") {
return "mc-status-warning text-amber-700";
}
if (value.includes("run") || value === "running" || value === "processing") {
return "mc-status-running text-blue-700";
}
if (value.includes("success") || value === "done" || value === "completed") {
return "mc-status-success text-emerald-700";
}
if (value.includes("fail") || value === "failed" || value === "error") {
return "mc-status-danger text-red-700";
}
return "mc-status-muted text-zinc-700";
}
export default function ImportsPage() {
const { tx } = useI18nText();
const [token, setToken] = useState("");
@@ -254,15 +272,26 @@ export default function ImportsPage() {
return (
<main className="mx-auto max-w-7xl px-3 py-6 max-[390px]:px-2 sm:px-4 md:px-6 md:py-8">
<h1 className="text-xl font-semibold max-[390px]:text-lg sm:text-2xl flex items-center gap-2">
<HardDrive size={24} />
{tx("题库导入/出题任务", "Import / Generation Jobs")}
</h1>
<div className="flex flex-wrap items-center gap-2">
<h1 className="text-xl font-semibold max-[390px]:text-lg sm:text-2xl flex items-center gap-2">
<HardDrive size={24} />
{tx("题库导入/出题任务", "Import / Generation Jobs")}
</h1>
<HintTip title={tx("管理说明", "Management Guide")}>
{tx(
"该页面仅管理员可用。支持 Luogu 导入与本地 PDF + RAG 出题两种模式,建议先小规模验证再扩大批量。",
"Admin-only page. Supports Luogu import and Local PDF + RAG generation. Start with a small batch before scaling up."
)}
</HintTip>
</div>
<section className="mt-4 rounded-xl border bg-white p-4">
<h2 className="text-base font-medium">{tx("平台管理快捷入口(原 /admin139", "Platform Shortcuts (moved from /admin139)")}</h2>
<p className="mt-1 text-xs text-zinc-600">
{tx("默认管理员账号admin / whoami139", "Default admin account: admin / whoami139")}
{tx(
"管理员凭据已配置,请使用授权账号登录。",
"Admin credentials are configured separately. Sign in with an authorized account."
)}
</p>
<div className="mt-3 grid gap-2 sm:grid-cols-2 lg:grid-cols-5">
<Link className="rounded border bg-zinc-50 px-3 py-2 text-sm hover:bg-zinc-100" href="/auth">
@@ -369,22 +398,24 @@ export default function ImportsPage() {
{running ? tx("运行中", "Running") : tx("空闲", "Idle")}
</span>
</div>
{runMode === "luogu" && (
<p className="mt-2 text-xs text-zinc-500">
{tx(
"抓取洛谷 CSP-J/CSP-S/NOIP 标签题;容器重启后可自动触发(可通过环境变量关闭)。",
"Fetch Luogu problems tagged CSP-J/CSP-S/NOIP. It can auto-start after container restart (configurable via env)."
)}
</p>
)}
{runMode === "local_pdf_rag" && (
<p className="mt-2 text-xs text-zinc-500">
{tx(
"从本地 PDF 提取文本做 RAG,调用 LLM 生成 CSP-J/S 题目,按现有题库难度分布补齐到目标题量并自动去重跳过。",
"Extract text from local PDFs for RAG, then call LLM to generate CSP-J/S problems with dedupe and target distribution."
)}
</p>
)}
<div className="mt-2 text-xs text-zinc-500 inline-flex items-center gap-2">
<span>
{runMode === "luogu"
? tx("当前模式Luogu 标签导入。", "Mode: Luogu tag import.")
: tx("当前模式:本地 PDF + RAG 出题。", "Mode: Local PDF + RAG generation.")}
</span>
<HintTip title={tx("模式说明", "Mode Notes")} align="left">
{runMode === "luogu"
? tx(
"抓取洛谷 CSP-J/CSP-S/NOIP 标签题。可按需选择启动前清空历史题库。",
"Fetch Luogu problems tagged CSP-J/CSP-S/NOIP. You can choose to clear historical problem sets before start."
)
: tx(
"从本地 PDF 提取文本做 RAG,再调用 LLM 生成题目。系统会按目标规模与去重策略补齐题库。",
"Extract text from local PDFs for RAG and generate problems with LLM. The system fills the set based on target size and dedupe strategy."
)}
</HintTip>
</div>
</div>
{error && <p className="mt-3 text-sm text-red-600">{error}</p>}
@@ -395,7 +426,8 @@ export default function ImportsPage() {
{job && (
<div className="mt-3 space-y-2 text-sm">
<p>
{tx("任务", "Job")} #{job.id} · {tx("状态", "Status")} <b>{job.status}</b> · {tx("触发方式", "Trigger")} {job.trigger}
{tx("任务", "Job")} #{job.id} · {tx("状态", "Status")}{" "}
<b className={statusToneClass(job.status)}>{job.status}</b> · {tx("触发方式", "Trigger")} {job.trigger}
</p>
<p className="text-zinc-600">
{tx("模式", "Mode")} {jobOpts?.mode || jobOpts?.source || "luogu"} · {tx("线程", "Workers")} {jobOpts?.workers ?? "-"}
@@ -452,7 +484,7 @@ export default function ImportsPage() {
<p className="font-medium">
{tx("明细", "Detail")} #{item.id}
</p>
<span>{item.status}</span>
<span className={statusToneClass(item.status)}>{item.status}</span>
</div>
<p className="break-all text-zinc-600">{tx("路径:", "Path: ")}{item.source_path}</p>
<p className="text-zinc-600">{tx("标题:", "Title: ")}{item.title || "-"}</p>
@@ -487,7 +519,7 @@ export default function ImportsPage() {
{item.source_path}
</div>
</td>
<td className="px-2 py-2">{item.status}</td>
<td className={`px-2 py-2 ${statusToneClass(item.status)}`}>{item.status}</td>
<td className="max-w-[220px] px-2 py-2">
<div className="truncate" title={item.title}>
{item.title || "-"}