修复问题

This commit is contained in:
2026-03-01 20:44:29 +08:00
parent 00d60b78c4
commit be427ca931
6 changed files with 123 additions and 13 deletions

View File

@@ -0,0 +1,61 @@
import type {
AudioItem,
TranscriptionResult,
TranscriptionResponse,
TranscriptionData
} from '@gold/config/types'
import BaiLianService from '@/api/bailian'
/**
* 音频转文本
* @param list - 音频项列表
* @returns 转录结果数组
*/
export async function getVoiceText(
list: AudioItem[]
): Promise<TranscriptionResult[]> {
const ret = await (BaiLianService as any).videoToCharacters({
fileLinkList: list.map(item => item.audio_url),
})
const data: string = ret.data
const rst: TranscriptionResponse = JSON.parse(data)
const transcription_url: string[] = rst.results.map(item => item.transcription_url)
const transcriptions: TranscriptionResult[] = await Promise.all(
(transcription_url || []).filter(Boolean).map(async (url: string): Promise<TranscriptionResult> => {
try {
const resp: Response = await fetch(url)
const contentType: string = resp.headers.get('content-type') || ''
const value: string = contentType.includes('application/json')
? JSON.stringify(await resp.json())
: await resp.text()
const parsed: TranscriptionData = JSON.parse(value)
return {
key: url,
audio_url: parsed.file_url,
value: parsed.transcripts?.[0]?.text || ''
}
} catch (e: unknown) {
console.warn('获取转写内容失败:', url, e)
return { key: url, value: '' }
}
})
)
return transcriptions
}
interface UseVoiceTextReturn {
getVoiceText: (list: AudioItem[]) => Promise<TranscriptionResult[]>
}
/**
* 语音文本转换Hook
* @returns 包含getVoiceText方法的对象
*/
export default function useVoiceText(): UseVoiceTextReturn {
return {
getVoiceText
}
}

View File

@@ -4,7 +4,7 @@ import { usePromptStore } from '@/stores/prompt'
import MarkdownIt from 'markdown-it' import MarkdownIt from 'markdown-it'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { CommonService } from '@/api/common' import { CommonService } from '@/api/common'
import useVoiceText from '@gold/hooks/web/useVoiceText' import useVoiceText from '@/hooks/web/useVoiceText'
import GmIcon from '@/components/icons/Icon.vue' import GmIcon from '@/components/icons/Icon.vue'
import { UserPromptApi } from '@/api/userPrompt' import { UserPromptApi } from '@/api/userPrompt'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'

View File

@@ -1,7 +1,7 @@
import { ref } from 'vue' import { ref } from 'vue'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { ChatMessageApi } from '@/api/chat' import { ChatMessageApi } from '@/api/chat'
import useVoiceText from '@gold/hooks/web/useVoiceText' import useVoiceText from '@/hooks/web/useVoiceText'
import { streamChat } from '@/utils/streamChat' import { streamChat } from '@/utils/streamChat'
import { buildPromptFromTranscription } from '../utils/benchmarkUtils' import { buildPromptFromTranscription } from '../utils/benchmarkUtils'

View File

@@ -63,6 +63,7 @@
ok-text="保存" ok-text="保存"
cancel-text="取消" cancel-text="取消"
:confirm-loading="submitting" :confirm-loading="submitting"
:ok-button-props="{ disabled: isSubmitDisabled }"
:mask-closable="false" :mask-closable="false"
centered centered
@ok="handleSubmit" @ok="handleSubmit"
@@ -100,11 +101,21 @@
<a-progress type="circle" :percent="50" :width="60" status="active" /> <a-progress type="circle" :percent="50" :width="60" status="active" />
<p class="upload-text" style="margin-top: 12px">正在上传...</p> <p class="upload-text" style="margin-top: 12px">正在上传...</p>
</template> </template>
<template v-else-if="extractingText">
<a-progress type="circle" :percent="50" :width="60" status="active" />
<p class="upload-text" style="margin-top: 12px">正在识别语音...</p>
</template>
<template v-else> <template v-else>
<div class="file-preview"> <div class="file-preview">
<SoundOutlined class="file-icon" /> <SoundOutlined class="file-icon" />
<div class="file-info"> <div class="file-info">
<span class="file-name">{{ fileList[0]?.name || '音频文件' }}</span> <span class="file-name">{{ fileList[0]?.name || '音频文件' }}</span>
<span v-if="formData.text" class="text-status success">
<CheckCircleOutlined /> 已识别语音文本
</span>
<span v-else class="text-status warning">
<ExclamationCircleOutlined /> 未识别到语音文本
</span>
<a-button type="link" size="small" danger @click.stop="handleRemoveFile"> <a-button type="link" size="small" danger @click.stop="handleRemoveFile">
<DeleteOutlined /> 移除 <DeleteOutlined /> 移除
</a-button> </a-button>
@@ -134,13 +145,13 @@
<script setup> <script setup>
import { ref, reactive, computed, onMounted } from 'vue' import { ref, reactive, computed, onMounted } from 'vue'
import { message, Modal } from 'ant-design-vue' import { message, Modal } from 'ant-design-vue'
import { PlusOutlined, SearchOutlined, PlayCircleOutlined, CloudUploadOutlined, SoundOutlined, DeleteOutlined } from '@ant-design/icons-vue' import { PlusOutlined, SearchOutlined, PlayCircleOutlined, CloudUploadOutlined, SoundOutlined, DeleteOutlined, CheckCircleOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import BasicLayout from '@/layouts/components/BasicLayout.vue' import BasicLayout from '@/layouts/components/BasicLayout.vue'
import { MaterialService } from '@/api/material' import { MaterialService } from '@/api/material'
import { VoiceService } from '@/api/voice' import { VoiceService } from '@/api/voice'
import { useUpload } from '@/composables/useUpload' import { useUpload } from '@/composables/useUpload'
import useVoiceText from '@gold/hooks/web/useVoiceText' import useVoiceText from '@/hooks/web/useVoiceText'
// ========== 常量 ========== // ========== 常量 ==========
@@ -169,6 +180,7 @@ const formMode = ref('create')
const formRef = ref(null) const formRef = ref(null)
const audioPlayer = ref(null) const audioPlayer = ref(null)
const fileList = ref([]) const fileList = ref([])
const extractingText = ref(false) // 语音识别中状态
const searchParams = reactive({ const searchParams = reactive({
name: '', name: '',
@@ -195,6 +207,22 @@ const { getVoiceText } = useVoiceText()
// ========== 计算属性 ========== // ========== 计算属性 ==========
const isCreateMode = computed(() => formMode.value === 'create') const isCreateMode = computed(() => formMode.value === 'create')
// 保存按钮是否禁用(新建模式下,文本为空或正在提取时禁用)
const isSubmitDisabled = computed(function() {
if (!isCreateMode.value) {
return false
}
// 正在提取文本时禁用
if (extractingText.value) {
return true
}
// 已上传文件但文本为空时禁用
if (formData.fileId && !formData.text) {
return true
}
return false
})
// ========== 表格配置 ========== // ========== 表格配置 ==========
const columns = [ const columns = [
{ title: '配音名称', key: 'name', dataIndex: 'name', width: 160 }, { title: '配音名称', key: 'name', dataIndex: 'name', width: 160 },
@@ -351,7 +379,6 @@ async function handleCustomUpload(options) {
onSuccess: async function(id, fileUrl) { onSuccess: async function(id, fileUrl) {
formData.fileId = id formData.fileId = id
formData.fileUrl = fileUrl formData.fileUrl = fileUrl
message.success('文件上传成功')
await fetchAudioTextById(id) await fetchAudioTextById(id)
onSuccess?.({ code: 0, data: id }, file) onSuccess?.({ code: 0, data: id }, file)
}, },
@@ -372,6 +399,8 @@ async function handleCustomUpload(options) {
// 通过fileId获取音频文本 // 通过fileId获取音频文本
async function fetchAudioTextById(fileId) { async function fetchAudioTextById(fileId) {
if (!fileId) return if (!fileId) return
extractingText.value = true
try { try {
const res = await MaterialService.getAudioPlayUrl(fileId) const res = await MaterialService.getAudioPlayUrl(fileId)
if (res.code === 0 && res.data) { if (res.code === 0 && res.data) {
@@ -380,18 +409,19 @@ async function fetchAudioTextById(fileId) {
if (results && results.length > 0) { if (results && results.length > 0) {
const text = results[0].value const text = results[0].value
formData.text = text formData.text = text
if (text) {
message.success('音频文本获取成功')
}
} }
} }
} catch (error) { } catch (error) {
console.error('获取音频文本失败:', error) console.error('获取音频文本失败:', error)
message.error('语音识别失败,请重试')
} finally {
extractingText.value = false
} }
} }
function handleRemoveFile() { function handleRemoveFile() {
formData.fileId = null formData.fileId = null
formData.text = ''
fileList.value = [] fileList.value = []
} }
@@ -547,5 +577,20 @@ onMounted(function() {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.text-status {
font-size: 12px;
display: flex;
align-items: center;
gap: 4px;
&.success {
color: var(--color-success, #52c41a);
}
&.warning {
color: var(--color-warning, #faad14);
}
}
} }
</style> </style>

View File

@@ -7,7 +7,7 @@ import { rewriteStream } from '@/api/forecast'
import { getAgentList } from '@/api/agent' import { getAgentList } from '@/api/agent'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { usePointsConfigStore } from '@/stores/pointsConfig' import { usePointsConfigStore } from '@/stores/pointsConfig'
import { getVoiceText } from '@gold/hooks/web/useVoiceText' import { getVoiceText } from '@/hooks/web/useVoiceText'
import { copyToClipboard } from '@/utils/clipboard' import { copyToClipboard } from '@/utils/clipboard'
defineOptions({ name: 'ForecastView' }) defineOptions({ name: 'ForecastView' })

View File

@@ -124,10 +124,8 @@ public class MemberUserProfileServiceImpl implements MemberUserProfileService {
@Override @Override
public void validateStorage(String userId, long fileSizeBytes) { public void validateStorage(String userId, long fileSizeBytes) {
MemberUserProfileDO profile = memberUserProfileMapper.selectByUserId(userId); // 档案不存在时自动创建(兼容旧用户)
if (profile == null) { MemberUserProfileDO profile = createIfAbsent(Long.parseLong(userId));
throw exception(new ErrorCode(1004, "会员用户档案不存在"));
}
// 将剩余存储空间GB转换为字节进行比较 // 将剩余存储空间GB转换为字节进行比较
BigDecimal remainingBytes = profile.getRemainingStorage().multiply(GB_TO_BYTES); BigDecimal remainingBytes = profile.getRemainingStorage().multiply(GB_TO_BYTES);
@@ -138,6 +136,9 @@ public class MemberUserProfileServiceImpl implements MemberUserProfileService {
@Override @Override
public boolean increaseUsedStorage(String userId, long fileSizeBytes) { public boolean increaseUsedStorage(String userId, long fileSizeBytes) {
// 确保档案存在(兼容旧用户)
createIfAbsent(Long.parseLong(userId));
// 将字节转换为GB保留6位小数 // 将字节转换为GB保留6位小数
BigDecimal storageGb = new BigDecimal(fileSizeBytes).divide(GB_TO_BYTES, 6, RoundingMode.HALF_UP); BigDecimal storageGb = new BigDecimal(fileSizeBytes).divide(GB_TO_BYTES, 6, RoundingMode.HALF_UP);
String storageGbStr = storageGb.toPlainString(); String storageGbStr = storageGb.toPlainString();
@@ -148,6 +149,9 @@ public class MemberUserProfileServiceImpl implements MemberUserProfileService {
@Override @Override
public boolean decreaseUsedStorage(String userId, long fileSizeBytes) { public boolean decreaseUsedStorage(String userId, long fileSizeBytes) {
// 确保档案存在(兼容旧用户)
createIfAbsent(Long.parseLong(userId));
// 将字节转换为GB保留6位小数 // 将字节转换为GB保留6位小数
BigDecimal storageGb = new BigDecimal(fileSizeBytes).divide(GB_TO_BYTES, 6, RoundingMode.HALF_UP); BigDecimal storageGb = new BigDecimal(fileSizeBytes).divide(GB_TO_BYTES, 6, RoundingMode.HALF_UP);
String storageGbStr = storageGb.toPlainString(); String storageGbStr = storageGb.toPlainString();