docs(plan): v0.2 adds Pi subagents and scheduled-jobs board
Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
1 parent
b839ed4056
commit
16ab2a6c84
97 files changed
+592
-648
No files matched your search
@@ -73,7 +73,6 @@ export default function SubscriptionsPage() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="订阅与收录规则"
|
||||
description="订阅决定追什么,收录目标决定要哪种版本,策略决定默认怎么做;启用即授权范围内的持续操作。"
|
||||
actions={
|
||||
tab === "subscriptions" ? (
|
||||
<Button onClick={() => setCreatingSub(true)}>
|
||||
@@ -240,7 +239,7 @@ function SubscriptionsTab() {
|
||||
empty={{
|
||||
icon: CalendarClock,
|
||||
title: filtered ? "没有符合条件的订阅" : "还没有订阅",
|
||||
description: "订阅一部作品后,系统按收录目标持续补齐与升级;也可以只填标题,等作品建档后自动关联。",
|
||||
description: filtered ? undefined : "订阅的作品会按版本目标补齐和升级。点「新建订阅」添加。",
|
||||
}}
|
||||
/>
|
||||
<SubscriptionSheet sub={open} onClose={() => setOpenId(null)} />
|
||||
@@ -256,7 +255,7 @@ function SubscriptionSheet({ sub, onClose }: { sub: Subscription | null; onClose
|
||||
const runSearch = () =>
|
||||
sub &&
|
||||
search.mutate(sub.id, {
|
||||
onSuccess: () => toast.success("已开始在已开启站点搜索,结果会按收录目标自动评估"),
|
||||
onSuccess: () => toast.success("已在启用的站点开始搜索"),
|
||||
onError: (e) => toast.error(errorMessage(e)),
|
||||
});
|
||||
const title = useEntityTitle(sub?.mediaEntityId);
|
||||
@@ -284,20 +283,16 @@ function SubscriptionSheet({ sub, onClose }: { sub: Subscription | null; onClose
|
||||
{sub.mediaEntityId ? (
|
||||
<Button variant="outline" onClick={runSearch} disabled={search.isPending}>
|
||||
<Search />
|
||||
{search.isPending ? "搜索中…" : "立即搜索"}
|
||||
{search.isPending ? "搜索中…" : "搜索资源"}
|
||||
</Button>
|
||||
) : null}
|
||||
{sub.status === "active" || sub.status === "pending" ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setStatus("paused", "已暂停,不再发起新下载")}
|
||||
disabled={update.isPending}
|
||||
>
|
||||
<Button variant="outline" onClick={() => setStatus("paused", "已暂停")} disabled={update.isPending}>
|
||||
<Pause />
|
||||
暂停
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="outline" onClick={() => setStatus("active", "已恢复追踪")} disabled={update.isPending}>
|
||||
<Button variant="outline" onClick={() => setStatus("active", "已恢复")} disabled={update.isPending}>
|
||||
<Play />
|
||||
恢复
|
||||
</Button>
|
||||
@@ -310,7 +305,7 @@ function SubscriptionSheet({ sub, onClose }: { sub: Subscription | null; onClose
|
||||
</Button>
|
||||
}
|
||||
title="取消这个订阅?"
|
||||
description="不再追踪后续内容;已入库文件、已有收录目标与下载历史都保留。"
|
||||
description="不再追踪后续内容。已入库文件、版本目标和下载记录保留。"
|
||||
confirmLabel="取消订阅"
|
||||
destructive
|
||||
onConfirm={() => setStatus("cancelled", "订阅已取消")}
|
||||
@@ -329,7 +324,7 @@ function SubscriptionSheet({ sub, onClose }: { sub: Subscription | null; onClose
|
||||
label: "作品",
|
||||
value: sub.mediaEntityId ? (
|
||||
<Link className="underline-offset-4 hover:underline" to={consolePath("library", sub.mediaEntityId)}>
|
||||
打开媒体详情
|
||||
查看作品
|
||||
</Link>
|
||||
) : (
|
||||
`按标题追踪:${sub.titleQuery ?? "—"}(尚未建档)`
|
||||
@@ -356,11 +351,11 @@ function SubscriptionSheet({ sub, onClose }: { sub: Subscription | null; onClose
|
||||
<DetailSection title="追踪方式">
|
||||
<SubscriptionEditForm key={`${sub.id}-${sub.updatedAt}`} sub={sub} />
|
||||
</DetailSection>
|
||||
<DetailSection title="收录目标">
|
||||
<DetailSection title="版本目标">
|
||||
{sub.mediaEntityId ? (
|
||||
<SlotsPanel mediaEntityId={sub.mediaEntityId} subscriptionId={sub.id} />
|
||||
) : (
|
||||
<p className="text-caption text-muted-foreground">作品建档并关联后才能设置收录目标。</p>
|
||||
<p className="text-caption text-muted-foreground">作品建档后才能设置版本目标。</p>
|
||||
)}
|
||||
</DetailSection>
|
||||
</>
|
||||
@@ -396,7 +391,7 @@ function ProgressPanel({ subId, readOnly }: { subId: string; readOnly: boolean }
|
||||
{ id: subId, seasonNumber: Number(season), total: Number(total), locked },
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success(locked ? "总集数已锁定" : "总集数已更新(只升)");
|
||||
toast.success(locked ? "总集数已锁定" : "总集数已更新");
|
||||
setTotalValue("");
|
||||
},
|
||||
onError: (e) => toast.error(errorMessage(e)),
|
||||
@@ -404,10 +399,8 @@ function ProgressPanel({ subId, readOnly }: { subId: string; readOnly: boolean }
|
||||
);
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{!p.completable ? (
|
||||
<p className="text-meta text-muted-foreground">总集数未知或元数据未就绪:继续追踪,不会自动结束。</p>
|
||||
) : null}
|
||||
{p.seasons.length === 0 ? <p className="text-caption text-muted-foreground">还没有季信息。</p> : null}
|
||||
{!p.completable ? <p className="text-meta text-muted-foreground">总集数未知,订阅不会自动结束。</p> : null}
|
||||
{p.seasons.length === 0 ? <p className="text-caption text-muted-foreground">没有季信息。</p> : null}
|
||||
{p.seasons.map((s) => (
|
||||
<div key={s.seasonNumber} className="space-y-1.5">
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
@@ -439,7 +432,7 @@ function ProgressPanel({ subId, readOnly }: { subId: string; readOnly: boolean }
|
||||
<ul className="space-y-0.5 text-meta text-muted-foreground">
|
||||
{p.slots.map((x) => (
|
||||
<li key={x.slotId}>
|
||||
{x.name}:在途 {x.inflightPlans.length} 个计划,延后 {x.deferred} 个发布
|
||||
{x.name}:在途 {x.inflightPlans.length} 个计划,延后 {x.deferred} 个资源
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -532,7 +525,7 @@ function SubscriptionEditForm({ sub }: { sub: Subscription }) {
|
||||
</FormField>
|
||||
</div>
|
||||
{mode === "continuous" && scope === "following_seasons" && sub.mediaKind !== "movie" ? (
|
||||
<p className="text-meta text-muted-foreground">特别篇、衍生节目和同名重启不直接视作下一季,按证据识别。</p>
|
||||
<p className="text-meta text-muted-foreground">特别篇、衍生节目和同名重启不算作下一季。</p>
|
||||
) : null}
|
||||
<div className="flex justify-end">
|
||||
<Button variant="outline" size="sm" onClick={save} disabled={!dirty || update.isPending}>
|
||||
@@ -616,14 +609,14 @@ function CreateSubscriptionDialog({ onClose }: { onClose: () => void }) {
|
||||
const v = SubscriptionCreate.safeParse(body);
|
||||
if (!v.success) {
|
||||
const errs = zodFieldErrors(v.error.issues);
|
||||
if (errs[""]) errs.target = byTitle ? "请填写要追踪的标题" : "请选择一部作品";
|
||||
if (errs[""]) errs.target = byTitle ? "填写标题" : "选择作品";
|
||||
setErrors(errs);
|
||||
return;
|
||||
}
|
||||
setErrors({});
|
||||
create.mutate(v.data, {
|
||||
onSuccess: () => {
|
||||
toast.success("订阅已创建,将按收录目标开始检查");
|
||||
toast.success("订阅已创建");
|
||||
onClose();
|
||||
},
|
||||
onError: (e) => toast.error(errorMessage(e)),
|
||||
@@ -635,7 +628,7 @@ function CreateSubscriptionDialog({ onClose }: { onClose: () => void }) {
|
||||
<DialogContent className="max-h-[90dvh] overflow-y-auto sm:max-w-xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>新建订阅</DialogTitle>
|
||||
<DialogDescription>选择库里的作品,或先按标题追踪,作品建档后自动关联。</DialogDescription>
|
||||
<DialogDescription>选择库里的作品,或按标题追踪,建档后自动关联。</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="min-w-0 space-y-3">
|
||||
<fieldset className="flex gap-1" aria-label="订阅对象">
|
||||
@@ -709,7 +702,7 @@ function CreateSubscriptionDialog({ onClose }: { onClose: () => void }) {
|
||||
) : search.isError ? (
|
||||
<p className="px-3 py-2 text-caption text-destructive">{errorMessage(search.error)}</p>
|
||||
) : (search.data?.items ?? []).filter((e) => e.status !== "merged").length === 0 ? (
|
||||
<p className="px-3 py-2 text-caption text-muted-foreground">没有匹配的作品,可以改用"按标题追踪"。</p>
|
||||
<p className="px-3 py-2 text-caption text-muted-foreground">没有匹配的作品。可改用"按标题追踪"。</p>
|
||||
) : (
|
||||
<ul className="divide-y divide-border/60">
|
||||
{(search.data?.items ?? [])
|
||||
@@ -757,7 +750,7 @@ function CreateSubscriptionDialog({ onClose }: { onClose: () => void }) {
|
||||
labels={SUB_MODE}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="季范围" htmlFor={`${id}-scope`} hint={mediaKind === "movie" ? "电影不适用" : "随时可改"}>
|
||||
<FormField label="季范围" htmlFor={`${id}-scope`} hint={mediaKind === "movie" ? "电影不适用" : undefined}>
|
||||
<EnumSelect
|
||||
id={`${id}-scope`}
|
||||
value={mediaKind === "movie" ? "current_season" : scope}
|
||||
|
||||
Reference in new issue
Block a user