diff --git a/src/components/ArticleRewritePanel.vue b/src/components/ArticleRewritePanel.vue index a996ba3..a28552e 100644 --- a/src/components/ArticleRewritePanel.vue +++ b/src/components/ArticleRewritePanel.vue @@ -141,9 +141,14 @@ -
- - AI 正在检查中... +
+
+ + AI 正在检查中... +
+
+ {{ checkProgress }} +
AI 正在重写中...
+
+ {{ rewriteProgress }} +

{{ rewriteStreamContent }}

@@ -295,6 +303,7 @@ :visible="showParadigmSelector" @close="showParadigmSelector = false" @select="handleParadigmSelect" + @create-custom="handleCreateCustomParadigm" /> @@ -303,18 +312,28 @@ @close="showDocSelector = false" @select="handleDocSelect" /> + + +
diff --git a/src/stores/paradigm.js b/src/stores/paradigm.js new file mode 100644 index 0000000..05aee0e --- /dev/null +++ b/src/stores/paradigm.js @@ -0,0 +1,267 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' + +/** + * 自定义范式管理 Store + * 用于管理用户通过需求文档生成的自定义范式 + */ +export const useParadigmStore = defineStore('paradigm', () => { + // 自定义范式列表(存储在 localStorage) + const customParadigms = ref([]) + + // 当前正在编辑的范式 + const editingParadigm = ref(null) + + // 是否正在解析需求文档 + const isParsing = ref(false) + + // 解析进度信息 + const parsingProgress = ref('') + + /** + * 从 localStorage 加载自定义范式 + */ + function loadCustomParadigms() { + try { + const stored = localStorage.getItem('customParadigms') + if (stored) { + customParadigms.value = JSON.parse(stored) + } + } catch (error) { + console.error('加载自定义范式失败:', error) + customParadigms.value = [] + } + } + + /** + * 保存自定义范式到 localStorage + */ + function saveCustomParadigms() { + try { + localStorage.setItem('customParadigms', JSON.stringify(customParadigms.value)) + } catch (error) { + console.error('保存自定义范式失败:', error) + } + } + + /** + * 添加自定义范式 + * @param {Object} paradigm - 范式对象 + */ + function addCustomParadigm(paradigm) { + // 检查是否已存在相同ID + const index = customParadigms.value.findIndex(p => p.id === paradigm.id) + if (index >= 0) { + // 更新现有范式 + customParadigms.value[index] = paradigm + } else { + // 添加新范式 + customParadigms.value.push(paradigm) + } + saveCustomParadigms() + } + + /** + * 删除自定义范式 + * @param {string} paradigmId - 范式ID + */ + function deleteCustomParadigm(paradigmId) { + customParadigms.value = customParadigms.value.filter(p => p.id !== paradigmId) + saveCustomParadigms() + } + + /** + * 根据ID获取自定义范式 + * @param {string} paradigmId - 范式ID + * @returns {Object|null} + */ + function getCustomParadigmById(paradigmId) { + return customParadigms.value.find(p => p.id === paradigmId) || null + } + + /** + * 更新范式的某个字段 + * @param {string} paradigmId - 范式ID + * @param {string} field - 字段名 + * @param {any} value - 新值 + */ + function updateParadigmField(paradigmId, field, value) { + const paradigm = getCustomParadigmById(paradigmId) + if (paradigm) { + paradigm[field] = value + saveCustomParadigms() + } + } + + /** + * 获取所有范式(包括内置和自定义) + * @param {Object} builtInParadigms - 内置范式对象 + * @returns {Array} + */ + function getAllParadigms(builtInParadigms = {}) { + const builtIn = Object.values(builtInParadigms).map(p => ({ + ...p, + type: 'builtin' + })) + + return [ + ...builtIn, + ...customParadigms.value + ] + } + + /** + * 计算属性:自定义范式数量 + */ + const customParadigmCount = computed(() => customParadigms.value.length) + + /** + * 计算属性:最近使用的范式(按创建时间排序) + */ + const recentCustomParadigms = computed(() => { + return [...customParadigms.value] + .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) + .slice(0, 5) + }) + + /** + * 导出范式为JSON + * @param {string} paradigmId - 范式ID + * @returns {string|null} JSON字符串 + */ + function exportParadigm(paradigmId) { + const paradigm = getCustomParadigmById(paradigmId) + if (!paradigm) return null + + try { + return JSON.stringify(paradigm, null, 2) + } catch (error) { + console.error('导出范式失败:', error) + return null + } + } + + /** + * 从JSON导入范式 + * @param {string} jsonString - JSON字符串 + * @returns {boolean} 是否成功 + */ + function importParadigm(jsonString) { + try { + const paradigm = JSON.parse(jsonString) + + // 基本验证 + if (!paradigm.id || !paradigm.name || !paradigm.specializedPrompt) { + throw new Error('范式格式不正确') + } + + // 生成新ID避免冲突 + paradigm.id = `custom-imported-${Date.now()}` + paradigm.createdAt = new Date().toISOString() + + addCustomParadigm(paradigm) + return true + } catch (error) { + console.error('导入范式失败:', error) + return false + } + } + + /** + * 清空所有自定义范式(慎用) + */ + function clearAllCustomParadigms() { + customParadigms.value = [] + saveCustomParadigms() + } + + /** + * 智能推断 guideline 的 scope + * @param {string|Object} guideline - 检查指令 + * @returns {string} scope ('sentence' | 'paragraph' | 'document') + */ + function inferScope(guideline) { + const text = typeof guideline === 'string' ? guideline : (guideline.description || guideline.title || '') + + // 全文级关键词 + if (/篇幅|章节|完整性|结构.*完整|会前准备|典型案例|巡视|巡察/.test(text)) { + return 'document' + } + + // 段落级关键词 + if (/递进|逻辑|段落|承接|前后.*呼应|层次|论述|因果/.test(text)) { + return 'paragraph' + } + + // 默认句子级 + return 'sentence' + } + + /** + * 为现有范式补充 scope 字段(返回新对象,不修改原对象) + * @param {Object} paradigm - 范式对象 + * @returns {Object} 更新后的范式(新对象) + */ + function ensureGuidelinesHasScope(paradigm) { + if (!paradigm.expertGuidelines || !Array.isArray(paradigm.expertGuidelines)) { + return paradigm + } + + // 创建深拷贝,避免修改原对象 + const updatedParadigm = { + ...paradigm, + expertGuidelines: paradigm.expertGuidelines.map(g => { + if (typeof g === 'string') { + // 字符串格式:转换为对象格式并推断 scope + // 提取前面的关键词作为标题(最多15个字符) + const title = g.length > 15 ? g.substring(0, 15) : g + return { + title: title, + description: g, + scope: inferScope(g) + } + } else if (!g.scope) { + // 对象格式但缺少 scope:推断并补充 + return { + ...g, + scope: inferScope(g) + } + } + return g + }) + } + + return updatedParadigm + } + + // 初始化时加载 + loadCustomParadigms() + + return { + // 状态 + customParadigms, + editingParadigm, + isParsing, + parsingProgress, + + // 计算属性 + customParadigmCount, + recentCustomParadigms, + + // 方法 + loadCustomParadigms, + saveCustomParadigms, + addCustomParadigm, + deleteCustomParadigm, + getCustomParadigmById, + updateParadigmField, + getAllParadigms, + exportParadigm, + importParadigm, + clearAllCustomParadigms, + + // Scope 工具函数 + inferScope, + ensureGuidelinesHasScope + } +}) diff --git a/src/utils/requirementParser.js b/src/utils/requirementParser.js new file mode 100644 index 0000000..7078e71 --- /dev/null +++ b/src/utils/requirementParser.js @@ -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 + }; +}