2026-07-13 15:28:48 +08:00
|
|
|
|
import { reactive, ref, type Ref } from 'vue'
|
|
|
|
|
|
import { ElMessage } from 'element-plus'
|
2026-07-23 15:10:13 +08:00
|
|
|
|
import {
|
|
|
|
|
|
generateDataProcess,
|
|
|
|
|
|
getDataProcessProgress,
|
|
|
|
|
|
getDataProcessResults,
|
|
|
|
|
|
restoreDataProcessResult,
|
|
|
|
|
|
stopDataProcess,
|
|
|
|
|
|
updateDataProcessResult,
|
|
|
|
|
|
type DataProcessProgress,
|
|
|
|
|
|
type DataProcessResult,
|
|
|
|
|
|
} from '@/api/modules/dataProcess'
|
|
|
|
|
|
import type { GenerationState, ResultItem } from './types'
|
2026-07-13 15:28:48 +08:00
|
|
|
|
|
|
|
|
|
|
interface GenerationBindings {
|
2026-07-23 15:10:13 +08:00
|
|
|
|
taskId: Ref<string | null>
|
2026-07-13 15:28:48 +08:00
|
|
|
|
dirty: Ref<boolean>
|
2026-07-27 10:43:48 +08:00
|
|
|
|
beforeGenerate?: () => Promise<boolean | void>
|
2026-07-23 15:10:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const RESULT_PAGE_SIZE = 500
|
|
|
|
|
|
const POLL_INTERVAL_MS = 1500
|
|
|
|
|
|
|
|
|
|
|
|
function mapResult(item: DataProcessResult): ResultItem {
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: String(item.id),
|
2026-07-24 16:30:29 +08:00
|
|
|
|
previewItemId: item.preview_item_id == null ? null : String(item.preview_item_id),
|
2026-07-23 15:10:13 +08:00
|
|
|
|
instruction: item.instruction,
|
|
|
|
|
|
input: item.input || '',
|
|
|
|
|
|
output: item.output,
|
|
|
|
|
|
originalInstruction: item.original_instruction ?? item.instruction,
|
|
|
|
|
|
originalInput: item.original_input ?? item.input ?? '',
|
|
|
|
|
|
originalOutput: item.original_output ?? item.output,
|
|
|
|
|
|
status: item.status,
|
|
|
|
|
|
error: item.error || undefined,
|
|
|
|
|
|
split: item.split || undefined,
|
|
|
|
|
|
qualityScore: item.quality_score?.overall,
|
|
|
|
|
|
qualityFlags: item.quality_score?.flags || [],
|
|
|
|
|
|
updatedAt: item.updated_at,
|
|
|
|
|
|
}
|
2026-07-13 15:28:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function useDataProcessGeneration(bindings: GenerationBindings) {
|
|
|
|
|
|
const results = ref<ResultItem[]>([])
|
|
|
|
|
|
const selectedResultId = ref<string | null>(null)
|
|
|
|
|
|
const generation = reactive<GenerationState>({
|
|
|
|
|
|
status: 'idle',
|
|
|
|
|
|
progress: 0,
|
|
|
|
|
|
message: '确认摘要后即可开始生成,过程中可查看实时进度。',
|
|
|
|
|
|
})
|
2026-07-23 15:10:13 +08:00
|
|
|
|
let generationTimer: ReturnType<typeof setTimeout> | null = null
|
|
|
|
|
|
let generationRun = 0
|
|
|
|
|
|
let pollFailureCount = 0
|
2026-07-27 10:43:48 +08:00
|
|
|
|
let generationStarting = false
|
2026-07-13 15:28:48 +08:00
|
|
|
|
|
|
|
|
|
|
function stopGenerationTimer() {
|
2026-07-23 15:10:13 +08:00
|
|
|
|
generationRun += 1
|
|
|
|
|
|
if (generationTimer) clearTimeout(generationTimer)
|
2026-07-13 15:28:48 +08:00
|
|
|
|
generationTimer = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resetDownstream() {
|
|
|
|
|
|
stopGenerationTimer()
|
|
|
|
|
|
generation.status = 'idle'
|
|
|
|
|
|
generation.progress = 0
|
|
|
|
|
|
generation.message = '确认摘要后即可开始生成,过程中可查看实时进度。'
|
|
|
|
|
|
results.value = []
|
|
|
|
|
|
selectedResultId.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-23 15:10:13 +08:00
|
|
|
|
function applyProgress(progress: DataProcessProgress) {
|
|
|
|
|
|
generation.progress = Math.max(0, Math.min(100, Number(progress.progress) || 0))
|
|
|
|
|
|
generation.message = progress.message || (
|
|
|
|
|
|
progress.status === 'running'
|
|
|
|
|
|
? '后端正在生成标准化结果并进行质量评分。'
|
|
|
|
|
|
: progress.status === 'completed'
|
|
|
|
|
|
? '数据处理已完成。'
|
|
|
|
|
|
: progress.failure_reason || '任务已停止。'
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
2026-07-13 15:28:48 +08:00
|
|
|
|
|
2026-07-23 15:10:13 +08:00
|
|
|
|
async function loadAllResults(taskId: string) {
|
|
|
|
|
|
const first = await getDataProcessResults(taskId, { page: 1, page_size: RESULT_PAGE_SIZE })
|
|
|
|
|
|
const items = [...first.items]
|
|
|
|
|
|
const pages = Math.ceil(first.total / first.page_size)
|
|
|
|
|
|
for (let page = 2; page <= pages; page += 1) {
|
|
|
|
|
|
const next = await getDataProcessResults(taskId, { page, page_size: RESULT_PAGE_SIZE })
|
|
|
|
|
|
items.push(...next.items)
|
|
|
|
|
|
}
|
|
|
|
|
|
results.value = items.map(mapResult)
|
|
|
|
|
|
selectedResultId.value = results.value[0]?.id ?? null
|
|
|
|
|
|
}
|
2026-07-13 15:28:48 +08:00
|
|
|
|
|
2026-07-23 15:10:13 +08:00
|
|
|
|
async function finishFromProgress(progress: DataProcessProgress) {
|
|
|
|
|
|
pollFailureCount = 0
|
|
|
|
|
|
applyProgress(progress)
|
|
|
|
|
|
if (progress.status === 'completed') {
|
|
|
|
|
|
const taskId = bindings.taskId.value
|
|
|
|
|
|
if (!taskId) return
|
|
|
|
|
|
await loadAllResults(taskId)
|
2026-07-13 15:28:48 +08:00
|
|
|
|
generation.status = 'success'
|
2026-07-23 15:10:13 +08:00
|
|
|
|
generation.progress = 100
|
2026-07-13 15:28:48 +08:00
|
|
|
|
generation.message = `已完成 ${results.value.length.toLocaleString()} 条数据处理,可进入结果页检查。`
|
|
|
|
|
|
bindings.dirty.value = true
|
|
|
|
|
|
ElMessage.success('数据处理完成')
|
2026-07-23 15:10:13 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (progress.status === 'failed' || progress.status === 'stopped') {
|
|
|
|
|
|
generation.status = 'failed'
|
|
|
|
|
|
generation.message = progress.failure_reason || progress.message || (
|
|
|
|
|
|
progress.status === 'stopped' ? '任务已停止,可以重新生成。' : '数据处理失败,请检查配置后重试。'
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function pollGeneration(runId: number) {
|
|
|
|
|
|
const taskId = bindings.taskId.value
|
|
|
|
|
|
if (!taskId || runId !== generationRun || generation.status !== 'running') return
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const progress = await getDataProcessProgress(taskId)
|
|
|
|
|
|
if (runId !== generationRun) return
|
|
|
|
|
|
pollFailureCount = 0
|
|
|
|
|
|
if (progress.status === 'running' || progress.status === 'pending') {
|
|
|
|
|
|
applyProgress(progress)
|
|
|
|
|
|
generationTimer = setTimeout(() => void pollGeneration(runId), POLL_INTERVAL_MS)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
await finishFromProgress(progress)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (runId !== generationRun) return
|
|
|
|
|
|
pollFailureCount += 1
|
|
|
|
|
|
if (pollFailureCount <= 3) {
|
|
|
|
|
|
generation.message = `进度查询暂时失败,正在重试(${pollFailureCount}/3)…`
|
|
|
|
|
|
generationTimer = setTimeout(() => void pollGeneration(runId), POLL_INTERVAL_MS)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
generation.status = 'failed'
|
|
|
|
|
|
generation.message = error instanceof Error ? error.message : '查询任务进度失败,请重试。'
|
|
|
|
|
|
}
|
2026-07-13 15:28:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-23 15:10:13 +08:00
|
|
|
|
async function startGeneration() {
|
2026-07-27 10:43:48 +08:00
|
|
|
|
if (generationStarting || generation.status === 'running') return
|
2026-07-23 15:10:13 +08:00
|
|
|
|
const taskId = bindings.taskId.value
|
|
|
|
|
|
if (!taskId) {
|
|
|
|
|
|
ElMessage.error('任务尚未创建,请返回上一步重试')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-27 10:43:48 +08:00
|
|
|
|
generationStarting = true
|
|
|
|
|
|
let runId: number | null = null
|
2026-07-23 15:10:13 +08:00
|
|
|
|
try {
|
2026-07-27 10:43:48 +08:00
|
|
|
|
const canStart = await bindings.beforeGenerate?.()
|
|
|
|
|
|
if (canStart === false) return
|
|
|
|
|
|
stopGenerationTimer()
|
|
|
|
|
|
const activeRunId = generationRun
|
|
|
|
|
|
runId = activeRunId
|
|
|
|
|
|
generation.status = 'running'
|
|
|
|
|
|
pollFailureCount = 0
|
|
|
|
|
|
generation.progress = 0
|
|
|
|
|
|
generation.message = '正在同步预览修改并启动后端处理,请稍候。'
|
2026-07-23 15:10:13 +08:00
|
|
|
|
const progress = await generateDataProcess(taskId)
|
2026-07-27 10:43:48 +08:00
|
|
|
|
if (activeRunId !== generationRun) return
|
2026-07-23 15:10:13 +08:00
|
|
|
|
if (progress.status === 'completed' || progress.status === 'failed' || progress.status === 'stopped') {
|
|
|
|
|
|
await finishFromProgress(progress)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
applyProgress(progress)
|
2026-07-27 10:43:48 +08:00
|
|
|
|
generationTimer = setTimeout(() => void pollGeneration(activeRunId), POLL_INTERVAL_MS)
|
2026-07-23 15:10:13 +08:00
|
|
|
|
} catch (error) {
|
2026-07-27 10:43:48 +08:00
|
|
|
|
if (runId !== null && runId !== generationRun) return
|
2026-07-23 15:10:13 +08:00
|
|
|
|
generation.status = 'failed'
|
|
|
|
|
|
generation.message = error instanceof Error ? error.message : '启动数据处理失败,请重试。'
|
2026-07-27 10:43:48 +08:00
|
|
|
|
} finally {
|
|
|
|
|
|
generationStarting = false
|
2026-07-23 15:10:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function stopGeneration() {
|
|
|
|
|
|
const taskId = bindings.taskId.value
|
|
|
|
|
|
if (!taskId) return
|
|
|
|
|
|
stopGenerationTimer()
|
|
|
|
|
|
try {
|
|
|
|
|
|
const progress = await stopDataProcess(taskId)
|
|
|
|
|
|
applyProgress(progress)
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
generation.status = 'running'
|
|
|
|
|
|
generation.message = '停止请求失败,继续查询后端任务状态。'
|
|
|
|
|
|
const runId = generationRun
|
|
|
|
|
|
generationTimer = setTimeout(() => void pollGeneration(runId), POLL_INTERVAL_MS)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-07-13 15:28:48 +08:00
|
|
|
|
generation.status = 'failed'
|
|
|
|
|
|
generation.message = '任务已停止,预览修改仍然保留,可以重新生成。'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updateResultField(id: string, field: 'instruction' | 'input' | 'output', value: string) {
|
|
|
|
|
|
const item = results.value.find((entry) => entry.id === id)
|
|
|
|
|
|
if (!item) return
|
|
|
|
|
|
item[field] = value
|
|
|
|
|
|
const valid = item.instruction.trim() && item.output.trim()
|
|
|
|
|
|
item.error = valid ? undefined : 'Instruction 和 Output 不能为空'
|
|
|
|
|
|
const changed = item.instruction !== item.originalInstruction
|
|
|
|
|
|
|| item.input !== item.originalInput
|
|
|
|
|
|
|| item.output !== item.originalOutput
|
|
|
|
|
|
item.status = item.error ? 'invalid' : changed ? 'modified' : 'valid'
|
|
|
|
|
|
bindings.dirty.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-23 15:10:13 +08:00
|
|
|
|
async function restoreResult(id: string) {
|
|
|
|
|
|
const taskId = bindings.taskId.value
|
2026-07-13 15:28:48 +08:00
|
|
|
|
const item = results.value.find((entry) => entry.id === id)
|
2026-07-23 15:10:13 +08:00
|
|
|
|
if (!taskId || !item) return
|
|
|
|
|
|
const restored = await restoreDataProcessResult(taskId, id)
|
|
|
|
|
|
const index = results.value.indexOf(item)
|
|
|
|
|
|
results.value[index] = mapResult(restored)
|
2026-07-13 15:28:48 +08:00
|
|
|
|
bindings.dirty.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-23 15:10:13 +08:00
|
|
|
|
async function persistResultChanges() {
|
|
|
|
|
|
const taskId = bindings.taskId.value
|
|
|
|
|
|
if (!taskId) throw new Error('任务尚未创建')
|
|
|
|
|
|
const changed = results.value.filter((item) => (
|
|
|
|
|
|
item.instruction !== item.originalInstruction
|
|
|
|
|
|
|| item.input !== item.originalInput
|
|
|
|
|
|
|| item.output !== item.originalOutput
|
|
|
|
|
|
))
|
|
|
|
|
|
for (const item of changed) {
|
|
|
|
|
|
const saved = await updateDataProcessResult(taskId, item.id, {
|
|
|
|
|
|
instruction: item.instruction,
|
|
|
|
|
|
input: item.input,
|
|
|
|
|
|
output: item.output,
|
|
|
|
|
|
expected_updated_at: item.updatedAt,
|
|
|
|
|
|
})
|
|
|
|
|
|
const index = results.value.findIndex((entry) => entry.id === item.id)
|
|
|
|
|
|
if (index >= 0) results.value[index] = mapResult(saved)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-13 15:28:48 +08:00
|
|
|
|
function validateResults() {
|
|
|
|
|
|
let firstInvalidId: string | null = null
|
|
|
|
|
|
for (const item of results.value) {
|
2026-07-23 15:10:13 +08:00
|
|
|
|
if (!item.instruction.trim() || !item.output.trim() || item.status === 'invalid') {
|
|
|
|
|
|
item.error ||= '结果未通过后端质量校验,请修改后重新保存'
|
2026-07-13 15:28:48 +08:00
|
|
|
|
item.status = 'invalid'
|
|
|
|
|
|
firstInvalidId ??= item.id
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (firstInvalidId) selectedResultId.value = firstInvalidId
|
|
|
|
|
|
return firstInvalidId == null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
generation,
|
|
|
|
|
|
results,
|
|
|
|
|
|
selectedResultId,
|
2026-07-23 15:10:13 +08:00
|
|
|
|
persistResultChanges,
|
2026-07-13 15:28:48 +08:00
|
|
|
|
resetDownstream,
|
|
|
|
|
|
restoreResult,
|
|
|
|
|
|
startGeneration,
|
|
|
|
|
|
stopGeneration,
|
|
|
|
|
|
stopGenerationTimer,
|
|
|
|
|
|
updateResultField,
|
|
|
|
|
|
validateResults,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|