Skip to content

Commit ab936bf

Browse files
feat: application node
1 parent fd5998c commit ab936bf

5 files changed

Lines changed: 105 additions & 78 deletions

File tree

‎ui/src/workflow-canvas/config/node-data.ts‎

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,27 @@ export const toolLibNode = {
377377
properties: { stepName: '自定义工具', config: { fields: [{ label: '结果', value: 'result' }] } },
378378
}
379379

380+
/**
381+
* 工作流工具配置数据
382+
*/
383+
export const toolWorkflowLibNode = {
384+
type: WorkflowNodeType.ToolWorkflowLib,
385+
text: '工作流工具',
386+
label: '工作流工具',
387+
height: 170,
388+
properties: { stepName: '工作流工具', config: { fields: [] } },
389+
}
390+
391+
392+
/* 智能体节点 */
393+
export const applicationNode = {
394+
type: WorkflowNodeType.Application,
395+
text: '智能体节点',
396+
label: '智能体节点',
397+
properties: { stepName: '智能体节点', config: { fields: [{ label: '结果', value: 'result' }] } },
398+
}
399+
400+
380401
export const loopStartNode = {
381402
id: WorkflowNodeType.LoopStartNode,
382403
type: WorkflowNodeType.LoopStartNode,
@@ -444,25 +465,6 @@ export const loopBreakNode = {
444465
properties: { width: 600, stepName: 'Break', config: { fields: [] } },
445466
}
446467

447-
/**
448-
* 工作流工具配置数据
449-
*/
450-
export const toolWorkflowLibNode = {
451-
type: WorkflowNodeType.ToolWorkflowLib,
452-
text: '工作流工具',
453-
label: '工作流工具',
454-
height: 170,
455-
properties: { stepName: '工作流工具', config: { fields: [] } },
456-
}
457-
458-
export const applicationNode = {
459-
type: WorkflowNodeType.Application,
460-
text: '智能体节点',
461-
label: '智能体节点',
462-
height: 260,
463-
properties: { stepName: '智能体节点', config: { fields: [{ label: '结果', value: 'result' }] } },
464-
}
465-
466468
/* 文档分段 */
467469
export const documentSplitNode = {
468470
type: WorkflowNodeType.DocumentSplitNode,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script setup lang="ts">
2+
const props = defineProps<{
3+
item: {
4+
name: string
5+
icon: string
6+
}
7+
size?: string | number
8+
}>()
9+
</script>
10+
<template>
11+
<ApplicationIcon :icon="props.item.icon" />
12+
</template>

‎ui/src/workflow-canvas/nodes/ai-chat-node/index.vue‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ const model = getModel()
2424
const store = useWorkflowStore(apiType)
2525
2626
const formRef = useTemplateRef<FormInstance>('formRef')
27-
const modelCascaderRef = useTemplateRef<InstanceType<typeof NodeCascader>>('modelCascaderRef')
2827
const imageCascaderRef = useTemplateRef<InstanceType<typeof NodeCascader>>('imageCascaderRef')
2928
const promptGenerateDialogRef = useTemplateRef<InstanceType<typeof PromptGenerateDialog>>('promptGenerateDialogRef')
3029
const reasoningSettingDialogRef = useTemplateRef<InstanceType<typeof ReasoningSettingDialog>>('reasoningSettingDialogRef')

‎ui/src/workflow-canvas/nodes/application-node/index.vue‎

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
<script setup lang="ts">
22
import { computed, inject, onMounted, useTemplateRef } from 'vue'
3-
import { QuestionFilled } from '@element-plus/icons-vue'
43
import { cloneDeep } from 'lodash'
54
import type { FormInstance } from 'element-plus'
65
import ApplicationApi from '@/api/admin/workspace/application/application'
76
import { APPLICATION_TYPE } from '@/api/enums'
87
import NodeCascader from '@/workflow-canvas/core/NodeCascader.vue'
98
import NodeContainer from '@/workflow-canvas/core/node-container/index.vue'
109
import type { WorkflowNodeModel } from '@/workflow-canvas/core/workflow-node'
11-
import { WorkflowNodeType } from '@/workflow-canvas/types'
10+
import { WorkflowMode, WorkflowNodeType } from '@/workflow-canvas/types'
1211
1312
defineOptions({ name: 'WorkflowApplicationNode' })
1413
@@ -55,35 +54,45 @@ interface ReferencedApplicationProperties {
5554
5655
const uploadFields: UploadField[] = ['document', 'image', 'audio', 'video']
5756
const getModel = inject('getModel') as () => WorkflowNodeModel
57+
const workflowMode = inject<WorkflowMode>('workflowMode', WorkflowMode.Application)
5858
const model = getModel()
5959
6060
const formRef = useTemplateRef<FormInstance>('formRef')
61-
const questionCascaderRef = useTemplateRef<InstanceType<typeof NodeCascader>>('questionCascaderRef')
6261
63-
const applicationNodeData = (model.properties.node_data ?? {}) as Partial<ApplicationNodeForm>
64-
if (!Array.isArray(applicationNodeData.question_reference_address)) {
65-
applicationNodeData.question_reference_address = [WorkflowNodeType.Start, 'question']
62+
// 一次性补齐旧节点表单,保留已保存的参数和文件引用。
63+
const defaultForm: ApplicationNodeForm = {
64+
api_input_field_list: [],
65+
user_input_field_list: [],
66+
question_reference_address: [WorkflowNodeType.Start, 'question'],
67+
is_result: false,
68+
}
69+
const savedForm = model.properties.node_data as Partial<ApplicationNodeForm> | undefined
70+
model.properties.node_data = {
71+
...defaultForm,
72+
...savedForm,
73+
api_input_field_list: Array.isArray(savedForm?.api_input_field_list) ? savedForm.api_input_field_list : defaultForm.api_input_field_list,
74+
user_input_field_list: Array.isArray(savedForm?.user_input_field_list) ? savedForm.user_input_field_list : defaultForm.user_input_field_list,
75+
question_reference_address: Array.isArray(savedForm?.question_reference_address)
76+
? savedForm.question_reference_address
77+
: defaultForm.question_reference_address,
78+
is_result: savedForm?.is_result ?? defaultForm.is_result,
6679
}
67-
if (!Array.isArray(applicationNodeData.api_input_field_list)) applicationNodeData.api_input_field_list = []
68-
if (!Array.isArray(applicationNodeData.user_input_field_list)) applicationNodeData.user_input_field_list = []
69-
if (applicationNodeData.is_result === undefined) applicationNodeData.is_result = false
70-
model.properties.node_data = applicationNodeData as ApplicationNodeForm
7180
7281
const formData = computed<ApplicationNodeForm>({
7382
get: () => model.properties.node_data as ApplicationNodeForm,
7483
set: (value) => (model.properties.node_data = value),
7584
})
7685
86+
const showReturnContent = computed(() =>
87+
[WorkflowMode.Application, WorkflowMode.ApplicationLoop, WorkflowMode.Tool, WorkflowMode.ToolLoop].includes(workflowMode),
88+
)
89+
7790
function formatFieldLabel(label: FieldLabel) {
7891
return typeof label === 'object' && label !== null ? (label.label ?? '') : label
7992
}
8093
81-
function validate() {
82-
return Promise.all([questionCascaderRef.value?.validate(), formRef.value?.validate()]).catch((error) =>
83-
Promise.reject({ node: model, errMessage: error }),
84-
)
85-
}
8694
95+
// 根据关联智能体配置同步输入字段,按字段标识保留已选变量。
8796
function syncUploadField(field: UploadField, enabled: boolean) {
8897
const listKey: UploadListKey = `${field}_list`
8998
if (enabled) {
@@ -152,6 +161,13 @@ function refreshApplicationFields() {
152161
})
153162
}
154163
164+
async function validate() {
165+
return formRef.value?.validate().catch((error) =>
166+
Promise.reject({ node: model, errMessage: error }),
167+
)
168+
}
169+
170+
155171
onMounted(() => {
156172
refreshApplicationFields()
157173
model.validate = validate
@@ -160,17 +176,12 @@ onMounted(() => {
160176

161177
<template>
162178
<NodeContainer :node-model="model">
163-
<h6 class="mb-3">节点设置</h6>
179+
<h6 class="mk-title-decoration mb-2">节点设置</h6>
164180

165-
<el-card shadow="never" class="card-never">
181+
<div class="mk-gray-card">
166182
<el-form ref="formRef" :model="formData" label-position="top" require-asterisk-position="right" @submit.prevent>
167183
<el-form-item label="用户问题" prop="question_reference_address" :rules="{ message: '请选择用户问题', trigger: 'change', required: true }">
168-
<NodeCascader
169-
ref="questionCascaderRef"
170-
v-model="formData.question_reference_address"
171-
:node-model="model"
172-
placeholder="请选择用户问题"
173-
/>
184+
<NodeCascader ref="questionCascaderRef" v-model="formData.question_reference_address" :node-model="model" placeholder="请选择用户问题" />
174185
</el-form-item>
175186

176187
<el-form-item v-if="formData.document_list !== undefined" label="选择文档" prop="document_list">
@@ -209,18 +220,17 @@ onMounted(() => {
209220
<NodeCascader v-model="field.value" :node-model="model" placeholder="请选择参数" />
210221
</el-form-item>
211222

212-
<el-form-item label="返回内容" @click.prevent>
213-
<template #label>
214-
<div class="flex items-center gap-1">
215-
<span>返回内容</span>
216-
<el-tooltip content="开启后,该节点的输出会作为工作流的最终回复内容" effect="dark" placement="right">
217-
<MkIcon :icon="QuestionFilled" class="cursor-help text-N600" />
218-
</el-tooltip>
219-
</div>
220-
</template>
223+
<!-- 返回内容 -->
224+
<div v-if="showReturnContent" class="flex-between w-full">
225+
<span class="flex items-center gap-1">
226+
返回内容
227+
<el-tooltip content="关闭后该节点的内容则不输出给用户。如果你想让用户看到该节点的输出内容,请打开开关。" placement="right">
228+
<MkIcon name="icon_info_outlined" class="text-N600!" />
229+
</el-tooltip>
230+
</span>
221231
<el-switch v-model="formData.is_result" size="small" />
222-
</el-form-item>
232+
</div>
223233
</el-form>
224-
</el-card>
234+
</div>
225235
</NodeContainer>
226236
</template>

‎ui/src/workflow-canvas/nodes/tool-lib-node/index.vue‎

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
<script setup lang="ts">
22
import { computed, inject, onMounted, useTemplateRef } from 'vue'
3-
import { QuestionFilled } from '@element-plus/icons-vue'
43
import { cloneDeep } from 'lodash'
54
import type { FormInstance } from 'element-plus'
65
import type { ToolInputField } from '@/api/types'
@@ -30,11 +29,17 @@ const model = getModel()
3029
3130
const formRef = useTemplateRef<FormInstance>('formRef')
3231
32+
// 新节点默认不返回内容,旧节点缺少开关时继续按末尾节点兼容。
33+
const defaultForm: ToolLibNodeForm = {
34+
input_field_list: [],
35+
is_result: false,
36+
}
3337
const savedForm = model.properties.node_data as Partial<ToolLibNodeForm> | undefined
3438
model.properties.node_data = {
39+
...defaultForm,
3540
...savedForm,
36-
input_field_list: Array.isArray(savedForm?.input_field_list) ? savedForm.input_field_list : [],
37-
is_result: savedForm ? savedForm.is_result : false,
41+
input_field_list: Array.isArray(savedForm?.input_field_list) ? savedForm.input_field_list : defaultForm.input_field_list,
42+
is_result: savedForm ? savedForm.is_result : defaultForm.is_result,
3843
}
3944
4045
const formData = computed<ToolLibNodeForm>({
@@ -50,6 +55,7 @@ function validate() {
5055
return formRef.value?.validate().catch((error) => Promise.reject({ node: model, errMessage: error })) ?? Promise.resolve()
5156
}
5257
58+
// 工具参数按名称和来源合并,保留已有引用或自定义值。
5359
function createInputField(field: ToolInputField, previousFields: ToolNodeInputField[]): ToolNodeInputField {
5460
const previousField = previousFields.find((item) => item.name === field.name && item.source === field.source)
5561
if (field.source === 'reference') {
@@ -94,11 +100,11 @@ onMounted(() => {
94100

95101
<template>
96102
<NodeContainer :node-model="model">
97-
<h6 class="mb-3">节点设置</h6>
98-
<h6 class="mb-2">输入参数</h6>
103+
<h6 class="mk-title-decoration mb-2">节点设置</h6>
99104

100-
<el-form ref="formRef" :model="formData" label-position="top" hide-required-asterisk @submit.prevent>
101-
<el-card shadow="never" class="card-never mb-4" style="--el-card-padding: 12px">
105+
<div class="mk-gray-card">
106+
<el-form ref="formRef" :model="formData" label-position="top" hide-required-asterisk @submit.prevent>
107+
<h6 class="mb-2">输入参数</h6>
102108
<template v-if="formData.input_field_list.length">
103109
<el-form-item
104110
v-for="(field, index) in formData.input_field_list"
@@ -112,11 +118,10 @@ onMounted(() => {
112118
>
113119
<template #label>
114120
<div class="flex w-full items-center gap-1">
115-
<span class="max-w-40 truncate" :title="field.name">{{ field.name }}</span>
121+
<span class="max-w-40 truncate" :class="{ 'mk-required': field.is_required }" :title="field.name">{{ field.name }}</span>
116122
<el-tooltip v-if="field.desc" :content="field.desc" effect="dark" placement="right">
117-
<MkIcon :icon="QuestionFilled" class="cursor-help text-N600" />
123+
<MkIcon name="icon_info_outlined" class="text-N600!" />
118124
</el-tooltip>
119-
<span v-if="field.is_required" class="text-danger">*</span>
120125
<el-tag size="small" type="info">{{ field.type }}</el-tag>
121126
</div>
122127
</template>
@@ -125,20 +130,19 @@ onMounted(() => {
125130
<el-input v-else v-model="field.value" placeholder="请输入参数" />
126131
</el-form-item>
127132
</template>
128-
<MkEmpty v-else :image-size="60" />
129-
</el-card>
130-
131-
<el-form-item v-if="showReturnContent" label="返回内容" @click.prevent>
132-
<template #label>
133-
<div class="flex items-center gap-1">
134-
<span>返回内容</span>
135-
<el-tooltip content="开启后,该节点的输出会作为工作流的最终回复内容" effect="dark" placement="right">
136-
<MkIcon :icon="QuestionFilled" class="cursor-help text-N600" />
133+
<MkEmpty v-else :image-size="60" class="mb-4" />
134+
135+
<!-- 返回内容 -->
136+
<div v-if="showReturnContent" class="flex-between w-full">
137+
<span class="flex items-center gap-1">
138+
返回内容
139+
<el-tooltip content="关闭后该节点的内容则不输出给用户。如果你想让用户看到该节点的输出内容,请打开开关。" placement="right">
140+
<MkIcon name="icon_info_outlined" class="text-N600!" />
137141
</el-tooltip>
138-
</div>
139-
</template>
140-
<el-switch v-model="formData.is_result" size="small" />
141-
</el-form-item>
142-
</el-form>
142+
</span>
143+
<el-switch v-model="formData.is_result" size="small" />
144+
</div>
145+
</el-form>
146+
</div>
143147
</NodeContainer>
144148
</template>

0 commit comments

Comments
 (0)