fix(aihr): recover stalled media processing

This commit is contained in:
key
2026-07-29 15:09:01 +08:00
parent f17b904b5c
commit 652e48884b
18 changed files with 838 additions and 76 deletions
+39 -7
View File
@@ -149,6 +149,20 @@
</el-table-column>
<el-table-column prop="summary" label="AI摘要" min-width="230" show-overflow-tooltip />
<el-table-column prop="updatedAt" label="更新时间" width="138" />
<el-table-column label="操作" width="86" fixed="right">
<template #default="{ row }">
<el-button
v-if="canRetryProcessingTask(row)"
link
type="danger"
:loading="taskRetryingId === row.id"
@click="retryTask(row)"
>
重试
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
</el-table>
<footer class="table-footer">
@@ -210,7 +224,7 @@
</div>
<div v-if="displayOverview.rules.failedTasks > 0" class="risk-tip">
<el-icon><WarningFilled /></el-icon>
<span>失败任务 {{ displayOverview.rules.failedTasks }} 个,建议先重试网络/格式异常文件</span>
<span>需处理任务 {{ displayOverview.rules.failedTasks }} 个,可从原文件重试图片/视频解析</span>
</div>
</section>
</aside>
@@ -260,7 +274,7 @@
import { CircleCheck, Document, Files, Operation, Refresh, Search, Upload, WarningFilled } from '@element-plus/icons-vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { computed, onMounted, onUnmounted, ref } from 'vue';
import { getProcessingOverview, type ProcessingOverview, type ProcessingTask } from '@/api/aihr/processing';
import { getProcessingOverview, retryProcessingTask, type ProcessingOverview, type ProcessingTask } from '@/api/aihr/processing';
import { uploadKnowledgeDocAsync, listUploadItems, retryUploadItem, type UploadItem } from '@/api/aihr/sop';
import { listKnowledgeSpaces, type KnowledgeSpace } from '@/api/aihr/space';
import { listPersonalPublishRequests, reviewPersonalPublishRequest, type PersonalPublishRequestRow } from '@/api/aihr/personal';
@@ -294,9 +308,10 @@ const uploadBatch = ref<UploadItem[]>([]);
const uploadBatchId = ref('');
const enqueueFailures = ref<string[]>([]);
const retryingId = ref<number | null>(null);
const taskRetryingId = ref<number | null>(null);
let batchPollTimer: number | undefined;
const statusTabs = ['全部', '等待解析', '解析中', '已完成', '失败'];
const statusTabs = ['全部', '待重试', '解析中', '已完成', '失败'];
const fileTypes = ['全部', 'Word', 'PDF', 'PPT', 'Excel', '图片', '视频'];
const seedOverview: ProcessingOverview = {
@@ -304,7 +319,7 @@ const seedOverview: ProcessingOverview = {
{ key: 'total', label: '资料总量', value: '3.2 GB', note: '已接入 1,286 个文件', type: 'danger' },
{ key: 'completed', label: '已完成', value: '1,148', note: '较昨日 ↑128 (12.6%)', type: 'success' },
{ key: 'processing', label: '处理中', value: '36', note: '进行中任务', type: 'primary' },
{ key: 'failed', label: '失败', value: '7', note: '较昨日 ↑2 (22.2%)', type: 'warning' }
{ key: 'failed', label: '需处理', value: '7', note: '可从任务列表重试', type: 'warning' }
],
categories: [
{ name: '住宅 SOP', files: 368, size: '2.1 GB', icon: 'folder', type: 'danger' },
@@ -331,7 +346,7 @@ const seedOverview: ProcessingOverview = {
''
),
task(2, '住宅 SOP', '物业收费催缴流程.pdf', 'PDF', '8.1MB', '解析中', 'primary', 62, 0, '待向量化', 'warning', '2026-07-02 14:28', '查看', ''),
task(3, '培训课件', '巡检标准培训课件.pptx', 'PPT', '42MB', '等待解析', 'warning', 0, 0, '待处理', 'warning', '2026-07-02 14:25', '开始', ''),
task(3, '培训课件', '巡检标准培训课件.pptx', 'PPT', '42MB', '待重试', 'warning', 0, 0, '失败原因', 'danger', '2026-07-02 14:25', '重试', ''),
task(4, '图片素材', '工单照片归档.zip', '图片', '286MB', 'OCR排队', 'primary', 0, 0, '待处理', 'warning', '2026-07-02 14:20', '查看', ''),
task(
5,
@@ -401,7 +416,7 @@ const emptyOverview: ProcessingOverview = {
{ key: 'total', label: '资料总量', value: '0', note: '暂无导入文件', type: 'danger' },
{ key: 'completed', label: '已完成', value: '0', note: '等待真实上传', type: 'success' },
{ key: 'processing', label: '处理中', value: '0', note: '暂无任务', type: 'primary' },
{ key: 'failed', label: '失败', value: '0', note: '暂无失败', type: 'warning' }
{ key: 'failed', label: '需处理', value: '0', note: '暂无异常', type: 'warning' }
],
categories: [],
tasks: [],
@@ -605,6 +620,23 @@ async function retryBatchItem(item: UploadItem) {
}
}
function canRetryProcessingTask(task: ProcessingTask) {
return task.action === '重试' && ['图片', '视频'].includes(task.fileType);
}
async function retryTask(task: ProcessingTask) {
taskRetryingId.value = task.id;
try {
await retryProcessingTask(task.id);
ElMessage.success(`已从原文件重新排队:${task.fileName}`);
await loadOverview();
} catch (error) {
ElMessage.error(uploadFailureMessage(error));
} finally {
taskRetryingId.value = null;
}
}
const batchCounts = computed(() => ({
queued: uploadBatch.value.filter((item) => item.status === 0).length,
processing: uploadBatch.value.filter((item) => item.status === 1).length,
@@ -635,7 +667,7 @@ function uploadFailureMessage(error: unknown) {
function matchStatus(status: string) {
if (activeStatus.value === '全部') return true;
if (activeStatus.value === '等待解析') return ['等待解析', 'OCR排队'].includes(status);
if (activeStatus.value === '待重试') return ['待重试', '等待解析', 'OCR排队'].includes(status);
return status === activeStatus.value;
}