- 版本目标 → 收录版本(前后端用户可见字符串、能力描述、给模型的提示) - Agent 上游 / 上游 → 模型服务;界面上的 Agent / Agent 会话 → 智能体 / 智能体会话 - 身份歧义 / 识别有歧义 / 无法确定作品 → 没识别出作品;待识别(池) → 未识别 - 建档 → 加入媒体库;资产 → 文件;库根 → 媒体库目录;纳管 → 本工具 / 外部任务; 哨兵 → 标记文件 / 检查目录;站点快照 → 抓取;Feed / RSS 订阅 / 来源健康 → RSS - 代码标识符、API 字段名、迁移文件不改;glossary.md 同步;smoke 选择器跟随 Co-Authored-By: Claude Opus 5.5 <[email protected]>
124 lines
5.0 KiB
TypeScript
124 lines
5.0 KiB
TypeScript
import type { ExternalIdentity, IdentityRevision } from "@umt/contracts";
|
|
import { ExternalLink, Lock, LockOpen } from "lucide-react";
|
|
import { Link } from "react-router";
|
|
import { toast } from "sonner";
|
|
import { useIdentityRevisions, useUpdateIdentity } from "~/api/identity.ts";
|
|
import { DetailSection, DetailSheet } from "~/components/console/detail-sheet.tsx";
|
|
import { EmptyState, ErrorState, LoadingState } from "~/components/console/empty-state.tsx";
|
|
import { JsonView } from "~/components/console/json-view.tsx";
|
|
import { KvList } from "~/components/console/kv-list.tsx";
|
|
import { consolePath } from "~/components/console/nav.ts";
|
|
import { Timeline } from "~/components/console/timeline.tsx";
|
|
import { Button } from "~/components/ui/button.tsx";
|
|
import { errorMessage } from "~/lib/api.ts";
|
|
import { formatDateTime } from "~/lib/utils.ts";
|
|
import { EVIDENCE_LEVEL, IDENTITY_STATUS, VERIFY_STATUS } from "./labels.ts";
|
|
import { Tone } from "./parts.tsx";
|
|
|
|
const ACTION: Record<IdentityRevision["action"], string> = {
|
|
created: "创建",
|
|
status_changed: "状态变更",
|
|
relinked: "重新关联",
|
|
evidence_updated: "证据更新",
|
|
locked: "加人工锁",
|
|
unlocked: "解除人工锁",
|
|
};
|
|
const ACTOR: Record<string, string> = { user: "用户", agent: "智能体", system: "系统", api_key: "API Key" };
|
|
|
|
export const identityRef = (i: Pick<ExternalIdentity, "provider" | "entityType" | "externalId">) =>
|
|
`${i.provider}:${i.entityType}:${i.externalId}`;
|
|
|
|
function Revisions({ id }: { id: string }) {
|
|
const q = useIdentityRevisions(id);
|
|
if (q.isPending) return <LoadingState rows={2} />;
|
|
if (q.isError) return <ErrorState plain error={q.error} onRetry={() => q.refetch()} />;
|
|
if (q.data.length === 0) return <EmptyState plain title="没有修订记录" className="py-6" />;
|
|
const items = [...q.data].sort((a, b) => b.revision - a.revision);
|
|
return (
|
|
<Timeline
|
|
items={items.map((r) => ({
|
|
id: r.id,
|
|
at: r.createdAt,
|
|
tone: r.toStatus ? IDENTITY_STATUS[r.toStatus].tone : "neutral",
|
|
title: `r${r.revision} · ${ACTION[r.action]}${
|
|
r.fromStatus || r.toStatus
|
|
? `:${r.fromStatus ? IDENTITY_STATUS[r.fromStatus].label : "无"} → ${r.toStatus ? IDENTITY_STATUS[r.toStatus].label : "无"}`
|
|
: ""
|
|
}`,
|
|
meta: `${ACTOR[r.actorType] ?? r.actorType}${r.actorId ? ` ${r.actorId}` : ""}`,
|
|
description: r.reason ?? undefined,
|
|
children: (
|
|
<div className="grid gap-2 sm:grid-cols-2">
|
|
<JsonView label="修订前" value={r.before} defaultOpen={false} maxHeight="12rem" />
|
|
<JsonView label="修订后" value={r.after} defaultOpen={false} maxHeight="12rem" />
|
|
</div>
|
|
),
|
|
}))}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export function ExternalIdentitySheet({ item, onClose }: { item: ExternalIdentity | null; onClose: () => void }) {
|
|
const update = useUpdateIdentity();
|
|
if (!item) return null;
|
|
const toggleLock = async () => {
|
|
try {
|
|
await update.mutateAsync({
|
|
id: item.id,
|
|
expectedRevision: item.revision,
|
|
locked: !item.locked,
|
|
reason: item.locked ? "控制台解除人工锁" : "控制台加人工锁",
|
|
});
|
|
toast.success(item.locked ? "已解除人工锁" : "已加人工锁");
|
|
onClose();
|
|
} catch (err) {
|
|
toast.error(errorMessage(err));
|
|
}
|
|
};
|
|
const target = item.mediaEntityId;
|
|
return (
|
|
<DetailSheet
|
|
open
|
|
width="lg"
|
|
onOpenChange={(o) => !o && onClose()}
|
|
title={identityRef(item)}
|
|
footer={
|
|
<Button variant="outline" onClick={toggleLock} disabled={update.isPending || item.status === "retired"}>
|
|
{item.locked ? <LockOpen /> : <Lock />}
|
|
{item.locked ? "解除人工锁" : "加人工锁"}
|
|
</Button>
|
|
}
|
|
>
|
|
<KvList
|
|
items={[
|
|
{ label: "状态", value: <Tone v={IDENTITY_STATUS[item.status]} /> },
|
|
{ label: "证据等级", value: EVIDENCE_LEVEL[item.evidenceLevel] ?? item.evidenceLevel },
|
|
{ label: "人工锁", value: item.locked ? "已锁定" : "未锁定" },
|
|
{ label: "外部校验", value: <Tone v={VERIFY_STATUS[item.verifyStatus]} /> },
|
|
{ label: "校验时间", value: formatDateTime(item.verifiedAt) },
|
|
{ label: "修订号", value: `r${item.revision}` },
|
|
{
|
|
label: "关联",
|
|
value: target ? (
|
|
<Link
|
|
to={consolePath("library", target)}
|
|
className="inline-flex items-center gap-1 underline-offset-4 hover:underline"
|
|
>
|
|
查看作品
|
|
<ExternalLink className="h-3 w-3" />
|
|
</Link>
|
|
) : (
|
|
`内容单元 ${item.contentUnitId ?? "—"}`
|
|
),
|
|
},
|
|
{ label: "有效期", value: item.validUntil ? `至 ${formatDateTime(item.validUntil)}` : "长期" },
|
|
]}
|
|
/>
|
|
<JsonView label="证据" value={Object.keys(item.evidence).length ? item.evidence : null} />
|
|
<DetailSection title="修订历史">
|
|
<Revisions id={item.id} />
|
|
</DetailSection>
|
|
</DetailSheet>
|
|
);
|
|
}
|