feat(identity): open identity_unresolved decisions for resources with no candidates

no_match resources (and lookup failures after auto-review gives up) now
open a decision in the inbox with options to set a TMDB ID, pick a local
work, or dismiss; ambiguous decisions get the same manual options.
Resolve accepts identity {tmdb | mediaEntityId} and assigns with a manual
lock. The inbox option dialog collects the TMDB type/ID or searches local
works; the identity pool links each pending item to its inbox filter.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 13:44:57 +08:00
1 parent a0b0a64246
commit fb7727cf66
14 files changed
+280 -37

No files matched your search

@@ -27,6 +27,7 @@ export function ConfirmDialog({
confirmLabel = "确认",
cancelLabel = "取消",
destructive = false,
confirmDisabled = false,
onConfirm,
children,
}: {
@@ -38,6 +39,8 @@ export function ConfirmDialog({
confirmLabel?: string;
cancelLabel?: string;
destructive?: boolean;
/** 输入未填完整时禁用确认按钮 */
confirmDisabled?: boolean;
onConfirm: () => unknown | Promise<unknown>;
/** 描述下方的额外内容(输入确认等) */
children?: ReactNode;
@@ -74,7 +77,7 @@ export function ConfirmDialog({
{children}
<AlertDialogFooter>
<AlertDialogCancel disabled={pending}>{cancelLabel}</AlertDialogCancel>
<Button variant={destructive ? "destructive" : "default"} onClick={run} disabled={pending}>
<Button variant={destructive ? "destructive" : "default"} onClick={run} disabled={pending || confirmDisabled}>
{pending ? <LoaderCircle className="animate-spin" /> : null}
{confirmLabel}
</Button>
@@ -15,16 +15,22 @@ import { Chip, Tone } from "./parts.tsx";
// 待识别资源详情(只读):候选证据 + 重新识别。选候选、忽略在待处理里做(判定只在待处理与作品详情)。
/** 多个候选 / 待确认的资源会生成"待确认身份"待处理事项 */
export function inInbox(item: Pick<IdentityPoolItem, "status" | "reason" | "candidates">): boolean {
return (
item.status === "pending" &&
(item.reason === "ambiguous" || item.reason === "proposed") &&
item.candidates.length > 0
);
/** 多个候选 / 待确认的资源开"待确认身份"事项;没有候选的(无结果、复查耗尽)开"待识别"事项 */
export function inInbox(item: Pick<IdentityPoolItem, "status" | "reason" | "candidates" | "decisionId">): boolean {
return item.status === "pending" && (item.decisionId !== null || hasCandidates(item));
}
function hasCandidates(item: Pick<IdentityPoolItem, "reason" | "candidates">): boolean {
return (item.reason === "ambiguous" || item.reason === "proposed") && item.candidates.length > 0;
}
export const INBOX_IDENTITY = `${consolePath("inbox")}?kind=identity_ambiguous`;
export const INBOX_UNRESOLVED = `${consolePath("inbox")}?kind=identity_unresolved`;
/** 条目对应的待处理筛选链接 */
export function inboxLink(item: Pick<IdentityPoolItem, "reason" | "candidates">): string {
return hasCandidates(item) ? INBOX_IDENTITY : INBOX_UNRESOLVED;
}
/** query 摘要:标题 / 年份 / 季集 */
export function querySummary(q: Record<string, unknown>): string {
@@ -110,7 +116,7 @@ export function PoolSheet({ item, onClose }: { item: IdentityPoolItem | null; on
</Button>
{queued ? (
<Button asChild>
<Link to={INBOX_IDENTITY}>
<Link to={inboxLink(item)}>
去待处理
<ArrowRight />
</Link>
@@ -133,6 +133,7 @@ export const CANDIDATE_DECISION: Record<string, ToneLabel> = {
export const DECISION_KIND: Record<DecisionKind, string> = {
identity_ambiguous: "身份歧义",
identity_unresolved: "待识别",
download_stalled: "下载无进展",
download_failed: "下载失败",
path_conflict: "路径冲突",