mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-25 16:43:55 +08:00
feat: mobile端同步更新上报模式、语言设置功能
This commit is contained in:
@@ -206,3 +206,19 @@ export function updateAgentTags(agentId: string, data) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取所有语言
|
||||||
|
export function getAllLanguage(modelId, voiceName) {
|
||||||
|
const queryParams = new URLSearchParams({
|
||||||
|
voiceName: voiceName || '',
|
||||||
|
}).toString()
|
||||||
|
return http.Get(`/models/${modelId}/voices?${queryParams}`, {
|
||||||
|
meta: {
|
||||||
|
ignoreAuth: false,
|
||||||
|
toast: false,
|
||||||
|
},
|
||||||
|
cacheFor: {
|
||||||
|
expire: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export interface Agent {
|
|||||||
summaryMemory: string | null
|
summaryMemory: string | null
|
||||||
lastConnectedAt: string | null
|
lastConnectedAt: string | null
|
||||||
deviceCount: number
|
deviceCount: number
|
||||||
tags: { id: string, tagName: string }[]
|
tags: Record<string, string>[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 智能体创建数据类型
|
// 智能体创建数据类型
|
||||||
@@ -43,6 +43,10 @@ export interface AgentDetail {
|
|||||||
createdAt: string
|
createdAt: string
|
||||||
updater: string
|
updater: string
|
||||||
updatedAt: string
|
updatedAt: string
|
||||||
|
ttsLanguage: string
|
||||||
|
ttsVolume: number
|
||||||
|
ttsRate: number
|
||||||
|
ttsPitch: number
|
||||||
functions: AgentFunction[]
|
functions: AgentFunction[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ watch(() => props.visible, (val) => {
|
|||||||
{{ t('contextProviderDialog.noHeaders') }}
|
{{ t('contextProviderDialog.noHeaders') }}
|
||||||
</text>
|
</text>
|
||||||
<wd-button type="text" size="small" @click="addHeader(pIndex, 0)">
|
<wd-button type="text" size="small" @click="addHeader(pIndex, 0)">
|
||||||
{{ t('contextProviderDialog.addHeaders') }}
|
{{ t('contextProviderDialog.addHeader') }}
|
||||||
</wd-button>
|
</wd-button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -0,0 +1,270 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
visible?: boolean
|
||||||
|
settings?: {
|
||||||
|
volume: number
|
||||||
|
speed: number
|
||||||
|
pitch: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
visible: false,
|
||||||
|
settings: () => ({
|
||||||
|
volume: 0,
|
||||||
|
speed: 0,
|
||||||
|
pitch: 0,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:visible': [value: boolean]
|
||||||
|
'confirm': [value: {
|
||||||
|
volume: number
|
||||||
|
speed: number
|
||||||
|
pitch: number
|
||||||
|
}]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const localSettings = ref({
|
||||||
|
volume: 0,
|
||||||
|
speed: 0,
|
||||||
|
pitch: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
function initLocalData() {
|
||||||
|
localSettings.value = {
|
||||||
|
volume: props.settings.volume || 0,
|
||||||
|
speed: props.settings.speed || 0,
|
||||||
|
pitch: props.settings.pitch || 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConfirm() {
|
||||||
|
emit('confirm', localSettings.value)
|
||||||
|
emit('update:visible', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
emit('update:visible', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.visible, (val) => {
|
||||||
|
if (val) {
|
||||||
|
initLocalData()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view v-if="visible" class="voice-settings-dialog-mask" @click="handleClose">
|
||||||
|
<view class="voice-settings-dialog" @click.stop>
|
||||||
|
<view class="dialog-header">
|
||||||
|
<text class="dialog-title">
|
||||||
|
{{ t('agent.languageConfig') }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="dialog-content">
|
||||||
|
<!-- 音量调节 -->
|
||||||
|
<view class="setting-item">
|
||||||
|
<text class="setting-label">
|
||||||
|
{{ t('agent.ttsVolume') }}
|
||||||
|
</text>
|
||||||
|
<view class="slider-container">
|
||||||
|
<wd-slider
|
||||||
|
v-model="localSettings.volume"
|
||||||
|
:min="-100"
|
||||||
|
:max="100"
|
||||||
|
:step="1"
|
||||||
|
:show-value="false"
|
||||||
|
custom-class="voice-slider"
|
||||||
|
/>
|
||||||
|
<text class="slider-value">
|
||||||
|
{{ localSettings.volume }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<text class="setting-desc">
|
||||||
|
{{ t('agent.volumeHint') }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 语速调节 -->
|
||||||
|
<view class="setting-item">
|
||||||
|
<text class="setting-label">
|
||||||
|
{{ t('agent.ttsRate') }}
|
||||||
|
</text>
|
||||||
|
<view class="slider-container">
|
||||||
|
<wd-slider
|
||||||
|
v-model="localSettings.speed"
|
||||||
|
:min="-100"
|
||||||
|
:max="100"
|
||||||
|
:step="1"
|
||||||
|
:show-value="false"
|
||||||
|
custom-class="voice-slider"
|
||||||
|
/>
|
||||||
|
<text class="slider-value">
|
||||||
|
{{ localSettings.speed }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<text class="setting-desc">
|
||||||
|
{{ t('agent.speedHint') }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 音调调节 -->
|
||||||
|
<view class="setting-item">
|
||||||
|
<text class="setting-label">
|
||||||
|
{{ t('agent.ttsPitch') }}
|
||||||
|
</text>
|
||||||
|
<view class="slider-container">
|
||||||
|
<wd-slider
|
||||||
|
v-model="localSettings.pitch"
|
||||||
|
:min="-100"
|
||||||
|
:max="100"
|
||||||
|
:step="1"
|
||||||
|
:show-value="false"
|
||||||
|
custom-class="voice-slider"
|
||||||
|
/>
|
||||||
|
<text class="slider-value">
|
||||||
|
{{ localSettings.pitch }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<text class="setting-desc">
|
||||||
|
{{ t('agent.pitchHint') }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="dialog-footer">
|
||||||
|
<wd-button class="cancel-btn" @click="handleClose">
|
||||||
|
{{ t('agent.cancel') }}
|
||||||
|
</wd-button>
|
||||||
|
<wd-button type="primary" class="confirm-btn" @click="handleConfirm">
|
||||||
|
{{ t('agent.save') }}
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.voice-settings-dialog-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voice-settings-dialog {
|
||||||
|
background: #fff;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 30rpx;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #232338;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
font-size: 40rpx;
|
||||||
|
color: #9d9ea3;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 40rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 50rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-label {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #232338;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voice-slider {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-value {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #336cff;
|
||||||
|
font-weight: 500;
|
||||||
|
min-width: 80rpx;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-desc {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #9d9ea3;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 20rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn,
|
||||||
|
.confirm-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #336cff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义滑块样式 */
|
||||||
|
:deep(.wd-slider) {
|
||||||
|
--wd-slider-bar-background: #e6ebff;
|
||||||
|
--wd-slider-bar-active-background: #336cff;
|
||||||
|
--wd-slider-thumb-border-color: #336cff;
|
||||||
|
--wd-slider-thumb-background: #336cff;
|
||||||
|
--wd-slider-thumb-size: 32rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -110,6 +110,7 @@ const alovaInstance = createAlova({
|
|||||||
if (config.meta?.isExposeError) {
|
if (config.meta?.isExposeError) {
|
||||||
return Promise.reject(msg)
|
return Promise.reject(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.meta?.toast !== false) {
|
if (config.meta?.toast !== false) {
|
||||||
toast.warning(msg)
|
toast.warning(msg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,6 +124,17 @@ export default {
|
|||||||
'agent.pleaseInputAgentName': 'Bitte Agenten-Namen eingeben',
|
'agent.pleaseInputAgentName': 'Bitte Agenten-Namen eingeben',
|
||||||
'agent.pleaseInputRoleDescription': 'Bitte Rollenbeschreibung eingeben',
|
'agent.pleaseInputRoleDescription': 'Bitte Rollenbeschreibung eingeben',
|
||||||
'agent.pleaseSelect': 'Bitte auswählen',
|
'agent.pleaseSelect': 'Bitte auswählen',
|
||||||
|
'agent.reportText': 'Text melden',
|
||||||
|
'agent.reportTextVoice': 'Text + Sprache melden',
|
||||||
|
'agent.reportMode': 'Meldemodus',
|
||||||
|
'agent.language': 'Unterhaltungssprache',
|
||||||
|
'agent.languageConfig': 'Sprachgeschwindigkeit & Tonhöhe',
|
||||||
|
'agent.ttsVolume': 'Lautstärke',
|
||||||
|
'agent.ttsRate': 'Sprechtempo',
|
||||||
|
'agent.ttsPitch': 'Tonhöhe',
|
||||||
|
'agent.volumeHint': '-100=min, 0=Standard, 100=max',
|
||||||
|
'agent.speedHint': '-100=langsamst, 0=Standard, 100=schnellst',
|
||||||
|
'agent.pitchHint': '-100=tiefst, 0=Standard, 100=höchst',
|
||||||
|
|
||||||
// Context provider dialog related
|
// Context provider dialog related
|
||||||
'contextProviderDialog.title': 'Quelle bearbeiten',
|
'contextProviderDialog.title': 'Quelle bearbeiten',
|
||||||
|
|||||||
@@ -124,6 +124,17 @@ export default {
|
|||||||
'agent.pleaseInputAgentName': 'Please input agent name',
|
'agent.pleaseInputAgentName': 'Please input agent name',
|
||||||
'agent.pleaseInputRoleDescription': 'Please input role description',
|
'agent.pleaseInputRoleDescription': 'Please input role description',
|
||||||
'agent.pleaseSelect': 'Please select',
|
'agent.pleaseSelect': 'Please select',
|
||||||
|
'agent.reportText': 'Report Text',
|
||||||
|
'agent.reportTextVoice': 'Report Text+Voice',
|
||||||
|
'agent.reportMode': 'Report Mode',
|
||||||
|
'agent.language': 'Conversation Language',
|
||||||
|
'agent.languageConfig': 'Speech Rate&Tone',
|
||||||
|
'agent.ttsVolume': 'Volume',
|
||||||
|
'agent.ttsRate': 'Speech Rate',
|
||||||
|
'agent.ttsPitch': 'Tone',
|
||||||
|
'agent.volumeHint': '-100=Minimum, 0=Standard, 100=Maximum',
|
||||||
|
'agent.speedHint': '-100=Slower, 0=Standard, 100=Faster',
|
||||||
|
'agent.pitchHint': '-100=Lowest, 0=Standard, 100=Highest',
|
||||||
|
|
||||||
// Context provider dialog related
|
// Context provider dialog related
|
||||||
'contextProviderDialog.title': 'Edit Source',
|
'contextProviderDialog.title': 'Edit Source',
|
||||||
|
|||||||
@@ -124,6 +124,17 @@ export default {
|
|||||||
'agent.pleaseInputAgentName': 'Por favor, insira o nome do agente',
|
'agent.pleaseInputAgentName': 'Por favor, insira o nome do agente',
|
||||||
'agent.pleaseInputRoleDescription': 'Por favor, insira a descrição do papel',
|
'agent.pleaseInputRoleDescription': 'Por favor, insira a descrição do papel',
|
||||||
'agent.pleaseSelect': 'Por favor, selecione',
|
'agent.pleaseSelect': 'Por favor, selecione',
|
||||||
|
'agent.reportText': 'Enviar Texto',
|
||||||
|
'agent.reportTextVoice': 'Enviar Texto+Voz',
|
||||||
|
'agent.reportMode': 'Modo de Envio',
|
||||||
|
'agent.language': 'Idioma de Conversa',
|
||||||
|
'agent.languageConfig': 'Configuração de Voz',
|
||||||
|
'agent.ttsVolume': 'Volume',
|
||||||
|
'agent.ttsRate': 'Taxa de Fala',
|
||||||
|
'agent.ttsPitch': 'Tonalidade',
|
||||||
|
'agent.volumeHint': '-100=Mínimo, 0=Padrão, 100=Máximo',
|
||||||
|
'agent.speedHint': '-100=Slower, 0=Standard, 100=Faster',
|
||||||
|
'agent.pitchHint': '-100=Lowest, 0=Standard, 100=Highest',
|
||||||
|
|
||||||
// Diálogo de provedor de contexto
|
// Diálogo de provedor de contexto
|
||||||
'contextProviderDialog.title': 'Editar Fonte',
|
'contextProviderDialog.title': 'Editar Fonte',
|
||||||
|
|||||||
@@ -124,6 +124,17 @@ export default {
|
|||||||
'agent.pleaseInputAgentName': 'Vui lòng nhập tên đại lý',
|
'agent.pleaseInputAgentName': 'Vui lòng nhập tên đại lý',
|
||||||
'agent.pleaseInputRoleDescription': 'Vui lòng nhập mô tả vai trò',
|
'agent.pleaseInputRoleDescription': 'Vui lòng nhập mô tả vai trò',
|
||||||
'agent.pleaseSelect': 'Vui lòng chọn',
|
'agent.pleaseSelect': 'Vui lòng chọn',
|
||||||
|
'agent.reportText': 'Gửi văn bản',
|
||||||
|
'agent.reportTextVoice': 'Gửi văn bản+giọng nói',
|
||||||
|
'agent.reportMode': 'Chế độ gửi',
|
||||||
|
'agent.language': 'Ngôn ngữ',
|
||||||
|
'agent.languageConfig': 'Giọng nói',
|
||||||
|
'agent.ttsVolume': 'Âm lượng',
|
||||||
|
'agent.ttsRate': 'Tốc độ',
|
||||||
|
'agent.ttsPitch': 'Tone',
|
||||||
|
'agent.volumeHint': '-100=Minimum, 0=Standard, 100=Maximum',
|
||||||
|
'agent.speedHint': '-100=Slowest, 0=Standard, 100=Fastest',
|
||||||
|
'agent.pitchHint': '-100=Lowest, 0=Standard, 100=Highest',
|
||||||
|
|
||||||
// Context provider dialog related
|
// Context provider dialog related
|
||||||
'contextProviderDialog.title': 'Chỉnh sửa nguồn',
|
'contextProviderDialog.title': 'Chỉnh sửa nguồn',
|
||||||
|
|||||||
@@ -124,6 +124,17 @@ export default {
|
|||||||
'agent.pleaseInputAgentName': '请输入智能体名称',
|
'agent.pleaseInputAgentName': '请输入智能体名称',
|
||||||
'agent.pleaseInputRoleDescription': '请输入角色介绍',
|
'agent.pleaseInputRoleDescription': '请输入角色介绍',
|
||||||
'agent.pleaseSelect': '请选择',
|
'agent.pleaseSelect': '请选择',
|
||||||
|
'agent.reportText': '上报文字',
|
||||||
|
'agent.reportTextVoice': '上报文字+语音',
|
||||||
|
'agent.reportMode': '上报模式',
|
||||||
|
'agent.language': '对话语言',
|
||||||
|
'agent.languageConfig': '语速音调',
|
||||||
|
'agent.ttsVolume': '音量',
|
||||||
|
'agent.ttsRate': '语速',
|
||||||
|
'agent.ttsPitch': '音调',
|
||||||
|
'agent.volumeHint': '-100=最小, 0=标准, 100=最大',
|
||||||
|
'agent.speedHint': '-100=最慢, 0=标准, 100=最快',
|
||||||
|
'agent.pitchHint': '-100=最低, 0=标准, 100=最高',
|
||||||
|
|
||||||
// 上下文源对话框相关
|
// 上下文源对话框相关
|
||||||
'contextProviderDialog.title': '编辑源',
|
'contextProviderDialog.title': '编辑源',
|
||||||
|
|||||||
@@ -145,6 +145,17 @@ export default {
|
|||||||
'agent.pleaseInputAgentName': '請輸入助手暱稱',
|
'agent.pleaseInputAgentName': '請輸入助手暱稱',
|
||||||
'agent.pleaseInputRoleDescription': '請輸入角色介紹',
|
'agent.pleaseInputRoleDescription': '請輸入角色介紹',
|
||||||
'agent.pleaseSelect': '請選擇',
|
'agent.pleaseSelect': '請選擇',
|
||||||
|
'agent.reportText': '上报文字',
|
||||||
|
'agent.reportTextVoice': '上报文字+语音',
|
||||||
|
'agent.reportMode': '上報模式',
|
||||||
|
'agent.language': '對話語言',
|
||||||
|
'agent.languageConfig': '語速音調',
|
||||||
|
'agent.ttsVolume': '音量',
|
||||||
|
'agent.ttsRate': '語速',
|
||||||
|
'agent.ttsPitch': '音調',
|
||||||
|
'agent.volumeHint': '-100=最小, 0=標準, 100=最大',
|
||||||
|
'agent.speedHint': '-100=最慢, 0=標準, 100=最快',
|
||||||
|
'agent.pitchHint': '-100=最低, 0=標準, 100=最高',
|
||||||
|
|
||||||
// 上下文源对话框相关
|
// 上下文源对话框相关
|
||||||
'contextProviderDialog.title': '編輯源',
|
'contextProviderDialog.title': '編輯源',
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { AgentDetail, ModelOption, PluginDefinition, RoleTemplate } from '@/api/agent/types'
|
import type { AgentDetail, ModelOption, PluginDefinition, RoleTemplate } from '@/api/agent/types'
|
||||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||||
import { getAgentDetail, getAgentTags, getModelOptions, getPluginFunctions, getRoleTemplates, getTTSVoices, updateAgent, updateAgentTags } from '@/api/agent/agent'
|
import { getAgentDetail, getAgentTags, getAllLanguage, getModelOptions, getPluginFunctions, getRoleTemplates, updateAgent, updateAgentTags } from '@/api/agent/agent'
|
||||||
import ContextProviderDialog from '@/components/ContextProviderDialog.vue'
|
import ContextProviderDialog from '@/components/ContextProviderDialog.vue'
|
||||||
|
import VoiceSettingsDialog from '@/components/VoiceSettingsDialog.vue'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
import { usePluginStore } from '@/store'
|
import { usePluginStore } from '@/store'
|
||||||
import { toast } from '@/utils/toast'
|
import { toast } from '@/utils/toast'
|
||||||
@@ -35,6 +36,10 @@ const formData = ref<Partial<AgentDetail>>({
|
|||||||
memModelId: '',
|
memModelId: '',
|
||||||
ttsModelId: '',
|
ttsModelId: '',
|
||||||
ttsVoiceId: '',
|
ttsVoiceId: '',
|
||||||
|
ttsLanguage: '',
|
||||||
|
ttsVolume: 0,
|
||||||
|
ttsRate: 0,
|
||||||
|
ttsPitch: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 显示名称数据
|
// 显示名称数据
|
||||||
@@ -48,6 +53,8 @@ const displayNames = ref({
|
|||||||
memory: t('agent.pleaseSelect'),
|
memory: t('agent.pleaseSelect'),
|
||||||
tts: t('agent.pleaseSelect'),
|
tts: t('agent.pleaseSelect'),
|
||||||
voiceprint: t('agent.pleaseSelect'),
|
voiceprint: t('agent.pleaseSelect'),
|
||||||
|
report: t('agent.pleaseSelect'),
|
||||||
|
language: t('agent.pleaseSelect'),
|
||||||
})
|
})
|
||||||
|
|
||||||
// 角色模板数据
|
// 角色模板数据
|
||||||
@@ -72,7 +79,15 @@ const modelOptions = ref<{
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 音色选项数据
|
// 音色选项数据
|
||||||
const voiceOptions = ref<{ id: string, name: string }[]>([])
|
const voiceOptions = ref([])
|
||||||
|
// 保存完整的音色信息
|
||||||
|
const voiceDetails = ref({})
|
||||||
|
|
||||||
|
// 上报模式选项数据
|
||||||
|
const reportOptions = [
|
||||||
|
{ name: t('agent.reportText'), value: 1 },
|
||||||
|
{ name: t('agent.reportTextVoice'), value: 2 },
|
||||||
|
]
|
||||||
|
|
||||||
// 选择器显示状态
|
// 选择器显示状态
|
||||||
const pickerShow = ref<{
|
const pickerShow = ref<{
|
||||||
@@ -86,6 +101,14 @@ const pickerShow = ref<{
|
|||||||
memory: false,
|
memory: false,
|
||||||
tts: false,
|
tts: false,
|
||||||
voiceprint: false,
|
voiceprint: false,
|
||||||
|
language: false,
|
||||||
|
report: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const ttsSettings = ref({
|
||||||
|
volume: 0,
|
||||||
|
speed: 0,
|
||||||
|
pitch: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
const allFunctions = ref<PluginDefinition[]>([])
|
const allFunctions = ref<PluginDefinition[]>([])
|
||||||
@@ -94,6 +117,14 @@ const inputValue = ref('')
|
|||||||
const inputVisible = ref(false)
|
const inputVisible = ref(false)
|
||||||
const showContextProviderDialog = ref(false)
|
const showContextProviderDialog = ref(false)
|
||||||
const currentContextProviders = ref([])
|
const currentContextProviders = ref([])
|
||||||
|
const showVoiceSettingsDialog = ref(false)
|
||||||
|
const voiceSettings = ref({
|
||||||
|
volume: 0,
|
||||||
|
speed: 0,
|
||||||
|
pitch: 0,
|
||||||
|
})
|
||||||
|
const languageOptions = ref([])
|
||||||
|
const isVisibleReport = ref(false)
|
||||||
|
|
||||||
// 使用插件store
|
// 使用插件store
|
||||||
const pluginStore = usePluginStore()
|
const pluginStore = usePluginStore()
|
||||||
@@ -151,6 +182,21 @@ function handleUpdateContext(providers: any[]) {
|
|||||||
currentContextProviders.value = providers
|
currentContextProviders.value = providers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openVoiceSettingsDialog() {
|
||||||
|
showVoiceSettingsDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUpdateVoiceSettings(settings: any) {
|
||||||
|
ttsSettings.value = settings
|
||||||
|
formData.value.ttsVolume = settings.volume
|
||||||
|
formData.value.ttsRate = settings.speed
|
||||||
|
formData.value.ttsPitch = settings.pitch
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRegulate() {
|
||||||
|
openVoiceSettingsDialog()
|
||||||
|
}
|
||||||
|
|
||||||
// 加载智能体详情
|
// 加载智能体详情
|
||||||
async function loadAgentDetail() {
|
async function loadAgentDetail() {
|
||||||
if (!agentId.value)
|
if (!agentId.value)
|
||||||
@@ -167,10 +213,16 @@ async function loadAgentDetail() {
|
|||||||
|
|
||||||
// 加载上下文配置
|
// 加载上下文配置
|
||||||
currentContextProviders.value = (detail as any).contextProviders || []
|
currentContextProviders.value = (detail as any).contextProviders || []
|
||||||
|
// 加载语音设置
|
||||||
|
voiceSettings.value = {
|
||||||
|
volume: (detail as any).volume || 0,
|
||||||
|
speed: (detail as any).speed || 0,
|
||||||
|
pitch: (detail as any).pitch || 0,
|
||||||
|
}
|
||||||
|
|
||||||
// 如果有TTS模型,加载对应的音色选项
|
// 如果有TTS模型,加载对应的音色选项
|
||||||
if (detail.ttsModelId) {
|
if (detail.ttsModelId) {
|
||||||
await loadVoiceOptions(detail.ttsModelId)
|
await fetchAllLanguag(detail.ttsModelId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等待模型选项加载完成后再更新显示名称
|
// 等待模型选项加载完成后再更新显示名称
|
||||||
@@ -242,7 +294,10 @@ function updateDisplayNames() {
|
|||||||
displayNames.value.tts = getModelDisplayName('TTS', formData.value.ttsModelId)
|
displayNames.value.tts = getModelDisplayName('TTS', formData.value.ttsModelId)
|
||||||
|
|
||||||
// 角色音色特殊处理
|
// 角色音色特殊处理
|
||||||
displayNames.value.voiceprint = getVoiceDisplayName(formData.value.ttsVoiceId || '')
|
displayNames.value.report = reportOptions.find(item => item.value === formData.value.chatHistoryConf)?.name
|
||||||
|
displayNames.value.language = formData.value.ttsLanguage
|
||||||
|
|
||||||
|
isVisibleReport.value = formData.value.memModelId !== 'Memory_nomem'
|
||||||
|
|
||||||
console.log('最终音色显示名称:', displayNames.value.voiceprint)
|
console.log('最终音色显示名称:', displayNames.value.voiceprint)
|
||||||
}
|
}
|
||||||
@@ -278,23 +333,110 @@ async function loadModelOptions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载TTS音色选项
|
// 根据语言筛选音色
|
||||||
async function loadVoiceOptions(ttsModelId?: string) {
|
function filterVoicesByLanguage() {
|
||||||
if (!ttsModelId)
|
if (!voiceDetails.value || Object.keys(voiceDetails.value).length === 0) {
|
||||||
return
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log(`加载音色选项: ${ttsModelId}`)
|
|
||||||
const voices = await getTTSVoices(ttsModelId)
|
|
||||||
voiceOptions.value = voices
|
|
||||||
console.log('音色选项:', voices)
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('加载音色选项失败:', error)
|
|
||||||
voiceOptions.value = []
|
voiceOptions.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const allVoices = Object.values(voiceDetails.value) as any[]
|
||||||
|
|
||||||
|
// 根据选中的语言筛选音色
|
||||||
|
const filteredVoices = allVoices.filter((voice) => {
|
||||||
|
if (!voice.languages) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const languagesArray = voice.languages.split(/[、;;,,]/).map(lang => lang.trim()).filter(lang => lang)
|
||||||
|
return languagesArray.includes(formData.value.language)
|
||||||
|
})
|
||||||
|
|
||||||
|
voiceOptions.value = filteredVoices.map(voice => ({
|
||||||
|
value: voice.id,
|
||||||
|
name: voice.name,
|
||||||
|
voiceDemo: voice.voiceDemo,
|
||||||
|
voice_demo: voice.voice_demo,
|
||||||
|
isClone: Boolean(voice.isClone),
|
||||||
|
train_status: voice.trainStatus,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 检查当前选中的音色是否支持当前语言,如果不支持则选择第一个
|
||||||
|
const currentVoiceSupportsLanguage = formData.value.ttsVoiceId
|
||||||
|
&& filteredVoices.some(voice => voice.id === formData.value.ttsVoiceId)
|
||||||
|
|
||||||
|
if (!currentVoiceSupportsLanguage) {
|
||||||
|
formData.value.ttsVoiceId = filteredVoices.length > 0 ? filteredVoices[0].id : ''
|
||||||
|
displayNames.value.voiceprint = filteredVoices.length > 0 ? filteredVoices[0].name : ''
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
displayNames.value.voiceprint = filteredVoices.find(item => item.id === formData.value.ttsVoiceId)?.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步到ttsSettings(如果值为null,使用0作为显示默认值,但不修改form中的值)
|
||||||
|
ttsSettings.value = {
|
||||||
|
volume: formData.value.ttsVolume !== null && formData.value.ttsVolume !== undefined ? formData.value.ttsVolume : 0,
|
||||||
|
speed: formData.value.ttsRate !== null && formData.value.ttsRate !== undefined ? formData.value.ttsRate : 0,
|
||||||
|
pitch: formData.value.ttsPitch !== null && formData.value.ttsPitch !== undefined ? formData.value.ttsPitch : 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 根据语音合成模型加载语言
|
||||||
|
async function fetchAllLanguag(ttsModelId: string) {
|
||||||
|
try {
|
||||||
|
const res = await getAllLanguage(ttsModelId, '') as any[]
|
||||||
|
// 保存完整的音色信息
|
||||||
|
voiceDetails.value = res.reduce((acc, voice) => {
|
||||||
|
acc[voice.id] = voice
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
// 提取所有语言选项并去重
|
||||||
|
const allLanguages = new Set()
|
||||||
|
res.forEach((voice) => {
|
||||||
|
if (voice.languages) {
|
||||||
|
const languagesArray = voice.languages.split(/[、;;,,]/).map(lang => lang.trim()).filter(lang => lang)
|
||||||
|
languagesArray.forEach(lang => allLanguages.add(lang))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
languageOptions.value = Array.from(allLanguages).map(lang => ({
|
||||||
|
value: lang,
|
||||||
|
name: lang,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 使用后端返回的用户选择的语言,如果没有则使用第一个语言选项
|
||||||
|
if (formData.value.ttsLanguage && languageOptions.value.some(option => option.value === formData.value.ttsLanguage)) {
|
||||||
|
formData.value.language = formData.value.ttsLanguage
|
||||||
|
displayNames.value.language = formData.value.ttsLanguage
|
||||||
|
}
|
||||||
|
else if (languageOptions.value.length > 0) {
|
||||||
|
formData.value.language = languageOptions.value[0].value
|
||||||
|
displayNames.value.language = languageOptions.value[0].value
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据选中的语言筛选音色
|
||||||
|
filterVoicesByLanguage()
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
languageOptions.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载TTS音色选项
|
||||||
|
// async function loadVoiceOptions(ttsModelId?: string) {
|
||||||
|
// if (!ttsModelId)
|
||||||
|
// return
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// console.log(`加载音色选项: ${ttsModelId}`)
|
||||||
|
// const voices = await getTTSVoices(ttsModelId)
|
||||||
|
// voiceOptions.value = voices
|
||||||
|
// console.log('音色选项:', voices)
|
||||||
|
// }
|
||||||
|
// catch (error) {
|
||||||
|
// console.error('加载音色选项失败:', error)
|
||||||
|
// voiceOptions.value = []
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// 选择角色模板
|
// 选择角色模板
|
||||||
function selectRoleTemplate(templateId: string) {
|
function selectRoleTemplate(templateId: string) {
|
||||||
if (selectedTemplateId.value === templateId) {
|
if (selectedTemplateId.value === templateId) {
|
||||||
@@ -348,20 +490,26 @@ async function onPickerConfirm(type: string, value: any, name: string) {
|
|||||||
break
|
break
|
||||||
case 'memory':
|
case 'memory':
|
||||||
formData.value.memModelId = value
|
formData.value.memModelId = value
|
||||||
|
formData.value.chatHistoryConf = value === 'Memory_nomem' ? 0 : 2
|
||||||
displayNames.value.memory = name // 确保显示名称正确更新
|
displayNames.value.memory = name // 确保显示名称正确更新
|
||||||
|
displayNames.value.report = reportOptions[1].name
|
||||||
|
isVisibleReport.value = value !== 'Memory_nomem'
|
||||||
break
|
break
|
||||||
case 'tts':
|
case 'tts':
|
||||||
formData.value.ttsModelId = value
|
formData.value.ttsModelId = value
|
||||||
// 当选择TTS模型时,自动加载对应的音色选项
|
await fetchAllLanguag(value)
|
||||||
await loadVoiceOptions(value)
|
break
|
||||||
// 重置音色选择
|
case 'language':
|
||||||
formData.value.ttsVoiceId = ''
|
formData.value.language = value
|
||||||
displayNames.value.voiceprint = '请选择'
|
filterVoicesByLanguage()
|
||||||
break
|
break
|
||||||
case 'voiceprint':
|
case 'voiceprint':
|
||||||
formData.value.ttsVoiceId = value
|
formData.value.ttsVoiceId = value
|
||||||
displayNames.value.voiceprint = name // 确保显示名称正确更新
|
displayNames.value.voiceprint = name // 确保显示名称正确更新
|
||||||
break
|
break
|
||||||
|
case 'report':
|
||||||
|
formData.value.chatHistoryConf = value
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
pickerShow.value[type] = false
|
pickerShow.value[type] = false
|
||||||
@@ -413,12 +561,14 @@ async function saveAgent() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
saving.value = true
|
saving.value = true
|
||||||
// 构建保存数据,包含上下文配置
|
// 构建保存数据,包含上下文配置和语音设置
|
||||||
const saveData = {
|
const saveData = {
|
||||||
...formData.value,
|
...formData.value,
|
||||||
|
ttsLanguage: formData.value.language,
|
||||||
contextProviders: currentContextProviders.value,
|
contextProviders: currentContextProviders.value,
|
||||||
}
|
}
|
||||||
await updateAgent(agentId.value, saveData)
|
await updateAgent(agentId.value, saveData)
|
||||||
|
loadAgentDetail()
|
||||||
|
|
||||||
toast.success(t('agent.saveSuccess'))
|
toast.success(t('agent.saveSuccess'))
|
||||||
}
|
}
|
||||||
@@ -545,7 +695,7 @@ onMounted(async () => {
|
|||||||
<wd-tag v-for="tag in dynamicTags" :key="tag.id" class="items-center border !flex !border-[rgba(51,108,255,0.2)] !bg-[rgba(51,108,255,0.1)] !text-[#336cff]" round closable @close="handleCloseTag(tag.id)">
|
<wd-tag v-for="tag in dynamicTags" :key="tag.id" class="items-center border !flex !border-[rgba(51,108,255,0.2)] !bg-[rgba(51,108,255,0.1)] !text-[#336cff]" round closable @close="handleCloseTag(tag.id)">
|
||||||
{{ tag.tagName }}
|
{{ tag.tagName }}
|
||||||
</wd-tag>
|
</wd-tag>
|
||||||
<wd-button class="!bg-[rgba(51,108,255,0.1)] !text-[#336cff]" v-if="!inputVisible" size="small" icon="add" @click="showInput">
|
<wd-button v-if="!inputVisible" class="!bg-[rgba(51,108,255,0.1)] !text-[#336cff]" size="small" icon="add" @click="showInput">
|
||||||
{{ t('agent.addAgentTag') }}
|
{{ t('agent.addAgentTag') }}
|
||||||
</wd-button>
|
</wd-button>
|
||||||
</view>
|
</view>
|
||||||
@@ -672,6 +822,16 @@ onMounted(async () => {
|
|||||||
</text>
|
</text>
|
||||||
<wd-icon name="arrow-right" custom-class="text-[20rpx] text-[#9d9ea3]" />
|
<wd-icon name="arrow-right" custom-class="text-[20rpx] text-[#9d9ea3]" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view v-show="isVisibleReport" class="flex cursor-pointer items-center justify-between border border-[#eeeeee] rounded-[12rpx] bg-[#f5f7fb] p-[20rpx] transition-all duration-300 active:bg-[#eef3ff]" @click="openPicker('report')">
|
||||||
|
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||||
|
{{ t('agent.reportMode') }}
|
||||||
|
</text>
|
||||||
|
<text class="mx-[16rpx] flex-1 text-right text-[26rpx] text-[#65686f]">
|
||||||
|
{{ displayNames.report }}
|
||||||
|
</text>
|
||||||
|
<wd-icon name="arrow-right" custom-class="text-[20rpx] text-[#9d9ea3]" />
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -695,6 +855,16 @@ onMounted(async () => {
|
|||||||
<wd-icon name="arrow-right" custom-class="text-[20rpx] text-[#9d9ea3]" />
|
<wd-icon name="arrow-right" custom-class="text-[20rpx] text-[#9d9ea3]" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view class="flex cursor-pointer items-center justify-between border border-[#eeeeee] rounded-[12rpx] bg-[#f5f7fb] p-[20rpx] transition-all duration-300 active:bg-[#eef3ff]" @click="openPicker('language')">
|
||||||
|
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||||
|
{{ t('agent.language') }}
|
||||||
|
</text>
|
||||||
|
<text class="mx-[16rpx] flex-1 text-right text-[26rpx] text-[#65686f]">
|
||||||
|
{{ displayNames.language }}
|
||||||
|
</text>
|
||||||
|
<wd-icon name="arrow-right" custom-class="text-[20rpx] text-[#9d9ea3]" />
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="flex cursor-pointer items-center justify-between border border-[#eeeeee] rounded-[12rpx] bg-[#f5f7fb] p-[20rpx] transition-all duration-300 active:bg-[#eef3ff]" @click="openPicker('voiceprint')">
|
<view class="flex cursor-pointer items-center justify-between border border-[#eeeeee] rounded-[12rpx] bg-[#f5f7fb] p-[20rpx] transition-all duration-300 active:bg-[#eef3ff]" @click="openPicker('voiceprint')">
|
||||||
<text class="text-[28rpx] text-[#232338] font-medium">
|
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||||
{{ t('agent.voiceprint') }}
|
{{ t('agent.voiceprint') }}
|
||||||
@@ -705,6 +875,15 @@ onMounted(async () => {
|
|||||||
<wd-icon name="arrow-right" custom-class="text-[20rpx] text-[#9d9ea3]" />
|
<wd-icon name="arrow-right" custom-class="text-[20rpx] text-[#9d9ea3]" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view class="flex items-center justify-between border border-[#eeeeee] rounded-[12rpx] bg-[#f5f7fb] p-[20rpx]">
|
||||||
|
<view class="text-[28rpx] text-[#232338] font-medium">
|
||||||
|
{{ t('agent.languageConfig') }}
|
||||||
|
</view>
|
||||||
|
<view class="cursor-pointer rounded-[20rpx] bg-[rgba(51,108,255,0.1)] px-[24rpx] py-[12rpx] text-[24rpx] text-[#336cff] transition-all duration-300 active:bg-[#336cff] active:text-white" @click="handleRegulate">
|
||||||
|
<text>{{ t('agent.editFunctions') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="flex items-center justify-between border border-[#eeeeee] rounded-[12rpx] bg-[#f5f7fb] p-[20rpx]">
|
<view class="flex items-center justify-between border border-[#eeeeee] rounded-[12rpx] bg-[#f5f7fb] p-[20rpx]">
|
||||||
<view class="text-[28rpx] text-[#232338] font-medium">
|
<view class="text-[28rpx] text-[#232338] font-medium">
|
||||||
{{ t('agent.plugins') }}
|
{{ t('agent.plugins') }}
|
||||||
@@ -799,15 +978,32 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<wd-action-sheet
|
<wd-action-sheet
|
||||||
v-model="pickerShow.voiceprint"
|
v-model="pickerShow.voiceprint"
|
||||||
:actions="voiceOptions && voiceOptions.map(item => ({ name: item.name, value: item.id }))"
|
:actions="voiceOptions"
|
||||||
@close="onPickerCancel('voiceprint')"
|
@close="onPickerCancel('voiceprint')"
|
||||||
@select="({ item }) => onPickerConfirm('voiceprint', item.value, item.name)"
|
@select="({ item }) => onPickerConfirm('voiceprint', item.value, item.name)"
|
||||||
/>
|
/>
|
||||||
|
<wd-action-sheet
|
||||||
|
v-model="pickerShow.language"
|
||||||
|
:actions="languageOptions"
|
||||||
|
@close="onPickerCancel('language')"
|
||||||
|
@select="({ item }) => onPickerConfirm('language', item.value, item.name)"
|
||||||
|
/>
|
||||||
|
<wd-action-sheet
|
||||||
|
v-model="pickerShow.report"
|
||||||
|
:actions="reportOptions"
|
||||||
|
@close="onPickerCancel('report')"
|
||||||
|
@select="({ item }) => onPickerConfirm('report', item.value, item.name)"
|
||||||
|
/>
|
||||||
<ContextProviderDialog
|
<ContextProviderDialog
|
||||||
v-model:visible="showContextProviderDialog"
|
v-model:visible="showContextProviderDialog"
|
||||||
:providers="currentContextProviders"
|
:providers="currentContextProviders"
|
||||||
@confirm="handleUpdateContext"
|
@confirm="handleUpdateContext"
|
||||||
/>
|
/>
|
||||||
|
<VoiceSettingsDialog
|
||||||
|
v-model:visible="showVoiceSettingsDialog"
|
||||||
|
:settings="ttsSettings"
|
||||||
|
@confirm="handleUpdateVoiceSettings"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ onMounted(() => {
|
|||||||
{{ t('home.lastConversation') }}{{ formatTime(agent.lastConnectedAt) }}
|
{{ t('home.lastConversation') }}{{ formatTime(agent.lastConnectedAt) }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<text v-if="agent.tags" class="flex-1 truncate text-[22rpx] text-[#666]">
|
<text v-if="agent.tags" class="flex-1 truncate text-right text-[22rpx] text-[#666]">
|
||||||
{{ agent.tags.map(tag => tag.tagName).join(',') }}
|
{{ agent.tags.map(tag => tag.tagName).join(',') }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
Reference in New Issue
Block a user