初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,158 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { Card, CardHeader } from "@/components/ui/Card";
|
||||
import { extractionApi } from "@/lib/api";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import { formatDateTime } from "@/lib/utils/format";
|
||||
import type { MemoryCandidate, WorldEntity } from "@/types/api";
|
||||
|
||||
/**
|
||||
* 候选审核面板(跨来源统一入口)。
|
||||
* 记忆候选(cycle 大周天 / import 资料导入)与世界实体候选逐条 approve/reject。
|
||||
* 审核后 router.refresh()。与 ImportWizard 的批量审核互补——此处为跨来源统一待审队列。
|
||||
*/
|
||||
export function CandidateReviewPanel({
|
||||
memoryCandidates,
|
||||
worldProposals,
|
||||
}: {
|
||||
memoryCandidates: MemoryCandidate[];
|
||||
worldProposals: WorldEntity[];
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [busyId, setBusyId] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
async function reviewMemory(id: string, approve: boolean) {
|
||||
setBusyId(id);
|
||||
setError(null);
|
||||
try {
|
||||
if (approve) await extractionApi.approveMemoryCandidate(id);
|
||||
else await extractionApi.rejectMemoryCandidate(id);
|
||||
router.refresh();
|
||||
} catch (e) {
|
||||
setError(describeError(e));
|
||||
} finally {
|
||||
setBusyId(null);
|
||||
}
|
||||
}
|
||||
async function reviewWorld(id: string, approve: boolean) {
|
||||
setBusyId(id);
|
||||
setError(null);
|
||||
try {
|
||||
if (approve) await extractionApi.approveWorldEntity(id);
|
||||
else await extractionApi.rejectWorldEntity(id);
|
||||
router.refresh();
|
||||
} catch (e) {
|
||||
setError(describeError(e));
|
||||
} finally {
|
||||
setBusyId(null);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Card className="p-5">
|
||||
<CardHeader
|
||||
title="记忆候选"
|
||||
subtitle="来自大周天反思与资料导入的待审记忆,逐条裁定。"
|
||||
/>
|
||||
{memoryCandidates.length === 0 ? (
|
||||
<p className="text-sm text-muted">暂无待审记忆候选。</p>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{memoryCandidates.map((c) => (
|
||||
<li key={c.id} className="rounded-lg bg-glass/40 px-3 py-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="rounded bg-gold/15 px-1.5 py-0.5 text-[10px] text-gold">
|
||||
{c.type}
|
||||
</span>
|
||||
<span className="font-serif text-sm font-medium text-fg">
|
||||
{c.title}
|
||||
</span>
|
||||
<span className="text-[10px] text-muted">
|
||||
{c.import_job_id ? "资料导入" : c.cycle_id ? "大周天" : ""}
|
||||
{` · ${formatDateTime(c.created_at)}`}
|
||||
</span>
|
||||
<span className="ml-auto font-mono text-[10px] text-muted">
|
||||
{(c.confidence ?? 0).toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-fg/80">{c.content}</p>
|
||||
<div className="mt-1.5 flex gap-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
loading={busyId === c.id}
|
||||
onClick={() => reviewMemory(c.id, true)}
|
||||
>
|
||||
通过
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
size="sm"
|
||||
loading={busyId === c.id}
|
||||
onClick={() => reviewMemory(c.id, false)}
|
||||
>
|
||||
驳回
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<Card className="p-5">
|
||||
<CardHeader
|
||||
title="世界实体候选"
|
||||
subtitle="待确认的世界实体,通过后进入世界模型(confirmed)。"
|
||||
/>
|
||||
{worldProposals.length === 0 ? (
|
||||
<p className="text-sm text-muted">暂无待审世界实体候选。</p>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{worldProposals.map((e) => (
|
||||
<li key={e.id} className="rounded-lg bg-glass/40 px-3 py-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="rounded bg-gold/15 px-1.5 py-0.5 text-[10px] text-gold">
|
||||
{e.type}
|
||||
</span>
|
||||
<span className="font-serif text-sm font-medium text-fg">
|
||||
{e.name}
|
||||
</span>
|
||||
<span className="ml-auto font-mono text-[10px] text-muted">
|
||||
{e.confidence.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1.5 flex gap-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
loading={busyId === e.id}
|
||||
onClick={() => reviewWorld(e.id, true)}
|
||||
>
|
||||
通过
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
size="sm"
|
||||
loading={busyId === e.id}
|
||||
onClick={() => reviewWorld(e.id, false)}
|
||||
>
|
||||
驳回
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{error && <p className="text-xs text-cinnabar">{error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user