Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ui/src/workflow-canvas/icons/variable-splitting-node-icon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<template>
<el-avatar class="ai-avatar-gradient" shape="square">
<img src="@/assets/workflow/icon_ai_chat.svg" style="width: 75%" alt="" />
</el-avatar>
</template>
<script setup lang="ts"></script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<template>
<MkDialog
v-model="dialogVisible"
:title="isEdit ? '编辑变量' : '添加变量'"
width="520px"
append-to-body
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<el-form ref="fieldFormRef" label-position="top" require-asterisk-position="right" hide-required-asterisk :rules="rules" :model="form">
<el-form-item prop="field">
<template #label>
<div class="flex items-center">
<span class="mr-4">变量<span class="text-danger">*</span></span>
</div>
</template>
<el-input v-model="form.field" :maxlength="64" placeholder="请输入变量" show-word-limit />
</el-form-item>
<el-form-item prop="label">
<template #label>
<div class="flex items-center">
<span class="mr-4">显示名称<span class="text-danger">*</span></span>
</div>
</template>
<el-input v-model="form.label" :maxlength="64" show-word-limit placeholder="请输入显示名称" />
</el-form-item>
<el-form-item prop="expression">
<template #label>
<div class="flex items-center">
<span class="mr-4">表达式<span class="text-danger">*</span></span>
<el-tooltip effect="dark" placement="right">
<template #content>
<span v-html="expressionTip"></span>
</template>
<MkIcon name="icon_info_outlined" class="cursor-pointer align-middle" />
</el-tooltip>
</div>
</template>
<el-input v-model="form.expression" :maxlength="64" show-word-limit placeholder="请输入表达式" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="close">取消</el-button>
<el-button type="primary" @click="submit(fieldFormRef)">保存</el-button>
</span>
</template>
</MkDialog>
</template>
<script setup lang="ts">
import { reactive, ref, useTemplateRef } from 'vue'
import type { FormInstance } from 'element-plus'
import { cloneDeep } from 'lodash'

defineOptions({ name: 'VariableSplittingFieldDialog' })

const emit = defineEmits<{ refresh: [data: { field: string; label: string; expression: string }, index?: number] }>()

const expressionTip =
'请使用 JSON Path 表达式拆分变量,例如:$.store.book ' +
'<a href="https://pypi.org/project/jsonpath-ng/1.8.0/" target="_blank" rel="noopener noreferrer" class="expression_tip">点击查看详情 ➜ pypi.org</a>'

const fieldFormRef = useTemplateRef<FormInstance>('fieldFormRef')
const isEdit = ref(false)
const currentIndex = ref<number | undefined>(undefined)
const form = ref<{ field: string; label: string; expression: string }>({ field: '', label: '', expression: '' })

const rules = reactive({
label: [{ required: true, message: '请输入显示名称', trigger: 'blur' }],
field: [
{ required: true, message: '请输入变量', trigger: 'blur' },
{ pattern: /^[a-zA-Z0-9_]+$/, message: '变量可由字母、数字、下划线组成', trigger: 'blur' },
],
expression: [{ required: true, message: '请输入表达式', trigger: 'blur' }],
})

const dialogVisible = ref(false)

function open(row?: { field: string; label: string; expression: string }, index?: number) {
if (row) {
form.value = cloneDeep(row)
isEdit.value = true
currentIndex.value = index
}
dialogVisible.value = true
}

function close() {
dialogVisible.value = false
isEdit.value = false
currentIndex.value = undefined
form.value = { field: '', label: '', expression: '' }
}

const submit = async (formEl: FormInstance | null) => {
if (!formEl) return
await formEl.validate((valid) => {
if (valid) {
emit('refresh', cloneDeep(form.value), currentIndex.value)
}
})
}

defineExpose({ open, close })
</script>
<style lang="scss">
.expression_tip {
color: var(--el-color-primary-light-5);

&:hover {
color: var(--el-color-primary-light-3);
}
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<template>
<div class="w-full">
<div class="flex w-full items-center justify-between">
<h6 class="font-medium">
拆分变量
<span class="text-danger">*</span>
</h6>
<el-button link type="primary" @click="openAddDialog()">
<MkIcon name="icon_add_outlined" class="mr-1" />
添加
</el-button>
</div>

<el-table v-if="inputFieldList.length > 0" :data="inputFieldList" row-key="field" class="border">
<el-table-column prop="field" label="变量" width="100" show-overflow-tooltip>
<template #default="{ row }">
<span :title="row.field" class="ellipsis-1">{{ row.field }}</span>
</template>
</el-table-column>
<el-table-column prop="label" label="显示名称">
<template #default="{ row }">
<span :title="row.label" class="ellipsis-1">{{ row.label }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="90">
<template #default="{ row, $index }">
<el-button link type="primary" @click="openAddDialog(row, $index)">
<MkIcon name="icon_edit_outlined" />
</el-button>
<el-button link type="info" @click="deleteField($index)">
<MkIcon name="icon_delete-trash_outlined" />
</el-button>
</template>
</el-table-column>
</el-table>

<VariableFieldDialog ref="VariableFieldDialogRef" @refresh="refreshFieldList" />
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { cloneDeep } from 'lodash'
import type { WorkflowNodeModel } from '@/workflow-canvas/core/workflow-node'

import VariableFieldDialog from './VariableFieldDialog.vue'
import { MsgError } from '@/utils/message'

defineOptions({ name: 'WorkflowVariableSplittingFieldTable' })

const props = defineProps<{ nodeModel: WorkflowNodeModel }>()

const VariableFieldDialogRef = ref<InstanceType<typeof VariableFieldDialog>>()

const nodeProps = props.nodeModel.properties as unknown as {
config: Record<string, unknown>
node_data: Record<string, unknown>
}

if (!nodeProps.node_data) {
nodeProps.node_data = {}
}
if (!Array.isArray(nodeProps.node_data.variable_list)) {
nodeProps.node_data.variable_list = []
}

const inputFieldList = computed<Array<Record<string, unknown>>>({
get: () => nodeProps.node_data.variable_list as Array<Record<string, unknown>>,
set: (value) => {
nodeProps.node_data.variable_list = value
},
})

function syncConfig() {
const fields = [{ label: '结果', value: 'result' }, ...inputFieldList.value.map((item) => ({ label: item.label, value: item.field }))]
if (!nodeProps.config) {
nodeProps.config = {}
}
nodeProps.config.fields = fields
props.nodeModel.clearNextNodeField(false)
}

function openAddDialog(data?: Record<string, unknown>, index?: number) {
VariableFieldDialogRef.value?.open(data as { field: string; label: string; expression: string }, index)
}

function deleteField(index: number) {
const list = cloneDeep(inputFieldList.value)
list.splice(index, 1)
inputFieldList.value = list
syncConfig()
}

function refreshFieldList(data: { field: string; label: string; expression: string }, index?: number) {
for (let i = 0; i < inputFieldList.value.length; i++) {
if ((inputFieldList.value[i] as { field: string }).field === data.field && i !== index) {
MsgError(`变量 "${data.field}" 已存在`)
return
}
}

const list = cloneDeep(inputFieldList.value)
if (index === undefined || index === null) {
list.push(data)
} else {
list.splice(index, 1, data)
}
inputFieldList.value = list
VariableFieldDialogRef.value?.close()
syncConfig()
}
</script>
11 changes: 11 additions & 0 deletions ui/src/workflow-canvas/nodes/variable-splitting-node/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import VariableSplittingNodeVue from './index.vue'
import { WorkflowNodeModel, WorkflowNodeView } from '@/workflow-canvas/core/workflow-node'
import { WorkflowNodeType } from '@/workflow-canvas/types'

class VariableSplittingNodeView extends WorkflowNodeView {
constructor(props: ConstructorParameters<typeof WorkflowNodeView>[0]) {
super(props, VariableSplittingNodeVue)
}
}

export default { type: WorkflowNodeType.VariableSplittingNode, model: WorkflowNodeModel, view: VariableSplittingNodeView }
67 changes: 67 additions & 0 deletions ui/src/workflow-canvas/nodes/variable-splitting-node/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<template>
<NodeContainer :node-model="model">
<h6 class="mb-3">节点设置</h6>
<el-card shadow="never" class="card-never" style="--el-card-padding: 12px">
<el-form ref="variableSplittingFormRef" :model="form_data" label-position="top" require-asterisk-position="right" label-width="auto" hide-required-asterisk @submit.prevent>
<el-form-item prop="input_variable" :rules="{ message: '请选择变量', trigger: 'blur', required: true }">
<template #label>
<div class="flex-between">
<div>输入变量<span class="text-danger">*</span></div>
</div>
</template>
<NodeCascader ref="nodeCascaderRef" :node-model="model" class="w-full" placeholder="请选择变量" v-model="form_data.input_variable" />
</el-form-item>

<el-form-item prop="variable_list" :rules="{ message: '请添加拆分变量', trigger: 'blur', required: true }">
<VariableFieldTable ref="VariableFieldTableRef" :node-model="model" />
</el-form-item>
</el-form>
</el-card>
</NodeContainer>
</template>
<script setup lang="ts">
import { computed, inject, onMounted, useTemplateRef } from 'vue'
import { set } from 'lodash'
import type { BaseNodeModel } from '@logicflow/core'
import type { FormInstance } from 'element-plus'

import NodeCascader from '@/workflow-canvas/core/NodeCascader.vue'
import NodeContainer from '@/workflow-canvas/core/node-container/index.vue'
import type { WorkflowNodeModel } from '@/workflow-canvas/core/workflow-node'
import VariableFieldTable from './component/VariableFieldTable.vue'

defineOptions({ name: 'WorkflowVariableSplittingNode' })
const getModel = inject('getModel') as () => BaseNodeModel
const model = getModel() as WorkflowNodeModel

if (!model.properties.node_data) {
model.properties.node_data = { input_variable: [], variable_list: [] }
}
const form_data = computed<{ input_variable: string[]; variable_list: Array<Record<string, unknown>> }>({
get: () => model.properties.node_data as { input_variable: string[]; variable_list: Array<Record<string, unknown>> },
set: (value) => {
set(model.properties, 'node_data', value)
},
})

const variableSplittingFormRef = useTemplateRef<FormInstance>('variableSplittingFormRef')
const nodeCascaderRef = useTemplateRef<InstanceType<typeof NodeCascader>>('nodeCascaderRef')
const VariableFieldTableRef = useTemplateRef<InstanceType<typeof VariableFieldTable>>('VariableFieldTableRef')

const validate = () => {
const vList: Array<Promise<unknown>> = []
const formResult = variableSplittingFormRef.value?.validate()
if (formResult) vList.push(formResult)
const cascaderResult = nodeCascaderRef.value?.validate()
if (cascaderResult) vList.push(cascaderResult)
if (!form_data.value.variable_list.length) {
vList.push(Promise.reject('请添加拆分变量'))
}
return Promise.all(vList).catch((error) => Promise.reject({ node: model, errMessage: error }))
}

onMounted(() => {
set(model, 'validate', validate)
})
</script>
<style lang="scss" scoped></style>
Loading