提交接口进度完善
This commit is contained in:
3
app/.env
3
app/.env
@@ -12,6 +12,7 @@ zn_username=%E8%87%AA%E5%8A%A8%E5%8C%96_Robot
|
|||||||
client_name=ShuFuAI
|
client_name=ShuFuAI
|
||||||
|
|
||||||
|
|
||||||
java_api_base=http://8.136.19.173:18080
|
java_api_base=http://127.0.0.1:18080
|
||||||
|
# java_api_base=http://8.136.19.173:18080
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -7,7 +7,7 @@
|
|||||||
<script type="module" crossorigin src="/assets/delete-brand.js"></script>
|
<script type="module" crossorigin src="/assets/delete-brand.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="/assets/pywebview-Dee3nQjE.js">
|
<link rel="modulepreload" crossorigin href="/assets/pywebview-Dee3nQjE.js">
|
||||||
<link rel="stylesheet" crossorigin href="/assets/pywebview-MkjZQlBu.css">
|
<link rel="stylesheet" crossorigin href="/assets/pywebview-MkjZQlBu.css">
|
||||||
<link rel="stylesheet" crossorigin href="/assets/delete-brand-D9tUv360.css">
|
<link rel="stylesheet" crossorigin href="/assets/delete-brand-_jGFWTAn.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
BIN
backend-java/aiimage-backend.log.2026-04-02.0.gz
Normal file
BIN
backend-java/aiimage-backend.log.2026-04-02.0.gz
Normal file
Binary file not shown.
@@ -17,4 +17,7 @@ public class DeleteBrandTaskDetailVo {
|
|||||||
|
|
||||||
@Schema(description = "任务结果项快照")
|
@Schema(description = "任务结果项快照")
|
||||||
private List<DeleteBrandResultItemVo> items = new ArrayList<>();
|
private List<DeleteBrandResultItemVo> items = new ArrayList<>();
|
||||||
|
|
||||||
|
@Schema(description = "文件级进度快照")
|
||||||
|
private List<DeleteBrandTaskFileProgressVo> fileProgress = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.nanri.aiimage.modules.deletebrand.model.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "删除品牌任务中的文件级进度")
|
||||||
|
public class DeleteBrandTaskFileProgressVo {
|
||||||
|
|
||||||
|
@Schema(description = "文件标识(优先 fileKey)")
|
||||||
|
private String fileKey;
|
||||||
|
|
||||||
|
@Schema(description = "源文件名")
|
||||||
|
private String sourceFilename;
|
||||||
|
|
||||||
|
@Schema(description = "已处理行数")
|
||||||
|
private Integer processedRows;
|
||||||
|
|
||||||
|
@Schema(description = "总行数")
|
||||||
|
private Integer totalRows;
|
||||||
|
|
||||||
|
@Schema(description = "百分比(0-100)")
|
||||||
|
private Integer percent;
|
||||||
|
|
||||||
|
@Schema(description = "文件状态:PENDING/RUNNING/COMPLETED/FAILED")
|
||||||
|
private String status;
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandResultItemVo;
|
|||||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandRunVo;
|
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandRunVo;
|
||||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDeletionStatusVo;
|
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDeletionStatusVo;
|
||||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDetailVo;
|
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDetailVo;
|
||||||
|
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskFileProgressVo;
|
||||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskItemVo;
|
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskItemVo;
|
||||||
import com.nanri.aiimage.modules.file.service.LocalFileStorageService;
|
import com.nanri.aiimage.modules.file.service.LocalFileStorageService;
|
||||||
import com.nanri.aiimage.modules.file.service.oss.OssStorageService;
|
import com.nanri.aiimage.modules.file.service.oss.OssStorageService;
|
||||||
@@ -569,9 +570,11 @@ public class DeleteBrandRunService {
|
|||||||
DeleteBrandLineProgressVo progressVo = buildLineProgress(task.getId(), progress);
|
DeleteBrandLineProgressVo progressVo = buildLineProgress(task.getId(), progress);
|
||||||
|
|
||||||
DeleteBrandTaskDetailVo detail = new DeleteBrandTaskDetailVo();
|
DeleteBrandTaskDetailVo detail = new DeleteBrandTaskDetailVo();
|
||||||
|
List<DeleteBrandResultItemVo> taskItems = resolveTaskResultItems(task);
|
||||||
detail.setTask(taskVo);
|
detail.setTask(taskVo);
|
||||||
detail.setLine_progress(progressVo);
|
detail.setLine_progress(progressVo);
|
||||||
detail.setItems(resolveTaskResultItems(task));
|
detail.setItems(taskItems);
|
||||||
|
detail.setFileProgress(buildFileProgress(task, taskItems, progressVo));
|
||||||
return detail;
|
return detail;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,6 +605,73 @@ public class DeleteBrandRunService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<DeleteBrandTaskFileProgressVo> buildFileProgress(FileTaskEntity task,
|
||||||
|
List<DeleteBrandResultItemVo> taskItems,
|
||||||
|
DeleteBrandLineProgressVo progressVo) {
|
||||||
|
if (taskItems == null || taskItems.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
String taskStatus = task == null ? null : task.getStatus();
|
||||||
|
DeleteBrandLineProgressInfoVo info = progressVo == null ? null : progressVo.getInfo();
|
||||||
|
Integer finishedFiles = info == null ? null : info.getFinished_files();
|
||||||
|
String runningFileName = info == null ? null : info.getFile_name();
|
||||||
|
Integer currentLine = info == null ? null : info.getCurrent_line();
|
||||||
|
Integer totalLines = info == null ? null : info.getTotal_lines();
|
||||||
|
|
||||||
|
List<DeleteBrandTaskFileProgressVo> fileProgress = new ArrayList<>();
|
||||||
|
int completedAssigned = 0;
|
||||||
|
int normalizedFinished = finishedFiles == null ? 0 : Math.max(0, finishedFiles);
|
||||||
|
|
||||||
|
for (DeleteBrandResultItemVo item : taskItems) {
|
||||||
|
if (item == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteBrandTaskFileProgressVo fp = new DeleteBrandTaskFileProgressVo();
|
||||||
|
fp.setFileKey(item.getFileKey());
|
||||||
|
fp.setSourceFilename(item.getSourceFilename());
|
||||||
|
|
||||||
|
Integer itemTotalRows = item.getTotalRows() == null ? 0 : Math.max(item.getTotalRows(), 0);
|
||||||
|
fp.setTotalRows(itemTotalRows);
|
||||||
|
|
||||||
|
String status = "PENDING";
|
||||||
|
int processedRows = 0;
|
||||||
|
int percent = 0;
|
||||||
|
|
||||||
|
if ("SUCCESS".equals(taskStatus)) {
|
||||||
|
status = "COMPLETED";
|
||||||
|
processedRows = itemTotalRows;
|
||||||
|
percent = 100;
|
||||||
|
} else if ("FAILED".equals(taskStatus)) {
|
||||||
|
status = "FAILED";
|
||||||
|
processedRows = Math.min(itemTotalRows, Math.max(0, currentLine == null ? 0 : currentLine));
|
||||||
|
percent = itemTotalRows > 0 ? Math.min(100, (int) Math.round(processedRows * 100.0 / itemTotalRows)) : 0;
|
||||||
|
} else {
|
||||||
|
if (completedAssigned < normalizedFinished) {
|
||||||
|
status = "COMPLETED";
|
||||||
|
processedRows = itemTotalRows;
|
||||||
|
percent = 100;
|
||||||
|
completedAssigned++;
|
||||||
|
} else if (runningFileName != null && runningFileName.equals(item.getSourceFilename())) {
|
||||||
|
status = "RUNNING";
|
||||||
|
processedRows = Math.min(itemTotalRows, Math.max(0, currentLine == null ? 0 : currentLine));
|
||||||
|
if (totalLines != null && totalLines > 0) {
|
||||||
|
percent = Math.min(100, Math.max(0, (int) Math.round(processedRows * 100.0 / totalLines)));
|
||||||
|
} else if (itemTotalRows > 0) {
|
||||||
|
percent = Math.min(100, Math.max(0, (int) Math.round(processedRows * 100.0 / itemTotalRows)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fp.setProcessedRows(processedRows);
|
||||||
|
fp.setPercent(percent);
|
||||||
|
fp.setStatus(status);
|
||||||
|
fileProgress.add(fp);
|
||||||
|
}
|
||||||
|
return fileProgress;
|
||||||
|
}
|
||||||
|
|
||||||
private FileTaskEntity refreshIndexedMatchesIfNeeded(FileTaskEntity task) {
|
private FileTaskEntity refreshIndexedMatchesIfNeeded(FileTaskEntity task) {
|
||||||
if (task == null || task.getResultJson() == null || task.getResultJson().isBlank()) {
|
if (task == null || task.getResultJson() == null || task.getResultJson().isBlank()) {
|
||||||
return task;
|
return task;
|
||||||
|
|||||||
@@ -115,13 +115,13 @@
|
|||||||
<div v-if="shouldShowProgress(item)" class="delete-brand-progress-block">
|
<div v-if="shouldShowProgress(item)" class="delete-brand-progress-block">
|
||||||
<div class="delete-brand-progress-header">
|
<div class="delete-brand-progress-header">
|
||||||
<span>任务进度</span>
|
<span>任务进度</span>
|
||||||
<span>{{ formatProgressPercent(item.taskId!) }}%</span>
|
<span>{{ formatProgressPercent(item) }}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="delete-brand-progress-bar">
|
<div class="delete-brand-progress-bar">
|
||||||
<div class="delete-brand-progress-bar-fill"
|
<div class="delete-brand-progress-bar-fill"
|
||||||
:style="{ width: `${formatProgressPercent(item.taskId!)}%` }"></div>
|
:style="{ width: `${formatProgressPercent(item)}%` }"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="files delete-brand-progress">{{ formatProgress(item.taskId!) }}</div>
|
<div class="files delete-brand-progress">{{ formatProgress(item) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="item.previewRows?.length" class="files split-entry-list delete-brand-preview">
|
<div v-if="item.previewRows?.length" class="files split-entry-list delete-brand-preview">
|
||||||
{{ formatPreview(item.previewRows) }}
|
{{ formatPreview(item.previewRows) }}
|
||||||
@@ -167,13 +167,13 @@
|
|||||||
<div v-if="shouldShowProgress(item)" class="delete-brand-progress-block">
|
<div v-if="shouldShowProgress(item)" class="delete-brand-progress-block">
|
||||||
<div class="delete-brand-progress-header">
|
<div class="delete-brand-progress-header">
|
||||||
<span>任务进度</span>
|
<span>任务进度</span>
|
||||||
<span>{{ formatProgressPercent(item.taskId!) }}%</span>
|
<span>{{ formatProgressPercent(item) }}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="delete-brand-progress-bar">
|
<div class="delete-brand-progress-bar">
|
||||||
<div class="delete-brand-progress-bar-fill"
|
<div class="delete-brand-progress-bar-fill"
|
||||||
:style="{ width: `${formatProgressPercent(item.taskId!)}%` }"></div>
|
:style="{ width: `${formatProgressPercent(item)}%` }"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="files delete-brand-progress">{{ formatProgress(item.taskId!) }}</div>
|
<div class="files delete-brand-progress">{{ formatProgress(item) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="item.previewRows?.length" class="files split-entry-list delete-brand-preview">
|
<div v-if="item.previewRows?.length" class="files split-entry-list delete-brand-preview">
|
||||||
{{ formatPreview(item.previewRows) }}
|
{{ formatPreview(item.previewRows) }}
|
||||||
@@ -421,25 +421,30 @@ function shouldShowProgress(item: DeleteBrandResultItem) {
|
|||||||
if (!taskId) return false
|
if (!taskId) return false
|
||||||
|
|
||||||
const progress = taskDetails.value[taskId]?.line_progress
|
const progress = taskDetails.value[taskId]?.line_progress
|
||||||
|
const status = getTaskStatus(taskId)
|
||||||
const multiFileTask = isMultiFileTask(taskId)
|
const multiFileTask = isMultiFileTask(taskId)
|
||||||
const qs = getQueueStatus(item)
|
const qs = getQueueStatus(item)
|
||||||
|
|
||||||
// 已完成/终态后隐藏进度条。
|
if (multiFileTask) {
|
||||||
if (qs === '本文件解析完毕' || qs === '已被全局判定为终态') {
|
const sessionItem = findSessionItem(taskId, item)
|
||||||
|
const isActiveItem = getItemKey(item) === activeItemKey.value
|
||||||
|
const isProgressTarget = Boolean(progress?.has_progress && progress?.info?.file_name === item.sourceFilename)
|
||||||
|
|
||||||
|
// 多文件任务下,展示规则更宽松:
|
||||||
|
// 1) 当前活跃文件;2) 后端进度指向的文件;3) 已推送文件;4) 已完成文件(任务未终态时保留)。
|
||||||
|
if (isActiveItem || isProgressTarget || sessionItem?._pushed) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if ((status === 'RUNNING' || status === 'PENDING' || !status) && sessionItem?._completed) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (multiFileTask) {
|
// 单文件任务:完成/终态后隐藏。
|
||||||
const sessionItem = findSessionItem(taskId, item)
|
if (qs === '本文件解析完毕' || qs === '已被全局判定为终态') {
|
||||||
// 多文件任务:未入队的文件不展示同一个 task 的公共进度,避免“两个文件同时在跑”的错觉。
|
return false
|
||||||
if (!sessionItem?._pushed) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress?.has_progress && progress?.info?.file_name) {
|
|
||||||
return progress.info.file_name === item.sourceFilename
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progress?.has_progress || progress?.info) {
|
if (progress?.has_progress || progress?.info) {
|
||||||
@@ -507,10 +512,40 @@ function updateSummary(items: DeleteBrandResultItem[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatProgress(taskId: number) {
|
function getFileProgress(item: DeleteBrandResultItem) {
|
||||||
|
const taskId = item.taskId
|
||||||
|
if (!taskId) return null
|
||||||
|
const detail = taskDetails.value[taskId]
|
||||||
|
const fileProgress = detail?.fileProgress || []
|
||||||
|
return fileProgress.find((fp) => {
|
||||||
|
if (fp.fileKey && item.fileKey) return fp.fileKey === item.fileKey
|
||||||
|
if (fp.sourceFilename && item.sourceFilename) return fp.sourceFilename === item.sourceFilename
|
||||||
|
return false
|
||||||
|
}) || null
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatProgress(item: DeleteBrandResultItem) {
|
||||||
|
const taskId = item.taskId
|
||||||
if (!taskId) return ''
|
if (!taskId) return ''
|
||||||
|
|
||||||
const detail = taskDetails.value[taskId]
|
const detail = taskDetails.value[taskId]
|
||||||
const info = detail?.line_progress?.info
|
const info = detail?.line_progress?.info
|
||||||
|
const fp = getFileProgress(item)
|
||||||
|
|
||||||
|
if (fp) {
|
||||||
|
const parts: string[] = []
|
||||||
|
const processedRows = fp.processedRows ?? 0
|
||||||
|
const totalRows = fp.totalRows ?? 0
|
||||||
|
if (fp.status) parts.push(`文件状态:${fp.status}`)
|
||||||
|
parts.push(`文件进度:${processedRows}/${totalRows}`)
|
||||||
|
if (info?.finished_files != null) {
|
||||||
|
const total = info.file_total && info.file_total > 0 ? `/${info.file_total}` : ''
|
||||||
|
parts.push(`已完成文件:${info.finished_files}${total}`)
|
||||||
|
}
|
||||||
|
if (info?.phase) parts.push(`阶段:${info.phase}`)
|
||||||
|
return parts.join('|')
|
||||||
|
}
|
||||||
|
|
||||||
if (!detail?.line_progress?.has_progress || !info) {
|
if (!detail?.line_progress?.has_progress || !info) {
|
||||||
const status = detail?.task?.status
|
const status = detail?.task?.status
|
||||||
return status ? `任务状态:${status}` : ''
|
return status ? `任务状态:${status}` : ''
|
||||||
@@ -532,8 +567,15 @@ function formatProgress(taskId: number) {
|
|||||||
return parts.join('|')
|
return parts.join('|')
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatProgressPercent(taskId: number) {
|
function formatProgressPercent(item: DeleteBrandResultItem) {
|
||||||
|
const taskId = item.taskId
|
||||||
if (!taskId) return 0
|
if (!taskId) return 0
|
||||||
|
|
||||||
|
const fp = getFileProgress(item)
|
||||||
|
if (fp) {
|
||||||
|
return Math.max(0, Math.min(100, fp.percent ?? 0))
|
||||||
|
}
|
||||||
|
|
||||||
const info = taskDetails.value[taskId]?.line_progress?.info
|
const info = taskDetails.value[taskId]?.line_progress?.info
|
||||||
if (!info) return 0
|
if (!info) return 0
|
||||||
|
|
||||||
@@ -578,20 +620,21 @@ function getQueueStatus(item: DeleteBrandResultItem) {
|
|||||||
return '已被全局判定为终态'
|
return '已被全局判定为终态'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 多文件:未推送的文件必须保持“未入队”,避免被同 task 的公共 RUNNING 状态误判成“处理中”。
|
|
||||||
if (multiFileTask && !sessionItem?._pushed && !sessionItem?._completed) {
|
|
||||||
return '未入队'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 先看前端会话态:一旦该文件已判定完成,优先展示完成,避免被 RUNNING 覆盖导致链式推进卡住。
|
// 先看前端会话态:一旦该文件已判定完成,优先展示完成,避免被 RUNNING 覆盖导致链式推进卡住。
|
||||||
if (sessionItem?._completed) {
|
if (sessionItem?._completed) {
|
||||||
return '本文件解析完毕'
|
return '本文件解析完毕'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 多文件场景下,如果后端进度明确指向当前文件,即使本地 _pushed 丢失也应判定为处理中(例如刷新页面后恢复场景)。
|
||||||
if (status === 'RUNNING' && info?.has_progress && info?.info?.file_name === item.sourceFilename) {
|
if (status === 'RUNNING' && info?.has_progress && info?.info?.file_name === item.sourceFilename) {
|
||||||
return '处理中'
|
return '处理中'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 多文件:未推送且未完成,才视为未入队。
|
||||||
|
if (multiFileTask && !sessionItem?._pushed && !sessionItem?._completed) {
|
||||||
|
return '未入队'
|
||||||
|
}
|
||||||
|
|
||||||
if (multiFileTask && status === 'SUCCESS') {
|
if (multiFileTask && status === 'SUCCESS') {
|
||||||
return '本文件解析完毕'
|
return '本文件解析完毕'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,10 +200,20 @@ export interface DeleteBrandTaskItem {
|
|||||||
downloadFilename?: string
|
downloadFilename?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DeleteBrandTaskFileProgress {
|
||||||
|
fileKey?: string
|
||||||
|
sourceFilename?: string
|
||||||
|
processedRows?: number
|
||||||
|
totalRows?: number
|
||||||
|
percent?: number
|
||||||
|
status?: 'PENDING' | 'RUNNING' | 'COMPLETED' | 'FAILED'
|
||||||
|
}
|
||||||
|
|
||||||
export interface DeleteBrandTaskDetailVo {
|
export interface DeleteBrandTaskDetailVo {
|
||||||
task: DeleteBrandTaskItem
|
task: DeleteBrandTaskItem
|
||||||
line_progress: DeleteBrandLineProgress
|
line_progress: DeleteBrandLineProgress
|
||||||
items?: DeleteBrandResultItem[]
|
items?: DeleteBrandResultItem[]
|
||||||
|
fileProgress?: DeleteBrandTaskFileProgress[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeleteBrandTaskBatchVo {
|
export interface DeleteBrandTaskBatchVo {
|
||||||
|
|||||||
Reference in New Issue
Block a user