1+ <script setup lang="ts">
2+ import { computed , inject , onMounted , useTemplateRef } from ' vue'
3+ import { cloneDeep , set } from ' lodash'
4+ import type { FormInstance } from ' element-plus'
5+ import ToolApi from ' @/api/admin/workspace/tool/tool'
6+ import NodeContainer from ' @/workflow-canvas/core/node-container/index.vue'
7+ import NodeCascader from ' @/workflow-canvas/core/NodeCascader.vue'
8+ import type { WorkflowNodeModel } from ' @/workflow-canvas/core/workflow-node'
9+ import { WorkflowMode } from ' @/workflow-canvas/types'
10+
11+ defineOptions ({ name: ' WorkflowToolWorkflowLibNode' })
12+
13+ interface InputField {
14+ field: string
15+ label: string
16+ name? : string
17+ type: string
18+ is_required? : boolean
19+ source: ' reference' | ' custom'
20+ value: any
21+ }
22+
23+ interface OutputField {
24+ label: string
25+ value: string
26+ }
27+
28+ interface ToolWorkflowLibNodeForm {
29+ input_field_list: InputField []
30+ input_title? : string
31+ is_result? : boolean
32+ tool_lib_id? : string
33+ }
34+
35+ const getModel = inject (' getModel' ) as () => WorkflowNodeModel
36+ const workflowMode = inject <WorkflowMode >(' workflowMode' , WorkflowMode .Application )
37+ const model = getModel ()
38+
39+ const formRef = useTemplateRef <FormInstance >(' formRef' )
40+
41+ const toolNodeData = (model .properties .node_data ?? {}) as Partial <ToolWorkflowLibNodeForm >
42+ if (! Array .isArray (toolNodeData .input_field_list )) toolNodeData .input_field_list = []
43+ model .properties .node_data = toolNodeData as ToolWorkflowLibNodeForm
44+
45+ const formData = computed <ToolWorkflowLibNodeForm >({
46+ get : () => model .properties .node_data as ToolWorkflowLibNodeForm ,
47+ set : (value ) => set (model .properties , ' node_data' , value ),
48+ })
49+
50+ const showReturnContent = computed (() =>
51+ [WorkflowMode .Application , WorkflowMode .ApplicationLoop , WorkflowMode .Tool , WorkflowMode .ToolLoop ].includes (workflowMode ),
52+ )
53+
54+ const inputTitle = computed (() => formData .value .input_title || ' 输入参数' )
55+
56+ function onSourceChange(item : InputField ) {
57+ if (item .type === ' boolean' ) {
58+ item .value = false
59+ } else if ([' array' , ' dict' ].includes (item .type )) {
60+ item .value = []
61+ } else {
62+ item .value = ' '
63+ }
64+ }
65+
66+ function validate() {
67+ return formRef .value ?.validate ().catch ((error ) => Promise .reject ({ node: model , errMessage: error })) ?? Promise .resolve ()
68+ }
69+
70+ function createInputField(field : InputField , previousFields : InputField []): InputField {
71+ const previousField = previousFields .find ((item ) => item .field === field .field )
72+ if (field .source === ' reference' ) {
73+ return {
74+ ... field ,
75+ source: ' reference' ,
76+ value: previousField ?.source === ' reference' ? cloneDeep (previousField .value ) : [],
77+ }
78+ }
79+ return {
80+ ... field ,
81+ source: ' custom' ,
82+ value: previousField ?.source === ' custom' ? previousField .value : ' ' ,
83+ }
84+ }
85+
86+ function updateField() {
87+ const toolId = formData .value .tool_lib_id
88+ if (! toolId ) {
89+ model .properties .status = 500
90+ return
91+ }
92+
93+ ToolApi .getToolDetail (toolId )
94+ .then ((tool ) => {
95+ const workflowNodes = (tool as any )?.work_flow ?.nodes || []
96+ const baseNode = workflowNodes .find ((n : any ) => n .type === ' tool-base-node' )
97+
98+ if (baseNode ) {
99+ const newInputList = baseNode .properties .user_input_field_list || []
100+ const newOutputList = baseNode .properties .user_output_field_list || []
101+
102+ const oldConfigFields = (model .properties .config ?.fields || []) as OutputField []
103+ const configFieldList = newOutputList .map ((item : any ) => {
104+ const old = oldConfigFields .find ((o ) => o .value === item .field )
105+ return old ? JSON .parse (JSON .stringify (old )) : { label: item .label , value: item .field }
106+ })
107+
108+ const inputTitleValue = baseNode .properties .user_input_config ?.title
109+ const outputTitle = baseNode .properties .user_output_config ?.title
110+ const previousFields = formData .value .input_field_list
111+ const mergedInputList = newInputList .map ((item : any ) => {
112+ const findField = previousFields .find ((oldItem ) => oldItem .field === item .field )
113+ if (findField ) {
114+ return {
115+ ... item ,
116+ source: findField .source ,
117+ value: JSON .parse (JSON .stringify (findField .value )),
118+ }
119+ }
120+ return { ... item , source: ' custom' , value: ' ' }
121+ })
122+
123+ set (formData .value , ' input_field_list' , mergedInputList )
124+ set (model .properties , ' config' , {
125+ fields: configFieldList ,
126+ output_title: outputTitle ,
127+ })
128+ set (formData .value , ' input_title' , inputTitleValue )
129+ }
130+ model .properties .status = (tool as any )?.is_active ? 200 : 500
131+ model .clearNextNodeField (true )
132+ })
133+ .catch (() => {
134+ model .properties .status = 500
135+ })
136+ }
137+
138+ onMounted (() => {
139+ if (typeof formData .value .is_result === ' undefined' ) {
140+ const isLast = ! model .graphModel .getNodeOutgoingNode (model .id ).length
141+ if (isLast ) {
142+ formData .value .is_result = true
143+ }
144+ }
145+ updateField ()
146+ model .validate = validate
147+ })
148+ </script >
149+
150+ <template >
151+ <NodeContainer :node-model =" model " >
152+ <el-form ref =" formRef" :model =" formData" label-position =" top" hide-required-asterisk @submit.prevent >
153+ <h6 class =" mk-title-decoration mb-2" >{{ inputTitle }}</h6 >
154+
155+ <el-card shadow =" never" class =" card-never mb-4" style =" --el-card-padding : 12px " >
156+ <template v-if =" formData .input_field_list .length " >
157+ <el-form-item
158+ v-for =" (field, index) in formData.input_field_list"
159+ :key =" `${field.field}-${index}`"
160+ :prop =" `input_field_list.${index}.value`"
161+ :rules =" {
162+ required: field.is_required,
163+ message: field.source === 'reference' ? '请选择参数' : '请输入参数',
164+ trigger: field.source === 'reference' ? 'change' : 'blur',
165+ }"
166+ >
167+ <template #label >
168+ <div class =" flex-between" >
169+ <div class =" flex items-center" >
170+ <div class =" mr-2 max-w-32 truncate" :title =" field.label" >
171+ {{ field.label }}
172+ </div >
173+ <span v-if =" field.is_required" class =" text-danger" >*</span >
174+ </div >
175+ <el-select
176+ :teleported =" false"
177+ v-model =" field.source"
178+ @change =" onSourceChange(field)"
179+ size =" small"
180+ style =" width : 85px "
181+ >
182+ <el-option label =" 引用" value =" reference" />
183+ <el-option label =" 自定义" value =" custom" />
184+ </el-select >
185+ </div >
186+ </template >
187+ <NodeCascader
188+ v-if =" field .source === ' reference' "
189+ v-model =" field .value "
190+ :node-model =" model "
191+ class="w-full"
192+ placeholder="请选择参数"
193+ />
194+ <template v-else >
195+ <el-input
196+ v-if =" ['string'].includes(field.type)"
197+ v-model =" field.value"
198+ placeholder =" 请输入参数"
199+ />
200+ <el-input-number
201+ v-if =" ['int', 'float'].includes(field.type)"
202+ v-model =" field.value"
203+ class =" w-full"
204+ />
205+ <el-switch
206+ v-if =" ['boolean'].includes(field.type)"
207+ v-model =" field.value"
208+ :active-value =" true"
209+ :inactive-value =" false"
210+ />
211+ </template >
212+ </el-form-item >
213+ </template >
214+ <MkEmpty v-else :image-size =" 60 " />
215+ </el-card >
216+
217+ <el-form-item v-if =" showReturnContent" label =" 返回内容" @click.prevent >
218+ <template #label >
219+ <div class =" flex items-center gap-1" >
220+ <span >返回内容</span >
221+ <el-tooltip content =" 开启后,该节点的输出会作为工作流的最终回复内容" effect =" dark" placement =" right" >
222+ <MkIcon name="icon_help_outlined" class="cursor-help text-N600" />
223+ </el-tooltip >
224+ </div >
225+ </template >
226+ <el-switch v-model =" formData.is_result" size =" small" />
227+ </el-form-item >
228+ </el-form >
229+ </NodeContainer >
230+ </template >
0 commit comments