文案(规则 / 命名):重写规则组、试算与命名模板文案

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-07 01:48:14 +08:00
1 parent 3853a65754
commit 0bf26b78c6
6 files changed
+20 -20

No files matched your search

@@ -25,13 +25,13 @@ const GROUP_ORDER = ["title", "episode", "tech", "id", "ours"] as const;
const ROLE: Record<NamingPreviewResult["segments"][number]["role"], { label: string; className: string }> = { const ROLE: Record<NamingPreviewResult["segments"][number]["role"], { label: string; className: string }> = {
category: { label: "分类", className: "text-muted-foreground" }, category: { label: "分类", className: "text-muted-foreground" },
root: { label: "作品根", className: "text-primary font-medium" }, root: { label: "作品目录", className: "text-primary font-medium" },
season: { label: "季目录", className: "text-foreground" }, season: { label: "季目录", className: "text-foreground" },
sub: { label: "子目录", className: "text-muted-foreground" }, sub: { label: "子目录", className: "text-muted-foreground" },
file: { label: "文件名", className: "text-foreground font-medium" }, file: { label: "文件名", className: "text-foreground font-medium" },
}; };
export const NAMING_NOTE = "已建立目录的作品不受模板影响,模板只决定新目录和新文件名。"; export const NAMING_NOTE = "已建立目录的作品沿用原目录,模板只决定新目录和文件名。";
function useDebounced<T>(v: T, ms = 300): T { function useDebounced<T>(v: T, ms = 300): T {
const [d, setD] = useState(v); const [d, setD] = useState(v);
@@ -167,7 +167,7 @@ export function NamingTemplateEditor({
<span> <span>
{override && !value.trim() {override && !value.trim()
? `留空:沿用${inherited?.[active]?.source ?? "上级"}模板` ? `留空:沿用${inherited?.[active]?.source ?? "上级"}模板`
: "/ 分目录,最后一段是文件名,须以 {{fileExt}} 结尾"} : "用 / 分目录,最后一段是文件名,以 {{fileExt}} 结尾"}
</span> </span>
<span className="tabular-nums">{value.length} / 1024</span> <span className="tabular-nums">{value.length} / 1024</span>
</div> </div>
@@ -317,10 +317,10 @@ function PreviewBody({
))} ))}
</div> </div>
)} )}
{r.usesBinding ? <Note>这部作品已有目录,目录沿用已建立的路径,模板只决定文件名。</Note> : null} {r.usesBinding ? <Note>这部作品已有目录,沿用原目录,模板只决定文件名。</Note> : null}
{r.missing.length ? ( {r.missing.length ? (
<p className="text-meta text-muted-foreground"> <p className="text-meta text-muted-foreground">
无值的变量(输出为空):<span className="font-mono">{r.missing.join("、")}</span> 没有值的变量:<span className="font-mono">{r.missing.join("、")}</span>
</p> </p>
) : null} ) : null}
{r.warnings.map((w) => ( {r.warnings.map((w) => (
@@ -108,7 +108,7 @@ export function RuleGroupEditor({
const save = async () => { const save = async () => {
if (!form.name.trim()) { if (!form.name.trim()) {
setNameError("名称必填"); setNameError("填写名称");
return; return;
} }
const scope: RuleGroupScope = { const scope: RuleGroupScope = {
@@ -263,7 +263,7 @@ export function RuleGroupEditor({
label="作用范围" label="作用范围"
hint={ hint={
group && (group.scope.entityIds?.length || group.scope.siteIds?.length || group.scope.subscriptionId) group && (group.scope.entityIds?.length || group.scope.siteIds?.length || group.scope.subscriptionId)
? `另有:${scopeText({ ...group.scope, mediaTypes: undefined, mediaKinds: undefined } as RuleGroupScope)}(此处不改)` ? `另有:${scopeText({ ...group.scope, mediaTypes: undefined, mediaKinds: undefined } as RuleGroupScope)}(这里不改)`
: "不选 = 全部类型" : "不选 = 全部类型"
} }
className="sm:col-span-2" className="sm:col-span-2"
@@ -277,15 +277,15 @@ export function RuleGroupEditor({
</FormField> </FormField>
<div className="flex items-center justify-between gap-3 py-1 sm:col-span-2"> <div className="flex items-center justify-between gap-3 py-1 sm:col-span-2">
<div className="min-w-0"> <div className="min-w-0">
<p className="text-caption font-medium text-foreground">一层都不中时拒绝</p> <p className="text-caption font-medium text-foreground">一层都没命中时拒绝</p>
<p className="text-meta text-muted-foreground"> <p className="text-meta text-muted-foreground">
{form.ladderRequired ? "资源至少要命中一层" : "只影响排序,不中的资源 pri 为 0"} {form.ladderRequired ? "资源至少要命中一层" : "只影响排序,没命中的资源 pri 为 0"}
</p> </p>
</div> </div>
<Switch <Switch
checked={form.ladderRequired} checked={form.ladderRequired}
onCheckedChange={(v) => set("ladderRequired", v)} onCheckedChange={(v) => set("ladderRequired", v)}
aria-label="一层都不中时拒绝" aria-label="一层都没命中时拒绝"
/> />
</div> </div>
<div className="flex items-center justify-between gap-3 py-1 sm:col-span-2"> <div className="flex items-center justify-between gap-3 py-1 sm:col-span-2">
@@ -379,7 +379,7 @@ const FIELD_LABEL: Record<string, string> = {
scope: "作用范围", scope: "作用范围",
layers: "阶梯", layers: "阶梯",
hardExcludes: "硬排除", hardExcludes: "硬排除",
ladderRequired: "一层不中拒绝", ladderRequired: "一层都没命中时拒绝",
enabled: "启用", enabled: "启用",
label: "名称", label: "名称",
rule: "规则", rule: "规则",
+2 -2
View File
@@ -29,7 +29,7 @@ export const SUPPRESS_REASON: Record<string, string> = {
disabled: "停用", disabled: "停用",
"status:disabled": "停用", "status:disabled": "停用",
"status:deleted": "删除", "status:deleted": "删除",
edited: "修改语义", edited: "改了条件",
}; };
export const suppressReason = (r: string | null) => (r ? (SUPPRESS_REASON[r] ?? r) : "未注明"); export const suppressReason = (r: string | null) => (r ? (SUPPRESS_REASON[r] ?? r) : "未注明");
@@ -59,5 +59,5 @@ export function ruleSummary(g: RuleGroup): string {
parts.push(`${g.ladderRequired ? "要求" : "偏好"} ${g.layers.map((l) => exprToString(l.expr)).join(" > ")}`); parts.push(`${g.ladderRequired ? "要求" : "偏好"} ${g.layers.map((l) => exprToString(l.expr)).join(" > ")}`);
for (const h of g.hardExcludes) parts.push(`排除 ${exprToString(h)}`); for (const h of g.hardExcludes) parts.push(`排除 ${exprToString(h)}`);
if (parts.length) return parts.join(";"); if (parts.length) return parts.join(";");
return g.legacyKind ? "旧偏好标签,没有可评估的条件" : "没有条件"; return g.legacyKind ? "旧版偏好,没有可评估的条件" : "没有条件";
} }
@@ -47,7 +47,7 @@ export function ladderFrom(layers: RuleLayer[]): LadderState {
/** 保存用:有错误 / 解析中 / 有空层时不可保存 */ /** 保存用:有错误 / 解析中 / 有空层时不可保存 */
export function ladderProblem(s: LadderState): string | null { export function ladderProblem(s: LadderState): string | null {
if (s.parsing) return "规则串还在解析"; if (s.parsing) return "规则串解析中";
if (s.failure) return s.failure; if (s.failure) return s.failure;
if (s.errors.length) return "规则串有错误"; if (s.errors.length) return "规则串有错误";
if (s.cards?.some((c) => c.items.length === 0)) return "有空层"; if (s.cards?.some((c) => c.items.length === 0)) return "有空层";
@@ -253,7 +253,7 @@ export function RuleStringField({
<li key={`${e.code}-${e.position}-${i}`} className="text-meta text-destructive"> <li key={`${e.code}-${e.position}-${i}`} className="text-meta text-destructive">
<ErrorCaret text={state.text} position={e.position} /> <ErrorCaret text={state.text} position={e.position} />
<span> <span>
第 {e.position + 1} 个字符{single ? "" : `(第 ${e.layer + 1} 层)`}:{e.message} 第 {e.position + 1} 个字符{single ? "" : `(第 ${e.layer + 1} 层)`}:{e.message}
</span> </span>
{e.suggestions?.length ? ( {e.suggestions?.length ? (
<span className="ml-1 inline-flex flex-wrap gap-1 align-middle"> <span className="ml-1 inline-flex flex-wrap gap-1 align-middle">
@@ -338,7 +338,7 @@ function AtomChip({
item.neg ? "border-destructive/35 bg-destructive/10 text-destructive" : "border-border bg-foreground/[0.03]", item.neg ? "border-destructive/35 bg-destructive/10 text-destructive" : "border-border bg-foreground/[0.03]",
unknown && "border-warning/50", unknown && "border-warning/50",
)} )}
title={keyword ? `${keyword.name}:${keyword.description}` : "未知条件"} title={keyword ? `${keyword.name}:${keyword.description}` : "未知条件"}
> >
<button <button
type="button" type="button"
@@ -150,7 +150,7 @@ function PreviewResult({ result }: { result: RulePreviewResult }) {
</details> </details>
) : null} ) : null}
<p className="mt-1 text-meta text-muted-foreground sm:hidden"> <p className="mt-1 text-meta text-muted-foreground sm:hidden">
{[...it.excludedBy.map((x) => `排除 ${x}`), ...it.reasons.map(reasonText)].join(";")} {[...it.excludedBy.map((x) => `排除 ${x}`), ...it.reasons.map(reasonText)].join(";")}
</p> </p>
</td> </td>
<td className="px-3 py-2 whitespace-nowrap"> <td className="px-3 py-2 whitespace-nowrap">
@@ -164,7 +164,7 @@ function PreviewResult({ result }: { result: RulePreviewResult }) {
)} )}
</td> </td>
<td className={cn("hidden px-3 py-2 text-meta text-muted-foreground sm:table-cell")}> <td className={cn("hidden px-3 py-2 text-meta text-muted-foreground sm:table-cell")}>
{[...it.excludedBy.map((x) => `排除 ${x}`), ...it.reasons.map(reasonText)].join(";") || "—"} {[...it.excludedBy.map((x) => `排除 ${x}`), ...it.reasons.map(reasonText)].join(";") || "—"}
</td> </td>
</tr> </tr>
))} ))}
+2 -2
View File
@@ -257,7 +257,7 @@ function atomSummary(a: RuleAtom): string {
if (a.minSeeders !== null) p.push(`做种 ≥ ${a.minSeeders}`); if (a.minSeeders !== null) p.push(`做种 ≥ ${a.minSeeders}`);
if (a.publishDelayMinutes) p.push(`发布 ${a.publishDelayMinutes.min ?? 0}–${a.publishDelayMinutes.max ?? "∞"} 分钟`); if (a.publishDelayMinutes) p.push(`发布 ${a.publishDelayMinutes.min ?? 0}–${a.publishDelayMinutes.max ?? "∞"} 分钟`);
if (a.free) p.push("免费"); if (a.free) p.push("免费");
return p.join(";") || "没有条件"; return p.join(";") || "没有条件";
} }
export function AtomsTab() { export function AtomsTab() {
@@ -299,7 +299,7 @@ export function AtomsTab() {
<Trash2 /> <Trash2 />
</Button> </Button>
} }
title={`删除条件 ${a.key}?`} title={`删除条件 ${a.key}?`}
description="被规则组引用时不能删除。" description="被规则组引用时不能删除。"
confirmLabel="删除" confirmLabel="删除"
destructive destructive