feat: 范式检查和复检功能支持智能粒度过滤和批量处理

## 核心功能
- 范式检查和复检支持自动分批处理(15句/批)
- 智能粒度过滤:根据选中句子数量自动调整检查粒度
  * 1-5句:仅句子级检查(术语、语气、表达)
  * 6-20句:句子级+段落级检查(逻辑结构、递进关系)
  * 21+句:全文级检查(章节完整性、篇幅占比等)

## 新增文件
- src/stores/paradigm.js:自定义范式管理 store
  * inferScope:智能推断检查项粒度
  * ensureGuidelinesHasScope:为旧范式补充 scope 字段
- src/utils/requirementParser.js:需求文档解析工具
  * buildRequirementParserPrompt:生成解析 prompt
  * validateParadigm:范式验证(兼容字符串/对象格式)
- src/components/RequirementParserPanel.vue:需求解析面板
  * 支持粘贴文本或选择文件
  * AI 自动生成范式配置(specializedPrompt + expertGuidelines)
  * 可视化编辑检查项(title/description/scope)

## 主要改进
- ArticleRewritePanel.vue:
  * 范式检查支持粒度过滤和批量处理
  * 复检功能支持粒度过滤和批量处理
  * 集成 paradigm store 自动补充 scope 字段
  * 添加批次进度显示
- RequirementParserPanel.vue:
  * 修复对象格式 expertGuidelines 显示问题
  * 支持编辑 title/description/scope 三个字段
  * 添加粒度下拉选择器(句子级/段落级/全文级)

## 技术细节
- 向后兼容:支持字符串和对象两种 expertGuidelines 格式
- 自动转换:旧范式字符串格式自动转换为对象格式并推断 scope
- 错误处理:JSON 解析失败时提供降级方案
- 控制台日志:[复检] 前缀标识复检相关日志

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
empty
2026-01-11 13:51:40 +08:00
parent f9f0785106
commit 29bb7e2e87
4 changed files with 1206 additions and 105 deletions

View File

@@ -0,0 +1,199 @@
/**
* 需求文档解析工具
* 将需求文档转换为范式配置
*/
/**
* 构建需求解析的 Prompt
*/
export function buildRequirementParserPrompt(requirementText) {
return `你是一位专业的文档分析专家,擅长提取文档中的核心要求并将其转化为结构化的写作范式配置。
【任务】
分析以下需求文档,提取关键要求并生成"范式配置"用于指导AI检查和润色文稿。
【需求文档】
${requirementText}
【输出要求】
请生成以下三个部分使用JSON格式输出
1. **specializedPrompt** (string): 系统提示词,包含:
- 会议主题/文档目标
- 核心要求(列表形式)
- 论述规范(结构要求、术语规范、语气要求等)
- 维度要求(如五维度溯源等)
长度300-500字
2. **expertGuidelines** (array): 专家检查指令,每条指令应该:
- 针对需求文档中的具体要求
- 可直接用于检查文稿是否符合标准
- 清晰、可执行
- **包含 scope 字段**(指定检查粒度):
* "sentence" - 句子级检查(如术语规范、语气分寸、表达方式)
* "paragraph" - 段落级检查(如逻辑结构、递进关系、论述层次)
* "document" - 全文级检查(如章节完整性、篇幅占比、结构要求)
数量8-12条
3. **metadata** (object): 元数据,包含:
- name (string): 范式名称简短10字以内
- description (string): 范式描述30字以内
- keyRequirements (array): 核心要求关键词3-5个
【输出格式】
\`\`\`json
{
"specializedPrompt": "你是一位资深的...",
"expertGuidelines": [
{"title": "党内术语规范", "description": "检查是否使用...", "scope": "sentence"},
{"title": "递进式结构", "description": "检查段落是否符合...", "scope": "paragraph"},
{"title": "章节完整性", "description": "检查是否包含会前准备...", "scope": "document"},
...
],
"metadata": {
"name": "范式名称",
"description": "范式描述",
"keyRequirements": ["要求1", "要求2", "要求3"]
}
}
\`\`\`
【注意事项】
1. specializedPrompt 应该完整、系统,涵盖所有关键要求
2. expertGuidelines 应该具体、可操作,每条针对一个检查点
3. 保留原文中的专业术语和标准表述
4. 如果需求文档提到篇幅要求、格式要求等,务必在 specializedPrompt 中明确体现
5. **scope 分配原则**
- sentence 级适用于任何句子片段1-5句
- paragraph 级需要段落上下文6-20句
- document 级需要完整文档或大段落21+句)
请开始分析并生成范式配置。`;
}
/**
* 解析AI返回的范式配置
* @param {string} aiResponse - AI返回的文本
* @returns {Object|null} 解析后的范式配置失败返回null
*/
export function parseParadigmConfig(aiResponse) {
try {
// 调试:输出原始响应
console.log('AI 原始响应长度:', aiResponse.length);
console.log('AI 原始响应内容前500字符:', aiResponse.substring(0, 500));
// 检查响应是否为空
if (!aiResponse || aiResponse.trim().length === 0) {
console.error('AI 返回内容为空');
return null;
}
// 尝试提取 JSON 代码块
const jsonMatch = aiResponse.match(/```json\s*([\s\S]*?)\s*```/);
let jsonText = jsonMatch ? jsonMatch[1] : aiResponse;
console.log('提取的 JSON 文本前200字符:', jsonText.substring(0, 200));
// 移除可能的注释
jsonText = jsonText.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*/g, '');
const config = JSON.parse(jsonText);
// 验证必需字段
if (!config.specializedPrompt || !Array.isArray(config.expertGuidelines) || !config.metadata) {
console.error('配置缺少必需字段:', {
hasPrompt: !!config.specializedPrompt,
hasGuidelines: Array.isArray(config.expertGuidelines),
hasMetadata: !!config.metadata
});
throw new Error('缺少必需字段');
}
return config;
} catch (error) {
console.error('解析范式配置失败:', error);
console.error('完整的 AI 响应:', aiResponse);
return null;
}
}
/**
* 将解析的配置转换为完整的范式对象
* @param {Object} parsedConfig - 解析后的配置
* @param {string} sourceDocPath - 源需求文档路径(可选)
* @returns {Object} 完整的范式对象
*/
export function buildParadigmObject(parsedConfig, sourceDocPath = null) {
const timestamp = Date.now();
const id = `custom-${timestamp}`;
return {
id,
name: parsedConfig.metadata.name,
description: parsedConfig.metadata.description,
type: 'custom', // 标记为自定义范式
createdAt: new Date().toISOString(),
sourceDoc: sourceDocPath,
specializedPrompt: parsedConfig.specializedPrompt,
expertGuidelines: parsedConfig.expertGuidelines,
// 可选:继承默认的逻辑范式和维度集
logicParadigms: null, // 由使用者决定是否继承
dimensionSetId: null,
defaultReference: null,
// 元数据
metadata: {
...parsedConfig.metadata,
customGenerated: true
}
};
}
/**
* 验证范式配置的完整性
* @param {Object} paradigm - 范式对象
* @returns {Object} 验证结果 { valid: boolean, errors: string[] }
*/
export function validateParadigm(paradigm) {
const errors = [];
if (!paradigm.name || paradigm.name.trim().length === 0) {
errors.push('范式名称不能为空');
}
if (!paradigm.specializedPrompt || paradigm.specializedPrompt.length < 100) {
errors.push('specializedPrompt 内容过短建议至少300字');
}
if (!Array.isArray(paradigm.expertGuidelines) || paradigm.expertGuidelines.length < 5) {
errors.push('expertGuidelines 至少需要5条指令');
}
if (paradigm.expertGuidelines) {
paradigm.expertGuidelines.forEach((guideline, index) => {
// 兼容字符串和对象格式
if (typeof guideline === 'string') {
if (guideline.trim().length === 0) {
errors.push(`${index + 1}条指令为空`);
}
} else if (typeof guideline === 'object') {
// 对象格式:检查 description 字段
if (!guideline.description || guideline.description.trim().length === 0) {
errors.push(`${index + 1}条指令的描述为空`);
}
if (!guideline.scope || !['sentence', 'paragraph', 'document'].includes(guideline.scope)) {
errors.push(`${index + 1}条指令的 scope 字段无效`);
}
} else {
errors.push(`${index + 1}条指令格式错误`);
}
});
}
return {
valid: errors.length === 0,
errors
};
}