feat: 应用支持语音样式优化

This commit is contained in:
wangdan-fit2cloud 2024-09-13 15:47:05 +08:00
parent 22d08252c8
commit df1fd3f89e
7 changed files with 397 additions and 313 deletions

View File

@ -5,6 +5,15 @@
</el-text>
</div>
<div>
<!-- 语音播放 -->
<span v-if="tts">
<el-tooltip effect="dark" content="语音播放" placement="top">
<el-button text @click="playAnswerText(data?.answer_text)">
<AppIcon iconName="app-video-play"></AppIcon>
</el-button>
</el-tooltip>
<el-divider direction="vertical" />
</span>
<el-tooltip effect="dark" content="复制" placement="top">
<el-button text @click="copyClick(data?.answer_text)">
<AppIcon iconName="app-copy"></AppIcon>
@ -38,6 +47,8 @@
</el-button>
<EditContentDialog ref="EditContentDialogRef" @refresh="refreshContent" />
<EditMarkDialog ref="EditMarkDialogRef" @refresh="refreshMark" />
<!-- 先渲染不然不能播放 -->
<audio ref="audioPlayer" controls hidden="hidden"></audio>
</div>
</template>
<script setup lang="ts">
@ -46,6 +57,7 @@ import { copyClick } from '@/utils/clipboard'
import EditContentDialog from '@/views/log/component/EditContentDialog.vue'
import EditMarkDialog from '@/views/log/component/EditMarkDialog.vue'
import { datetimeFormat } from '@/utils/time'
import applicationApi from '@/api/application'
const props = defineProps({
data: {
@ -56,15 +68,18 @@ const props = defineProps({
type: String,
default: ''
},
log: Boolean
tts: Boolean
})
const emit = defineEmits(['update:data'])
const audioPlayer = ref<HTMLAudioElement | null>(null)
const EditContentDialogRef = ref()
const EditMarkDialogRef = ref()
const buttonData = ref(props.data)
const loading = ref(false)
function editContent(data: any) {
EditContentDialogRef.value.open(data)
@ -74,6 +89,44 @@ function editMark(data: any) {
EditMarkDialogRef.value.open(data)
}
const playAnswerText = (text: string) => {
if (props.data.tts_type === 'BROWSER') {
// SpeechSynthesisUtterance
const utterance = new SpeechSynthesisUtterance(text)
//
window.speechSynthesis.speak(utterance)
}
if (props.data.tts_type === 'TTS') {
applicationApi
.postTextToSpeech(props.data.id as string, { text: text }, loading)
.then((res: any) => {
// MP3
// Blob
const blob = new Blob([res], { type: 'audio/mp3' })
// URL
const url = URL.createObjectURL(blob)
// blob
// const link = document.createElement('a')
// link.href = window.URL.createObjectURL(blob)
// link.download = "abc.mp3"
// link.click()
// audioPlayer DOM
if (audioPlayer.value instanceof HTMLAudioElement) {
audioPlayer.value.src = url
audioPlayer.value.play() //
} else {
console.error('audioPlayer.value is not an instance of HTMLAudioElement')
}
})
.catch((err) => {
console.log('err: ', err)
})
}
}
function refreshMark() {
buttonData.value.improve_paragraph_id_list = []
emit('update:data', buttonData.value)

View File

@ -5,9 +5,18 @@
</el-text>
</div>
<div>
<!-- 语音播放 -->
<span v-if="tts">
<el-tooltip effect="dark" content="语音播放" placement="top">
<el-button text :disabled="!data?.write_ed" @click="playAnswerText(data?.answer_text)">
<AppIcon iconName="VideoPlay"></AppIcon>
</el-button>
</el-tooltip>
<el-divider direction="vertical" />
</span>
<el-tooltip effect="dark" content="换个答案" placement="top">
<el-button :disabled="chat_loading" text @click="regeneration">
<AppIcon iconName="VideoPlay"></AppIcon>
<el-icon><RefreshRight /></el-icon>
</el-button>
</el-tooltip>
<el-divider direction="vertical" />
@ -59,6 +68,8 @@
</el-button>
</el-tooltip>
</div>
<!-- 先渲染不然不能播放 -->
<audio ref="audioPlayer" controls hidden="hidden"></audio>
</template>
<script setup lang="ts">
import { ref } from 'vue'
@ -81,11 +92,13 @@ const props = defineProps({
chat_loading: {
type: Boolean
},
log: Boolean
log: Boolean,
tts: Boolean
})
const emit = defineEmits(['update:data', 'regeneration'])
const audioPlayer = ref<HTMLAudioElement | null>(null)
const buttonData = ref(props.data)
const loading = ref(false)
@ -101,5 +114,43 @@ function voteHandle(val: string) {
emit('update:data', buttonData.value)
})
}
const playAnswerText = (text: string) => {
if (props.data.tts_type === 'BROWSER') {
// SpeechSynthesisUtterance
const utterance = new SpeechSynthesisUtterance(text)
//
window.speechSynthesis.speak(utterance)
}
if (props.data.tts_type === 'TTS') {
applicationApi
.postTextToSpeech(props.data.id as string, { text: text }, loading)
.then((res: any) => {
// MP3
// Blob
const blob = new Blob([res], { type: 'audio/mp3' })
// URL
const url = URL.createObjectURL(blob)
// blob
// const link = document.createElement('a')
// link.href = window.URL.createObjectURL(blob)
// link.download = "abc.mp3"
// link.click()
// audioPlayer DOM
if (audioPlayer.value instanceof HTMLAudioElement) {
audioPlayer.value.src = url
audioPlayer.value.play() //
} else {
console.error('audioPlayer.value is not an instance of HTMLAudioElement')
}
})
.catch((err) => {
console.log('err: ', err)
})
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -108,7 +108,11 @@
</div>
</el-card>
<div class="flex-between mt-8" v-if="log">
<LogOperationButton v-model:data="chatList[index]" :applicationId="appId" />
<LogOperationButton
v-model:data="chatList[index]"
:applicationId="appId"
:tts="props.data.tts_model_enable"
/>
</div>
<div class="flex-between mt-8" v-else>
@ -127,6 +131,7 @@
</div>
<div v-if="item.write_ed && props.appId && 500 != item.status" class="flex-between">
<OperationButton
:tts="props.data.tts_model_enable"
:data="item"
:applicationId="appId"
:chatId="chartOpenId"
@ -134,14 +139,6 @@
@regeneration="regenerationChart(item)"
/>
</div>
<!-- 语音播放 -->
<div style="float: right" v-if="props.data.tts_model_enable">
<el-button :disabled="!item.write_ed" @click="playAnswerText(item.answer_text)">
<el-icon>
<VideoPlay />
</el-icon>
</el-button>
</div>
</div>
</div>
</template>
@ -159,19 +156,22 @@
:maxlength="100000"
@keydown.enter="sendChatHandle($event)"
/>
<div class="operate" v-if="props.data.stt_model_enable">
<el-button v-if="mediaRecorderStatus" @click="startRecording">
<el-icon>
<Microphone />
</el-icon>
</el-button>
<el-button v-else @click="stopRecording">
<el-icon>
<VideoPause />
</el-icon>
</el-button>
</div>
<div class="operate">
<div class="operate flex align-center">
<span v-if="props.data.stt_model_enable">
<el-button text v-if="mediaRecorderStatus" @click="startRecording">
<el-icon>
<Microphone />
</el-icon>
</el-button>
<el-button text v-else @click="stopRecording">
<el-icon>
<VideoPause />
</el-icon>
</el-button>
<el-divider direction="vertical" />
</span>
<el-button
text
class="sent-button"
@ -180,17 +180,10 @@
>
<img v-show="isDisabledChart || loading" src="@/assets/icon_send.svg" alt="" />
<SendIcon v-show="!isDisabledChart && !loading" />
<!-- <img
src="@/assets/icon_send_colorful.svg"
alt=""
/> -->
</el-button>
</div>
</div>
</div>
<!-- 先渲染不然不能播放 -->
<audio ref="audioPlayer" controls hidden="hidden"></audio>
</div>
</template>
<script setup lang="ts">
@ -318,50 +311,54 @@ function handleInputFieldList() {
?.filter((v: any) => v.id === 'base-node')
.map((v: any) => {
inputFieldList.value = v.properties.input_field_list
? v.properties.input_field_list.filter((v: any) => v.assignment_method === 'user_input').map((v: any) => {
switch (v.type) {
case 'input':
return {
field: v.variable,
input_type: 'TextInput',
label: v.name,
required: v.is_required
}
case 'select':
return {
field: v.variable,
input_type: 'SingleSelect',
label: v.name,
required: v.is_required,
option_list: v.optionList.map((o: any) => {
return { key: o, value: o }
})
}
case 'date':
return {
field: v.variable,
input_type: 'DatePicker',
label: v.name,
required: v.is_required,
attrs: {
format: 'YYYY-MM-DD HH:mm:ss',
'value-format': 'YYYY-MM-DD HH:mm:ss',
type: 'datetime'
? v.properties.input_field_list
.filter((v: any) => v.assignment_method === 'user_input')
.map((v: any) => {
switch (v.type) {
case 'input':
return {
field: v.variable,
input_type: 'TextInput',
label: v.name,
required: v.is_required
}
}
default:
break
}
})
case 'select':
return {
field: v.variable,
input_type: 'SingleSelect',
label: v.name,
required: v.is_required,
option_list: v.optionList.map((o: any) => {
return { key: o, value: o }
})
}
case 'date':
return {
field: v.variable,
input_type: 'DatePicker',
label: v.name,
required: v.is_required,
attrs: {
format: 'YYYY-MM-DD HH:mm:ss',
'value-format': 'YYYY-MM-DD HH:mm:ss',
type: 'datetime'
}
}
default:
break
}
})
: []
apiInputFieldList.value = v.properties.input_field_list
? v.properties.input_field_list.filter((v: any) => v.assignment_method === 'api_input').map((v: any) => {
return {
field: v.variable,
label: v.name,
required: v.is_required
}
})
? v.properties.input_field_list
.filter((v: any) => v.assignment_method === 'api_input')
.map((v: any) => {
return {
field: v.variable,
label: v.name,
required: v.is_required
}
})
: []
})
}
@ -720,7 +717,7 @@ const handleScroll = () => {
//
const mediaRecorder = ref<any>(null)
const audioPlayer = ref<HTMLAudioElement | null>(null)
const mediaRecorderStatus = ref(true)
//
@ -782,43 +779,6 @@ const uploadRecording = async (audioBlob: Blob) => {
}
}
const playAnswerText = (text: string) => {
if (props.data.tts_type === 'BROWSER') {
// SpeechSynthesisUtterance
const utterance = new SpeechSynthesisUtterance(text)
//
window.speechSynthesis.speak(utterance)
}
if (props.data.tts_type === 'TTS') {
applicationApi.postTextToSpeech(props.data.id as string, { 'text': text }, loading)
.then((res: any) => {
// MP3
// Blob
const blob = new Blob([res], { type: 'audio/mp3' })
// URL
const url = URL.createObjectURL(blob)
// blob
// const link = document.createElement('a')
// link.href = window.URL.createObjectURL(blob)
// link.download = "abc.mp3"
// link.click()
// audioPlayer DOM
if (audioPlayer.value instanceof HTMLAudioElement) {
audioPlayer.value.src = url
audioPlayer.value.play() //
} else {
console.error('audioPlayer.value is not an instance of HTMLAudioElement')
}
})
.catch((err) => {
console.log('err: ', err)
})
}
}
onMounted(() => {
handleInputFieldList()
})
@ -946,10 +906,12 @@ defineExpose({
.operate {
padding: 6px 10px;
.el-icon {
font-size: 20px;
}
.sent-button {
max-height: none;
.el-icon {
font-size: 24px;
}

View File

@ -1147,5 +1147,26 @@ export const iconMap: any = {
)
])
}
},
'app-video-play': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 16 16',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M7.66667 3.68233V12.3177L4.66667 10.01V5.99L7.66667 3.68233ZM7.89333 2C7.74633 2 7.60333 2.04867 7.487 2.13833L3.59367 5.13333C3.42933 5.25933 3.33333 5.45467 3.33333 5.66167V10.3383C3.33333 10.5453 3.42933 10.7407 3.59367 10.8667L7.487 13.8617C7.60333 13.9513 7.74633 14 7.89333 14H8.33333C8.70167 14 9 13.7017 9 13.3333V2.66667C9 2.29833 8.70167 2 8.33333 2H7.89333ZM1 5.66667C1 5.48267 1.14933 5.33333 1.33333 5.33333H2C2.184 5.33333 2.33333 5.48267 2.33333 5.66667V10.3333C2.33333 10.5173 2.184 10.6667 2 10.6667H1.33333C1.14933 10.6667 1 10.5173 1 10.3333V5.66667ZM13.2973 12.1873C13.1727 12.3153 12.968 12.3107 12.8417 12.184L12.3723 11.715C12.2373 11.58 12.244 11.36 12.3757 11.222C13.1757 10.3843 13.6667 9.24967 13.6667 8C13.6667 6.75467 13.179 5.62333 12.384 4.78667C12.253 4.64833 12.2467 4.42933 12.3813 4.29467L12.8507 3.82533C12.9773 3.69867 13.182 3.694 13.307 3.82267C14.355 4.903 15 6.376 15 8C15 9.62867 14.351 11.106 13.2973 12.1873ZM11.4043 10.3087C11.2833 10.4347 11.084 10.4263 10.9603 10.303L10.4987 9.84099C10.3573 9.69966 10.3737 9.46733 10.5053 9.31666C10.8133 8.96499 11 8.50433 11 7.99999C11 7.49966 10.816 7.04199 10.5123 6.69133C10.382 6.54066 10.3663 6.30933 10.507 6.16866L10.9693 5.70666C11.0933 5.58266 11.2933 5.57466 11.4143 5.70166C11.9837 6.29966 12.3333 7.10899 12.3333 7.99999C12.3333 8.89599 11.9797 9.70966 11.4043 10.3087Z',
fill: 'currentColor'
})
]
)
])
}
}
}

View File

@ -289,17 +289,19 @@
<el-switch size="small" v-model="applicationForm.problem_optimization"></el-switch>
</el-form-item>
<el-form-item>
<template #label>
<div class="flex align-center">
<span class="mr-4">语音输入</span>
<el-tooltip
effect="dark"
content="开启后,需要设定语音转文本模型,语音输入完成后会转化为文字直接发送提问"
placement="right"
>
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
</el-tooltip>
<el-switch v-model="applicationForm.stt_model_enable"/>
<template #label>
<div class="flex-between">
<div class="flex align-center">
<span class="mr-4">语音输入</span>
<el-tooltip
effect="dark"
content="开启后,需要设定语音转文本模型,语音输入完成后会转化为文字直接发送提问"
placement="right"
>
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
</el-tooltip>
</div>
<el-switch size="small" v-model="applicationForm.stt_model_enable" />
</div>
</template>
<el-select
@ -363,15 +365,15 @@
</el-select>
</el-form-item>
<el-form-item>
<template #label>
<div class="flex align-center">
<template #label>
<div class="flex-between">
<span class="mr-4">语音播放</span>
<el-switch v-model="applicationForm.tts_model_enable"/>
<el-switch size="small" v-model="applicationForm.tts_model_enable" />
</div>
</template>
<el-radio-group v-model="applicationForm.tts_type">
<el-radio label="浏览器播放(免费)" value="BROWSER"/>
<el-radio label="TTS模型" value="TTS"/>
<el-radio label="BROWSER">浏览器播放(免费)</el-radio>
<el-radio label="TTS">TTS模型</el-radio>
</el-radio-group>
<el-select
v-if="applicationForm.tts_type === 'TTS'"

View File

@ -34,9 +34,9 @@
</el-form-item>
<el-form-item label="输入类型">
<el-select v-model="form.type">
<el-option label="文本框" value="input"/>
<el-option label="日期" value="date"/>
<el-option label="下拉选项" value="select"/>
<el-option label="文本框" value="input" />
<el-option label="日期" value="date" />
<el-option label="下拉选项" value="select" />
</el-select>
</el-form-item>
<el-form-item v-if="form.type === 'select'">
@ -49,10 +49,18 @@
</div>
</template>
<template #default>
<div class="w-full flex-between" :key="option" v-for="(option, $index) in form.optionList">
<input class="el-textarea__inner" v-model.lazy="form.optionList[$index]" placeholder="请输入选项值"/>
<el-button link type="primary" @click="delOption($index)">
<el-icon class="mr-4"><Remove /></el-icon>
<div
class="w-full flex-between"
:key="option"
v-for="(option, $index) in form.optionList"
>
<input
class="el-textarea__inner"
v-model.lazy="form.optionList[$index]"
placeholder="请输入选项值"
/>
<el-button link class="ml-8" @click="delOption($index)">
<el-icon><Delete /></el-icon>
</el-button>
</div>
</template>
@ -62,11 +70,10 @@
</el-form-item>
<el-form-item label="赋值方式">
<el-radio-group v-model="form.assignment_method">
<el-radio label="user_input">用户输入</el-radio>
<el-radio label="api_input">接口传参</el-radio>
<el-radio value="user_input">用户输入</el-radio>
<el-radio value="api_input">接口传参</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
@ -153,7 +160,6 @@ const delOption = (index: number) => {
form.value.optionList.splice(index, 1)
}
defineExpose({ open, close })
</script>
<style lang="scss" scoped></style>

View File

@ -50,164 +50,16 @@
<el-button text type="info" @click="openDialog">
<AppIcon iconName="app-magnify" style="font-size: 16px"></AppIcon>
</el-button>
</template
>
</template>
</MdEditor>
</el-form-item>
<el-form-item>
<template #label>
<div class="flex align-center">
<span class="mr-4">语音输入</span>
<el-tooltip
effect="dark"
content="开启后,需要设定语音转文本模型,语音输入完成后会转化为文字直接发送提问"
placement="right"
>
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
</el-tooltip>
<el-switch v-model="form_data.stt_model_enable" />
</div>
</template>
<el-select
v-model="form_data.stt_model_id"
class="w-full"
popper-class="select-model"
>
<el-option-group
v-for="(value, label) in sttModelOptions"
:key="value"
:label="relatedObject(providerOptions, label, 'provider')?.name"
>
<el-option
v-for="item in value.filter((v: any) => v.status === 'SUCCESS')"
:key="item.id"
:label="item.name"
:value="item.id"
class="flex-between"
>
<div class="flex align-center">
<span
v-html="relatedObject(providerOptions, label, 'provider')?.icon"
class="model-icon mr-8"
></span>
<span>{{ item.name }}</span>
<el-tag
v-if="item.permission_type === 'PUBLIC'"
type="info"
class="info-tag ml-8"
>公用
</el-tag>
</div>
<el-icon class="check-icon" v-if="item.id === form_data.stt_model_id">
<Check />
</el-icon>
</el-option>
<!-- 不可用 -->
<el-option
v-for="item in value.filter((v: any) => v.status !== 'SUCCESS')"
:key="item.id"
:label="item.name"
:value="item.id"
class="flex-between"
disabled
>
<div class="flex">
<span
v-html="relatedObject(providerOptions, label, 'provider')?.icon"
class="model-icon mr-8"
></span>
<span>{{ item.name }}</span>
<span class="danger">{{
$t('views.application.applicationForm.form.aiModel.unavailable')
}}</span>
</div>
<el-icon class="check-icon" v-if="item.id === form_data.stt_model_id">
<Check />
</el-icon>
</el-option>
</el-option-group>
</el-select>
</el-form-item>
<el-form-item>
<template #label>
<div class="flex align-center">
<span class="mr-4">语音播放</span>
<el-switch v-model="form_data.tts_model_enable" />
</div>
</template>
<el-radio-group v-model="form_data.tts_type">
<el-radio label="浏览器播放(免费)" value="BROWSER"/>
<el-radio label="TTS模型" value="TTS"/>
</el-radio-group>
<el-select
v-if="form_data.tts_type === 'TTS'"
v-model="form_data.tts_model_id"
class="w-full"
popper-class="select-model"
>
<el-option-group
v-for="(value, label) in ttsModelOptions"
:key="value"
:label="relatedObject(providerOptions, label, 'provider')?.name"
>
<el-option
v-for="item in value.filter((v: any) => v.status === 'SUCCESS')"
:key="item.id"
:label="item.name"
:value="item.id"
class="flex-between"
>
<div class="flex align-center">
<span
v-html="relatedObject(providerOptions, label, 'provider')?.icon"
class="model-icon mr-8"
></span>
<span>{{ item.name }}</span>
<el-tag
v-if="item.permission_type === 'PUBLIC'"
type="info"
class="info-tag ml-8"
>公用
</el-tag>
</div>
<el-icon class="check-icon" v-if="item.id === form_data.tts_model_id">
<Check />
</el-icon>
</el-option>
<!-- 不可用 -->
<el-option
v-for="item in value.filter((v: any) => v.status !== 'SUCCESS')"
:key="item.id"
:label="item.name"
:value="item.id"
class="flex-between"
disabled
>
<div class="flex">
<span
v-html="relatedObject(providerOptions, label, 'provider')?.icon"
class="model-icon mr-8"
></span>
<span>{{ item.name }}</span>
<span class="danger">{{
$t('views.application.applicationForm.form.aiModel.unavailable')
}}</span>
</div>
<el-icon class="check-icon" v-if="item.id === form_data.tts_model_id">
<Check />
</el-icon>
</el-option>
</el-option-group>
</el-select>
</el-form-item>
</el-form>
<div class="flex-between">
全局变量
<el-button link type="primary" @click="openAddDialog()">
<el-icon class="mr-4"><Plus /></el-icon>
</el-button>
</div>
<el-table :data="props.nodeModel.properties.input_field_list" class="mb-16">
<div class="flex-between mb-8">
<h5 class="lighter">输入变量</h5>
<el-button link type="primary" @click="openAddDialog()">
<el-icon class="mr-4"><Plus /></el-icon>
</el-button>
</div>
<el-table :data="props.nodeModel.properties.input_field_list" class="mb-16">
<el-table-column prop="name" label="变量名" />
<el-table-column prop="variable" label="变量" />
<el-table-column label="输入类型">
@ -248,6 +100,152 @@
</template>
</el-table-column>
</el-table>
<el-form-item>
<template #label>
<div class="flex-between">
<div class="flex align-center">
<span class="mr-4">语音输入</span>
<el-tooltip
effect="dark"
content="开启后,需要设定语音转文本模型,语音输入完成后会转化为文字直接发送提问"
placement="right"
>
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
</el-tooltip>
</div>
<el-switch size="small" v-model="form_data.stt_model_enable" />
</div>
</template>
<el-select
v-model="form_data.stt_model_id"
class="w-full"
popper-class="select-model"
placeholder="请输入"
>
<el-option-group
v-for="(value, label) in sttModelOptions"
:key="value"
:label="relatedObject(providerOptions, label, 'provider')?.name"
>
<el-option
v-for="item in value.filter((v: any) => v.status === 'SUCCESS')"
:key="item.id"
:label="item.name"
:value="item.id"
class="flex-between"
>
<div class="flex align-center">
<span
v-html="relatedObject(providerOptions, label, 'provider')?.icon"
class="model-icon mr-8"
></span>
<span>{{ item.name }}</span>
<el-tag v-if="item.permission_type === 'PUBLIC'" type="info" class="info-tag ml-8"
>公用
</el-tag>
</div>
<el-icon class="check-icon" v-if="item.id === form_data.stt_model_id">
<Check />
</el-icon>
</el-option>
<!-- 不可用 -->
<el-option
v-for="item in value.filter((v: any) => v.status !== 'SUCCESS')"
:key="item.id"
:label="item.name"
:value="item.id"
class="flex-between"
disabled
>
<div class="flex">
<span
v-html="relatedObject(providerOptions, label, 'provider')?.icon"
class="model-icon mr-8"
></span>
<span>{{ item.name }}</span>
<span class="danger">{{
$t('views.application.applicationForm.form.aiModel.unavailable')
}}</span>
</div>
<el-icon class="check-icon" v-if="item.id === form_data.stt_model_id">
<Check />
</el-icon>
</el-option>
</el-option-group>
</el-select>
</el-form-item>
<el-form-item>
<template #label>
<div class="flex-between">
<span class="mr-4">语音播放</span>
<el-switch size="small" v-model="form_data.tts_model_enable" />
</div>
</template>
<el-radio-group v-model="form_data.tts_type">
<el-radio value="BROWSER">浏览器播放(免费)</el-radio>
<el-radio value="TTS">TTS模型</el-radio>
</el-radio-group>
<el-select
v-if="form_data.tts_type === 'TTS'"
v-model="form_data.tts_model_id"
class="w-full"
popper-class="select-model"
placeholder="请输入"
>
<el-option-group
v-for="(value, label) in ttsModelOptions"
:key="value"
:label="relatedObject(providerOptions, label, 'provider')?.name"
>
<el-option
v-for="item in value.filter((v: any) => v.status === 'SUCCESS')"
:key="item.id"
:label="item.name"
:value="item.id"
class="flex-between"
>
<div class="flex align-center">
<span
v-html="relatedObject(providerOptions, label, 'provider')?.icon"
class="model-icon mr-8"
></span>
<span>{{ item.name }}</span>
<el-tag v-if="item.permission_type === 'PUBLIC'" type="info" class="info-tag ml-8"
>公用
</el-tag>
</div>
<el-icon class="check-icon" v-if="item.id === form_data.tts_model_id">
<Check />
</el-icon>
</el-option>
<!-- 不可用 -->
<el-option
v-for="item in value.filter((v: any) => v.status !== 'SUCCESS')"
:key="item.id"
:label="item.name"
:value="item.id"
class="flex-between"
disabled
>
<div class="flex">
<span
v-html="relatedObject(providerOptions, label, 'provider')?.icon"
class="model-icon mr-8"
></span>
<span>{{ item.name }}</span>
<span class="danger">{{
$t('views.application.applicationForm.form.aiModel.unavailable')
}}</span>
</div>
<el-icon class="check-icon" v-if="item.id === form_data.tts_model_id">
<Check />
</el-icon>
</el-option>
</el-option-group>
</el-select>
</el-form-item>
</el-form>
<!-- 回复内容弹出层 -->
<el-dialog v-model="dialogVisible" title="开场白" append-to-body>
<MdEditor v-model="cloneContent" :preview="false" :toolbars="[]" :footers="[]"></MdEditor>
@ -335,28 +333,21 @@ const validate = () => {
}
function getProvider() {
model
.asyncGetProvider()
.then((res: any) => {
providerOptions.value = res?.data
})
model.asyncGetProvider().then((res: any) => {
providerOptions.value = res?.data
})
}
function getSTTModel() {
applicationApi
.getApplicationSTTModel(id)
.then((res: any) => {
sttModelOptions.value = groupBy(res?.data, 'provider')
})
applicationApi.getApplicationSTTModel(id).then((res: any) => {
sttModelOptions.value = groupBy(res?.data, 'provider')
})
}
function getTTSModel() {
applicationApi
.getApplicationTTSModel(id)
.then((res: any) => {
ttsModelOptions.value = groupBy(res?.data, 'provider')
})
applicationApi.getApplicationTTSModel(id).then((res: any) => {
ttsModelOptions.value = groupBy(res?.data, 'provider')
})
}
const currentIndex = ref(null)
@ -391,7 +382,6 @@ function refreshFieldList(data: any) {
FieldFormDialogRef.value.close()
}
onMounted(() => {
set(props.nodeModel, 'validate', validate)
if (props.nodeModel.properties.input_field_list) {
@ -403,7 +393,6 @@ onMounted(() => {
getProvider()
getTTSModel()
getSTTModel()
})
</script>
<style lang="scss" scoped>