mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-27 01:23:55 +08:00
Merge pull request #3023 from xinnan-tech/update-manager-mobile
mobile 播放声纹弹窗样式调整,切换角色模板更新表单状态
This commit is contained in:
@@ -131,6 +131,7 @@ export function getMcpAddress(agentId: string) {
|
|||||||
meta: {
|
meta: {
|
||||||
ignoreAuth: false,
|
ignoreAuth: false,
|
||||||
toast: false,
|
toast: false,
|
||||||
|
isExposeError: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,3 +60,13 @@ export function updateVoicePrint(data: VoicePrint) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取音频下载ID
|
||||||
|
export function getAudioDownloadId(audioId: string) {
|
||||||
|
return http.Post<string>(`/agent/audio/${audioId}`, {}, {
|
||||||
|
meta: {
|
||||||
|
ignoreAuth: false,
|
||||||
|
toast: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { uniappRequestAdapter } from '@alova/adapter-uniapp'
|
import type { uniappRequestAdapter } from '@alova/adapter-uniapp'
|
||||||
import type { IResponse } from './types'
|
import type { IResponse } from './types'
|
||||||
|
import type { Language } from '@/store/lang'
|
||||||
import AdapterUniapp from '@alova/adapter-uniapp'
|
import AdapterUniapp from '@alova/adapter-uniapp'
|
||||||
import { createAlova } from 'alova'
|
import { createAlova } from 'alova'
|
||||||
import { createServerTokenAuthentication } from 'alova/client'
|
import { createServerTokenAuthentication } from 'alova/client'
|
||||||
@@ -8,6 +9,16 @@ import { getEnvBaseUrl } from '@/utils'
|
|||||||
import { toast } from '@/utils/toast'
|
import { toast } from '@/utils/toast'
|
||||||
import { ContentTypeEnum, ResultEnum, ShowMessage } from './enum'
|
import { ContentTypeEnum, ResultEnum, ShowMessage } from './enum'
|
||||||
|
|
||||||
|
// 语言映射, 用于设置 Accept-language 头
|
||||||
|
const langMap: Record<Language, string> = {
|
||||||
|
zh_CN: 'zh-CN',
|
||||||
|
en: 'en-US',
|
||||||
|
zh_TW: 'zh-TW',
|
||||||
|
de: 'de',
|
||||||
|
vi: 'vi',
|
||||||
|
pt_BR: 'pt-BR',
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建请求实例
|
* 创建请求实例
|
||||||
*/
|
*/
|
||||||
@@ -51,6 +62,7 @@ const alovaInstance = createAlova({
|
|||||||
// 检查混合内容错误(HTTPS页面请求HTTP接口)
|
// 检查混合内容错误(HTTPS页面请求HTTP接口)
|
||||||
const currentProtocol = typeof window !== 'undefined' && window.location.protocol
|
const currentProtocol = typeof window !== 'undefined' && window.location.protocol
|
||||||
const requestProtocol = method.baseURL?.split(':')[0]
|
const requestProtocol = method.baseURL?.split(':')[0]
|
||||||
|
const currentLang = langMap[uni.getStorageSync('app_language') as Language || 'zh_CN']
|
||||||
if (currentProtocol === 'https:' && requestProtocol === 'http') {
|
if (currentProtocol === 'https:' && requestProtocol === 'http') {
|
||||||
const errorMessage = '无法配置http协议地址,请检查接口地址'
|
const errorMessage = '无法配置http协议地址,请检查接口地址'
|
||||||
throw new Error(errorMessage)
|
throw new Error(errorMessage)
|
||||||
@@ -60,6 +72,7 @@ const alovaInstance = createAlova({
|
|||||||
method.config.headers = {
|
method.config.headers = {
|
||||||
'Content-Type': ContentTypeEnum.JSON,
|
'Content-Type': ContentTypeEnum.JSON,
|
||||||
'Accept': 'application/json, text/plain, */*',
|
'Accept': 'application/json, text/plain, */*',
|
||||||
|
'Accept-language': currentLang,
|
||||||
...method.config.headers,
|
...method.config.headers,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default {
|
|||||||
'login.navigationTitle': 'Anmelden',
|
'login.navigationTitle': 'Anmelden',
|
||||||
'login.fetchConfigError': 'Konfiguration konnte nicht abgerufen werden:',
|
'login.fetchConfigError': 'Konfiguration konnte nicht abgerufen werden:',
|
||||||
'login.selectLanguage': 'Sprache auswählen',
|
'login.selectLanguage': 'Sprache auswählen',
|
||||||
'login.selectLanguageTip': 'Vi',
|
'login.selectLanguageTip': 'De',
|
||||||
'login.welcomeBack': 'Willkommen zurück',
|
'login.welcomeBack': 'Willkommen zurück',
|
||||||
'login.pleaseLogin': 'Bitte melden Sie sich an',
|
'login.pleaseLogin': 'Bitte melden Sie sich an',
|
||||||
'login.enterUsername': 'Bitte Benutzernamen eingeben',
|
'login.enterUsername': 'Bitte Benutzernamen eingeben',
|
||||||
@@ -492,4 +492,9 @@ export default {
|
|||||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': 'Nach erfolgreicher Konfiguration startet Gerät automatisch neu',
|
'deviceConfig.afterConfigSuccessDeviceWillRestart': 'Nach erfolgreicher Konfiguration startet Gerät automatisch neu',
|
||||||
'deviceConfig.audioPlaybackError': 'Audio-Wiedergabe-Fehler',
|
'deviceConfig.audioPlaybackError': 'Audio-Wiedergabe-Fehler',
|
||||||
'deviceConfig.playbackFailed': 'Wiedergabe fehlgeschlagen',
|
'deviceConfig.playbackFailed': 'Wiedergabe fehlgeschlagen',
|
||||||
|
|
||||||
|
// Voiceprint page
|
||||||
|
'voiceprint.audioNotExist': 'Audio existiert nicht',
|
||||||
|
'voiceprint.getAudioFailed': 'Audio konnte nicht abgerufen werden',
|
||||||
|
'voiceprint.audioPlayFailed': 'Audio-Wiedergabe fehlgeschlagen',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default {
|
|||||||
'login.navigationTitle': 'Login',
|
'login.navigationTitle': 'Login',
|
||||||
'login.fetchConfigError': 'Failed to fetch configuration:',
|
'login.fetchConfigError': 'Failed to fetch configuration:',
|
||||||
'login.selectLanguage': 'Select Language',
|
'login.selectLanguage': 'Select Language',
|
||||||
'login.selectLanguageTip': '中文',
|
'login.selectLanguageTip': 'En',
|
||||||
'login.welcomeBack': 'Welcome Back',
|
'login.welcomeBack': 'Welcome Back',
|
||||||
'login.pleaseLogin': 'Please log in to your account',
|
'login.pleaseLogin': 'Please log in to your account',
|
||||||
'login.enterUsername': 'Please enter username',
|
'login.enterUsername': 'Please enter username',
|
||||||
@@ -492,4 +492,9 @@ export default {
|
|||||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': 'After successful configuration, device will automatically restart',
|
'deviceConfig.afterConfigSuccessDeviceWillRestart': 'After successful configuration, device will automatically restart',
|
||||||
'deviceConfig.audioPlaybackError': 'Audio playback error',
|
'deviceConfig.audioPlaybackError': 'Audio playback error',
|
||||||
'deviceConfig.playbackFailed': 'Playback failed',
|
'deviceConfig.playbackFailed': 'Playback failed',
|
||||||
|
|
||||||
|
// Voiceprint page
|
||||||
|
'voiceprint.audioNotExist': 'Audio does not exist',
|
||||||
|
'voiceprint.getAudioFailed': 'Failed to get audio',
|
||||||
|
'voiceprint.audioPlayFailed': 'Audio playback failed',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default {
|
|||||||
'login.navigationTitle': 'Entrar',
|
'login.navigationTitle': 'Entrar',
|
||||||
'login.fetchConfigError': 'Falha ao buscar configuração:',
|
'login.fetchConfigError': 'Falha ao buscar configuração:',
|
||||||
'login.selectLanguage': 'Selecionar Idioma',
|
'login.selectLanguage': 'Selecionar Idioma',
|
||||||
'login.selectLanguageTip': '中文',
|
'login.selectLanguageTip': 'Pt',
|
||||||
'login.welcomeBack': 'Bem-vindo de Volta',
|
'login.welcomeBack': 'Bem-vindo de Volta',
|
||||||
'login.pleaseLogin': 'Por favor, entre na sua conta',
|
'login.pleaseLogin': 'Por favor, entre na sua conta',
|
||||||
'login.enterUsername': 'Por favor, insira o nome de usuário',
|
'login.enterUsername': 'Por favor, insira o nome de usuário',
|
||||||
@@ -492,4 +492,9 @@ export default {
|
|||||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': 'Após a configuração bem-sucedida, o dispositivo reiniciará automaticamente',
|
'deviceConfig.afterConfigSuccessDeviceWillRestart': 'Após a configuração bem-sucedida, o dispositivo reiniciará automaticamente',
|
||||||
'deviceConfig.audioPlaybackError': 'Erro na reprodução de áudio',
|
'deviceConfig.audioPlaybackError': 'Erro na reprodução de áudio',
|
||||||
'deviceConfig.playbackFailed': 'Falha na reprodução',
|
'deviceConfig.playbackFailed': 'Falha na reprodução',
|
||||||
|
|
||||||
|
// Voiceprint page
|
||||||
|
'voiceprint.audioNotExist': 'O áudio não existe',
|
||||||
|
'voiceprint.getAudioFailed': 'Falha ao obter áudio',
|
||||||
|
'voiceprint.audioPlayFailed': 'Falha na reprodução de áudio',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default {
|
|||||||
'login.navigationTitle': 'Đăng nhập',
|
'login.navigationTitle': 'Đăng nhập',
|
||||||
'login.fetchConfigError': 'Không thể tải cấu hình:',
|
'login.fetchConfigError': 'Không thể tải cấu hình:',
|
||||||
'login.selectLanguage': 'Chọn ngôn ngữ',
|
'login.selectLanguage': 'Chọn ngôn ngữ',
|
||||||
'login.selectLanguageTip': 'de',
|
'login.selectLanguageTip': 'Vi',
|
||||||
'login.welcomeBack': 'Chào mừng trở lại',
|
'login.welcomeBack': 'Chào mừng trở lại',
|
||||||
'login.pleaseLogin': 'Vui lòng đăng nhập vào tài khoản của bạn',
|
'login.pleaseLogin': 'Vui lòng đăng nhập vào tài khoản của bạn',
|
||||||
'login.enterUsername': 'Vui lòng nhập tên đăng nhập',
|
'login.enterUsername': 'Vui lòng nhập tên đăng nhập',
|
||||||
@@ -492,4 +492,9 @@ export default {
|
|||||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': 'Sau khi cấu hình thành công, thiết bị sẽ tự động khởi động lại',
|
'deviceConfig.afterConfigSuccessDeviceWillRestart': 'Sau khi cấu hình thành công, thiết bị sẽ tự động khởi động lại',
|
||||||
'deviceConfig.audioPlaybackError': 'Lỗi phát âm thanh',
|
'deviceConfig.audioPlaybackError': 'Lỗi phát âm thanh',
|
||||||
'deviceConfig.playbackFailed': 'Phát thất bại',
|
'deviceConfig.playbackFailed': 'Phát thất bại',
|
||||||
|
|
||||||
|
// Voiceprint page
|
||||||
|
'voiceprint.audioNotExist': 'Âm thanh không tồn tại',
|
||||||
|
'voiceprint.getAudioFailed': 'Không thể lấy âm thanh',
|
||||||
|
'voiceprint.audioPlayFailed': 'Phát âm thanh thất bại',
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ export default {
|
|||||||
'login.navigationTitle': '登录',
|
'login.navigationTitle': '登录',
|
||||||
'login.fetchConfigError': '获取配置失败:',
|
'login.fetchConfigError': '获取配置失败:',
|
||||||
'login.selectLanguage': '选择语言',
|
'login.selectLanguage': '选择语言',
|
||||||
'login.selectLanguageTip': 'En',
|
'login.selectLanguageTip': '中文',
|
||||||
'login.welcomeBack': '欢迎回来',
|
'login.welcomeBack': '欢迎回来',
|
||||||
'login.pleaseLogin': '请登录您的账户',
|
'login.pleaseLogin': '请登录您的账户',
|
||||||
'login.enterUsername': '请输入用户名',
|
'login.enterUsername': '请输入用户名',
|
||||||
@@ -492,4 +492,9 @@ export default {
|
|||||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': '配网成功后设备将自动重启',
|
'deviceConfig.afterConfigSuccessDeviceWillRestart': '配网成功后设备将自动重启',
|
||||||
'deviceConfig.audioPlaybackError': '音频播放错误',
|
'deviceConfig.audioPlaybackError': '音频播放错误',
|
||||||
'deviceConfig.playbackFailed': '播放失败',
|
'deviceConfig.playbackFailed': '播放失败',
|
||||||
|
|
||||||
|
// Voiceprint page
|
||||||
|
'voiceprint.audioNotExist': '该音频不存在',
|
||||||
|
'voiceprint.getAudioFailed': '获取音频失败',
|
||||||
|
'voiceprint.audioPlayFailed': '音频播放失败',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default {
|
|||||||
'login.navigationTitle': '登錄',
|
'login.navigationTitle': '登錄',
|
||||||
'login.fetchConfigError': '獲取配置失敗:',
|
'login.fetchConfigError': '獲取配置失敗:',
|
||||||
'login.selectLanguage': '選擇語言',
|
'login.selectLanguage': '選擇語言',
|
||||||
'login.selectLanguageTip': '简体',
|
'login.selectLanguageTip': '繁體',
|
||||||
'login.welcomeBack': '歡迎回來',
|
'login.welcomeBack': '歡迎回來',
|
||||||
'login.pleaseLogin': '請登錄您的賬戶',
|
'login.pleaseLogin': '請登錄您的賬戶',
|
||||||
'login.enterUsername': '請輸入用戶名',
|
'login.enterUsername': '請輸入用戶名',
|
||||||
@@ -492,4 +492,9 @@ export default {
|
|||||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': '配網成功後設備將自動重啟',
|
'deviceConfig.afterConfigSuccessDeviceWillRestart': '配網成功後設備將自動重啟',
|
||||||
'deviceConfig.audioPlaybackError': '音頻播放錯誤',
|
'deviceConfig.audioPlaybackError': '音頻播放錯誤',
|
||||||
'deviceConfig.playbackFailed': '播放失敗',
|
'deviceConfig.playbackFailed': '播放失敗',
|
||||||
|
|
||||||
|
// Voiceprint page
|
||||||
|
'voiceprint.audioNotExist': '該音頻不存在',
|
||||||
|
'voiceprint.getAudioFailed': '獲取音頻失敗',
|
||||||
|
'voiceprint.audioPlayFailed': '音頻播放失敗',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,6 +110,10 @@ const inputVisible = ref(false)
|
|||||||
const languageOptions = ref([])
|
const languageOptions = ref([])
|
||||||
const isVisibleReport = ref(false)
|
const isVisibleReport = ref(false)
|
||||||
|
|
||||||
|
// 音频播放相关
|
||||||
|
const audioRef = ref<UniApp.InnerAudioContext | null>(null)
|
||||||
|
const playingVoiceId = ref<string>('')
|
||||||
|
|
||||||
// 使用插件store
|
// 使用插件store
|
||||||
const pluginStore = usePluginStore()
|
const pluginStore = usePluginStore()
|
||||||
const speedPitchStore = useSpeedPitch()
|
const speedPitchStore = useSpeedPitch()
|
||||||
@@ -422,15 +426,24 @@ function selectRoleTemplate(templateId: string) {
|
|||||||
selectedTemplateId.value = templateId
|
selectedTemplateId.value = templateId
|
||||||
const template = roleTemplates.value.find(t => t.id === templateId)
|
const template = roleTemplates.value.find(t => t.id === templateId)
|
||||||
if (template) {
|
if (template) {
|
||||||
formData.value.systemPrompt = template.systemPrompt
|
formData.value = {
|
||||||
formData.value.vadModelId = template.vadModelId
|
...formData.value,
|
||||||
formData.value.asrModelId = template.asrModelId
|
systemPrompt: template.systemPrompt || formData.value.systemPrompt,
|
||||||
formData.value.llmModelId = template.llmModelId
|
vadModelId: template.vadModelId || formData.value.vadModelId,
|
||||||
formData.value.vllmModelId = template.vllmModelId
|
asrModelId: template.asrModelId || formData.value.asrModelId,
|
||||||
formData.value.intentModelId = template.intentModelId
|
llmModelId: template.llmModelId || formData.value.llmModelId,
|
||||||
formData.value.memModelId = template.memModelId
|
vllmModelId: template.vllmModelId || formData.value.vllmModelId,
|
||||||
formData.value.ttsModelId = template.ttsModelId
|
intentModelId: template.intentModelId || formData.value.intentModelId,
|
||||||
formData.value.ttsVoiceId = template.ttsVoiceId
|
memModelId: template.memModelId || formData.value.memModelId,
|
||||||
|
ttsModelId: template.ttsModelId || formData.value.ttsModelId,
|
||||||
|
ttsVoiceId: template.ttsVoiceId || formData.value.ttsVoiceId,
|
||||||
|
agentName: template.agentName || formData.value.agentName,
|
||||||
|
chatHistoryConf: template.chatHistoryConf || formData.value.chatHistoryConf,
|
||||||
|
summaryMemory: template.summaryMemory || formData.value.summaryMemory,
|
||||||
|
langCode: template.langCode || formData.value.langCode,
|
||||||
|
}
|
||||||
|
fetchAllLanguag(template.ttsModelId || formData.value.ttsModelId)
|
||||||
|
updateDisplayNames()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,6 +506,57 @@ async function onPickerConfirm(type: string, value: any, name: string) {
|
|||||||
// 选择器取消
|
// 选择器取消
|
||||||
function onPickerCancel(type: string) {
|
function onPickerCancel(type: string) {
|
||||||
pickerShow.value[type] = false
|
pickerShow.value[type] = false
|
||||||
|
// 关闭时停止播放
|
||||||
|
if (type === 'voiceprint') {
|
||||||
|
stopAudio()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 播放音频
|
||||||
|
function playAudio(voiceDemo: string, voiceId: string, event: Event) {
|
||||||
|
event.stopPropagation() // 阻止事件冒泡,防止关闭下拉框
|
||||||
|
|
||||||
|
if (!voiceDemo) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果正在播放同一个音频,则停止
|
||||||
|
if (playingVoiceId.value === voiceId) {
|
||||||
|
stopAudio()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止之前的音频
|
||||||
|
stopAudio()
|
||||||
|
|
||||||
|
// 创建新的音频实例
|
||||||
|
audioRef.value = uni.createInnerAudioContext()
|
||||||
|
audioRef.value.src = voiceDemo
|
||||||
|
playingVoiceId.value = voiceId
|
||||||
|
|
||||||
|
// 监听播放结束
|
||||||
|
audioRef.value.onEnded(() => {
|
||||||
|
playingVoiceId.value = ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听播放错误
|
||||||
|
audioRef.value.onError(() => {
|
||||||
|
toast.error('音频播放失败')
|
||||||
|
playingVoiceId.value = ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 播放音频
|
||||||
|
audioRef.value.play()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止音频
|
||||||
|
function stopAudio() {
|
||||||
|
if (audioRef.value) {
|
||||||
|
audioRef.value.stop()
|
||||||
|
audioRef.value.destroy()
|
||||||
|
audioRef.value = null
|
||||||
|
}
|
||||||
|
playingVoiceId.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取模型显示名称
|
// 获取模型显示名称
|
||||||
@@ -946,12 +1010,30 @@ onMounted(async () => {
|
|||||||
@select="({ item }) => onPickerConfirm('tts', item.value, item.name)"
|
@select="({ item }) => onPickerConfirm('tts', item.value, item.name)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<wd-action-sheet
|
<!-- 自定义语音选择弹出层 -->
|
||||||
v-model="pickerShow.voiceprint"
|
<wd-popup v-model="pickerShow.voiceprint" class="custom-popup" position="bottom" @close="onPickerCancel('voiceprint')">
|
||||||
:actions="voiceOptions"
|
<view class="overflow-hidden rounded-[20rpx] bg-white pb-[20rpx] pt-[20rpx]">
|
||||||
@close="onPickerCancel('voiceprint')"
|
<view class="max-h-[600rpx] overflow-y-auto">
|
||||||
@select="({ item }) => onPickerConfirm('voiceprint', item.value, item.name)"
|
<view
|
||||||
/>
|
v-for="voice in voiceOptions"
|
||||||
|
:key="voice.value"
|
||||||
|
class="flex items-center justify-between border-b border-[#f5f5f5] p-[32rpx] transition-all active:bg-[#f5f7fb]"
|
||||||
|
@click="onPickerConfirm('voiceprint', voice.value, voice.name)"
|
||||||
|
>
|
||||||
|
<text :class="`flex-1 text-[28rpx] text-[#232338] ${(voice.voiceDemo || voice.voice_demo) ? '' : 'text-center'}`">
|
||||||
|
{{ voice.name }}
|
||||||
|
</text>
|
||||||
|
<view v-if="voice.voiceDemo || voice.voice_demo" class="ml-[20rpx]" @click.stop="playAudio(voice.voiceDemo || voice.voice_demo, voice.value, $event)">
|
||||||
|
<wd-icon
|
||||||
|
:name="playingVoiceId === voice.value ? 'pause-circle' : 'play-circle'"
|
||||||
|
size="24px"
|
||||||
|
:custom-class="playingVoiceId === voice.value ? 'text-[#336cff]' : 'text-[#9d9ea3]'"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
<wd-action-sheet
|
<wd-action-sheet
|
||||||
v-model="pickerShow.language"
|
v-model="pickerShow.language"
|
||||||
:actions="languageOptions"
|
:actions="languageOptions"
|
||||||
@@ -971,4 +1053,10 @@ onMounted(async () => {
|
|||||||
::v-deep .wd-tag__close {
|
::v-deep .wd-tag__close {
|
||||||
color: #336cff !important;
|
color: #336cff !important;
|
||||||
}
|
}
|
||||||
|
::v-deep .custom-popup {
|
||||||
|
.wd-popup {
|
||||||
|
padding: 20rpx !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -51,17 +51,18 @@ const currentTab = ref('agent-config')
|
|||||||
|
|
||||||
// 刷新和加载状态
|
// 刷新和加载状态
|
||||||
const refreshing = ref(false)
|
const refreshing = ref(false)
|
||||||
|
const refresherEnabled = ref(false)
|
||||||
// 计算是否启用下拉刷新(角色编辑页面不启用)
|
|
||||||
const refresherEnabled = computed(() => {
|
|
||||||
return currentTab.value !== 'agent-config'
|
|
||||||
})
|
|
||||||
|
|
||||||
// 子组件引用
|
// 子组件引用
|
||||||
const deviceRef = ref()
|
const deviceRef = ref()
|
||||||
const chatRef = ref()
|
const chatRef = ref()
|
||||||
const voiceprintRef = ref()
|
const voiceprintRef = ref()
|
||||||
|
|
||||||
|
// 更新刷新器状态
|
||||||
|
function updateRefresherEnabled(value: boolean) {
|
||||||
|
refresherEnabled.value = value
|
||||||
|
}
|
||||||
|
|
||||||
// Tab 配置
|
// Tab 配置
|
||||||
const tabList = [
|
const tabList = [
|
||||||
{
|
{
|
||||||
@@ -144,6 +145,10 @@ async function onLoadMore() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(() => currentTab.value, (newTab) => {
|
||||||
|
updateRefresherEnabled(newTab !== 'agent-config')
|
||||||
|
})
|
||||||
|
|
||||||
// 接收页面参数
|
// 接收页面参数
|
||||||
onLoad((options) => {
|
onLoad((options) => {
|
||||||
if (options?.agentId) {
|
if (options?.agentId) {
|
||||||
@@ -204,6 +209,7 @@ onMounted(async () => {
|
|||||||
v-else-if="currentTab === 'voiceprint-management'"
|
v-else-if="currentTab === 'voiceprint-management'"
|
||||||
ref="voiceprintRef"
|
ref="voiceprintRef"
|
||||||
:agent-id="currentAgentId"
|
:agent-id="currentAgentId"
|
||||||
|
@update-refresher-enabled="updateRefresherEnabled"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ async function mergeFunctions() {
|
|||||||
uni.setStorageSync(`cachedMcpAddress_${agentId.value}`, address)
|
uni.setStorageSync(`cachedMcpAddress_${agentId.value}`, address)
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
mcpAddress.value = error
|
||||||
console.error('获取MCP地址失败:', error)
|
console.error('获取MCP地址失败:', error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,10 +127,10 @@ async function saveServerBaseUrl() {
|
|||||||
uni.request({
|
uni.request({
|
||||||
url: `${getEnvBaseUrl()}/user/pub-config`,
|
url: `${getEnvBaseUrl()}/user/pub-config`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
success: (res) => {
|
success: (res: any) => {
|
||||||
if (res.statusCode === 200) {
|
if (res.statusCode === 200) {
|
||||||
configStore.setConfig(res.data.data)
|
configStore.setConfig(res.data.data)
|
||||||
uni.setStorageSync('config', res.data.data.sm2PubKey)
|
uni.setStorageSync('config', res.data.data)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
@@ -165,6 +165,7 @@ const showLanguageSheet = ref(false)
|
|||||||
function handleLanguageChange(lang: Language) {
|
function handleLanguageChange(lang: Language) {
|
||||||
changeLanguage(lang)
|
changeLanguage(lang)
|
||||||
showLanguageSheet.value = false
|
showLanguageSheet.value = false
|
||||||
|
currentLanguage.value = lang
|
||||||
toast.success(t('settings.languageChanged'))
|
toast.success(t('settings.languageChanged'))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +226,8 @@ function clearAllCacheAfterUrlChange() {
|
|||||||
|
|
||||||
// 重新获取缓存信息
|
// 重新获取缓存信息
|
||||||
getCacheInfo()
|
getCacheInfo()
|
||||||
} catch (error) {
|
}
|
||||||
|
catch (error) {
|
||||||
console.error('清除缓存失败:', error)
|
console.error('清除缓存失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,7 +252,8 @@ async function clearCache() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (error) {
|
}
|
||||||
|
catch (error) {
|
||||||
console.error('清除缓存失败:', error)
|
console.error('清除缓存失败:', error)
|
||||||
toast.error(t('settings.clearCacheFailed'))
|
toast.error(t('settings.clearCacheFailed'))
|
||||||
}
|
}
|
||||||
@@ -278,7 +281,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
// 动态设置导航栏标题为国际化文本
|
// 动态设置导航栏标题为国际化文本
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: t('settings.title')
|
title: t('settings.title'),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -296,8 +299,10 @@ onMounted(async () => {
|
|||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx] overflow-hidden"
|
<view
|
||||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);">
|
class="overflow-hidden border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||||
|
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);"
|
||||||
|
>
|
||||||
<view class="mb-[24rpx]">
|
<view class="mb-[24rpx]">
|
||||||
<text class="text-[28rpx] text-[#232338] font-semibold">
|
<text class="text-[28rpx] text-[#232338] font-semibold">
|
||||||
{{ t('settings.serverApiUrl') }}
|
{{ t('settings.serverApiUrl') }}
|
||||||
@@ -308,11 +313,13 @@ onMounted(async () => {
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="mb-[24rpx]">
|
<view class="mb-[24rpx]">
|
||||||
<view class="w-full rounded-[16rpx] border border-[#eeeeee] bg-[#f5f7fb] overflow-hidden">
|
<view class="w-full overflow-hidden border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb]">
|
||||||
<wd-input v-model="baseUrlInput" type="text" clearable :maxlength="200"
|
<wd-input
|
||||||
|
v-model="baseUrlInput" type="text" clearable :maxlength="200"
|
||||||
:placeholder="t('settings.enterServerUrl')"
|
:placeholder="t('settings.enterServerUrl')"
|
||||||
custom-class="!border-none !bg-transparent h-[64rpx] px-[24rpx] items-center"
|
custom-class="!border-none !bg-transparent h-[64rpx] px-[24rpx] items-center"
|
||||||
input-class="text-[28rpx] text-[#232338]" @input="validateUrl" @blur="validateUrl" />
|
input-class="text-[28rpx] text-[#232338]" @input="validateUrl" @blur="validateUrl"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
<text v-if="urlError" class="mt-[8rpx] block text-[24rpx] text-[#ff4d4f]">
|
<text v-if="urlError" class="mt-[8rpx] block text-[24rpx] text-[#ff4d4f]">
|
||||||
{{ urlError }}
|
{{ urlError }}
|
||||||
@@ -320,14 +327,18 @@ onMounted(async () => {
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="flex gap-[16rpx]">
|
<view class="flex gap-[16rpx]">
|
||||||
<wd-button type="primary"
|
<wd-button
|
||||||
|
type="primary"
|
||||||
custom-class="flex-1 h-[88rpx] rounded-[20rpx] text-[28rpx] font-semibold bg-[#336cff] border-none shadow-[0_4rpx_16rpx_rgba(51,108,255,0.3)] active:shadow-[0_2rpx_8rpx_rgba(51,108,255,0.4)] active:scale-98"
|
custom-class="flex-1 h-[88rpx] rounded-[20rpx] text-[28rpx] font-semibold bg-[#336cff] border-none shadow-[0_4rpx_16rpx_rgba(51,108,255,0.3)] active:shadow-[0_2rpx_8rpx_rgba(51,108,255,0.4)] active:scale-98"
|
||||||
@click="saveServerBaseUrl">
|
@click="saveServerBaseUrl"
|
||||||
|
>
|
||||||
{{ t('settings.saveSettings') }}
|
{{ t('settings.saveSettings') }}
|
||||||
</wd-button>
|
</wd-button>
|
||||||
<wd-button type="default"
|
<wd-button
|
||||||
|
type="default"
|
||||||
custom-class="flex-1 h-[88rpx] rounded-[20rpx] text-[28rpx] font-semibold bg-white border-[#eeeeee] text-[#65686f] active:bg-[#f5f7fb]"
|
custom-class="flex-1 h-[88rpx] rounded-[20rpx] text-[28rpx] font-semibold bg-white border-[#eeeeee] text-[#65686f] active:bg-[#f5f7fb]"
|
||||||
@click="resetServerBaseUrl">
|
@click="resetServerBaseUrl"
|
||||||
|
>
|
||||||
{{ t('settings.resetDefault') }}
|
{{ t('settings.resetDefault') }}
|
||||||
</wd-button>
|
</wd-button>
|
||||||
</view>
|
</view>
|
||||||
@@ -342,12 +353,15 @@ onMounted(async () => {
|
|||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
<view
|
||||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);">
|
class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||||
|
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);"
|
||||||
|
>
|
||||||
<view class="space-y-[16rpx]">
|
<view class="space-y-[16rpx]">
|
||||||
<!-- 缓存信息展示,参考插件样式 -->
|
<!-- 缓存信息展示,参考插件样式 -->
|
||||||
<view
|
<view
|
||||||
class="flex items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx] transition-all active:bg-[#eef3ff]">
|
class="flex items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx] transition-all active:bg-[#eef3ff]"
|
||||||
|
>
|
||||||
<view>
|
<view>
|
||||||
<text class="text-[28rpx] text-[#232338] font-medium">
|
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||||
{{ t('settings.totalCacheSize') }}
|
{{ t('settings.totalCacheSize') }}
|
||||||
@@ -363,7 +377,8 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<!-- 清除缓存按钮,参考插件编辑按钮样式 -->
|
<!-- 清除缓存按钮,参考插件编辑按钮样式 -->
|
||||||
<view
|
<view
|
||||||
class="flex items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx]">
|
class="flex items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx]"
|
||||||
|
>
|
||||||
<view>
|
<view>
|
||||||
<text class="text-[28rpx] text-[#232338] font-medium">
|
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||||
{{ t('settings.cacheClear') }}
|
{{ t('settings.cacheClear') }}
|
||||||
@@ -374,7 +389,8 @@ onMounted(async () => {
|
|||||||
</view>
|
</view>
|
||||||
<view
|
<view
|
||||||
class="cursor-pointer rounded-[24rpx] bg-[rgba(255,107,107,0.1)] px-[28rpx] py-[16rpx] text-[24rpx] text-[#ff6b6b] font-semibold transition-all duration-300 active:scale-95 active:bg-[#ff6b6b] active:text-white"
|
class="cursor-pointer rounded-[24rpx] bg-[rgba(255,107,107,0.1)] px-[28rpx] py-[16rpx] text-[24rpx] text-[#ff6b6b] font-semibold transition-all duration-300 active:scale-95 active:bg-[#ff6b6b] active:text-white"
|
||||||
@click="clearCache">
|
@click="clearCache"
|
||||||
|
>
|
||||||
{{ t('settings.clearCache') }}
|
{{ t('settings.clearCache') }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -390,11 +406,14 @@ onMounted(async () => {
|
|||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
<view
|
||||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);">
|
class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||||
|
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);"
|
||||||
|
>
|
||||||
<view
|
<view
|
||||||
class="flex cursor-pointer items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx] transition-all active:bg-[#eef3ff]"
|
class="flex cursor-pointer items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx] transition-all active:bg-[#eef3ff]"
|
||||||
@click="showAbout">
|
@click="showAbout"
|
||||||
|
>
|
||||||
<view>
|
<view>
|
||||||
<text class="text-[28rpx] text-[#232338] font-medium">
|
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||||
{{ t('settings.aboutUs') }}
|
{{ t('settings.aboutUs') }}
|
||||||
@@ -416,11 +435,14 @@ onMounted(async () => {
|
|||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
<view
|
||||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);">
|
class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||||
|
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);"
|
||||||
|
>
|
||||||
<view
|
<view
|
||||||
class="flex cursor-pointer items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx] transition-all active:bg-[#eef3ff]"
|
class="flex cursor-pointer items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx] transition-all active:bg-[#eef3ff]"
|
||||||
@click="showLanguageSheet = true">
|
@click="showLanguageSheet = true"
|
||||||
|
>
|
||||||
<view>
|
<view>
|
||||||
<text class="text-[32rpx] text-[#232338] font-medium">
|
<text class="text-[32rpx] text-[#232338] font-medium">
|
||||||
{{ t('settings.language') }}
|
{{ t('settings.language') }}
|
||||||
@@ -430,8 +452,8 @@ onMounted(async () => {
|
|||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="flex items-center">
|
<view class="flex items-center">
|
||||||
<text class="text-[32rpx] text-[#9d9ea3] font-semibold mr-[16rpx]">
|
<text class="mr-[16rpx] text-[32rpx] text-[#9d9ea3] font-semibold">
|
||||||
{{supportedLanguages.find(lang => lang.code === currentLanguage)?.name}}
|
{{ supportedLanguages.find(lang => lang.code === currentLanguage)?.name }}
|
||||||
</text>
|
</text>
|
||||||
<wd-icon name="arrow-right" custom-class="text-[32rpx] text-[#9d9ea3]" />
|
<wd-icon name="arrow-right" custom-class="text-[32rpx] text-[#9d9ea3]" />
|
||||||
</view>
|
</view>
|
||||||
@@ -443,8 +465,10 @@ onMounted(async () => {
|
|||||||
<wd-action-sheet v-model="showLanguageSheet" :title="t('settings.selectLanguage')" :close-on-click-modal="true">
|
<wd-action-sheet v-model="showLanguageSheet" :title="t('settings.selectLanguage')" :close-on-click-modal="true">
|
||||||
<view class="language-sheet">
|
<view class="language-sheet">
|
||||||
<scroll-view scroll-y class="language-list">
|
<scroll-view scroll-y class="language-list">
|
||||||
<view v-for="lang in supportedLanguages" :key="lang.code" class="language-item"
|
<view
|
||||||
@click="handleLanguageChange(lang.code)">
|
v-for="lang in supportedLanguages" :key="lang.code" class="language-item"
|
||||||
|
@click="handleLanguageChange(lang.code)"
|
||||||
|
>
|
||||||
<text class="language-name">
|
<text class="language-name">
|
||||||
{{ lang.name }}
|
{{ lang.name }}
|
||||||
</text>
|
</text>
|
||||||
|
|||||||
@@ -3,22 +3,25 @@ import type { ChatHistory, CreateSpeakerData, VoicePrint } from '@/api/voiceprin
|
|||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useMessage } from 'wot-design-uni'
|
import { useMessage } from 'wot-design-uni'
|
||||||
import { useToast } from 'wot-design-uni/components/wd-toast'
|
import { useToast } from 'wot-design-uni/components/wd-toast'
|
||||||
import { createVoicePrint, deleteVoicePrint, getChatHistory, getVoicePrintList, updateVoicePrint } from '@/api/voiceprint'
|
import { createVoicePrint, deleteVoicePrint, getAudioDownloadId, getChatHistory, getVoicePrintList, updateVoicePrint } from '@/api/voiceprint'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
|
import { getEnvBaseUrl } from '@/utils'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'VoicePrintManage',
|
name: 'VoicePrintManage',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
agentId: 'default',
|
||||||
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits(['update-refresher-enabled'])
|
||||||
|
|
||||||
// 接收props
|
// 接收props
|
||||||
interface Props {
|
interface Props {
|
||||||
agentId?: string
|
agentId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
agentId: 'default'
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取屏幕边界到安全区域距离
|
// 获取屏幕边界到安全区域距离
|
||||||
let safeAreaInsets: any
|
let safeAreaInsets: any
|
||||||
let systemInfo: any
|
let systemInfo: any
|
||||||
@@ -50,6 +53,10 @@ const chatHistoryActions = ref<any[]>([])
|
|||||||
const swipeStates = ref<Record<string, 'left' | 'close' | 'right'>>({})
|
const swipeStates = ref<Record<string, 'left' | 'close' | 'right'>>({})
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 音频播放相关
|
||||||
|
const audioRef = ref<UniApp.InnerAudioContext | null>(null)
|
||||||
|
const playingAudioId = ref<string>('')
|
||||||
|
|
||||||
// 使用传入的智能体ID
|
// 使用传入的智能体ID
|
||||||
const currentAgentId = computed(() => {
|
const currentAgentId = computed(() => {
|
||||||
return props.agentId
|
return props.agentId
|
||||||
@@ -157,11 +164,13 @@ function openAddDialog() {
|
|||||||
introduce: '',
|
introduce: '',
|
||||||
}
|
}
|
||||||
showAddDialog.value = true
|
showAddDialog.value = true
|
||||||
} catch (error: any) {
|
}
|
||||||
|
catch (error: any) {
|
||||||
// 捕捉声纹接口未配置错误
|
// 捕捉声纹接口未配置错误
|
||||||
if (error.message && error.message.includes('请求错误[10054]')) {
|
if (error.message && error.message.includes('请求错误[10054]')) {
|
||||||
toast.error(t('voiceprint.voiceprintInterfaceNotConfigured'))
|
toast.error(t('voiceprint.voiceprintInterfaceNotConfigured'))
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
// 其他错误,继续打开弹窗
|
// 其他错误,继续打开弹窗
|
||||||
addForm.value = {
|
addForm.value = {
|
||||||
agentId: currentAgentId.value,
|
agentId: currentAgentId.value,
|
||||||
@@ -202,6 +211,11 @@ function selectAudioId({ item }: { item: any }) {
|
|||||||
showChatHistoryDialog.value = false
|
showChatHistoryDialog.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 点击选择
|
||||||
|
function handleItemClick(item: any) {
|
||||||
|
selectAudioId({ item })
|
||||||
|
}
|
||||||
|
|
||||||
// 提交添加说话人
|
// 提交添加说话人
|
||||||
async function submitAdd() {
|
async function submitAdd() {
|
||||||
if (!addForm.value.sourceName.trim()) {
|
if (!addForm.value.sourceName.trim()) {
|
||||||
@@ -274,6 +288,82 @@ async function handleDelete(id: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 播放音频
|
||||||
|
async function playAudio(audioId: string, event: Event) {
|
||||||
|
event.stopPropagation() // 阻止事件冒泡,防止关闭下拉框
|
||||||
|
|
||||||
|
if (!audioId) {
|
||||||
|
toast.warning(t('voiceprint.audioNotExist'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果正在播放同一个音频,则停止
|
||||||
|
if (playingAudioId.value === audioId) {
|
||||||
|
stopAudio()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止之前的音频
|
||||||
|
stopAudio()
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 先获取音频下载ID
|
||||||
|
playingAudioId.value = audioId
|
||||||
|
const downloadId = await getAudioDownloadId(audioId)
|
||||||
|
|
||||||
|
if (!downloadId) {
|
||||||
|
toast.error(t('voiceprint.getAudioFailed'))
|
||||||
|
playingAudioId.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取baseURL
|
||||||
|
const baseURL = getEnvBaseUrl()
|
||||||
|
const audioUrl = `${baseURL}/agent/play/${downloadId}`
|
||||||
|
|
||||||
|
// 创建新的音频实例
|
||||||
|
audioRef.value = uni.createInnerAudioContext()
|
||||||
|
audioRef.value.src = audioUrl
|
||||||
|
audioRef.value.autoplay = true
|
||||||
|
|
||||||
|
// 监听播放结束
|
||||||
|
audioRef.value.onEnded(() => {
|
||||||
|
playingAudioId.value = ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听播放错误
|
||||||
|
audioRef.value.onError((error) => {
|
||||||
|
console.error('音频播放错误:', error)
|
||||||
|
toast.error(t('voiceprint.audioPlayFailed'))
|
||||||
|
playingAudioId.value = ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('播放音频失败:', error)
|
||||||
|
toast.error(t('voiceprint.audioPlayFailed'))
|
||||||
|
playingAudioId.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止音频
|
||||||
|
function stopAudio() {
|
||||||
|
if (audioRef.value) {
|
||||||
|
audioRef.value.stop()
|
||||||
|
audioRef.value.destroy()
|
||||||
|
audioRef.value = null
|
||||||
|
}
|
||||||
|
playingAudioId.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => [showAddDialog.value, showEditDialog.value], (newValues) => {
|
||||||
|
if (newValues.some((value: boolean) => value)) {
|
||||||
|
emits('update-refresher-enabled', false)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emits('update-refresher-enabled', true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 智能体已简化为默认
|
// 智能体已简化为默认
|
||||||
|
|
||||||
@@ -282,6 +372,8 @@ onMounted(async () => {
|
|||||||
|
|
||||||
// 暴露方法给父组件
|
// 暴露方法给父组件
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
showAddDialog,
|
||||||
|
showEditDialog,
|
||||||
refresh,
|
refresh,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -349,7 +441,7 @@ defineExpose({
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 浮动操作按钮 -->
|
<!-- 浮动操作按钮 -->
|
||||||
<wd-fab type="primary" size="small" :draggable="true" :expandable="false" @click="openAddDialog">
|
<wd-fab custom-style="z-index:10" type="primary" size="small" :draggable="true" :expandable="false" @click="openAddDialog">
|
||||||
<wd-icon name="add" />
|
<wd-icon name="add" />
|
||||||
</wd-fab>
|
</wd-fab>
|
||||||
|
|
||||||
@@ -359,20 +451,16 @@ defineExpose({
|
|||||||
|
|
||||||
<!-- 添加说话人弹窗 -->
|
<!-- 添加说话人弹窗 -->
|
||||||
<wd-popup
|
<wd-popup
|
||||||
v-model="showAddDialog" position="center" custom-style="width: 90%; max-width: 400px; border-radius: 16px;"
|
v-model="showAddDialog"
|
||||||
|
position="center"
|
||||||
|
custom-style="width: 90%; max-width: 400px; border-radius: 16px;"
|
||||||
safe-area-inset-bottom
|
safe-area-inset-bottom
|
||||||
>
|
>
|
||||||
<view>
|
<view>
|
||||||
<view class="w-full flex items-center justify-between border-b-[2rpx] border-[#eeeeee] p-[32rpx_32rpx_24rpx]">
|
|
||||||
<text class="w-full text-center text-[32rpx] text-[#232338] font-semibold">
|
|
||||||
{{ t('voiceprint.addSpeaker') }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="p-[32rpx]">
|
<view class="p-[32rpx]">
|
||||||
<!-- 声纹向量选择 -->
|
<!-- 声纹向量选择 -->
|
||||||
<view class="mb-[32rpx]">
|
<view class="mb-[32rpx]">
|
||||||
<text class="mb-[16rpx] block text-[28rpx] text-[#232338] font-medium">
|
<text class="mb-[16rpx] block text-[28rpx] text-red font-medium">
|
||||||
* {{ t('voiceprint.voiceVector') }}
|
* {{ t('voiceprint.voiceVector') }}
|
||||||
</text>
|
</text>
|
||||||
<view
|
<view
|
||||||
@@ -391,7 +479,7 @@ defineExpose({
|
|||||||
|
|
||||||
<!-- 姓名 -->
|
<!-- 姓名 -->
|
||||||
<view class="mb-[32rpx]">
|
<view class="mb-[32rpx]">
|
||||||
<text class="mb-[16rpx] block text-[28rpx] text-[#232338] font-medium">
|
<text class="mb-[16rpx] block text-[28rpx] text-red font-medium">
|
||||||
* {{ t('voiceprint.name') }}
|
* {{ t('voiceprint.name') }}
|
||||||
</text>
|
</text>
|
||||||
<input
|
<input
|
||||||
@@ -403,7 +491,7 @@ defineExpose({
|
|||||||
|
|
||||||
<!-- 描述 -->
|
<!-- 描述 -->
|
||||||
<view>
|
<view>
|
||||||
<text class="mb-[16rpx] block text-[28rpx] text-[#232338] font-medium">
|
<text class="mb-[16rpx] block text-[28rpx] text-red font-medium">
|
||||||
* {{ t('voiceprint.description') }}
|
* {{ t('voiceprint.description') }}
|
||||||
</text>
|
</text>
|
||||||
<textarea
|
<textarea
|
||||||
@@ -418,11 +506,11 @@ defineExpose({
|
|||||||
|
|
||||||
<view class="flex gap-[16rpx] border-t-[2rpx] border-[#eeeeee] p-[24rpx_32rpx_32rpx]">
|
<view class="flex gap-[16rpx] border-t-[2rpx] border-[#eeeeee] p-[24rpx_32rpx_32rpx]">
|
||||||
<wd-button type="info" custom-class="flex-1" @click="showAddDialog = false">
|
<wd-button type="info" custom-class="flex-1" @click="showAddDialog = false">
|
||||||
{{ t('voiceprint.cancel') }}
|
{{ t('voiceprint.cancel') }}
|
||||||
</wd-button>
|
</wd-button>
|
||||||
<wd-button type="primary" custom-class="flex-1" @click="submitAdd">
|
<wd-button type="primary" custom-class="flex-1" @click="submitAdd">
|
||||||
{{ t('voiceprint.save') }}
|
{{ t('voiceprint.save') }}
|
||||||
</wd-button>
|
</wd-button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</wd-popup>
|
</wd-popup>
|
||||||
@@ -488,23 +576,42 @@ defineExpose({
|
|||||||
|
|
||||||
<view class="flex gap-[16rpx] border-t-[2rpx] border-[#eeeeee] p-[24rpx_32rpx_32rpx]">
|
<view class="flex gap-[16rpx] border-t-[2rpx] border-[#eeeeee] p-[24rpx_32rpx_32rpx]">
|
||||||
<wd-button type="info" custom-class="flex-1" @click="showEditDialog = false">
|
<wd-button type="info" custom-class="flex-1" @click="showEditDialog = false">
|
||||||
{{ t('voiceprint.cancel') }}
|
{{ t('voiceprint.cancel') }}
|
||||||
</wd-button>
|
</wd-button>
|
||||||
<wd-button type="primary" custom-class="flex-1" @click="submitEdit">
|
<wd-button type="primary" custom-class="flex-1" @click="submitEdit">
|
||||||
{{ t('voiceprint.save') }}
|
{{ t('voiceprint.save') }}
|
||||||
</wd-button>
|
</wd-button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</wd-popup>
|
</wd-popup>
|
||||||
|
|
||||||
<!-- 语音对话记录选择动作面板 -->
|
<!-- 自定义语音对话记录选择弹出层 -->
|
||||||
<wd-action-sheet
|
<wd-popup v-model="showChatHistoryDialog" class="custom-popup" position="bottom" @close="stopAudio">
|
||||||
v-model="showChatHistoryDialog" :actions="chatHistoryActions" :title="t('voiceprint.selectVector')"
|
<view class="rounded-[20rpx] bg-white pb-[20rpx] pt-[20rpx]">
|
||||||
@select="selectAudioId"
|
<view class="max-h-[600rpx] overflow-y-auto rounded-[20rpx]">
|
||||||
/>
|
<view
|
||||||
|
v-for="item in chatHistoryActions"
|
||||||
|
:key="item.audioId"
|
||||||
|
class="flex items-center justify-between border-b border-[#f5f5f5] p-[32rpx] transition-all active:bg-[#f5f7fb]"
|
||||||
|
@click="handleItemClick(item)"
|
||||||
|
>
|
||||||
|
<text class="flex-1 text-[28rpx] text-[#232338]">
|
||||||
|
{{ item.name }}
|
||||||
|
</text>
|
||||||
|
<view class="ml-[20rpx]" @click.stop="playAudio(item.audioId, $event)">
|
||||||
|
<wd-icon
|
||||||
|
:name="playingAudioId === item.audioId ? 'pause-circle' : 'play-circle'"
|
||||||
|
size="24px"
|
||||||
|
:custom-class="playingAudioId === item.audioId ? 'text-[#336cff]' : 'text-[#9d9ea3]'"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style lang="scss" scoped>
|
||||||
.voiceprint-container {
|
.voiceprint-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@@ -523,14 +630,10 @@ defineExpose({
|
|||||||
color: #666666;
|
color: #666666;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.wd-swipe-action) {
|
::v-deep .custom-popup {
|
||||||
border-radius: 20rpx;
|
.wd-popup {
|
||||||
overflow: hidden;
|
padding: 20rpx !important;
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
background: transparent;
|
||||||
border: 1rpx solid #eeeeee;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.flex-1) {
|
|
||||||
flex: 1;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -670,10 +670,10 @@ export default {
|
|||||||
'home.wish': 'Vamos ter um dia maravilhoso!',
|
'home.wish': 'Vamos ter um dia maravilhoso!',
|
||||||
'home.languageModel': 'LLM',
|
'home.languageModel': 'LLM',
|
||||||
'home.voiceModel': 'TTS',
|
'home.voiceModel': 'TTS',
|
||||||
'home.configureRole': 'Configurar Papel',
|
'home.configureRole': 'ConfRoles',
|
||||||
'home.voiceprintRecognition': 'Impressão Vocal',
|
'home.voiceprintRecognition': 'RecVoz',
|
||||||
'home.deviceManagement': 'Dispositivos',
|
'home.deviceManagement': 'Dispositivos',
|
||||||
'home.chatHistory': 'Histórico de Chat',
|
'home.chatHistory': 'Conversa',
|
||||||
'home.lastConversation': 'Última Conversa',
|
'home.lastConversation': 'Última Conversa',
|
||||||
'home.noConversation': 'Nenhuma conversa',
|
'home.noConversation': 'Nenhuma conversa',
|
||||||
'home.justNow': 'Agora mesmo',
|
'home.justNow': 'Agora mesmo',
|
||||||
|
|||||||
Reference in New Issue
Block a user