import type { RuleLayer, RulePreviewResult } from "@umt/contracts"; import { FlaskConical } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { usePreviewRuleGroup } from "~/api/rules.ts"; import { FilterSelect } from "~/components/console/filter-bar.tsx"; import { StatusBadge } from "~/components/console/status-badge.tsx"; import { Button } from "~/components/ui/button.tsx"; import { Textarea } from "~/components/ui/textarea.tsx"; import { errorMessage } from "~/lib/api.ts"; import { cn } from "~/lib/utils.ts"; // 试算:对最近 N 条已识别资源(或粘贴的标题)跑当前编辑中的规则组,给 pri / 命中层 / 拒绝原因与逐层依据。 const REASON: Record = { hard_exclude: "命中硬排除", "rule.hard_exclude": "命中硬排除", no_layer_matched: "一层都没命中", "rule.no_layer_matched": "一层都没命中", }; const reasonText = (r: string) => REASON[r] ?? r; export type PreviewDraft = { layers: RuleLayer[]; hardExcludes: string[]; ladderRequired: boolean }; export function RulePreviewPanel({ groupId, draft, blocked, }: { groupId: string; draft: PreviewDraft; blocked: string | null; }) { const preview = usePreviewRuleGroup(); const [limit, setLimit] = useState(""); const [titles, setTitles] = useState(""); const [pasting, setPasting] = useState(false); const [result, setResult] = useState(null); const run = () => { const list = titles .split("\n") .map((t) => t.trim()) .filter(Boolean) .slice(0, 50) .map((title) => ({ title })); preview.mutate( { id: groupId, ...(pasting && list.length ? { titles: list } : { limit: Number(limit || 50) }), draft: { layers: draft.layers, hardExcludes: draft.hardExcludes, ladderRequired: draft.ladderRequired }, }, { onSuccess: setResult, onError: (e) => toast.error(errorMessage(e, "试算失败")) }, ); }; return (
{pasting ? null : ( )}
{pasting ? (