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

@@ -141,9 +141,14 @@
</div>
</div>
<!-- 检查中状态 -->
<div v-if="isChecking" class="flex items-center gap-2 text-blue-300">
<span class="animate-spin"></span>
<span class="text-sm">AI 正在检查中...</span>
<div v-if="isChecking" class="flex flex-col gap-2 text-blue-300">
<div class="flex items-center gap-2">
<span class="animate-spin"></span>
<span class="text-sm">AI 正在检查中...</span>
</div>
<div v-if="checkProgress" class="text-xs text-slate-400 pl-6">
{{ checkProgress }}
</div>
</div>
<!-- 检查结果列表 -->
<div
@@ -213,6 +218,9 @@
<span class="animate-spin"></span>
<span class="text-sm">AI 正在重写中...</span>
</div>
<div v-if="rewriteProgress" class="text-xs text-slate-400 pl-6">
{{ rewriteProgress }}
</div>
<div v-if="rewriteStreamContent" class="bg-slate-800/50 rounded-lg p-3 border border-slate-700">
<p class="text-sm text-slate-200 whitespace-pre-wrap">{{ rewriteStreamContent }}</p>
</div>
@@ -295,6 +303,7 @@
:visible="showParadigmSelector"
@close="showParadigmSelector = false"
@select="handleParadigmSelect"
@create-custom="handleCreateCustomParadigm"
/>
<!-- 文稿选择弹窗 -->
@@ -303,18 +312,28 @@
@close="showDocSelector = false"
@select="handleDocSelect"
/>
<!-- 需求解析面板 -->
<RequirementParserPanel
v-if="showRequirementParser"
@close="showRequirementParser = false"
@paradigm-created="handleParadigmCreated"
/>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { useAppStore } from '../stores/app'
import { useParadigmStore } from '../stores/paradigm'
import { splitIntoSentencesWithPosition, computeDiff } from '../utils/textDiff'
import { updateDocument, saveDocumentVersion } from '../db/index.js'
import ParadigmSelectorModal from './ParadigmSelectorModal.vue'
import DocumentSelectorModal from './DocumentSelectorModal.vue'
import RequirementParserPanel from './RequirementParserPanel.vue'
const appStore = useAppStore()
const paradigmStore = useParadigmStore()
// ========== 数据状态 ==========
// 原文相关
@@ -328,16 +347,19 @@ const selectedSentenceIdxs = ref([])
// 范式相关
const selectedParadigm = ref(null)
const showParadigmSelector = ref(false)
const showRequirementParser = ref(false) // 需求解析面板
// 文稿选择
const showDocSelector = ref(false)
// 检查相关
const isChecking = ref(false)
const checkProgress = ref('') // 批次进度信息
const checkResults = ref([]) // {sentenceIdx, status, message}
// 重写相关
const isRewriting = ref(false)
const rewriteProgress = ref('') // 批次进度信息
const rewriteStreamContent = ref('')
const rewrittenSentences = ref([]) // {text, start, end}
const selectedRewriteIdxs = ref([])
@@ -403,8 +425,11 @@ const toggleRewriteSentence = (idx) => {
// 处理范式选择
const handleParadigmSelect = (paradigm) => {
selectedParadigm.value = paradigm
// 确保 expertGuidelines 有 scope 字段
const paradigmWithScope = paradigmStore.ensureGuidelinesHasScope(paradigm)
selectedParadigm.value = paradigmWithScope
showParadigmSelector.value = false
console.log('选择范式后的 expertGuidelines:', paradigmWithScope.expertGuidelines)
}
// 处理文稿选择
@@ -420,70 +445,172 @@ const handleDocSelect = (doc) => {
showDocSelector.value = false
}
// 处理创建自定义范式(打开需求解析面板)
const handleCreateCustomParadigm = () => {
showParadigmSelector.value = false
showRequirementParser.value = true
}
// 处理范式创建完成
const handleParadigmCreated = (paradigm) => {
selectedParadigm.value = paradigm
showRequirementParser.value = false
// 可选:显示成功提示
console.log('自定义范式已创建:', paradigm)
}
// 粒度判断函数:根据句子数量确定允许的检查粒度
const getCheckScope = (sentenceCount) => {
if (sentenceCount <= 5) return ['sentence'] // 1-5 句:仅句子级
if (sentenceCount <= 20) return ['sentence', 'paragraph'] // 6-20 句:句子级+段落级
return ['sentence', 'paragraph', 'document'] // 21+ 句:所有级别
}
// 执行范式检查
const runParadigmCheck = async () => {
if (!canCheck.value) return
isChecking.value = true
checkProgress.value = ''
checkResults.value = []
try {
// 获取选中的句子文本
const selectedSentences = selectedSentenceIdxs.value
.sort((a, b) => a - b)
.map(idx => sentences.value[idx])
// 获取选中的句子索引(排序)
const sortedIdxs = [...selectedSentenceIdxs.value].sort((a, b) => a - b)
const sentenceCount = sortedIdxs.length
// 构建检查 Prompt
// 自动分批处理每批最多15个句子
const BATCH_SIZE = 15
const totalBatches = Math.ceil(sortedIdxs.length / BATCH_SIZE)
// 确定当前检查粒度
const allowedScopes = getCheckScope(sentenceCount)
console.log(`总共选中 ${sentenceCount} 个句子,将分 ${totalBatches} 批处理,允许粒度:`, allowedScopes)
// 构建公共的检查参数
const paradigmPrompt = selectedParadigm.value.specializedPrompt || ''
const guidelinesText = (selectedParadigm.value.expertGuidelines || [])
.map((g, i) => `${i + 1}. 【${g.title}${g.description}`)
// expertGuidelines 可能是字符串数组或对象数组,需要兼容处理并过滤
const guidelines = selectedParadigm.value.expertGuidelines || []
const filteredGuidelines = guidelines.filter(g => {
// 兼容字符串和对象格式
if (typeof g === 'string') {
// 字符串格式:默认为 sentence 级别
return allowedScopes.includes('sentence')
} else {
// 对象格式:检查 scope 字段
const scope = g.scope || 'sentence' // 默认句子级
return allowedScopes.includes(scope)
}
})
console.log(`过滤前 expertGuidelines: ${guidelines.length} 条,过滤后: ${filteredGuidelines.length}`)
const guidelinesText = filteredGuidelines
.map((g, i) => {
if (typeof g === 'string') {
return `${i + 1}. ${g}`
} else {
return `${i + 1}. 【${g.title || '检查项'}${g.description || g}`
}
})
.join('\n')
const checkPrompt = `你是一个写作质量检查专家。请基于以下范式规则和专家指令,逐条检查给定的句子。
// 逐批处理
for (let batchIndex = 0; batchIndex < totalBatches; batchIndex++) {
const batchStart = batchIndex * BATCH_SIZE
const batchEnd = Math.min(batchStart + BATCH_SIZE, sortedIdxs.length)
const batchIdxs = sortedIdxs.slice(batchStart, batchEnd)
const batchSentences = batchIdxs.map(idx => sentences.value[idx])
checkProgress.value = `正在检查第 ${batchIndex + 1}/${totalBatches} 批(句子 ${batchStart + 1}-${batchEnd}...`
console.log(`检查批次 ${batchIndex + 1}/${totalBatches}(句子 ${batchStart + 1}-${batchEnd}`)
const checkPrompt = `你是一个写作质量检查专家。请基于以下范式规则和专家指令,逐条检查给定的句子。
【检查上下文】
本次检查的是 ${batchSentences.length} 个句子的片段(共选中 ${sentenceCount} 句)。
${sentenceCount <= 5 ? '⚠️ 这是句子级检查,请忽略文档结构要求,专注于句子质量(如术语、语气、表达)。' : ''}${sentenceCount <= 20 ? '⚠️ 这是段落级检查,请检查局部逻辑和结构,忽略全文结构要求(如篇幅占比、章节完整性)。' : ''}
# 范式规则
${paradigmPrompt}
# 专家检查项
# 专家检查项(已根据检查粒度自动过滤)
${guidelinesText || '(无特定检查项)'}
# 待检查的句子
${selectedSentences.map((s, i) => `${i + 1}. ${s.text}`).join('\n')}
${batchSentences.map((s, i) => `${i + 1}. ${s.text}`).join('\n')}
# 输出要求
请严格按照以下 JSON 格式输出检查结果(仅输出 JSON不要其他内容
请严格按照以下JSON格式输出(只输出JSON不要markdown代码块标记
{
"results": [
{"sentenceIdx": 0, "status": "pass|warning|fail", "message": "评价说明"}
{"sentenceIdx": 0, "status": "pass", "message": "符合要求"},
{"sentenceIdx": 1, "status": "warning", "message": "建议改进"},
{"sentenceIdx": 2, "status": "fail", "message": "不符合要求"}
]
}`
}
let fullResponse = ''
await appStore.callApi(checkPrompt, (chunk) => {
fullResponse += chunk
}, { temperature: 0.3 })
注意:
1. status只能是 pass/warning/fail 三个值之一
2. message要简洁明了不超过50字
3. sentenceIdx从0开始对应上面句子的序号
4. 必须检查所有${batchSentences.length}个句子
5. 确保JSON格式完全正确不要有多余的逗号或换行`
let fullResponse = ''
await appStore.callApi(checkPrompt, (chunk) => {
fullResponse += chunk
}, { temperature: 0.3 })
console.log(`批次 ${batchIndex + 1} - AI完整响应长度:`, fullResponse.length)
// 解析本批次结果
try {
// 1. 尝试提取JSON代码块
let jsonText = fullResponse
const jsonBlockMatch = fullResponse.match(/```json\s*([\s\S]*?)\s*```/)
if (jsonBlockMatch) {
jsonText = jsonBlockMatch[1]
}
// 2. 提取JSON对象
const jsonMatch = jsonText.match(/\{[\s\S]*\}/)
if (!jsonMatch) {
throw new Error('未找到有效的JSON对象')
}
// 解析结果
try {
const jsonMatch = fullResponse.match(/\{[\s\S]*\}/)
if (jsonMatch) {
const parsed = JSON.parse(jsonMatch[0])
// 映射回原始句子索引
checkResults.value = (parsed.results || []).map((r, i) => ({
sentenceIdx: selectedSentenceIdxs.value[r.sentenceIdx] ?? selectedSentenceIdxs.value[i],
if (!parsed.results || !Array.isArray(parsed.results)) {
throw new Error('JSON格式错误缺少results数组')
}
// 映射回原始句子索引并添加到总结果
const batchResults = parsed.results.map((r, i) => ({
sentenceIdx: batchIdxs[r.sentenceIdx] ?? batchIdxs[i],
status: r.status || 'warning',
message: r.message || '检查完成'
}))
checkResults.value.push(...batchResults)
console.log(`批次 ${batchIndex + 1} 解析成功,本批 ${batchResults.length} 个结果`)
} catch (e) {
console.error(`批次 ${batchIndex + 1} 解析失败:`, e)
console.error('完整响应内容:', fullResponse)
// 降级:为本批次的每个句子生成通用结果
const fallbackResults = batchIdxs.map((idx) => ({
sentenceIdx: idx,
status: 'warning',
message: '解析异常,建议人工复核'
}))
checkResults.value.push(...fallbackResults)
}
} catch (e) {
console.warn('检查结果解析失败:', e)
// 降级:为每个句子生成通用结果
checkResults.value = selectedSentenceIdxs.value.map((idx) => ({
sentenceIdx: idx,
status: 'warning',
message: '解析异常,建议人工复核'
}))
}
console.log(`所有批次检查完成,共 ${checkResults.value.length} 个结果`)
checkProgress.value = `检查完成!共检查 ${checkResults.value.length} 个句子`
} catch (error) {
console.error('范式检查失败:', error)
alert('检查失败: ' + error.message)
@@ -497,33 +624,53 @@ const runAIRewrite = async () => {
if (!canRewrite.value) return
isRewriting.value = true
rewriteProgress.value = ''
rewriteStreamContent.value = ''
rewrittenSentences.value = []
selectedRewriteIdxs.value = []
try {
// 获取选中的句子和检查结果
const selectedSentences = selectedSentenceIdxs.value
.sort((a, b) => a - b)
.map(idx => sentences.value[idx])
// 获取选中的句子索引(排序)
const sortedIdxs = [...selectedSentenceIdxs.value].sort((a, b) => a - b)
const checkResultsText = checkResults.value
.map(r => `句子${r.sentenceIdx + 1}: ${r.status} - ${r.message}`)
.join('\n')
// 自动分批处理每批最多10个句子重写比检查需要更多tokens所以批次更小
const BATCH_SIZE = 10
const totalBatches = Math.ceil(sortedIdxs.length / BATCH_SIZE)
// 构建重写 Prompt
console.log(`总共需要重写 ${sortedIdxs.length} 个句子,将分 ${totalBatches} 批处理`)
// 构建公共的重写参数
const paradigmPrompt = selectedParadigm.value.specializedPrompt || ''
const rewritePrompt = `你是一个专业的写作润色专家。请基于以下范式规则和检查结果,重写给定的句子。
// 逐批处理
for (let batchIndex = 0; batchIndex < totalBatches; batchIndex++) {
const batchStart = batchIndex * BATCH_SIZE
const batchEnd = Math.min(batchStart + BATCH_SIZE, sortedIdxs.length)
const batchIdxs = sortedIdxs.slice(batchStart, batchEnd)
const batchSentences = batchIdxs.map(idx => sentences.value[idx])
rewriteProgress.value = `正在重写第 ${batchIndex + 1}/${totalBatches} 批(句子 ${batchStart + 1}-${batchEnd}...`
console.log(`重写批次 ${batchIndex + 1}/${totalBatches}(句子 ${batchStart + 1}-${batchEnd}`)
// 获取本批次对应的检查结果
const batchCheckResults = checkResults.value
.filter(r => batchIdxs.includes(r.sentenceIdx))
.map(r => {
const localIdx = batchIdxs.indexOf(r.sentenceIdx)
return `句子${localIdx + 1}: ${r.status} - ${r.message}`
})
.join('\n')
const rewritePrompt = `你是一个专业的写作润色专家。请基于以下范式规则和检查结果,重写给定的句子。
# 范式规则
${paradigmPrompt}
# 检查结果
${checkResultsText}
${batchCheckResults || '(无检查结果)'}
# 原句
${selectedSentences.map((s, i) => `${i + 1}. ${s.text}`).join('\n')}
${batchSentences.map((s, i) => `${i + 1}. ${s.text}`).join('\n')}
# 输出要求
请直接按照顺序输出重写后的句子,每句一行,格式如下(仅输出重写内容,不要其他说明):
@@ -531,34 +678,42 @@ ${selectedSentences.map((s, i) => `${i + 1}. ${s.text}`).join('\n')}
2. 重写后的句子2
...`
let fullResponse = ''
await appStore.callApi(rewritePrompt, (chunk) => {
fullResponse += chunk
// 实时显示时尝试提取 draft 内容
const draftMatch = fullResponse.match(/<draft>([\s\S]*?)(?:<\/draft>|$)/)
if (draftMatch) {
rewriteStreamContent.value = draftMatch[1].trim()
} else {
// 如果还没有 draft 标签,显示完整内容(可能在 thinking 阶段)
rewriteStreamContent.value = fullResponse
}
}, { temperature: 0.7 })
let fullResponse = ''
await appStore.callApi(rewritePrompt, (chunk) => {
fullResponse += chunk
// 实时显示时尝试提取 draft 内容
const draftMatch = fullResponse.match(/<draft>([\s\S]*?)(?:<\/draft>|$)/)
if (draftMatch) {
rewriteStreamContent.value = draftMatch[1].trim()
} else {
// 如果还没有 draft 标签,显示完整内容(可能在 thinking 阶段)
rewriteStreamContent.value = fullResponse
}
}, { temperature: 0.7 })
// 解析重写结果:提取 <draft> 标签内的内容
let contentToParse = fullResponse
const draftMatch = fullResponse.match(/<draft>([\s\S]*?)<\/draft>/)
if (draftMatch) {
contentToParse = draftMatch[1].trim()
console.log(`批次 ${batchIndex + 1} - AI完整响应长度:`, fullResponse.length)
// 解析重写结果:提取 <draft> 标签内的内容
let contentToParse = fullResponse
const draftMatch = fullResponse.match(/<draft>([\s\S]*?)<\/draft>/)
if (draftMatch) {
contentToParse = draftMatch[1].trim()
}
// 按行拆分并处理
const lines = contentToParse.split('\n').filter(line => line.trim())
const batchRewritten = lines.map((line, i) => {
// 移除行首的序号
const text = line.replace(/^\d+\.\s*/, '').trim()
return { text, start: 0, end: text.length }
}).filter(s => s.text) // 过滤空行
rewrittenSentences.value.push(...batchRewritten)
console.log(`批次 ${batchIndex + 1} 重写完成,本批 ${batchRewritten.length} 个句子`)
}
// 按行拆分并处理
const lines = contentToParse.split('\n').filter(line => line.trim())
rewrittenSentences.value = lines.map((line, i) => {
// 移除行首的序号
const text = line.replace(/^\d+\.\s*/, '').trim()
return { text, start: 0, end: text.length }
}).filter(s => s.text) // 过滤空行
console.log(`所有批次重写完成,共 ${rewrittenSentences.value.length} 个句子`)
rewriteProgress.value = `重写完成!共重写 ${rewrittenSentences.value.length} 个句子`
} catch (error) {
console.error('AI 重写失败:', error)
alert('重写失败: ' + error.message)
@@ -688,27 +843,70 @@ const recheckRewrittenContent = async () => {
checkResults.value = []
try {
// 获取选中的重写句子
const selectedSentences = selectedRewriteIdxs.value
.sort((a, b) => a - b)
.map(idx => rewrittenSentences.value[idx])
// 获取选中的重写句子索引(排序)
const sortedIdxs = [...selectedRewriteIdxs.value].sort((a, b) => a - b)
const sentenceCount = sortedIdxs.length
// 构建检查 Prompt
// 自动分批处理每批最多15个句子
const BATCH_SIZE = 15
const totalBatches = Math.ceil(sortedIdxs.length / BATCH_SIZE)
// 确定当前检查粒度
const allowedScopes = getCheckScope(sentenceCount)
console.log(`[复检] 总共选中 ${sentenceCount} 个句子,将分 ${totalBatches} 批处理,允许粒度:`, allowedScopes)
// 构建公共的检查参数
const paradigmPrompt = selectedParadigm.value.specializedPrompt || ''
const guidelinesText = (selectedParadigm.value.expertGuidelines || [])
.map((g, i) => `${i + 1}. 【${g.title}${g.description}`)
// expertGuidelines 可能是字符串数组或对象数组,需要兼容处理并过滤
const guidelines = selectedParadigm.value.expertGuidelines || []
const filteredGuidelines = guidelines.filter(g => {
// 兼容字符串和对象格式
if (typeof g === 'string') {
// 字符串格式:默认为 sentence 级别
return allowedScopes.includes('sentence')
} else {
// 对象格式:检查 scope 字段
const scope = g.scope || 'sentence' // 默认句子级
return allowedScopes.includes(scope)
}
})
console.log(`[复检] 过滤前 expertGuidelines: ${guidelines.length} 条,过滤后: ${filteredGuidelines.length}`)
const guidelinesText = filteredGuidelines
.map((g, i) => {
if (typeof g === 'string') {
return `${i + 1}. ${g}`
} else {
return `${i + 1}. 【${g.title || '检查项'}${g.description || g}`
}
})
.join('\n')
const checkPrompt = `你是一个写作质量检查专家。请基于以下范式规则和专家指令,逐条检查给定的句子。
// 逐批处理
for (let batchIndex = 0; batchIndex < totalBatches; batchIndex++) {
const batchStart = batchIndex * BATCH_SIZE
const batchEnd = Math.min(batchStart + BATCH_SIZE, sortedIdxs.length)
const batchIdxs = sortedIdxs.slice(batchStart, batchEnd)
const batchSentences = batchIdxs.map(idx => rewrittenSentences.value[idx])
console.log(`[复检] 批次 ${batchIndex + 1}/${totalBatches}: 处理句子 ${batchStart + 1}-${batchEnd}`)
const checkPrompt = `你是一个写作质量检查专家。请基于以下范式规则和专家指令,逐条检查给定的句子。
【检查上下文】
本次检查的是 ${batchSentences.length} 个句子的片段(共选中 ${sentenceCount} 句,这是重写后的内容复检)。
${sentenceCount <= 5 ? '⚠️ 这是句子级检查,请忽略文档结构要求,专注于句子质量(如术语、语气、表达)。' : ''}${sentenceCount <= 20 ? '⚠️ 这是段落级检查,请检查局部逻辑和结构,忽略全文结构要求(如篇幅占比、章节完整性)。' : ''}
# 范式规则
${paradigmPrompt}
# 专家检查项
# 专家检查项(已根据检查粒度自动过滤)
${guidelinesText || '(无特定检查项)'}
# 待检查的句子(重写后的内容)
${selectedSentences.map((s, i) => `${i + 1}. ${s.text}`).join('\n')}
${batchSentences.map((s, i) => `${i + 1}. ${s.text}`).join('\n')}
# 输出要求
请严格按照以下 JSON 格式输出检查结果(仅输出 JSON不要其他内容
@@ -718,33 +916,39 @@ ${selectedSentences.map((s, i) => `${i + 1}. ${s.text}`).join('\n')}
]
}`
let fullResponse = ''
await appStore.callApi(checkPrompt, (chunk) => {
fullResponse += chunk
}, { temperature: 0.3 })
let fullResponse = ''
await appStore.callApi(checkPrompt, (chunk) => {
fullResponse += chunk
}, { temperature: 0.3 })
// 解析结果
try {
const jsonMatch = fullResponse.match(/\{[\s\S]*\}/)
if (jsonMatch) {
const parsed = JSON.parse(jsonMatch[0])
// 映射回重写句子索引,并标记为「复检」
checkResults.value = (parsed.results || []).map((r, i) => ({
sentenceIdx: selectedRewriteIdxs.value[r.sentenceIdx] ?? selectedRewriteIdxs.value[i],
status: r.status || 'warning',
message: `[复检] ${r.message || '检查完成'}`,
isRecheck: true // 标记为复检结果
// 解析结果
try {
const jsonMatch = fullResponse.match(/\{[\s\S]*\}/)
if (jsonMatch) {
const parsed = JSON.parse(jsonMatch[0])
// 映射回重写句子索引,并标记为「复检」
const batchResults = (parsed.results || []).map((r, i) => ({
sentenceIdx: batchIdxs[r.sentenceIdx] ?? batchIdxs[i],
status: r.status || 'warning',
message: `[复检] ${r.message || '检查完成'}`,
isRecheck: true // 标记为复检结果
}))
checkResults.value.push(...batchResults)
}
} catch (e) {
console.warn(`[复检] 批次 ${batchIndex + 1} 结果解析失败:`, e)
// 为当前批次的句子添加警告结果
const fallbackResults = batchIdxs.map((idx) => ({
sentenceIdx: idx,
status: 'warning',
message: '[复检] 解析异常,建议人工复核',
isRecheck: true
}))
checkResults.value.push(...fallbackResults)
}
} catch (e) {
console.warn('检查结果解析失败:', e)
checkResults.value = selectedRewriteIdxs.value.map((idx) => ({
sentenceIdx: idx,
status: 'warning',
message: '[复检] 解析异常,建议人工复核',
isRecheck: true
}))
}
console.log(`[复检] 所有批次处理完成,共 ${checkResults.value.length} 条结果`)
} catch (error) {
console.error('复检失败:', error)
alert('复检失败: ' + error.message)