feat: sync rule management and review ui fixes

This commit is contained in:
wren
2026-05-07 17:27:42 +08:00
parent 87e82d1caa
commit c00e5feff0
13 changed files with 565 additions and 161 deletions
+68 -24
View File
@@ -42,6 +42,7 @@ export type ExtractFieldSummary = {
name: string;
type: string;
multipleEntities: boolean;
allowed?: string[];
requiredFrom: string;
description: string;
};
@@ -89,9 +90,13 @@ export type RuleYamlPack = RulePackScope & {
name: string;
type: string;
required: string;
requiredFrom?: string;
signerRoles?: string[];
signatureTypes?: string[];
privateSealRestricted?: boolean;
expectedMatchField?: string;
expectedMatchAlternatives?: string[];
prompt?: string;
}>;
};
@@ -144,6 +149,13 @@ function stripYamlValue(value = ''): string {
return value.trim().replace(/^['"]|['"]$/g, '').replace(/\u0000/g, '');
}
function toStringList(value: unknown): string[] {
if (!Array.isArray(value)) {
return [];
}
return value.map((item) => String(item || '').trim()).filter(Boolean);
}
function parseScalar(section: string, key: string): string {
const match = section.match(new RegExp(`^\\s{2}${key}:\\s*(.*)$`, 'm'));
return stripYamlValue(match?.[1] || '');
@@ -400,38 +412,58 @@ export function parseRuleSummariesFromYaml(source: string): RuleSummary[] {
}
function parseTopLevelFields(source: string): ExtractFieldSummary[] {
const section = getTopLevelSection(source, 'extract');
const extractedFields = splitBlocks(section, /^\s*-\s+group:\s*/).flatMap(groupBlock => {
const group = stripYamlValue(groupBlock.match(/^\s*-\s+group:\s*(.+)$/m)?.[1] || '未分组');
return splitBlocks(groupBlock, /^\s*-\s+name:\s*/).flatMap(fieldBlock => {
const name = stripYamlValue(fieldBlock.match(/^\s*-\s+name:\s*(.+)$/m)?.[1] || '');
const rawType = stripYamlValue(fieldBlock.match(/^\s+type:\s*(.+)$/m)?.[1] || '-');
const parentField = {
const parsed = YAML.parse(source || '') as Record<string, unknown> | null;
const extractGroups = Array.isArray(parsed?.extract) ? parsed.extract : [];
const extractedFields = extractGroups.flatMap((groupNode) => {
if (!groupNode || typeof groupNode !== 'object') {
return [];
}
const groupObject = groupNode as Record<string, unknown>;
const group = String(groupObject.group || '未分组').trim() || '未分组';
const fields = Array.isArray(groupObject.fields) ? groupObject.fields : [];
return fields.flatMap((fieldNode) => {
if (!fieldNode || typeof fieldNode !== 'object') {
return [];
}
const field = fieldNode as Record<string, unknown>;
const name = String(field.name || '').trim();
if (!name) {
return [];
}
const rawType = String(field.type || '-').trim();
const requiredFrom = String(field.required_from || '-').trim() || '-';
const parentField: ExtractFieldSummary = {
id: `${group}-${name}`,
group,
name,
type: rawType === 'multi_entity' ? 'verbatim' : rawType,
multipleEntities: rawType === 'multi_entity',
requiredFrom: stripYamlValue(fieldBlock.match(/^\s+required_from:\s*(.+)$/m)?.[1] || '-'),
description: stripYamlValue(fieldBlock.match(/^\s+desc:\s*(.+)$/m)?.[1] || '')
allowed: toStringList(field.allowed),
requiredFrom,
description: String(field.desc || '').trim(),
};
const childFields = Array.from(fieldBlock.matchAll(/^\s{2,}-\s+name:\s*(.+)$/gm)).map(match => {
const childName = stripYamlValue(match[1]);
const start = fieldBlock.indexOf(match[0]);
const next = fieldBlock.slice(start + match[0].length).search(/^\s{2,}-\s+name:\s*/m);
const childBlock = next === -1 ? fieldBlock.slice(start) : fieldBlock.slice(start, start + match[0].length + next);
const childType = stripYamlValue(childBlock.match(/^\s+type:\s*(.+)$/m)?.[1] || 'verbatim');
return {
const childFields = Array.isArray(field.fields) ? field.fields : [];
const normalizedChildFields = childFields.flatMap((childNode) => {
if (!childNode || typeof childNode !== 'object') {
return [];
}
const childField = childNode as Record<string, unknown>;
const childName = String(childField.name || '').trim();
if (!childName) {
return [];
}
return [{
id: `${group}-${name}-${childName}`,
group,
name: `${name}[*].${childName}`,
type: childType,
type: String(childField.type || 'verbatim').trim() || 'verbatim',
multipleEntities: false,
requiredFrom: stripYamlValue(childBlock.match(/^\s+required_from:\s*(.+)$/m)?.[1] || parentField.requiredFrom),
description: stripYamlValue(childBlock.match(/^\s+desc:\s*(.+)$/m)?.[1] || `${name}的子字段`)
};
allowed: toStringList(childField.allowed),
requiredFrom: String(childField.required_from || requiredFrom).trim() || requiredFrom,
description: String(childField.desc || `${name}的子字段`).trim(),
}];
});
return [parentField, ...childFields];
return [parentField, ...normalizedChildFields];
});
}).filter(field => field.name);
@@ -493,6 +525,7 @@ function parseSubDocuments(source: string): SubDocumentSummary[] {
name,
type: rawType === 'multi_entity' ? 'verbatim' : rawType,
multipleEntities: rawType === 'multi_entity',
allowed: toStringList(field.allowed),
requiredFrom: '-',
description: String(field.desc || '').trim(),
}];
@@ -553,14 +586,25 @@ function parseVisualElements(source: string): RuleYamlPack['visualElements'] {
: []
);
const expectedMatch = node.expected_text_match && typeof node.expected_text_match === 'object'
? (node.expected_text_match as Record<string, unknown>)
: null;
const signatureTypes = key === 'seals'
? toStringList(node.allowed_types)
: toStringList(node.signature_types);
return [{
id,
name: String(node.name || id).trim(),
type: label,
required: String(node.required ?? '-').trim(),
signerRoles: toStringList(node.signer_roles),
signatureTypes: toStringList(node.signature_types),
privateSealRestricted: Boolean(node.private_seal_restricted),
requiredFrom: String(node.required_from ?? '').trim(),
signerRoles: key === 'signatures' ? toStringList(node.signer_roles) : [],
signatureTypes,
privateSealRestricted: key === 'signatures' ? Boolean(node.private_seal_restricted) : false,
expectedMatchField: String(expectedMatch?.field || '').trim(),
expectedMatchAlternatives: toStringList(expectedMatch?.alternatives),
prompt: String(node.prompt || '').trim(),
}];
});
});