Merge branch 'deai2'

Co-Authored-By: Claude Fable 5.1 <[email protected]>

# Conflicts:
#	apps/server/src/modules/acquisition-organize/services/organize.ts
#	apps/web/app/routes/console/sites.$siteId.tsx
This commit is contained in:
root committed 2026-09-29 21:26:16 +08:00
commit 12bc4664b6
247 files changed
+2658 -2148

No files matched your search

@@ -1,5 +1,5 @@
import { type SearchAction, type SearchWork, tmdbImageUrl } from "@umt/contracts";
import { ArrowRight, CornerDownLeft, Film, Globe, LoaderCircle, type LucideIcon, Search } from "lucide-react";
import { CornerDownLeft, Film, Globe, LoaderCircle, type LucideIcon, Search } from "lucide-react";
import { type ReactNode, useEffect, useId, useMemo, useRef, useState } from "react";
import { Link, useNavigate } from "react-router";
import { useSearch } from "~/api/search.ts";
@@ -111,7 +111,6 @@ function PaletteBody({ initial }: { initial: string }) {
查看「<span className="font-medium">{term}</span>」的全部结果
</span>
),
meta: "作品卡上可以选站点搜资源",
hint: "打开",
run: () => go(searchPagePath(term)),
});
@@ -254,10 +253,6 @@ function PaletteBody({ initial }: { initial: string }) {
<div className="hidden items-center gap-4 border-t border-border/70 px-4 py-2 text-meta text-muted-foreground sm:flex">
<span>↑↓ 选择</span>
<span>↵ 打开</span>
<span className="ml-auto inline-flex items-center gap-1">
<ArrowRight className="h-3 w-3" />
搜资源在作品详情页
</span>
</div>
</div>
);
@@ -412,7 +412,7 @@ function ReleaseRow({ r, onDownload }: { r: SearchRelease; onDownload: () => voi
) : null}
<Button variant="ghost" size="sm" onClick={() => delegateToAgent(r)}>
<Bot />
委托 Agent
委托智能体
</Button>
<Button size="sm" onClick={onDownload}>
<Download />
@@ -23,7 +23,7 @@ import { formatBytes } from "~/lib/utils.ts";
// 与站点工作台同一套下载说明;请求走 POST /search/releases/{siteId}/{siteReleaseId}/plan(同 /acquisition-plans 流程)
const STEPS = [
"生成下载计划,预留版本目标和预算;每个版本目标同时只有一个在途计划",
"生成下载计划,预留收录版本和预算;每个收录版本同时只有一个在途计划",
"已有相同资源或相同 hash 的任务时直接接管,不重复提交",
"缓存卷超过硬阈值或读不到磁盘状态时无法提交",
"提交前复核免费、做种等易变字段",
@@ -109,11 +109,11 @@ export function SearchDownloadDialog({
{!entityId ? (
<p className="text-meta text-muted-foreground">资源未关联本地作品。先在「身份与元数据」指定。</p>
) : slots.isPending ? (
<p className="text-meta text-muted-foreground">正在读取版本目标…</p>
<p className="text-meta text-muted-foreground">正在读取收录版本…</p>
) : slotList.length ? (
<div className="flex flex-wrap items-center gap-2">
<FilterSelect
label="版本目标"
label="收录版本"
value={slotId}
onChange={setSlotId}
allLabel="自动匹配"
@@ -124,7 +124,7 @@ export function SearchDownloadDialog({
/>
</div>
) : (
<p className="text-meta text-muted-foreground">这个作品没有版本目标。先在订阅里新建。</p>
<p className="text-meta text-muted-foreground">这个作品没有收录版本。先在订阅里新建。</p>
)}
{release.entityMatch === "alias" ? (
<p className="text-meta text-warning">作品关联来自标题别名,尚未确认。</p>
@@ -159,7 +159,7 @@ export function SearchDownloadDialog({
}}
>
<Bot />
委托 Agent
委托智能体
</Button>
<Button onClick={go} disabled={plan.isPending || !!done || !entityId}>
<Download />
@@ -15,7 +15,7 @@ export function slotSummary(h: LocalMediaHit): string {
if (h.versionCount) parts.push(`${h.versionCount} 个版本`);
if (h.downloadingCount) parts.push(`${h.downloadingCount} 个下载中`);
const gaps = c.empty + c.attention + c.upgrade_pending;
if (gaps) parts.push(`${gaps} 个版本目标未满足`);
if (gaps) parts.push(`${gaps} 个收录版本未满足`);
return parts.join(" · ") || "未订阅";
}
@@ -82,14 +82,14 @@ describe("站点选择", () => {
});
describe("库内状态文案", () => {
it("未收录 / 已建档 / 已收录 / 正在下载;版本目标满足与缺失", () => {
expect(inventoryBadge(inv({})).label).toBe("未收录");
expect(inventoryBadge(inv({ state: "tracked" })).label).toBe("已建档 · 未入库");
expect(inventoryBadge(inv({ state: "owned", versionCount: 2 })).label).toBe("已收录 · 2 个版本");
it("未收录 / 已建档 / 已收录 / 正在下载;收录版本满足与缺失", () => {
expect(inventoryBadge(inv({})).label).toBe("不在媒体库");
expect(inventoryBadge(inv({ state: "tracked" })).label).toBe("在媒体库 · 未下载");
expect(inventoryBadge(inv({ state: "owned", versionCount: 2 })).label).toBe("已入库 · 2 个版本");
expect(inventoryBadge(inv({ state: "downloading", downloadingCount: 1 })).label).toBe("正在下载 1 个");
expect(
slotLine(inv({ state: "owned", slotTotal: 2, slotSatisfied: 1, missing: [{ slotId: ID, name: "2160p" }] })),
).toBe("版本目标满足 1/2 · 缺「2160p」");
).toBe("收录版本满足 1/2 · 缺「2160p」");
expect(slotLine(inv({}))).toBeNull();
});
+5 -5
View File
@@ -33,20 +33,20 @@ export function releaseSearchPath(w: { title: string; ref: string }, siteIds: st
export function inventoryBadge(inv: WorkInventory): { label: string; tone: StatusTone } {
switch (inv.state) {
case "not_collected":
return { label: "未收录", tone: "neutral" };
return { label: "不在媒体库", tone: "neutral" };
case "downloading":
return { label: `正在下载 ${inv.downloadingCount} 个`, tone: "warning" };
case "owned":
return { label: `已收录 · ${inv.versionCount} 个版本`, tone: "success" };
return { label: `已入库 · ${inv.versionCount} 个版本`, tone: "success" };
default:
return { label: "已建档 · 未入库", tone: "neutral" };
return { label: "在媒体库 · 未下载", tone: "neutral" };
}
}
/** 版本目标满足情况一行:满足 a/b · 缺「x」「y」 */
/** 收录版本满足情况一行:满足 a/b · 缺「x」「y」 */
export function slotLine(inv: WorkInventory): string | null {
if (inv.state === "not_collected" || inv.slotTotal === 0) return null;
const parts = [`版本目标满足 ${inv.slotSatisfied}/${inv.slotTotal}`];
const parts = [`收录版本满足 ${inv.slotSatisfied}/${inv.slotTotal}`];
if (inv.missing.length) parts.push(`缺${inv.missing.map((m) => `「${m.name}」`).join("")}`);
return parts.join(" · ");
}