diff --git a/app/routes/rulesTest.detail.tsx b/app/routes/rulesTest.detail.tsx index 42f3b5b..08f169d 100644 --- a/app/routes/rulesTest.detail.tsx +++ b/app/routes/rulesTest.detail.tsx @@ -185,6 +185,28 @@ function fallbackDependencyOption(value: string, optionMap?: Map, + visited = new Set(), +): string[] { + if (!rule) return []; + const key = rule.ruleId || rule.id; + if (visited.has(key)) return []; + visited.add(key); + + const merged = new Set((rule.dependencies || []).filter(Boolean)); + + (rule.subRuleIds || []).forEach((ruleId) => { + const referenced = rulesById.get(ruleId); + resolveRuleDependencies(referenced, rulesById, visited).forEach((dependency) => { + if (dependency) merged.add(dependency); + }); + }); + + return Array.from(merged); +} + function makeId(prefix: string): string { return `${prefix}-${Date.now()}`; } @@ -571,29 +593,36 @@ export default function RulesTestDetail() { 'ai_rule', 'rule_group' ]), [rules]); + const rulesById = useMemo(() => new Map(rules.map(rule => [rule.ruleId || rule.id, rule])), [rules]); const selectedDependencyOptions = useMemo(() => { return ruleDraft.dependencies.map(value => dependencyOptionMap.get(value) || fallbackDependencyOption(value, dependencyOptionMap)); }, [dependencyOptionMap, ruleDraft.dependencies]); + const resolvedCurrentDependencies = useMemo(() => { + return resolveRuleDependencies(currentRule, rulesById); + }, [currentRule, rulesById]); + const currentRuleWithResolvedDependencies = useMemo(() => ( + currentRule ? { ...currentRule, dependencies: resolvedCurrentDependencies } : undefined + ), [currentRule, resolvedCurrentDependencies]); const currentDependencyRows = useMemo(() => { - return (currentRule?.dependencies || []).map(value => dependencyOptionMap.get(value) || fallbackDependencyOption(value, dependencyOptionMap)); - }, [currentRule, dependencyOptionMap]); + return resolvedCurrentDependencies.map(value => dependencyOptionMap.get(value) || fallbackDependencyOption(value, dependencyOptionMap)); + }, [dependencyOptionMap, resolvedCurrentDependencies]); const currentRuleFields = useMemo( - () => fields.filter((field) => matchesCurrentRuleDependency(currentRule, [field.name])), - [currentRule, fields], + () => fields.filter((field) => matchesCurrentRuleDependency(currentRuleWithResolvedDependencies, [field.name])), + [currentRuleWithResolvedDependencies, fields], ); const currentRuleSubDocuments = useMemo( - () => subDocuments.filter((document) => matchesCurrentRuleDependency(currentRule, [document.name, document.id])), - [currentRule, subDocuments], + () => subDocuments.filter((document) => matchesCurrentRuleDependency(currentRuleWithResolvedDependencies, [document.name, document.id])), + [currentRuleWithResolvedDependencies, subDocuments], ); const currentRuleVisualElements = useMemo( - () => visualElements.filter((item) => matchesCurrentRuleDependency(currentRule, [ + () => visualElements.filter((item) => matchesCurrentRuleDependency(currentRuleWithResolvedDependencies, [ item.id, item.name, `visual.${item.id}`, `visual.${item.name || item.id}`, item.type, ])), - [currentRule, visualElements], + [currentRuleWithResolvedDependencies, visualElements], ); const dialogDependencyOptions = useMemo(() => { const selectedValues = new Set(ruleDraft.dependencies); @@ -644,7 +673,6 @@ export default function RulesTestDetail() { const fullYamlText = serializedYamlResult.yamlText; const isSmartRuleDraft = ruleDraft.type === 'ai_rule' || ruleDraft.checkTypes.includes('ai'); const isRuleGroupDraft = ruleDraft.type === 'rule_group'; - const rulesById = useMemo(() => new Map(rules.map(rule => [rule.ruleId || rule.id, rule])), [rules]); const saveButtonBusy = saveFetcher.state !== 'idle'; const latestDraftVersion = useMemo( () => versionItems.find((item) => !['published', 'rollback'].includes(item.status)), @@ -1362,7 +1390,7 @@ export default function RulesTestDetail() { stripYamlValue(block.match(new RegExp(`^\\s+${key}:\\s*(.+)$`, 'm'))?.[1] || ''); + const readStagePairValues = (block: string): string[] => { + const lines = block.split('\n'); + const values: string[] = []; + + for (let index = 0; index < lines.length; index += 1) { + const match = lines[index].match(/^\s+(?:source|target|ref_field):\s*(.+)$/); + if (match) { + values.push(stripYamlValue(match[1])); + } + } + + return values; + }; const summarizeStage = (stageBlock: string): string => { const fields = readStageList(stageBlock, 'fields'); const field = readStageScalar(stageBlock, 'field'); @@ -376,11 +389,22 @@ function parseRules(source: string): RuleSummary[] { const ruleId = stripYamlValue(ruleBlock.match(/^\s*-\s+rule_id:\s*(.+)$/m)?.[1] || ''); const name = stripYamlValue(ruleBlock.match(/^\s+name:\s*(.+)$/m)?.[1] || '未命名规则'); const checkTypes = Array.from(new Set(Array.from(ruleBlock.matchAll(/^\s+(?:check|type):\s*(.+)$/gm)).map(match => stripYamlValue(match[1])))); - const stageDependencies = Array.from(ruleBlock.matchAll(/^\s+(?:field|number|chinese|left|right|left_field|right_field|target|element|seal_id|signature_id):\s*(.+)$/gm)) + const stageDependencies = Array.from(ruleBlock.matchAll(/^\s+(?:field|number|chinese|left|right|left_field|right_field|target|element|seal_id|signature_id|ref_field):\s*(.+)$/gm)) .map(match => normalizeDependency(match[1])); + const stageFieldDependencies = splitBlocks(ruleBlock, /^\s{4,}-\s+id:\s*/) + .flatMap(stageBlock => [ + ...readStageList(stageBlock, 'fields'), + ...readStagePairValues(stageBlock), + ]) + .map(normalizeDependency); const prompts = readPrompts(ruleBlock); const promptDependencies = readPromptDependencies(prompts).map(normalizeDependency); - const dependencies = Array.from(new Set([...readExplicitDependencies(ruleBlock), ...stageDependencies, ...promptDependencies])); + const dependencies = Array.from(new Set([ + ...readExplicitDependencies(ruleBlock), + ...stageDependencies, + ...stageFieldDependencies, + ...promptDependencies, + ])); const scope = Array.from(new Set(Array.from(ruleBlock.matchAll(/^\s{4,}-\s*([^:\n]+)$/gm)).map(match => stripYamlValue(match[1])).filter(value => !/^\d+$/.test(value)))); const subRules = readSubRules(ruleBlock);