修复问题
This commit is contained in:
61
frontend/app/web-gold/src/hooks/web/useVoiceText.ts
Normal file
61
frontend/app/web-gold/src/hooks/web/useVoiceText.ts
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { usePromptStore } from '@/stores/prompt'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import { message } from 'ant-design-vue'
|
||||
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 { UserPromptApi } from '@/api/userPrompt'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { ChatMessageApi } from '@/api/chat'
|
||||
import useVoiceText from '@gold/hooks/web/useVoiceText'
|
||||
import useVoiceText from '@/hooks/web/useVoiceText'
|
||||
import { streamChat } from '@/utils/streamChat'
|
||||
import { buildPromptFromTranscription } from '../utils/benchmarkUtils'
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
ok-text="保存"
|
||||
cancel-text="取消"
|
||||
:confirm-loading="submitting"
|
||||
:ok-button-props="{ disabled: isSubmitDisabled }"
|
||||
:mask-closable="false"
|
||||
centered
|
||||
@ok="handleSubmit"
|
||||
@@ -100,11 +101,21 @@
|
||||
<a-progress type="circle" :percent="50" :width="60" status="active" />
|
||||
<p class="upload-text" style="margin-top: 12px">正在上传...</p>
|
||||
</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>
|
||||
<div class="file-preview">
|
||||
<SoundOutlined class="file-icon" />
|
||||
<div class="file-info">
|
||||
<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">
|
||||
<DeleteOutlined /> 移除
|
||||
</a-button>
|
||||
@@ -134,13 +145,13 @@
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from '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 BasicLayout from '@/layouts/components/BasicLayout.vue'
|
||||
import { MaterialService } from '@/api/material'
|
||||
import { VoiceService } from '@/api/voice'
|
||||
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 audioPlayer = ref(null)
|
||||
const fileList = ref([])
|
||||
const extractingText = ref(false) // 语音识别中状态
|
||||
|
||||
const searchParams = reactive({
|
||||
name: '',
|
||||
@@ -195,6 +207,22 @@ const { getVoiceText } = useVoiceText()
|
||||
// ========== 计算属性 ==========
|
||||
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 = [
|
||||
{ title: '配音名称', key: 'name', dataIndex: 'name', width: 160 },
|
||||
@@ -351,7 +379,6 @@ async function handleCustomUpload(options) {
|
||||
onSuccess: async function(id, fileUrl) {
|
||||
formData.fileId = id
|
||||
formData.fileUrl = fileUrl
|
||||
message.success('文件上传成功')
|
||||
await fetchAudioTextById(id)
|
||||
onSuccess?.({ code: 0, data: id }, file)
|
||||
},
|
||||
@@ -372,6 +399,8 @@ async function handleCustomUpload(options) {
|
||||
// 通过fileId获取音频文本
|
||||
async function fetchAudioTextById(fileId) {
|
||||
if (!fileId) return
|
||||
|
||||
extractingText.value = true
|
||||
try {
|
||||
const res = await MaterialService.getAudioPlayUrl(fileId)
|
||||
if (res.code === 0 && res.data) {
|
||||
@@ -380,18 +409,19 @@ async function fetchAudioTextById(fileId) {
|
||||
if (results && results.length > 0) {
|
||||
const text = results[0].value
|
||||
formData.text = text
|
||||
if (text) {
|
||||
message.success('音频文本获取成功')
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取音频文本失败:', error)
|
||||
message.error('语音识别失败,请重试')
|
||||
} finally {
|
||||
extractingText.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleRemoveFile() {
|
||||
formData.fileId = null
|
||||
formData.text = ''
|
||||
fileList.value = []
|
||||
}
|
||||
|
||||
@@ -547,5 +577,20 @@ onMounted(function() {
|
||||
text-overflow: ellipsis;
|
||||
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>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { rewriteStream } from '@/api/forecast'
|
||||
import { getAgentList } from '@/api/agent'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { usePointsConfigStore } from '@/stores/pointsConfig'
|
||||
import { getVoiceText } from '@gold/hooks/web/useVoiceText'
|
||||
import { getVoiceText } from '@/hooks/web/useVoiceText'
|
||||
import { copyToClipboard } from '@/utils/clipboard'
|
||||
|
||||
defineOptions({ name: 'ForecastView' })
|
||||
|
||||
@@ -124,10 +124,8 @@ public class MemberUserProfileServiceImpl implements MemberUserProfileService {
|
||||
|
||||
@Override
|
||||
public void validateStorage(String userId, long fileSizeBytes) {
|
||||
MemberUserProfileDO profile = memberUserProfileMapper.selectByUserId(userId);
|
||||
if (profile == null) {
|
||||
throw exception(new ErrorCode(1004, "会员用户档案不存在"));
|
||||
}
|
||||
// 档案不存在时自动创建(兼容旧用户)
|
||||
MemberUserProfileDO profile = createIfAbsent(Long.parseLong(userId));
|
||||
|
||||
// 将剩余存储空间(GB)转换为字节进行比较
|
||||
BigDecimal remainingBytes = profile.getRemainingStorage().multiply(GB_TO_BYTES);
|
||||
@@ -138,6 +136,9 @@ public class MemberUserProfileServiceImpl implements MemberUserProfileService {
|
||||
|
||||
@Override
|
||||
public boolean increaseUsedStorage(String userId, long fileSizeBytes) {
|
||||
// 确保档案存在(兼容旧用户)
|
||||
createIfAbsent(Long.parseLong(userId));
|
||||
|
||||
// 将字节转换为GB(保留6位小数)
|
||||
BigDecimal storageGb = new BigDecimal(fileSizeBytes).divide(GB_TO_BYTES, 6, RoundingMode.HALF_UP);
|
||||
String storageGbStr = storageGb.toPlainString();
|
||||
@@ -148,6 +149,9 @@ public class MemberUserProfileServiceImpl implements MemberUserProfileService {
|
||||
|
||||
@Override
|
||||
public boolean decreaseUsedStorage(String userId, long fileSizeBytes) {
|
||||
// 确保档案存在(兼容旧用户)
|
||||
createIfAbsent(Long.parseLong(userId));
|
||||
|
||||
// 将字节转换为GB(保留6位小数)
|
||||
BigDecimal storageGb = new BigDecimal(fileSizeBytes).divide(GB_TO_BYTES, 6, RoundingMode.HALF_UP);
|
||||
String storageGbStr = storageGb.toPlainString();
|
||||
|
||||
Reference in New Issue
Block a user