feat(data-process): 完善后台生成与失败重试
This commit is contained in:
@@ -1,23 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import type { PreviewItem, ResultItem } from './types'
|
||||
import type { BulkResultRegenerationState, PreviewItem, ResultItem } from './types'
|
||||
|
||||
const props = defineProps<{
|
||||
items: ResultItem[]
|
||||
previewItems: PreviewItem[]
|
||||
selectedId: string | null
|
||||
regeneratingResultId: string | null
|
||||
bulkRegeneration: BulkResultRegenerationState
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:selectedId': [value: string]
|
||||
'update:field': [id: string, field: 'instruction' | 'input' | 'output', value: string]
|
||||
'restore:item': [id: string]
|
||||
'regenerate:item': [id: string]
|
||||
'regenerate:all': []
|
||||
}>()
|
||||
|
||||
const search = ref('')
|
||||
const invalidOnly = ref(false)
|
||||
const selectedItem = computed(() => props.items.find((item) => item.id === props.selectedId) ?? props.items[0])
|
||||
const selectedIndex = computed(() => props.items.findIndex((item) => item.id === selectedItem.value?.id))
|
||||
const invalidCount = computed(() => (
|
||||
props.items.filter((item) => item.savedStatus === 'invalid').length
|
||||
))
|
||||
const bulkRegenerationActive = computed(() => props.bulkRegeneration.status === 'running')
|
||||
const bulkRegenerationVisible = computed(() => (
|
||||
props.bulkRegeneration.status !== 'idle' && props.bulkRegeneration.total > 0
|
||||
))
|
||||
const bulkRegenerationPercentage = computed(() => (
|
||||
props.bulkRegeneration.total > 0
|
||||
? Math.round((props.bulkRegeneration.completed / props.bulkRegeneration.total) * 100)
|
||||
: 0
|
||||
))
|
||||
const itemRegenerating = (id: string) => (
|
||||
props.regeneratingResultId === id
|
||||
|| props.bulkRegeneration.targetIds.includes(id)
|
||||
)
|
||||
const selectedItemRegenerating = computed(() => (
|
||||
selectedItem.value ? itemRegenerating(selectedItem.value.id) : false
|
||||
))
|
||||
const selectedSource = computed(() => {
|
||||
const previewItemId = selectedItem.value?.previewItemId
|
||||
if (!previewItemId) return null
|
||||
@@ -67,8 +89,34 @@ function selectRelative(offset: number) {
|
||||
<template>
|
||||
<section class="result-step">
|
||||
<div class="result-workspace">
|
||||
<aside class="result-list-pane">
|
||||
<div class="pane-header"><strong>生成结果</strong><span>共 {{ items.length }} 条</span></div>
|
||||
<aside class="result-list-pane" :class="{ 'has-bulk-progress': bulkRegenerationVisible }">
|
||||
<div class="pane-header result-list-header">
|
||||
<div class="result-list-title"><strong>生成结果</strong><span>共 {{ items.length }} 条</span></div>
|
||||
<el-button
|
||||
v-if="invalidCount > 0"
|
||||
size="small"
|
||||
plain
|
||||
type="primary"
|
||||
:loading="bulkRegenerationActive"
|
||||
:disabled="Boolean(regeneratingResultId) || bulkRegenerationActive"
|
||||
@click="emit('regenerate:all')"
|
||||
>
|
||||
<i v-if="!bulkRegenerationActive" class="fa fa-refresh" style="margin-right: 4px;" />
|
||||
{{ bulkRegenerationActive ? '重新生成中' : `全部重新生成(${invalidCount})` }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="bulkRegenerationVisible" class="bulk-regeneration-progress">
|
||||
<div>
|
||||
<span>已处理 {{ bulkRegeneration.completed }} / {{ bulkRegeneration.total }}</span>
|
||||
<span>成功 {{ bulkRegeneration.succeeded }} · 失败 {{ bulkRegeneration.failed }}</span>
|
||||
</div>
|
||||
<el-progress
|
||||
:percentage="bulkRegenerationPercentage"
|
||||
:show-text="false"
|
||||
:stroke-width="5"
|
||||
:color="bulkRegeneration.failed > 0 ? '#d97706' : '#5b50f2'"
|
||||
/>
|
||||
</div>
|
||||
<div class="result-toolbar">
|
||||
<el-input v-model="search" clearable size="small" placeholder="搜索结果">
|
||||
<template #prefix><i class="fa fa-search" /></template>
|
||||
@@ -89,7 +137,12 @@ function selectRelative(offset: number) {
|
||||
<strong>{{ item.instruction || '未填写指令' }}</strong>
|
||||
<small>{{ item.output || '未填写输出' }}</small>
|
||||
</span>
|
||||
<i class="fa" :class="item.status === 'invalid' ? 'fa-exclamation-circle is-error' : 'fa-check-circle is-valid'" />
|
||||
<i v-if="itemRegenerating(item.id)" class="css-spinner" />
|
||||
<i
|
||||
v-else
|
||||
class="fa"
|
||||
:class="item.status === 'invalid' ? 'fa-exclamation-circle is-error' : 'fa-check-circle is-valid'"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -106,7 +159,19 @@ function selectRelative(offset: number) {
|
||||
:type="selectedItem.qualityScore >= 80 ? 'success' : selectedItem.qualityScore >= 60 ? 'warning' : 'danger'"
|
||||
>质量 {{ selectedItem.qualityScore.toFixed(1) }}</el-tag>
|
||||
</div>
|
||||
<el-button link @click="emit('restore:item', selectedItem.id)"><i class="fa fa-undo" /> 恢复生成结果</el-button>
|
||||
<div class="result-header-actions">
|
||||
<el-button
|
||||
v-if="selectedItem.savedStatus === 'invalid'"
|
||||
size="small"
|
||||
plain
|
||||
type="primary"
|
||||
:loading="selectedItemRegenerating"
|
||||
:disabled="bulkRegenerationActive || (Boolean(regeneratingResultId) && !selectedItemRegenerating)"
|
||||
@click="emit('regenerate:item', selectedItem.id)"
|
||||
>
|
||||
<i v-if="!selectedItemRegenerating" class="fa fa-refresh" style="margin-right: 4px;" /> 重新生成
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article class="source-reference" aria-labelledby="source-reference-title">
|
||||
@@ -126,6 +191,7 @@ function selectRelative(offset: number) {
|
||||
<label>Instruction <em>必填</em></label>
|
||||
<el-input
|
||||
:model-value="selectedItem.instruction"
|
||||
:disabled="selectedItemRegenerating"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
@update:model-value="emit('update:field', selectedItem.id, 'instruction', $event)"
|
||||
@@ -135,6 +201,7 @@ function selectRelative(offset: number) {
|
||||
<label>Input <span>选填</span></label>
|
||||
<el-input
|
||||
:model-value="selectedItem.input"
|
||||
:disabled="selectedItemRegenerating"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
@update:model-value="emit('update:field', selectedItem.id, 'input', $event)"
|
||||
@@ -144,6 +211,7 @@ function selectRelative(offset: number) {
|
||||
<label>Output <em>必填</em></label>
|
||||
<el-input
|
||||
:model-value="selectedItem.output"
|
||||
:disabled="selectedItemRegenerating"
|
||||
type="textarea"
|
||||
:rows="7"
|
||||
@update:model-value="emit('update:field', selectedItem.id, 'output', $event)"
|
||||
@@ -155,15 +223,6 @@ function selectRelative(offset: number) {
|
||||
<div v-else class="validation-success">
|
||||
<i class="fa fa-check-circle" /> 字段校验通过
|
||||
</div>
|
||||
<div v-if="selectedItem.qualityFlags?.length" class="quality-flags">
|
||||
<el-tag
|
||||
v-for="flag in selectedItem.qualityFlags"
|
||||
:key="flag"
|
||||
size="small"
|
||||
type="warning"
|
||||
effect="plain"
|
||||
>{{ flag }}</el-tag>
|
||||
</div>
|
||||
<div class="editor-pagination">
|
||||
<el-button :disabled="selectedIndex <= 0" @click="selectRelative(-1)">上一条</el-button>
|
||||
<span>{{ selectedIndex + 1 }} / {{ items.length }}</span>
|
||||
@@ -194,6 +253,25 @@ function selectRelative(offset: number) {
|
||||
border-right: 1px solid #e5e8ee;
|
||||
}
|
||||
|
||||
.result-list-title {
|
||||
min-width: 0;
|
||||
|
||||
span {
|
||||
margin-left: 8px;
|
||||
color: #8a93a3;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.result-list-header {
|
||||
gap: 10px;
|
||||
|
||||
:deep(.el-button) {
|
||||
flex: none;
|
||||
}
|
||||
}
|
||||
|
||||
.pane-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -227,12 +305,37 @@ function selectRelative(offset: number) {
|
||||
border-bottom: 1px solid #edf0f5;
|
||||
}
|
||||
|
||||
.bulk-regeneration-progress {
|
||||
padding: 9px 12px 10px;
|
||||
background: #fafaff;
|
||||
border-bottom: 1px solid #e8e7ff;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 7px;
|
||||
color: #667085;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.result-list {
|
||||
height: 476px;
|
||||
padding: 7px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.has-bulk-progress .result-list {
|
||||
height: 420px;
|
||||
}
|
||||
|
||||
.result-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.result-item {
|
||||
display: grid;
|
||||
grid-template-columns: 46px minmax(0, 1fr) 18px;
|
||||
@@ -294,6 +397,20 @@ function selectRelative(offset: number) {
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
.css-spinner {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 2px solid rgba(91, 80, 242, 0.2);
|
||||
border-top-color: #5b50f2;
|
||||
border-radius: 50%;
|
||||
animation: css-spin 0.8s linear infinite;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@keyframes css-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.result-editor-pane {
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -389,12 +506,6 @@ function selectRelative(offset: number) {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.quality-flags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.validation-error {
|
||||
color: #b45309;
|
||||
background: #fff7e8;
|
||||
|
||||
Reference in New Issue
Block a user