- /console/rules tabs: rule groups, custom conditions, defaults, acquisition policies, suppressions - editor: rule string parsed live via /rules/parse (position + suggestions) and synced both ways with layer cards; hard excludes; expectedRevision conflicts keep local edits - preview against recent releases or pasted titles with pri / level / reasons / trace - single-rule groups shown as preference tags; disable/delete writes suppression records - /console/preferences redirects to /console/rules (origin passed through) Co-Authored-By: Claude Opus 5.5 <[email protected]>
64 lines
2.7 KiB
TypeScript
64 lines
2.7 KiB
TypeScript
import type { RuleGroup, RuleGroupKind, RuleGroupScope, RuleGroupSource } from "@umt/contracts";
|
|
import { MEDIA_KIND } from "~/components/library/labels.ts";
|
|
import { exprToString } from "./expr.ts";
|
|
|
|
// 规则页文案:类型 / 来源 / 作用范围 / 默认链 / 抑制原因
|
|
|
|
export const RULE_KIND: Record<RuleGroupKind, { label: string; hint: string }> = {
|
|
acquisition: { label: "订阅", hint: "订阅与版本目标挑资源时用" },
|
|
upgrade: { label: "洗版", hint: "版本目标开了升级时额外叠加" },
|
|
search: { label: "搜索", hint: "手动搜索与 Agent 搜索资源时用" },
|
|
};
|
|
|
|
export const SUPPRESS_NOTE = "停用或删除会留下抑制记录,Agent 不会自动加回;重新启用或手动新建同样的规则后解除。";
|
|
|
|
export const RULE_SOURCE: Record<RuleGroupSource, string> = {
|
|
manual: "手动",
|
|
chat: "对话",
|
|
agent: "Agent",
|
|
};
|
|
|
|
export const DEFAULT_CHAIN_LABEL: Record<RuleGroupKind, string> = {
|
|
acquisition: "订阅默认",
|
|
upgrade: "洗版默认",
|
|
search: "搜索默认",
|
|
};
|
|
|
|
export const SUPPRESS_REASON: Record<string, string> = {
|
|
deleted: "删除",
|
|
disabled: "停用",
|
|
"status:disabled": "停用",
|
|
"status:deleted": "删除",
|
|
edited: "修改语义",
|
|
};
|
|
export const suppressReason = (r: string | null) => (r ? (SUPPRESS_REASON[r] ?? r) : "未注明");
|
|
|
|
/** 作用范围摘要:空 = 全部;旧数据里的 mediaKinds 同 mediaTypes */
|
|
export function scopeText(scope: RuleGroupScope): string {
|
|
const s = scope as RuleGroupScope & { mediaKinds?: string[] };
|
|
const parts: string[] = [];
|
|
const kinds = s.mediaTypes ?? s.mediaKinds;
|
|
if (kinds?.length) parts.push(kinds.map((k) => MEDIA_KIND[k as keyof typeof MEDIA_KIND] ?? k).join("、"));
|
|
if (s.entityIds?.length) parts.push(`${s.entityIds.length} 个作品`);
|
|
if (s.siteIds?.length) parts.push(`${s.siteIds.length} 个站点`);
|
|
if (s.subscriptionId) parts.push("订阅私有");
|
|
return parts.length ? parts.join(" · ") : "全部";
|
|
}
|
|
|
|
/** 单条规则(偏好标签形态):不在默认链、不是订阅私有,层 + 硬排除合计一条;旧偏好标签迁来的都算 */
|
|
export function isTagLike(g: RuleGroup): boolean {
|
|
if (g.defaultOrder !== null || g.scope.subscriptionId) return false;
|
|
if (g.legacyKind) return true;
|
|
return g.layers.length + g.hardExcludes.length === 1;
|
|
}
|
|
|
|
/** 标签上的一句规则摘要 */
|
|
export function ruleSummary(g: RuleGroup): string {
|
|
const parts: string[] = [];
|
|
if (g.layers.length)
|
|
parts.push(`${g.ladderRequired ? "要求" : "偏好"} ${g.layers.map((l) => exprToString(l.expr)).join(" > ")}`);
|
|
for (const h of g.hardExcludes) parts.push(`排除 ${exprToString(h)}`);
|
|
if (parts.length) return parts.join(";");
|
|
return g.legacyKind ? "旧偏好标签,没有可评估的条件" : "没有条件";
|
|
}
|