Files
UHD_Media_Tool/apps/web/app/components/library/identity-external-sheet.tsx
T
rootandClaude Opus 5.5 4480fc346d refactor(copy): 术语统一——收录版本、模型服务、智能体等(DEAI-2)
- 版本目标 → 收录版本(前后端用户可见字符串、能力描述、给模型的提示)
- Agent 上游 / 上游 → 模型服务;界面上的 Agent / Agent 会话 → 智能体 / 智能体会话
- 身份歧义 / 识别有歧义 / 无法确定作品 → 没识别出作品;待识别(池) → 未识别
- 建档 → 加入媒体库;资产 → 文件;库根 → 媒体库目录;纳管 → 本工具 / 外部任务;
  哨兵 → 标记文件 / 检查目录;站点快照 → 抓取;Feed / RSS 订阅 / 来源健康 → RSS
- 代码标识符、API 字段名、迁移文件不改;glossary.md 同步;smoke 选择器跟随

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-29 20:35:43 +08:00

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>
);
}