Merge branch 'dl3'

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-30 01:15:40 +08:00
commit 85aa9d47d0
50 files changed
+1535 -348

No files matched your search

+19 -1
View File
@@ -1118,6 +1118,24 @@ export const Api = {
body: execution.ManualAcquisitionRequest,
response: execution.ManualAcquisitionResult,
}),
why: ep({
method: "get",
path: "/acquisition-plans/{id}/why",
scope: "read",
summary:
"下载的来由:来源(订阅 / 智能体会话 / 手动)、目的(补缺 / 升级 / 换源)、被替换的版本、选中理由要点与原始评估",
params: IdParam,
response: board.DownloadWhy,
}),
cancel: ep({
method: "post",
path: "/acquisition-plans/{id}/cancel",
scope: "organize.execute",
summary:
"取消还没提交到下载器的计划(待批准 / 已预留):释放收录版本与预算预留;已有下载任务的返回 409。待批准的计划请拒绝审批(拒绝后计划自动取消)",
params: IdParam,
response: execution.AcquisitionPlan,
}),
},
downloadTasks: {
...crud(
@@ -1138,7 +1156,7 @@ export const Api = {
path: "/download-tasks/board",
scope: "read",
summary:
"下载面板:按进行中 / 已完成分组列出下载任务,附每个种子的实时速度、剩余时间、连接数与下载器总速度(实时数据短时缓存)",
"下载面板:按进行中 / 已完成分组列出下载任务(进行中含还没提交、在等批准或下载盘空间的计划,kind=plan),附每个种子的实时速度、剩余时间、连接数与下载器总速度(实时数据短时缓存)",
query: board.DownloadBoardQuery,
response: board.DownloadBoard,
}),
+6 -4
View File
@@ -177,8 +177,8 @@ export const CAPABILITY_CATALOG: Record<KnownCapabilityId, CapabilityDescriptor>
),
"download.plan": d(
"download.plan",
"生成下载计划",
"评估候选,生成占用收录版本(slot)与预算预留的下载计划;不提交。选定资源后、提交前用;不会提交下载,也不预留收录版本",
"准备下载",
"评估选定的资源能否下到这个收录版本(slot),准备一份待提交的下载(返回 planId);不提交,也不占用收录版本与预算。选定资源后、提交前用",
"preview",
"DownloadPlan",
"organize.execute",
@@ -189,7 +189,7 @@ export const CAPABILITY_CATALOG: Record<KnownCapabilityId, CapabilityDescriptor>
"download.submit": d(
"download.submit",
"提交下载",
"按已保存的计划提交到下载器;校验授权与硬阈值。只提交 download.plan 生成的计划;结果未知时先用 download.inspect / task.inspect 核对,不要重复提交",
"把 download.plan 准备好的下载(planId)提交到下载器;校验授权与硬阈值。只提交 download.plan 返回的 planId;结果未知时先用 download.inspect / task.inspect 核对,不要重复提交",
"execute",
"DownloadSubmit",
"organize.execute",
@@ -602,6 +602,8 @@ export interface CapabilityActivity {
noun: string;
unit: string | null;
hidden?: boolean;
/** 动作后不加「了」(「准备下载 1 个资源」) */
bare?: boolean;
}
const act = (verb: string, noun: string, unit: string | null = null): CapabilityActivity => ({ verb, noun, unit });
@@ -611,7 +613,7 @@ export const CAPABILITY_ACTIVITY: Record<KnownCapabilityId, CapabilityActivity>
"release.search": act("搜", "资源", "次"),
"release.inspect": act("看", "资源", "个"),
"identity.resolve": act("识别", "作品", "个"),
"download.plan": act("生成", "下载计划", "份"),
"download.plan": { ...act("准备下载", "资源", "个"), bare: true },
"download.submit": act("提交", "下载", "个"),
"download.recheck": act("校验", "下载任务", "个"),
"organize.plan": act("生成", "整理计划", "份"),
+65 -3
View File
@@ -1,13 +1,16 @@
import { z } from "zod";
import { DecisionKind } from "./agent.ts";
import { CursorQuery, Id, NonNegInt, Timestamp } from "./common.ts";
import { type DownloadStatus, DownloadTask } from "./execution.ts";
import { type DownloadStatus, DownloadTask, PlanPurpose, PlanStatus } from "./execution.ts";
import { OrganizationStatus } from "./organization.ts";
// 下载面板(DL-2):下载任务 + 下载器实时数据(速度、剩余时间、连接数)+ 分组计数 + 下载器总速度。
// 实时数据来自 qB torrents/info 与 transfer/info,服务端短 TTL 缓存(轮询不直打 qB)。
/** 进行中 = 排队/提交中/下载中/暂停/无进展/出错/缺失;已完成 = 下载完成(含做种)/入库中/已入库;已移除不出现 */
/**
* 进行中 = 排队/提交中/下载中/暂停/无进展/出错/缺失 + 还没提交、在等条件的计划(DL-3);
* 已完成 = 下载完成(含做种)/入库中/已入库;已移除不出现
*/
export const DownloadBoardState = z.enum(["active", "completed"]);
export type DownloadBoardState = z.infer<typeof DownloadBoardState>;
@@ -19,6 +22,8 @@ export const DOWNLOAD_BOARD_STATUSES: Record<DownloadBoardState, readonly Downlo
export const DownloadBoardQuery = CursorQuery.extend({
state: DownloadBoardState.default("active"),
instanceId: Id.optional(),
/** 深链(DL-3):只返回这个计划对应的行——等待中的计划,或它的下载任务(不分进行中 / 已完成) */
planId: Id.optional(),
});
export type DownloadBoardQuery = z.infer<typeof DownloadBoardQuery>;
@@ -44,6 +49,7 @@ export const DownloadLive = z.object({
export type DownloadLive = z.infer<typeof DownloadLive>;
export const DownloadBoardItem = DownloadTask.extend({
kind: z.literal("task"),
live: DownloadLive.nullable(),
/** 已识别的作品 */
media: z.object({ id: Id, title: z.string(), year: z.number().int().nullable() }).nullable(),
@@ -59,6 +65,61 @@ export const DownloadBoardItem = DownloadTask.extend({
});
export type DownloadBoardItem = z.infer<typeof DownloadBoardItem>;
/**
* 计划在等什么(DL-3):approval = 等管理员批准(有待处理的审批);space = 下载盘超过暂停线或用量未知;
* automation_off = 自动化总开关关闭 / 只读对账;queued = 条件已满足、等提交任务执行。
* 每日下载额度在生成计划时核对,超额不生成计划,所以没有"等额度"的计划。
*/
export const PlanWait = z.enum(["approval", "space", "automation_off", "queued"]);
export type PlanWait = z.infer<typeof PlanWait>;
/** 还没提交到下载器的计划,作为「进行中」的一行(id 是计划 id) */
export const DownloadBoardPlanItem = z.object({
kind: z.literal("plan"),
id: Id,
createdAt: Timestamp,
/** 资源标题 */
name: z.string(),
sizeBytes: NonNegInt.nullable(),
purpose: PlanPurpose,
status: PlanStatus,
media: z.object({ id: Id, title: z.string(), year: z.number().int().nullable() }).nullable(),
wait: PlanWait,
/** 原因码(提交任务的延后原因等;人话见 reasons 表,原码进技术信息) */
waitReason: z.string().nullable(),
/** 待批准时的审批(批准 / 拒绝回传 payloadHash) */
approval: z.object({ id: Id, payloadHash: z.string(), decisionId: Id.nullable() }).nullable(),
});
export type DownloadBoardPlanItem = z.infer<typeof DownloadBoardPlanItem>;
export const DownloadBoardRow = z.discriminatedUnion("kind", [DownloadBoardItem, DownloadBoardPlanItem]);
export type DownloadBoardRow = z.infer<typeof DownloadBoardRow>;
/** 下载的来由(DL-3「为什么下这个」):来源、目的、替换的版本、选中理由的原始要点 */
export const DownloadWhy = z.object({
planId: Id,
source: z.discriminatedUnion("kind", [
z.object({ kind: z.literal("subscription"), id: Id, name: z.string() }),
z.object({ kind: z.literal("agent"), runId: Id, name: z.string() }),
z.object({ kind: z.literal("manual") }),
z.object({ kind: z.literal("auto") }),
]),
purpose: PlanPurpose,
/** 收录版本名 */
slotName: z.string().nullable(),
/** 升级 / 换源时被替换的版本(规格短句) */
replaces: z.object({ assetId: Id, label: z.string() }).nullable(),
/** evaluation.reasons 原串(hard_conditions_met、source_tier_improved、tier:xxx…) */
reasons: z.array(z.string()),
/** 资源的促销类型(free / 2xfree / 50% …)与做种数 */
promotion: z.string().nullable(),
seeders: NonNegInt.nullable(),
siteName: z.string().nullable(),
/** 原始评估结果(技术信息里显示) */
evaluation: z.record(z.string(), z.unknown()),
});
export type DownloadWhy = z.infer<typeof DownloadWhy>;
export const DownloaderTransfer = z.object({
instanceId: Id,
name: z.string(),
@@ -69,7 +130,8 @@ export const DownloaderTransfer = z.object({
export type DownloaderTransfer = z.infer<typeof DownloaderTransfer>;
export const DownloadBoard = z.object({
items: z.array(DownloadBoardItem),
/** 进行中:下载任务与等待中的计划按创建时间一起倒序;已完成只有下载任务 */
items: z.array(DownloadBoardRow),
nextCursor: z.string().nullable(),
counts: z.object({ active: NonNegInt, completed: NonNegInt }),
/** 下载器总速度(transfer/info,含非本系统管理的任务);不可达的实例速度记 0 */
+8
View File
@@ -363,6 +363,13 @@ export const LibraryDownloadView = z.object({
});
export type LibraryDownloadView = z.infer<typeof LibraryDownloadView>;
/** 没提交到下载器就结束的下载(DL-3):失败 / 取消 / 过期且没有下载任务的计划,进作品的下载历史 */
export const LibraryDroppedPlanView = z.object({
plan: AcquisitionPlan,
releaseTitle: z.string().nullable(),
});
export type LibraryDroppedPlanView = z.infer<typeof LibraryDroppedPlanView>;
export const LibraryDetail = z.object({
entity: MediaEntity,
/** 身份映射状态(含 proposed/conflicted,前端区分展示) */
@@ -374,6 +381,7 @@ export const LibraryDetail = z.object({
slots: z.array(AcquisitionSlot),
candidates: z.array(CandidateEvaluation),
downloads: z.array(LibraryDownloadView),
droppedPlans: z.array(LibraryDroppedPlanView).default([]),
/**
* 该作品的整理记录(最近 100 条,新→旧):含没有下载任务的手动整理(文件管理按路径整理)。
* 下载历史里已按下载任务列出的记录也在这里,前端按 downloadTaskId 去重展示。
+10
View File
@@ -77,6 +77,16 @@ const REASONS: Record<string, ReasonText> = {
space_insufficient: { text: "空间不足" },
unidentifiable: { text: "没识别出作品" },
user_deleted: { text: "已删除" },
// DL-3:还没提交就结束或在等的下载(计划的 statusReason、提交任务的延后原因)
cancelled_by_user: { text: "你取消了这个下载" },
approval_rejected: { text: "你没批准这个下载" },
approval_revoked: { text: "会话停止,下载没有提交" },
approval_expired: { text: "等批准超时,下载没有提交" },
subscription_reset: { text: "订阅重置时取消" },
automation_disabled: { text: "自动化已关闭", action: "在设置 → 自动化里打开" },
read_only_reconcile: { text: "只读对账中,暂不提交下载" },
rejected: { text: "下载器拒绝了这个种子" },
source_failed: { text: "从站点取种子失败", action: "检查站点登录后重新下载" },
};
/**