Commit 29c7bfa7 by pangchong

feat(editor): 优化批量翻译功能及日志展示

- 将批量翻译按钮中的加载状态文本调整为“进行中…”
- 批量翻译弹窗添加多个新配置项,包括标签后缀、并行处理模式及保存到Milvus功能
- 增加翻译日志面板,实现动态日志滚动及高亮显示成功与失败信息
- 批量翻译状态信息中新增详细日志输出,提升用户反馈体验
- 使用监听器滚动日志容器,确保最新日志自动可见
- 在批量翻译逻辑中添加日志记录函数,详细跟踪翻译状态和错误
- 优化UI布局,删除冗余div标签,提升代码整洁度
parent f2d4548f
import { type TranslateXmlOptions, type TranslateXmlResponse, type TranslationStatusResponse } from '../constants' import { type TranslateXmlResponse, type TranslationStatusResponse } from '../constants'
import { useEditorStore } from '@/store/editor' import { useEditorStore } from '@/store/editor'
import { serializeTreeToXml, parseXmlToTreeAsync } from '@/utils/xmlParser'
import { getBaseOrigin } from '@/utils/render'
export function useBatchTranslate() { export function useBatchTranslate() {
const editorStore = useEditorStore() const editorStore = useEditorStore()
...@@ -12,6 +10,15 @@ export function useBatchTranslate() { ...@@ -12,6 +10,15 @@ export function useBatchTranslate() {
const progressText = ref('正在准备翻译文档...') const progressText = ref('正在准备翻译文档...')
const resultFileId = ref('') const resultFileId = ref('')
const resultDownloadUrl = ref('') const resultDownloadUrl = ref('')
const logs = ref<Array<{ time: string; text: string }>>([])
const addLog = (text: string) => {
const time = new Date().toLocaleTimeString('zh-CN', { hour12: false })
if (logs.value.length > 0 && logs.value[logs.value.length - 1].text === text) {
return
}
logs.value.push({ time, text })
}
const availableTags = ref<string[]>(['PARAC', 'TITLEC']) const availableTags = ref<string[]>(['PARAC', 'TITLEC'])
const availableTagOptions = computed(() => { const availableTagOptions = computed(() => {
...@@ -58,6 +65,7 @@ export function useBatchTranslate() { ...@@ -58,6 +65,7 @@ export function useBatchTranslate() {
progressText.value = '正在准备翻译文档...' progressText.value = '正在准备翻译文档...'
resultFileId.value = '' resultFileId.value = ''
resultDownloadUrl.value = '' resultDownloadUrl.value = ''
logs.value = []
if (pollInterval) { if (pollInterval) {
clearInterval(pollInterval) clearInterval(pollInterval)
pollInterval = null pollInterval = null
...@@ -98,6 +106,7 @@ export function useBatchTranslate() { ...@@ -98,6 +106,7 @@ export function useBatchTranslate() {
progressText.value = '翻译完成!' progressText.value = '翻译完成!'
loading.value = false loading.value = false
completed.value = true completed.value = true
addLog('翻译成功完成!已生成翻译对照包。')
// 构造下载链接 // 构造下载链接
let dlUrl = statusRes.download_url let dlUrl = statusRes.download_url
if (dlUrl) { if (dlUrl) {
...@@ -111,16 +120,20 @@ export function useBatchTranslate() { ...@@ -111,16 +120,20 @@ export function useBatchTranslate() {
clearInterval(pollInterval) clearInterval(pollInterval)
pollInterval = null pollInterval = null
loading.value = false loading.value = false
window.$message.error(`翻译失败: ${statusRes.error || statusRes.message || '服务器内部错误'}`) const errMsg = statusRes.error || statusRes.message || '服务器内部错误'
addLog(`翻译处理失败: ${errMsg}`)
window.$message.error(`翻译失败: ${errMsg}`)
} else { } else {
// 更新进度 // 更新进度
const currentProgress = statusRes.progress || 0 const currentProgress = statusRes.progress || 0
progress.value = Math.max(30, Math.min(95, Math.floor(currentProgress))) progress.value = Math.max(30, Math.min(95, Math.floor(currentProgress)))
progressText.value = statusRes.message || '翻译正在进行中...' progressText.value = statusRes.message || '翻译正在进行中...'
addLog(statusRes.message || '大语言模型正在逐句翻译中,请稍候...')
} }
} }
} catch (e) { } catch (e) {
console.error('轮询状态异常:', e) console.error('轮询状态异常:', e)
addLog('轮询翻译进度时连接异常,正在自动重试...')
} }
}, 1500) }, 1500)
} }
...@@ -134,6 +147,7 @@ export function useBatchTranslate() { ...@@ -134,6 +147,7 @@ export function useBatchTranslate() {
loading.value = true loading.value = true
progress.value = 5 progress.value = 5
progressText.value = '正在序列化当前 XML...' progressText.value = '正在序列化当前 XML...'
addLog('初始化翻译环境,正在将当前 XML 树序列化为文本结构...')
try { try {
const xmlContent = serializeTreeToXml(editorStore.xmlTree, 0, true) const xmlContent = serializeTreeToXml(editorStore.xmlTree, 0, true)
...@@ -141,6 +155,8 @@ export function useBatchTranslate() { ...@@ -141,6 +155,8 @@ export function useBatchTranslate() {
progress.value = 15 progress.value = 15
progressText.value = '正在上传文件至翻译服务器...' progressText.value = '正在上传文件至翻译服务器...'
addLog(`文档序列化完成,大小: ${Math.round(blob.size / 1024)} KB。准备上传...`)
addLog(`请求配置: 模型 = ${form.value.model_name}, 目标标签 = [${form.value.target_tags.join(', ')}]`)
const formData = new FormData() const formData = new FormData()
formData.append('files', blob, 'document.xml') formData.append('files', blob, 'document.xml')
...@@ -169,6 +185,8 @@ export function useBatchTranslate() { ...@@ -169,6 +185,8 @@ export function useBatchTranslate() {
resultFileId.value = res.file_id resultFileId.value = res.file_id
progress.value = 30 progress.value = 30
progressText.value = '翻译任务已创建,正在排队等待处理...' progressText.value = '翻译任务已创建,正在排队等待处理...'
addLog(`文件上传成功。翻译服务已成功创建任务,任务 ID: ${res.file_id}`)
addLog('任务已进入服务器执行队列,排队处理中...')
// 开始轮询翻译状态 // 开始轮询翻译状态
startPolling(res.file_id) startPolling(res.file_id)
...@@ -177,7 +195,9 @@ export function useBatchTranslate() { ...@@ -177,7 +195,9 @@ export function useBatchTranslate() {
} }
} catch (err: any) { } catch (err: any) {
loading.value = false loading.value = false
window.$message.error(`翻译出错: ${err.message || err}`) const errMsg = err.message || err
addLog(`翻译遇到错误终止: ${errMsg}`)
window.$message.error(`翻译出错: ${errMsg}`)
} }
} }
...@@ -239,6 +259,7 @@ export function useBatchTranslate() { ...@@ -239,6 +259,7 @@ export function useBatchTranslate() {
reset, reset,
startTranslation, startTranslation,
downloadResult, downloadResult,
importResult importResult,
logs
} }
} }
<template> <template>
<CommonModal <CommonModal v-model="showModal" title="批量 XML 翻译" :width="600" :show-confirm="false" :show-cancel="false">
v-model="showModal"
title="批量 XML 翻译"
:width="600"
:show-confirm="false"
:show-cancel="false"
>
<template #header-extra> <template #header-extra>
<n-tag :type="statusType" size="small">{{ statusLabel }}</n-tag> <n-tag :type="statusType" size="small">{{ statusLabel }}</n-tag>
</template> </template>
<div class="py-2"> <n-form v-if="!loading && !completed" label-placement="left" label-width="120" size="medium">
<n-form v-if="!loading && !completed" label-placement="left" label-width="120" size="medium"> <n-form-item label="翻译模型">
<n-form-item label="翻译模型"> <CommonSelect v-model:value="form.model_name" :options="modelOptions" />
<CommonSelect v-model:value="form.model_name" :options="modelOptions" /> </n-form-item>
</n-form-item> <n-form-item label="翻译方向">
<n-form-item label="翻译方向"> <CommonSelect v-model:value="form.search_direction" :options="directionOptions" />
<CommonSelect v-model:value="form.search_direction" :options="directionOptions" /> </n-form-item>
</n-form-item> <n-form-item label="目标翻译标签">
<n-form-item label="目标翻译标签"> <div class="flex flex-col gap-2 w-full">
<div class="flex flex-col gap-2 w-full"> <CommonCheckbox v-model:value="form.target_tags" :options="availableTagOptions" />
<CommonCheckbox v-model:value="form.target_tags" :options="availableTagOptions" /> <span class="text-xs text-color3">仅翻译所勾选的 XML 节点标签</span>
<span class="text-xs text-color3">仅翻译所勾选的 XML 节点标签</span>
</div>
</n-form-item>
<n-form-item label="翻译标签后缀">
<n-input v-model:value="form.suffix" placeholder="默认无后缀,如填 'Z' 则 PARA 翻译为 PARAZ" />
</n-form-item>
<n-form-item label="并行处理模式">
<n-space align="center">
<n-switch v-model:value="form.use_parallel_method" />
<span class="text-xs text-color3">开启并行模式可显著提升大型文件翻译速度</span>
</n-space>
</n-form-item>
<n-form-item label="保存翻译至 Milvus">
<n-space align="center">
<n-switch v-model:value="form.save_to_database" />
<span class="text-xs text-color3">将翻译对照结果保存到 Milvus 数据库以供以后检索</span>
</n-space>
</n-form-item>
<n-form-item v-if="form.save_to_database" label="Milvus 分区名">
<n-input v-model:value="form.database_partition_name" placeholder="请输入数据库分区名称" />
</n-form-item>
</n-form>
<!-- 翻译进行中/进度条 -->
<div v-else-if="loading" class="flex flex-col items-center justify-center py-6 gap-4">
<div class="text-sm font-semibold text-color1">{{ progressText }}</div>
<div class="w-full px-8">
<n-progress
type="line"
:percentage="progress"
:indicator-placement="'inside'"
processing
status="info"
/>
</div> </div>
</n-form-item>
<n-form-item label="翻译标签后缀">
<n-input v-model:value="form.suffix" placeholder="默认无后缀,如填 'Z' 则 PARA 翻译为 PARAZ" />
</n-form-item>
<n-form-item label="并行处理模式">
<n-space align="center">
<n-switch v-model:value="form.use_parallel_method" />
<span class="text-xs text-color3">开启并行模式可显著提升大型文件翻译速度</span>
</n-space>
</n-form-item>
<n-form-item label="保存翻译至 Milvus">
<n-space align="center">
<n-switch v-model:value="form.save_to_database" />
<span class="text-xs text-color3">将翻译对照结果保存到 Milvus 数据库以供以后检索</span>
</n-space>
</n-form-item>
<n-form-item v-if="form.save_to_database" label="Milvus 分区名">
<n-input v-model:value="form.database_partition_name" placeholder="请输入数据库分区名称" />
</n-form-item>
</n-form>
<!-- 翻译进行中:极客控制台日志与动态 Loading 环 -->
<div v-else-if="loading" class="flex flex-col items-center gap-4">
<div class="flex items-center space-x-3">
<n-spin size="medium" />
<span class="text-sm font-semibold text-color1">{{ progressText }}</span>
</div> </div>
<!-- 翻译完成/结果展示 --> <div class="w-full mt-2">
<div v-else-if="completed" class="flex flex-col items-center justify-center py-6 gap-4 text-center"> <div ref="logContainerRef" class="bg-fill-1 rounded-lg p-3 border border-divider shadow-inner text-[11px] font-mono leading-relaxed">
<n-icon size="48" class="text-success"> <div v-for="(log, idx) in logs" :key="idx" class="flex items-start space-x-1.5 py-0.5 select-text animate-fade-in">
<checkmark-circle-outline /> <span class="text-colorText3 select-none">[{{ log.time }}]</span>
</n-icon> <span
<div class="text-base font-bold text-success">XML 文档批量翻译完成!</div> class="flex-1 whitespace-pre-wrap"
<div class="text-xs text-color3">所有指定标签的文本已翻译成功。你可以直接导入当前编辑器或下载文件。</div> :class="{
'text-success font-semibold': log.text.includes('成功') || log.text.includes('完成'),
'text-danger font-semibold': log.text.includes('失败') || log.text.includes('错误'),
'text-colorText2':
!log.text.includes('成功') &&
!log.text.includes('完成') &&
!log.text.includes('失败') &&
!log.text.includes('错误')
}"
>
{{ log.text }}
</span>
</div>
</div>
</div> </div>
</div> </div>
<!-- 翻译完成/结果展示 -->
<div v-else-if="completed" class="flex flex-col items-center justify-center py-6 gap-4 text-center">
<n-icon size="48" class="text-success">
<checkmark-circle-outline />
</n-icon>
<div class="text-base font-bold text-success">XML 文档批量翻译完成!</div>
<div class="text-xs text-color3">所有指定标签的文本已翻译成功。你可以直接导入当前编辑器或下载文件。</div>
</div>
<template #footer> <template #footer>
<div class="flex justify-end gap-3 w-full"> <div class="flex justify-end gap-3 w-full">
<!-- 翻译中状态:允许隐入后台 --> <!-- 翻译中状态:允许隐入后台 -->
<template v-if="loading"> <template v-if="loading">
<CommonButton type="primary" secondary @click="showModal = false"> <CommonButton type="primary" secondary @click="showModal = false">后台运行 (隐藏)</CommonButton>
后台运行 (隐藏)
</CommonButton>
</template> </template>
<!-- 翻译完成状态 --> <!-- 翻译完成状态 -->
<template v-else-if="completed"> <template v-else-if="completed">
...@@ -93,6 +99,7 @@ ...@@ -93,6 +99,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, nextTick } from 'vue'
import { CheckmarkCircleOutline } from '@vicons/ionicons5' import { CheckmarkCircleOutline } from '@vicons/ionicons5'
import { useBatchTranslate } from './functionals' import { useBatchTranslate } from './functionals'
...@@ -113,9 +120,23 @@ const { ...@@ -113,9 +120,23 @@ const {
reset, reset,
startTranslation, startTranslation,
downloadResult, downloadResult,
importResult importResult,
logs
} = useBatchTranslate() } = useBatchTranslate()
const logContainerRef = ref<HTMLElement | null>(null)
watch(
() => logs.value.length,
() => {
nextTick(() => {
if (logContainerRef.value) {
logContainerRef.value.scrollTop = logContainerRef.value.scrollHeight
}
})
}
)
defineExpose({ defineExpose({
open, open,
loading, loading,
...@@ -124,3 +145,19 @@ defineExpose({ ...@@ -124,3 +145,19 @@ defineExpose({
completed completed
}) })
</script> </script>
<style scoped>
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in {
animation: fadeIn 0.25s ease-out forwards;
}
</style>
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
<language-outline v-else /> <language-outline v-else />
</n-icon> </n-icon>
</template> </template>
<span v-if="batchTranslateModalRef?.loading">批量翻译 ({{ batchTranslateModalRef.progress }}%)</span> <span v-if="batchTranslateModalRef?.loading">批量翻译 (进行中...)</span>
<span v-else-if="batchTranslateModalRef?.completed">批量翻译 (已完成)</span> <span v-else-if="batchTranslateModalRef?.completed">批量翻译 (已完成)</span>
<span v-else>批量翻译</span> <span v-else>批量翻译</span>
</CommonButton> </CommonButton>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment