feat: 右侧点击展示菜单 (#1171)

This commit is contained in:
shaohuzhang1 2024-09-14 14:51:49 +08:00 committed by GitHub
parent 2ea9e02b66
commit 1bc9f9f2b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 420 additions and 238 deletions

View File

@ -4,23 +4,28 @@
"id": "base-node",
"type": "base-node",
"x": 440,
"y": 3350,
"y": 3040,
"properties": {
"config": {},
"height": 517,
"config": {
},
"height": 825.6,
"stepName": "基本信息",
"node_data": {
"desc": "",
"name": "",
"name": "maxkbapplication",
"prologue": "您好,我是 MaxKB 小助手,您可以向我提出 MaxKB 使用问题。\n- MaxKB 主要功能有什么?\n- MaxKB 支持哪些大语言模型?\n- MaxKB 支持哪些文档类型?"
}
},
"input_field_list": [
]
}
},
{
"id": "start-node",
"type": "start-node",
"x": 440,
"y": 3710,
"x": 430,
"y": 3660,
"properties": {
"config": {
"fields": [
@ -31,8 +36,8 @@
],
"globalFields": [
{
"value": "time",
"label": "当前时间"
"label": "当前时间",
"value": "time"
}
]
},
@ -42,7 +47,7 @@
"value": "question"
}
],
"height": 268.533,
"height": 276,
"stepName": "开始",
"globalFields": [
{
@ -55,8 +60,8 @@
{
"id": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"type": "search-dataset-node",
"x": 830,
"y": 3470,
"x": 840,
"y": 3210,
"properties": {
"config": {
"fields": [
@ -78,10 +83,12 @@
}
]
},
"height": 754.8,
"height": 794,
"stepName": "知识库检索",
"node_data": {
"dataset_id_list": [],
"dataset_id_list": [
],
"dataset_setting": {
"top_n": 3,
"similarity": 0.6,
@ -91,6 +98,9 @@
"question_reference_address": [
"start-node",
"question"
],
"source_dataset_id_list": [
]
}
}
@ -98,8 +108,8 @@
{
"id": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"type": "condition-node",
"x": 1380,
"y": 3470,
"x": 1490,
"y": 3210,
"properties": {
"width": 600,
"config": {
@ -110,7 +120,7 @@
}
]
},
"height": 524.6669999999999,
"height": 543.675,
"stepName": "判断器",
"node_data": {
"branch": [
@ -148,24 +158,26 @@
"id": "161",
"type": "ELSE",
"condition": "and",
"conditions": []
"conditions": [
]
}
]
},
"branch_condition_list": [
{
"index": 0,
"height": 116.133,
"height": 121.225,
"id": "1009"
},
{
"index": 1,
"height": 116.133,
"height": 121.225,
"id": "4908"
},
{
"index": 2,
"height": 40,
"height": 44,
"id": "161"
}
]
@ -174,8 +186,8 @@
{
"id": "4ffe1086-25df-4c85-b168-979b5bbf0a26",
"type": "reply-node",
"x": 2090,
"y": 2820,
"x": 2170,
"y": 2480,
"properties": {
"config": {
"fields": [
@ -185,7 +197,7 @@
}
]
},
"height": 312.267,
"height": 378,
"stepName": "指定回复",
"node_data": {
"fields": [
@ -193,15 +205,16 @@
"directly_return"
],
"content": "",
"reply_type": "referencing"
"reply_type": "referencing",
"is_result": true
}
}
},
{
"id": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb",
"type": "ai-chat-node",
"x": 2090,
"y": 3460,
"x": 2160,
"y": 3200,
"properties": {
"config": {
"fields": [
@ -211,21 +224,22 @@
}
]
},
"height": 681.4,
"height": 763,
"stepName": "AI 对话",
"node_data": {
"prompt": "已知信息:\n{{知识库检索.data}}\n问题\n{{开始.question}}",
"system": "",
"model_id": "",
"dialogue_number": 0
"dialogue_number": 0,
"is_result": true
}
}
},
{
"id": "309d0eef-c597-46b5-8d51-b9a28aaef4c7",
"type": "ai-chat-node",
"x": 2090,
"y": 4180,
"x": 2160,
"y": 3970,
"properties": {
"config": {
"fields": [
@ -235,13 +249,14 @@
}
]
},
"height": 681.4,
"height": 763,
"stepName": "AI 对话1",
"node_data": {
"prompt": "{{开始.question}}",
"system": "",
"model_id": "",
"dialogue_number": 0
"dialogue_number": 0,
"is_result": true
}
}
}
@ -253,30 +268,32 @@
"sourceNodeId": "start-node",
"targetNodeId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"startPoint": {
"x": 600,
"y": 3710
"x": 590,
"y": 3660
},
"endPoint": {
"x": 670,
"y": 3470
"x": 680,
"y": 3210
},
"properties": {
},
"properties": {},
"pointsList": [
{
"x": 600,
"y": 3710
"x": 590,
"y": 3660
},
{
"x": 710,
"y": 3710
"x": 700,
"y": 3660
},
{
"x": 560,
"y": 3470
"x": 570,
"y": 3210
},
{
"x": 670,
"y": 3470
"x": 680,
"y": 3210
}
],
"sourceAnchorId": "start-node_right",
@ -288,30 +305,32 @@
"sourceNodeId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"targetNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"startPoint": {
"x": 990,
"y": 3470
"x": 1000,
"y": 3210
},
"endPoint": {
"x": 1090,
"y": 3470
"x": 1200,
"y": 3210
},
"properties": {
},
"properties": {},
"pointsList": [
{
"x": 990,
"y": 3470
"x": 1000,
"y": 3210
},
{
"x": 1100,
"y": 3470
},
{
"x": 980,
"y": 3470
"x": 1110,
"y": 3210
},
{
"x": 1090,
"y": 3470
"y": 3210
},
{
"x": 1200,
"y": 3210
}
],
"sourceAnchorId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5_right",
@ -323,30 +342,32 @@
"sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"targetNodeId": "4ffe1086-25df-4c85-b168-979b5bbf0a26",
"startPoint": {
"x": 1670,
"y": 3340.733
"x": 1780,
"y": 3073.775
},
"endPoint": {
"x": 1930,
"y": 2820
"x": 2010,
"y": 2480
},
"properties": {
},
"properties": {},
"pointsList": [
{
"x": 1670,
"y": 3340.733
},
{
"x": 1780,
"y": 3340.733
"y": 3073.775
},
{
"x": 1820,
"y": 2820
"x": 1890,
"y": 3073.775
},
{
"x": 1930,
"y": 2820
"x": 1900,
"y": 2480
},
{
"x": 2010,
"y": 2480
}
],
"sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_1009_right",
@ -358,30 +379,32 @@
"sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"targetNodeId": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb",
"startPoint": {
"x": 1670,
"y": 3464.866
"x": 1780,
"y": 3203
},
"endPoint": {
"x": 1930,
"y": 3460
"x": 2000,
"y": 3200
},
"properties": {
},
"properties": {},
"pointsList": [
{
"x": 1670,
"y": 3464.866
},
{
"x": 1780,
"y": 3464.866
"y": 3203
},
{
"x": 1820,
"y": 3460
"x": 1890,
"y": 3203
},
{
"x": 1930,
"y": 3460
"x": 1890,
"y": 3200
},
{
"x": 2000,
"y": 3200
}
],
"sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_4908_right",
@ -393,30 +416,32 @@
"sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"targetNodeId": "309d0eef-c597-46b5-8d51-b9a28aaef4c7",
"startPoint": {
"x": 1670,
"y": 3550.9325000000003
"x": 1780,
"y": 3293.6124999999997
},
"endPoint": {
"x": 1930,
"y": 4180
"x": 2000,
"y": 3970
},
"properties": {
},
"properties": {},
"pointsList": [
{
"x": 1670,
"y": 3550.9325000000003
},
{
"x": 1780,
"y": 3550.9325000000003
"y": 3293.6124999999997
},
{
"x": 1820,
"y": 4180
"x": 1890,
"y": 3293.6124999999997
},
{
"x": 1930,
"y": 4180
"x": 1890,
"y": 3970
},
{
"x": 2000,
"y": 3970
}
],
"sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_161_right",

View File

@ -0,0 +1,149 @@
<template>
<div v-show="show" class="workflow-dropdown-menu border border-r-4">
<el-tabs v-model="activeName" class="workflow-dropdown-tabs">
<el-tab-pane label="基础组件" name="base">
<template v-for="(item, index) in menuNodes" :key="index">
<div
class="workflow-dropdown-item cursor flex p-8-12"
@click.stop="clickNodes(item)"
@mousedown.stop="onmousedown(item)"
>
<component :is="iconComponent(`${item.type}-icon`)" class="mr-8 mt-4" :size="32" />
<div class="pre-wrap">
<div class="lighter">{{ item.label }}</div>
<el-text type="info" size="small">{{ item.text }}</el-text>
</div>
</div>
</template>
</el-tab-pane>
<el-tab-pane label="函数库" name="function">
<el-scrollbar max-height="300">
<div
class="workflow-dropdown-item cursor flex p-8-12"
@click.stop="clickNodes(functionNode)"
@mousedown.stop="onmousedown(functionNode)"
>
<component :is="iconComponent(`function-lib-node-icon`)" class="mr-8 mt-4" :size="32" />
<div class="pre-wrap">
<div class="lighter">{{ functionNode.label }}</div>
<el-text type="info" size="small">{{ functionNode.text }}</el-text>
</div>
</div>
<template v-for="(item, index) in functionLibList" :key="index">
<div
class="workflow-dropdown-item cursor flex p-8-12"
@click.stop="clickNodes(functionLibNode, item)"
@mousedown.stop="onmousedown(functionLibNode, item)"
>
<component
:is="iconComponent(`function-lib-node-icon`)"
class="mr-8 mt-4"
:size="32"
/>
<div class="pre-wrap">
<div class="lighter">{{ item.name }}</div>
<el-text type="info" size="small">{{ item.desc }}</el-text>
</div>
</div>
</template>
</el-scrollbar>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, watch, onMounted } from 'vue'
import { menuNodes, functionLibNode, functionNode } from '@/workflow/common/data'
import { iconComponent } from '@/workflow/icons/utils'
import applicationApi from '@/api/application'
const props = defineProps({
show: {
type: Boolean,
default: false
},
id: {
type: String,
default: ''
},
workflowRef: Object
})
const emit = defineEmits(['clickNodes', 'onmousedown'])
const loading = ref(false)
const activeName = ref('base')
const functionLibList = ref<any[]>([])
function clickNodes(item: any, data?: any) {
if (data) {
item['properties']['stepName'] = data.name
item['properties']['node_data'] = {
...data,
function_lib_id: data.id,
input_field_list: data.input_field_list.map((field: any) => ({
...field,
value: field.source == 'reference' ? [] : ''
}))
}
}
props.workflowRef?.addNode(item)
emit('clickNodes', item)
}
function onmousedown(item: any, data?: any) {
if (data) {
item['properties']['stepName'] = data.name
item['properties']['node_data'] = {
...data,
function_lib_id: data.id,
input_field_list: data.input_field_list.map((field: any) => ({
...field,
value: field.source == 'reference' ? [] : ''
}))
}
}
props.workflowRef?.onmousedown(item)
emit('onmousedown', item)
}
function getList() {
applicationApi.listFunctionLib(props.id, loading).then((res: any) => {
functionLibList.value = res.data
})
}
onMounted(() => {
getList()
})
</script>
<style lang="scss" scoped>
.workflow-dropdown-menu {
-moz-user-select: none; /* Firefox */
-webkit-user-select: none; /* WebKit内核 */
-ms-user-select: none; /* IE10及以后 */
-khtml-user-select: none; /* 早期浏览器 */
-o-user-select: none; /* Opera */
user-select: none; /* CSS3属性 */
position: absolute;
top: 49px;
right: 90px;
z-index: 99;
width: 268px;
box-shadow: 0px 4px 8px 0px var(--app-text-color-light-1);
background: #ffffff;
padding-bottom: 8px;
.title {
padding: 12px 12px 4px;
}
.workflow-dropdown-item {
&:hover {
background: var(--app-text-color-light-1);
}
}
}
</style>

View File

@ -25,66 +25,14 @@
</div>
<!-- 下拉框 -->
<el-collapse-transition>
<div
v-show="showPopover"
class="workflow-dropdown-menu border border-r-4"
<DropdownMenu
:show="showPopover"
:id="id"
v-click-outside="clickoutside"
>
<el-tabs v-model="activeName" class="workflow-dropdown-tabs">
<el-tab-pane label="基础组件" name="base">
<template v-for="(item, index) in menuNodes" :key="index">
<div
class="workflow-dropdown-item cursor flex p-8-12"
@click="clickNodes(item)"
@mousedown="onmousedown(item)"
>
<component :is="iconComponent(`${item.type}-icon`)" class="mr-8 mt-4" :size="32" />
<div class="pre-wrap">
<div class="lighter">{{ item.label }}</div>
<el-text type="info" size="small">{{ item.text }}</el-text>
</div>
</div>
</template>
</el-tab-pane>
<el-tab-pane label="函数库" name="function">
<el-scrollbar max-height="300">
<div
class="workflow-dropdown-item cursor flex p-8-12"
@click="clickNodes(functionNode)"
@mousedown="onmousedown(functionNode)"
>
<component
:is="iconComponent(`function-lib-node-icon`)"
class="mr-8 mt-4"
:size="32"
/>
<div class="pre-wrap">
<div class="lighter">{{ functionNode.label }}</div>
<el-text type="info" size="small">{{ functionNode.text }}</el-text>
</div>
</div>
<template v-for="(item, index) in functionLibList" :key="index">
<div
class="workflow-dropdown-item cursor flex p-8-12"
@click="clickNodes(functionLibNode, item)"
@mousedown="onmousedown(functionLibNode, item)"
>
<component
:is="iconComponent(`function-lib-node-icon`)"
class="mr-8 mt-4"
:size="32"
/>
<div class="pre-wrap">
<div class="lighter">{{ item.name }}</div>
<el-text type="info" size="small">{{ item.desc }}</el-text>
</div>
</div>
</template>
</el-scrollbar>
</el-tab-pane>
</el-tabs>
</div>
@clickNodes="clickNodes"
@onmousedown="onmousedown"
:workflowRef="workflowRef"
/>
</el-collapse-transition>
<!-- 主画布 -->
<div class="workflow-main">
@ -148,8 +96,7 @@
import { ref, onMounted, onBeforeUnmount, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import Workflow from '@/workflow/index.vue'
import { menuNodes, functionLibNode, functionNode } from '@/workflow/common/data'
import { iconComponent } from '@/workflow/icons/utils'
import DropdownMenu from '@/views/application-workflow/component/DropdownMenu.vue'
import applicationApi from '@/api/application'
import { isAppIcon } from '@/utils/application'
import { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'
@ -179,9 +126,19 @@ const showPopover = ref(false)
const showDebug = ref(false)
const enlarge = ref(false)
const saveTime = ref<any>('')
const activeName = ref('base')
const functionLibList = ref<any[]>([])
function clickNodes(item: any) {
// workflowRef.value?.addNode(item)
showPopover.value = false
}
function onmousedown(item: any) {
// workflowRef.value?.onmousedown(item)
showPopover.value = false
}
function clickoutside() {
showPopover.value = false
}
function publicHandle() {
workflowRef.value
?.validate()
@ -206,9 +163,6 @@ function publicHandle() {
})
}
function clickoutside() {
showPopover.value = false
}
const clickShowDebug = () => {
workflowRef.value
?.validate()
@ -247,38 +201,6 @@ function clickoutsideDebug(e: any) {
}
}
function clickNodes(item: any, data?: any) {
if (data) {
item['properties']['stepName'] = data.name
item['properties']['node_data'] = {
...data,
function_lib_id: data.id,
input_field_list: data.input_field_list.map((field: any) => ({
...field,
value: field.source == 'reference' ? [] : ''
}))
}
}
workflowRef.value?.addNode(item)
showPopover.value = false
}
function onmousedown(item: any, data?: any) {
if (data) {
item['properties']['stepName'] = data.name
item['properties']['node_data'] = {
...data,
function_lib_id: data.id,
input_field_list: data.input_field_list.map((field: any) => ({
...field,
value: field.source == 'reference' ? [] : ''
}))
}
}
workflowRef.value?.onmousedown(item)
showPopover.value = false
}
function getGraphData() {
return workflowRef.value?.getGraphData()
}
@ -308,12 +230,6 @@ function saveApplication(bool?: boolean) {
})
}
function getList() {
applicationApi.listFunctionLib(id, loading).then((res: any) => {
functionLibList.value = res.data
})
}
/**
* 定时保存
*/
@ -334,7 +250,7 @@ const closeInterval = () => {
onMounted(() => {
getDetail()
getList()
//
if (hasPermission(`APPLICATION:MANAGE:${id}`, 'AND')) {
initInterval()
@ -358,31 +274,7 @@ onBeforeUnmount(() => {
height: calc(100vh - 62px);
box-sizing: border-box;
}
.workflow-dropdown-menu {
-moz-user-select: none; /* Firefox */
-webkit-user-select: none; /* WebKit内核 */
-ms-user-select: none; /* IE10及以后 */
-khtml-user-select: none; /* 早期浏览器 */
-o-user-select: none; /* Opera */
user-select: none; /* CSS3属性 */
position: absolute;
top: 49px;
right: 90px;
z-index: 99;
width: 268px;
box-shadow: 0px 4px 8px 0px var(--app-text-color-light-1);
background: #ffffff;
padding-bottom: 8px;
.title {
padding: 12px 12px 4px;
}
.workflow-dropdown-item {
&:hover {
background: var(--app-text-color-light-1);
}
}
}
.workflow-dropdown-tabs {
.el-tabs__nav-wrap {
padding: 0 16px;

View File

@ -82,15 +82,35 @@
</div>
</div>
</div>
<el-collapse-transition>
<DropdownMenu
v-if="showAnchor"
@mousemove.stop
@mousedown.stop
@keydown.stop
@click.stop
:show="showAnchor"
:id="id"
style="left: 100%; top: 50%; transform: translate(0, -50%)"
@clickNodes="clickNodes"
/>
</el-collapse-transition>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { app } from '@/main'
import DropdownMenu from '@/views/application-workflow/component/DropdownMenu.vue'
import { set } from 'lodash'
import { iconComponent } from '../icons/utils'
import { copyClick } from '@/utils/clipboard'
import { WorkflowType } from '@/enums/workflow'
import { MsgError, MsgConfirm } from '@/utils/message'
const {
params: { id }
} = app.config.globalProperties.$route as any
const height = ref<{
stepContainerHeight: number
inputContainerHeight: number
@ -100,7 +120,8 @@ const height = ref<{
inputContainerHeight: 0,
outputContainerHeight: 0
})
const showAnchor = ref<boolean>(false)
const anchorData = ref<any>()
const node_status = computed(() => {
if (props.nodeModel.properties.status) {
return props.nodeModel.properties.status
@ -152,6 +173,23 @@ const resizeStepContainer = (wh: any) => {
}
}
function clickNodes(item: any) {
const nodeModel = props.nodeModel.graphModel.addNode({
type: item.type,
properties: item.properties,
x: anchorData.value?.x + props.nodeModel.width + 100,
y: anchorData.value?.y - item.height
})
props.nodeModel.graphModel.addEdge({
type: 'app-edge',
sourceNodeId: props.nodeModel.id,
sourceAnchorId: anchorData.value?.id,
targetNodeId: nodeModel.id
})
closeNodeMenu()
}
const props = defineProps<{
nodeModel: any
}>()
@ -173,6 +211,19 @@ const nodeFields = computed(() => {
function showOperate(type: string) {
return type !== WorkflowType.Base && type !== WorkflowType.Start
}
const openNodeMenu = (anchorValue: any) => {
showAnchor.value = true
anchorData.value = anchorValue
}
const closeNodeMenu = () => {
showAnchor.value = false
anchorData.value = undefined
}
onMounted(() => {
set(props.nodeModel, 'openNodeMenu', (anchorData: any) => {
showAnchor.value ? closeNodeMenu() : openNodeMenu(anchorData)
})
})
</script>
<style lang="scss" scoped>
.workflow-node-container {

View File

@ -18,7 +18,6 @@ class AppNode extends HtmlResize.view {
constructor(props: any, VueNode: any) {
super(props)
this.isMounted = false
this.r = h(VueNode, {
properties: props.model.properties,
nodeModel: props.model
@ -70,6 +69,12 @@ class AppNode extends HtmlResize.view {
},
[
lh('div', {
style: { zindex: 0 },
onClick: () => {
if (!isConnect && type == 'right') {
this.props.model.openNodeMenu(anchorData)
}
},
dangerouslySetInnerHTML: {
__html: isConnect
? `<svg width="100%" height="100%" viewBox="0 0 42 42" fill="none" xmlns="http://www.w3.org/2000/svg">

View File

@ -29,9 +29,11 @@ export const baseNode = {
type: WorkflowType.Base,
x: 200,
y: 270,
text: '',
properties: {
height: 200,
stepName: '基本信息',
input_field_list: [],
node_data: {
name: '',
desc: '',
@ -53,6 +55,7 @@ export const aiChatNode = {
type: WorkflowType.AiChat,
text: '与 AI 大模型进行对话',
label: 'AI 对话',
height: 340,
properties: {
stepName: 'AI 对话',
config: {
@ -72,6 +75,7 @@ export const searchDatasetNode = {
type: WorkflowType.SearchDataset,
text: '关联知识库,查找与问题相关的分段',
label: '知识库检索',
height: 355,
properties: {
stepName: '知识库检索',
config: {
@ -94,6 +98,7 @@ export const questionNode = {
type: WorkflowType.Question,
text: '根据历史聊天记录优化完善当前问题,更利于匹配知识库分段',
label: '问题优化',
height: 345,
properties: {
stepName: '问题优化',
config: {
@ -110,6 +115,7 @@ export const conditionNode = {
type: WorkflowType.Condition,
text: '根据不同条件执行不同的节点',
label: '判断器',
height: 175,
properties: {
width: 600,
stepName: '判断器',
@ -127,6 +133,7 @@ export const replyNode = {
type: WorkflowType.Reply,
text: '指定回复内容,引用变量会转换为字符串进行输出',
label: '指定回复',
height: 210,
properties: {
stepName: '指定回复',
config: {
@ -143,6 +150,7 @@ export const rerankerNode = {
type: WorkflowType.RrerankerNode,
text: '使用重排模型对多个知识库的检索结果进行二次召回',
label: '多路召回',
height: 252,
properties: {
stepName: '多路召回',
config: {
@ -171,6 +179,7 @@ export const functionNode = {
type: WorkflowType.FunctionLibCustom,
text: '通过执行自定义脚本,实现数据处理',
label: '自定义函数',
height: 260,
properties: {
stepName: '自定义函数',
config: {
@ -187,6 +196,7 @@ export const functionLibNode = {
type: WorkflowType.FunctionLib,
text: '通过执行自定义脚本,实现数据处理',
label: '自定义函数',
height: 170,
properties: {
stepName: '自定义函数',
config: {

View File

@ -1,5 +1,6 @@
import BaseNodeVue from './index.vue'
import { AppNode, AppNodeModel } from '@/workflow/common/app-node'
class BaseNode extends AppNode {
constructor(props: any) {
super(props, BaseNodeVue)

View File

@ -116,6 +116,7 @@
<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"
@ -182,8 +183,8 @@
</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 label="浏览器播放(免费)" value="BROWSER" />
<el-radio label="TTS模型" value="TTS" />
</el-radio-group>
<el-select
v-if="form_data.tts_type === 'TTS'"
@ -246,6 +247,53 @@
</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>
@ -255,8 +303,8 @@
</div>
</template>
</el-dialog>
<FieldFormDialog ref="FieldFormDialogRef" @refresh="refreshFieldList" />
</NodeContainer>
<FieldFormDialog ref="FieldFormDialogRef" @refresh="refreshFieldList" />
</template>
<script setup lang="ts">
import { app } from '@/main'

View File

@ -9,6 +9,7 @@ const envDir = './env'
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
const ENV = loadEnv(mode, envDir)
const prefix = process.env.VITE_DYNAMIC_PREFIX || ENV.VITE_BASE_PATH;
const proxyConf: Record<string, string | ProxyOptions> = {}
proxyConf['/api'] = {
target: 'http://127.0.0.1:8080',
@ -18,7 +19,7 @@ export default defineConfig(({ mode }) => {
return {
preflight: false,
lintOnSave: false,
base: ENV.VITE_BASE_PATH,
base: prefix,
envDir: envDir,
plugins: [vue(), DefineOptions()],
server: {