mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
feat: 应用支持语音样式优化
This commit is contained in:
parent
22d08252c8
commit
df1fd3f89e
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue