mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2026-01-01 02:52:48 +00:00
feat: 支持用户输入变量
--story=1016155 --user=刘瑞斌 【应用编排】-支持设置用户输入变量 https://www.tapd.cn/57709429/s/1576480
This commit is contained in:
parent
ba023d20f0
commit
d48b51c3e0
|
|
@ -166,10 +166,10 @@ class Flow:
|
|||
|
||||
class WorkflowManage:
|
||||
def __init__(self, flow: Flow, params, work_flow_post_handler: WorkFlowPostHandler,
|
||||
base_to_response: BaseToResponse = SystemToResponse()):
|
||||
base_to_response: BaseToResponse = SystemToResponse(), form_data = {}):
|
||||
self.params = params
|
||||
self.flow = flow
|
||||
self.context = {}
|
||||
self.context = form_data
|
||||
self.node_context = []
|
||||
self.work_flow_post_handler = work_flow_post_handler
|
||||
self.current_node = None
|
||||
|
|
|
|||
|
|
@ -694,6 +694,7 @@ class ApplicationSerializer(serializers.Serializer):
|
|||
'tts_model_id': application.tts_model_id,
|
||||
'stt_model_enable': application.stt_model_enable,
|
||||
'tts_model_enable': application.tts_model_enable,
|
||||
'work_flow': application.work_flow,
|
||||
'show_source': application_access_token.show_source})
|
||||
|
||||
@transaction.atomic
|
||||
|
|
@ -855,10 +856,15 @@ class ApplicationSerializer(serializers.Serializer):
|
|||
nodes = instance.get('work_flow')['nodes']
|
||||
for node in nodes:
|
||||
if node['id'] == 'base-node':
|
||||
instance['stt_model_id'] = node['properties']['node_data']['stt_model_id']
|
||||
instance['tts_model_id'] = node['properties']['node_data']['tts_model_id']
|
||||
instance['stt_model_enable'] = node['properties']['node_data']['stt_model_enable']
|
||||
instance['tts_model_enable'] = node['properties']['node_data']['tts_model_enable']
|
||||
node_data = node['properties']['node_data']
|
||||
if 'stt_model_id' in node_data:
|
||||
instance['stt_model_id'] = node_data['stt_model_id']
|
||||
if 'tts_model_id' in node_data:
|
||||
instance['tts_model_id'] = node_data['tts_model_id']
|
||||
if 'stt_model_enable' in node_data:
|
||||
instance['stt_model_enable'] = node_data['stt_model_enable']
|
||||
if 'tts_model_enable' in node_data:
|
||||
instance['tts_model_enable'] = node_data['tts_model_enable']
|
||||
break
|
||||
|
||||
def speech_to_text(self, file, with_valid=True):
|
||||
|
|
|
|||
|
|
@ -208,6 +208,7 @@ class ChatMessageSerializer(serializers.Serializer):
|
|||
application_id = serializers.UUIDField(required=False, allow_null=True, error_messages=ErrMessage.uuid("应用id"))
|
||||
client_id = serializers.CharField(required=True, error_messages=ErrMessage.char("客户端id"))
|
||||
client_type = serializers.CharField(required=True, error_messages=ErrMessage.char("客户端类型"))
|
||||
form_data = serializers.DictField(required=False, error_messages=ErrMessage.char("全局变量"))
|
||||
|
||||
def is_valid_application_workflow(self, *, raise_exception=False):
|
||||
self.is_valid_intraday_access_num()
|
||||
|
|
@ -284,6 +285,7 @@ class ChatMessageSerializer(serializers.Serializer):
|
|||
stream = self.data.get('stream')
|
||||
client_id = self.data.get('client_id')
|
||||
client_type = self.data.get('client_type')
|
||||
form_data = self.data.get('form_data')
|
||||
user_id = chat_info.application.user_id
|
||||
work_flow_manage = WorkflowManage(Flow.new_instance(chat_info.work_flow_version.work_flow),
|
||||
{'history_chat_record': chat_info.chat_record_list, 'question': message,
|
||||
|
|
@ -291,7 +293,7 @@ class ChatMessageSerializer(serializers.Serializer):
|
|||
'stream': stream,
|
||||
're_chat': re_chat,
|
||||
'user_id': user_id}, WorkFlowPostHandler(chat_info, client_id, client_type),
|
||||
base_to_response)
|
||||
base_to_response, form_data)
|
||||
r = work_flow_manage.run()
|
||||
return r
|
||||
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ class ChatView(APIView):
|
|||
'application_id': (request.auth.keywords.get(
|
||||
'application_id') if request.auth.client_type == AuthenticationType.APPLICATION_ACCESS_TOKEN.value else None),
|
||||
'client_id': request.auth.client_id,
|
||||
'form_data': (request.data.get('form_data') if 'form_data' in request.data else []),
|
||||
'client_type': request.auth.client_type}).chat()
|
||||
|
||||
@action(methods=['GET'], detail=False)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@
|
|||
class="problem-button ellipsis-2 mb-8"
|
||||
:class="log ? 'disabled' : 'cursor'"
|
||||
>
|
||||
<el-icon><EditPen /></el-icon>
|
||||
<el-icon>
|
||||
<EditPen />
|
||||
</el-icon>
|
||||
{{ item.str }}
|
||||
</div>
|
||||
<MdPreview
|
||||
|
|
@ -33,6 +35,24 @@
|
|||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="inputFieldList.length > 0">
|
||||
<div class="avatar">
|
||||
<img v-if="data.avatar" :src="data.avatar" height="30px" />
|
||||
<LogoIcon v-else height="30px" />
|
||||
</div>
|
||||
<div class="content">
|
||||
<el-card shadow="always" class="dialog-card">
|
||||
<DynamicsForm
|
||||
v-model="form_data"
|
||||
:model="form_data"
|
||||
label-position="left"
|
||||
require-asterisk-position="right"
|
||||
:render_data="inputFieldList"
|
||||
ref="dynamicsFormRef"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
<template v-for="(item, index) in chatList" :key="index">
|
||||
<!-- 问题 -->
|
||||
<div class="item-content mb-16 lighter">
|
||||
|
|
@ -98,10 +118,12 @@
|
|||
v-if="item.is_stop && !item.write_ed"
|
||||
@click="startChat(item)"
|
||||
link
|
||||
>继续</el-button
|
||||
>继续
|
||||
</el-button
|
||||
>
|
||||
<el-button type="primary" v-else-if="!item.write_ed" @click="stopChat(item)" link
|
||||
>停止回答</el-button
|
||||
>停止回答
|
||||
</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -116,7 +138,9 @@
|
|||
</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-icon>
|
||||
<VideoPlay />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -141,13 +165,17 @@
|
|||
v-if="mediaRecorderStatus"
|
||||
@click="startRecording"
|
||||
>
|
||||
<el-icon><Microphone /></el-icon>
|
||||
<el-icon>
|
||||
<Microphone />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
@click="stopRecording"
|
||||
>
|
||||
<el-icon><VideoPause /></el-icon>
|
||||
<el-icon>
|
||||
<VideoPause />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="operate">
|
||||
|
|
@ -189,6 +217,9 @@ import { debounce } from 'lodash'
|
|||
import Recorder from 'recorder-core'
|
||||
import 'recorder-core/src/engine/mp3'
|
||||
import 'recorder-core/src/engine/mp3-engine'
|
||||
import { MsgWarning } from '@/utils/message'
|
||||
import DynamicsForm from '@/components/dynamics-form/index.vue'
|
||||
import type { FormField } from '@/components/dynamics-form/type'
|
||||
|
||||
defineOptions({ name: 'AiChat' })
|
||||
const route = useRoute()
|
||||
|
|
@ -199,7 +230,8 @@ const {
|
|||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
default: () => {
|
||||
}
|
||||
},
|
||||
appId: String, // 仅分享链接有
|
||||
log: Boolean,
|
||||
|
|
@ -234,6 +266,8 @@ const loading = ref(false)
|
|||
const inputValue = ref('')
|
||||
const chartOpenId = ref('')
|
||||
const chatList = ref<any[]>([])
|
||||
const inputFieldList = ref<FormField[]>([])
|
||||
const form_data = ref<any>({})
|
||||
|
||||
const isDisabledChart = computed(
|
||||
() => !(inputValue.value.trim() && (props.appId || props.data?.name))
|
||||
|
|
@ -248,15 +282,15 @@ const prologueList = computed(() => {
|
|||
.reduce((pre_array: Array<any>, current: string, index: number) => {
|
||||
const currentObj = isMdArray(current)
|
||||
? {
|
||||
type: 'question',
|
||||
str: current.replace(/^-\s+/, ''),
|
||||
index: index
|
||||
}
|
||||
type: 'question',
|
||||
str: current.replace(/^-\s+/, ''),
|
||||
index: index
|
||||
}
|
||||
: {
|
||||
type: 'md',
|
||||
str: current,
|
||||
index: index
|
||||
}
|
||||
type: 'md',
|
||||
str: current,
|
||||
index: index
|
||||
}
|
||||
if (pre_array.length > 0) {
|
||||
const pre = pre_array[pre_array.length - 1]
|
||||
if (!isMdArray(current) && pre.type == 'md') {
|
||||
|
|
@ -286,10 +320,48 @@ watch(
|
|||
{ deep: true }
|
||||
)
|
||||
|
||||
function handleInputFieldList() {
|
||||
props.data.work_flow?.nodes
|
||||
.filter((v: any) => v.id === 'base-node')
|
||||
.map((v: any) => {
|
||||
inputFieldList.value = v.properties.input_field_list.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'
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
chartOpenId.value = ''
|
||||
handleInputFieldList()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
|
@ -327,6 +399,13 @@ const handleDebounceClick = debounce((val) => {
|
|||
}, 200)
|
||||
|
||||
function sendChatHandle(event: any) {
|
||||
// 检查inputFieldList是否有未填写的字段
|
||||
for (let i = 0; i < inputFieldList.value.length; i++) {
|
||||
if (inputFieldList.value[i].required && !form_data.value[inputFieldList.value[i].field]) {
|
||||
MsgWarning('请填写所有必填字段')
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!event.ctrlKey) {
|
||||
// 如果没有按下组合键ctrl,则会阻止默认事件
|
||||
event.preventDefault()
|
||||
|
|
@ -340,12 +419,14 @@ function sendChatHandle(event: any) {
|
|||
inputValue.value += '\n'
|
||||
}
|
||||
}
|
||||
|
||||
const stopChat = (chat: chatType) => {
|
||||
ChatManagement.stop(chat.id)
|
||||
}
|
||||
const startChat = (chat: chatType) => {
|
||||
ChatManagement.write(chat.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话
|
||||
*/
|
||||
|
|
@ -398,6 +479,7 @@ function getChartOpenId(chat?: any) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个递归函数,处理流式数据
|
||||
* @param chat 每一条对话记录
|
||||
|
|
@ -483,6 +565,7 @@ const errorWrite = (chat: any, message?: string) => {
|
|||
ChatManagement.updateStatus(chat.id, 500)
|
||||
ChatManagement.close(chat.id)
|
||||
}
|
||||
|
||||
function chatMessage(chat?: any, problem?: string, re_chat?: boolean) {
|
||||
loading.value = true
|
||||
if (!chat) {
|
||||
|
|
@ -513,7 +596,8 @@ function chatMessage(chat?: any, problem?: string, re_chat?: boolean) {
|
|||
} else {
|
||||
const obj = {
|
||||
message: chat.problem_text,
|
||||
re_chat: re_chat || false
|
||||
re_chat: re_chat || false,
|
||||
form_data: form_data.value
|
||||
}
|
||||
// 对话
|
||||
applicationApi
|
||||
|
|
@ -618,8 +702,8 @@ const handleScroll = () => {
|
|||
}
|
||||
|
||||
// 定义响应式引用
|
||||
const mediaRecorder= ref<any>(null)
|
||||
const audioPlayer= ref<HTMLAudioElement | null>(null)
|
||||
const mediaRecorder = ref<any>(null)
|
||||
const audioPlayer = ref<HTMLAudioElement | null>(null)
|
||||
const mediaRecorderStatus = ref(true)
|
||||
|
||||
|
||||
|
|
@ -630,11 +714,11 @@ const startRecording = async () => {
|
|||
mediaRecorder.value = new Recorder({
|
||||
type: 'mp3',
|
||||
bitRate: 128,
|
||||
sampleRate: 44100,
|
||||
sampleRate: 44100
|
||||
})
|
||||
|
||||
mediaRecorder.value.open(() => {
|
||||
mediaRecorder.value.start()
|
||||
mediaRecorder.value.start()
|
||||
}, (err: any) => {
|
||||
console.error(err)
|
||||
})
|
||||
|
|
@ -648,13 +732,13 @@ const stopRecording = () => {
|
|||
if (mediaRecorder.value) {
|
||||
mediaRecorderStatus.value = true
|
||||
mediaRecorder.value.stop((blob: Blob, duration: number) => {
|
||||
// 测试blob是否能正常播放
|
||||
// const link = document.createElement('a')
|
||||
// link.href = window.URL.createObjectURL(blob)
|
||||
// link.download = 'abc.mp3'
|
||||
// link.click()
|
||||
// 测试blob是否能正常播放
|
||||
// const link = document.createElement('a')
|
||||
// link.href = window.URL.createObjectURL(blob)
|
||||
// link.download = 'abc.mp3'
|
||||
// link.click()
|
||||
|
||||
uploadRecording(blob) // 上传录音文件
|
||||
uploadRecording(blob) // 上传录音文件
|
||||
}, (err: any) => {
|
||||
console.error('录音失败:', err)
|
||||
})
|
||||
|
|
@ -666,12 +750,12 @@ const uploadRecording = async (audioBlob: Blob) => {
|
|||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('file', audioBlob, 'recording.mp3')
|
||||
applicationApi.postSpeechToText(props.data.id as string, formData, loading)
|
||||
.then((response) => {
|
||||
console.log('上传成功:', response.data)
|
||||
inputValue.value = response.data
|
||||
// chatMessage(null, res.data)
|
||||
})
|
||||
applicationApi.postSpeechToText(props.data.id as string, formData, loading)
|
||||
.then((response) => {
|
||||
console.log('上传成功:', response.data)
|
||||
inputValue.value = response.data
|
||||
// chatMessage(null, res.data)
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error)
|
||||
|
|
@ -697,10 +781,10 @@ const playAnswerText = (text: string) => {
|
|||
|
||||
// 检查 audioPlayer 是否已经引用了 DOM 元素
|
||||
if (audioPlayer.value instanceof HTMLAudioElement) {
|
||||
audioPlayer.value.src = url;
|
||||
audioPlayer.value.play(); // 自动播放音频
|
||||
audioPlayer.value.src = url
|
||||
audioPlayer.value.play() // 自动播放音频
|
||||
} else {
|
||||
console.error("audioPlayer.value is not an instance of HTMLAudioElement");
|
||||
console.error('audioPlayer.value is not an instance of HTMLAudioElement')
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
|
|
@ -708,6 +792,10 @@ const playAnswerText = (text: string) => {
|
|||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
handleInputFieldList()
|
||||
})
|
||||
|
||||
function setScrollBottom() {
|
||||
// 将滚动条滚动到最下面
|
||||
scrollDiv.value.setScrollTop(getMaxHeight())
|
||||
|
|
@ -751,15 +839,19 @@ defineExpose({
|
|||
.avatar {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-left: var(--padding-left);
|
||||
|
||||
:deep(ol) {
|
||||
margin-left: 16px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.problem-button {
|
||||
width: 100%;
|
||||
border: none;
|
||||
|
|
@ -772,25 +864,30 @@ defineExpose({
|
|||
color: var(--el-text-color-regular);
|
||||
-webkit-line-clamp: 1;
|
||||
word-break: break-all;
|
||||
|
||||
&:hover {
|
||||
background: var(--el-color-primary-light-9);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
&:hover {
|
||||
background: var(--app-layout-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-icon) {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__operate {
|
||||
background: #f3f7f9;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
z-index: 10;
|
||||
|
||||
&:before {
|
||||
background: linear-gradient(0deg, #f3f7f9 0%, rgba(243, 247, 249, 0) 100%);
|
||||
content: '';
|
||||
|
|
@ -800,6 +897,7 @@ defineExpose({
|
|||
left: 0;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.operate-textarea {
|
||||
box-shadow: 0px 6px 24px 0px rgba(31, 35, 41, 0.08);
|
||||
background-color: #ffffff;
|
||||
|
|
@ -818,16 +916,21 @@ defineExpose({
|
|||
padding: 12px 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.operate {
|
||||
padding: 6px 10px;
|
||||
|
||||
.sent-button {
|
||||
max-height: none;
|
||||
|
||||
.el-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-loading-spinner) {
|
||||
margin-top: -15px;
|
||||
|
||||
.circular {
|
||||
width: 31px;
|
||||
height: 31px;
|
||||
|
|
@ -836,11 +939,13 @@ defineExpose({
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-card {
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-width {
|
||||
max-width: var(--app-chat-width, 860px);
|
||||
margin: 0 auto;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<el-date-picker v-bind="$attrs" />
|
||||
</template>
|
||||
<script setup lang="ts"></script>
|
||||
<style lang="scss"></style>
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="isEdit ? '编辑变量' : '添加变量'"
|
||||
v-model="dialogVisible"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:destroy-on-close="true"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
label-position="top"
|
||||
ref="fieldFormRef"
|
||||
:rules="rules"
|
||||
:model="form"
|
||||
require-asterisk-position="right"
|
||||
>
|
||||
<el-form-item label="变量名" prop="name">
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
placeholder="请输入变量名"
|
||||
maxlength="64"
|
||||
show-word-limit
|
||||
@blur="form.name = form.name.trim()"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="变量" prop="variable">
|
||||
<el-input
|
||||
v-model="form.variable"
|
||||
placeholder="请输入变量"
|
||||
maxlength="64"
|
||||
show-word-limit
|
||||
@blur="form.variable = form.variable.trim()"
|
||||
/>
|
||||
</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-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.type === 'select'">
|
||||
<template #label>
|
||||
<div class="flex-between">
|
||||
选项值
|
||||
<el-button link type="primary" @click="addOption()">
|
||||
<el-icon class="mr-4"><Plus /></el-icon> 添加
|
||||
</el-button>
|
||||
</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> 删除
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否必填" @click.prevent>
|
||||
<el-switch size="small" v-model="form.is_required"></el-switch>
|
||||
</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-group>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> 取消 </el-button>
|
||||
<el-button type="primary" @click="submit(fieldFormRef)" :loading="loading">
|
||||
{{ isEdit ? '保存' : '添加' }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { cloneDeep, debounce } from 'lodash'
|
||||
import { MsgError } from '@/utils/message'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const fieldFormRef = ref()
|
||||
const loading = ref<boolean>(false)
|
||||
const isEdit = ref(false)
|
||||
|
||||
const form = ref<any>({
|
||||
name: '',
|
||||
variable: '',
|
||||
type: 'input',
|
||||
is_required: true,
|
||||
assignment_method: 'user_input',
|
||||
optionList: []
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: '请输入变量名', trigger: 'blur' }],
|
||||
variable: [{ required: true, message: '请输入变量', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
form.value = {
|
||||
name: '',
|
||||
variable: '',
|
||||
type: 'input',
|
||||
is_required: true,
|
||||
assignment_method: 'user_input',
|
||||
optionList: []
|
||||
}
|
||||
isEdit.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const open = (row: any) => {
|
||||
if (row) {
|
||||
form.value = cloneDeep(row)
|
||||
isEdit.value = true
|
||||
}
|
||||
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
if (form.value.type === 'select' && form.value.optionList.length === 0) {
|
||||
return MsgError('请添加选项值')
|
||||
}
|
||||
await formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
emit('refresh', form.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const addOption = () => {
|
||||
form.value.optionList.push('')
|
||||
}
|
||||
|
||||
const delOption = (index: number) => {
|
||||
form.value.optionList.splice(index, 1)
|
||||
}
|
||||
|
||||
|
||||
defineExpose({ open, close })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -196,6 +196,53 @@
|
|||
</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">
|
||||
<el-table-column prop="name" label="变量名" />
|
||||
<el-table-column prop="variable" label="变量" />
|
||||
<el-table-column label="输入类型">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="info" class="info-tag" v-if="row.type === 'input'">文本框</el-tag>
|
||||
<el-tag type="info" class="info-tag" v-if="row.type === 'date'">日期</el-tag>
|
||||
<el-tag type="info" class="info-tag" v-if="row.type === 'select'">下拉选项</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="必填">
|
||||
<template #default="{ row }">
|
||||
<div @click.stop>
|
||||
<el-switch size="small" v-model="row.is_required" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="source" label="赋值方式">
|
||||
<template #default="{ row }">
|
||||
{{ row.source === 'user_input' ? '用户输入' : '接口传参' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="left" width="80">
|
||||
<template #default="{ row, $index }">
|
||||
<span class="mr-4">
|
||||
<el-tooltip effect="dark" content="修改" placement="top">
|
||||
<el-button type="primary" text @click.stop="openAddDialog(row, $index)">
|
||||
<el-icon><EditPen /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-tooltip effect="dark" content="删除" placement="top">
|
||||
<el-button type="primary" text @click="deleteField($index)">
|
||||
<el-icon>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 回复内容弹出层 -->
|
||||
<el-dialog v-model="dialogVisible" title="开场白" append-to-body>
|
||||
<MdEditor v-model="cloneContent" :preview="false" :toolbars="[]" :footers="[]"></MdEditor>
|
||||
|
|
@ -206,6 +253,7 @@
|
|||
</template>
|
||||
</el-dialog>
|
||||
</NodeContainer>
|
||||
<FieldFormDialog ref="FieldFormDialogRef" @refresh="refreshFieldList" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { app } from '@/main'
|
||||
|
|
@ -218,6 +266,8 @@ import { relatedObject } from '@/utils/utils'
|
|||
import useStore from '@/stores'
|
||||
import applicationApi from '@/api/application'
|
||||
import type { Provider } from '@/api/type/model'
|
||||
import FieldFormDialog from './component/FieldFormDialog.vue'
|
||||
import { MsgError } from '@/utils/message'
|
||||
const { model } = useStore()
|
||||
|
||||
const {
|
||||
|
|
@ -304,9 +354,47 @@ function getTTSModel() {
|
|||
})
|
||||
}
|
||||
|
||||
const currentIndex = ref(null)
|
||||
const FieldFormDialogRef = ref()
|
||||
const inputFieldList = ref<any[]>([])
|
||||
|
||||
function openAddDialog(data?: any, index?: any) {
|
||||
if (typeof index !== 'undefined') {
|
||||
currentIndex.value = index
|
||||
}
|
||||
|
||||
FieldFormDialogRef.value.open(data)
|
||||
}
|
||||
|
||||
function deleteField(index: any) {
|
||||
inputFieldList.value.splice(index, 1)
|
||||
}
|
||||
|
||||
function refreshFieldList(data: any) {
|
||||
for (let i = 0; i < inputFieldList.value.length; i++) {
|
||||
if (inputFieldList.value[i].variable === data.variable && currentIndex.value !== i) {
|
||||
MsgError('变量已存在: ' + data.variable)
|
||||
return
|
||||
}
|
||||
}
|
||||
if (currentIndex.value !== null) {
|
||||
inputFieldList.value.splice(currentIndex.value, 1, data)
|
||||
} else {
|
||||
inputFieldList.value.push(data)
|
||||
}
|
||||
currentIndex.value = null
|
||||
FieldFormDialogRef.value.close()
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
set(props.nodeModel, 'validate', validate)
|
||||
if (props.nodeModel.properties.input_field_list) {
|
||||
props.nodeModel.properties.input_field_list.forEach((item: any) => {
|
||||
inputFieldList.value.push(item)
|
||||
})
|
||||
}
|
||||
set(props.nodeModel.properties, 'input_field_list', inputFieldList)
|
||||
getProvider()
|
||||
getTTSModel()
|
||||
getSTTModel()
|
||||
|
|
|
|||
|
|
@ -13,6 +13,18 @@
|
|||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div v-for="(item, index) in inputFieldList" :key="index"
|
||||
class="flex-between border-r-4 p-8-12 mb-8 layout-bg lighter"
|
||||
@mouseenter="showicon = true"
|
||||
@mouseleave="showicon = false"
|
||||
>
|
||||
<span>{{ item.name }} {{ '{' + item.variable + '}' }}</span>
|
||||
<el-tooltip effect="dark" content="复制参数" placement="top" v-if="showicon === true">
|
||||
<el-button link @click="copyClick('{{' + '全局变量.' + item.variable + '}}')" style="padding: 0">
|
||||
<AppIcon iconName="app-copy"></AppIcon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</NodeContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
|
@ -27,8 +39,23 @@ const globeLabel = '{{全局变量.time}}'
|
|||
|
||||
const showicon = ref(false)
|
||||
|
||||
// onMounted(() => {
|
||||
// set(props.nodeModel, 'validate', validate)
|
||||
// })
|
||||
const inputFieldList = ref<any[]>([])
|
||||
|
||||
onMounted(() => {
|
||||
props.nodeModel.graphModel.nodes
|
||||
.filter((v: any) => v.id === 'base-node')
|
||||
.map((v: any) => {
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
props.nodeModel.properties.config.globalFields = [
|
||||
{
|
||||
label: '当前时间',
|
||||
value: 'time'
|
||||
}, ...v.properties.input_field_list.map((i: any) => {
|
||||
return { label: i.name, value: i.variable }
|
||||
})
|
||||
]
|
||||
inputFieldList.value = v.properties.input_field_list
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue