Skip to content

Commit 52dd41e

Browse files
committed
feat: form node
1 parent 3422c94 commit 52dd41e

9 files changed

Lines changed: 415 additions & 8 deletions

File tree

‎ui/src/components/conversation/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ const FORM = (prev: any, chunk: any) => {
9494
id: chunk.id ?? prev.id,
9595
content: (prev.content || '') + (chunk.content || ''),
9696
form_field_list: chunk.form_field_list ?? prev.form_field_list,
97+
form_content_format: chunk.form_content_format ?? prev.form_content_format,
9798
form_data: chunk.form_data ?? prev.form_data,
9899
is_submit: chunk.is_submit ?? prev.is_submit,
99100
position: chunk.position ?? prev.position,

‎ui/src/components/mk-dynamics-form/constructor/items/KnowledgeConstructor.vue‎

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
<script setup lang="ts">
22
import type { DynamicFormValue } from '../../type'
3-
import { computed, reactive } from 'vue'
3+
import { computed, reactive, ref } from 'vue'
4+
import { CaretBottom } from '@element-plus/icons-vue'
45
import Knowledge from '../../items/knowledge/Knowledge.vue'
56
import type { FormField } from '../../type'
7+
import KnowledgeApi from '@/api/admin/workspace/knowledge/knowledge'
8+
import type { KnowledgeItem } from '@/api/types'
9+
import { FOLDER_ENTRY_ID } from '@/constants'
610
711
const props = defineProps<{ modelValue: DynamicFormValue }>()
812
@@ -37,6 +41,46 @@ const render = (formData: DynamicFormValue) => {
3741
3842
defineExpose({ getData, render })
3943
44+
// ── 添加知识库 ─────────────────────────────────────────
45+
const knowledgeDialogVisible = ref(false)
46+
const knowledgeLoading = ref(false)
47+
const knowledgeSearch = ref('')
48+
const knowledgeList = ref<KnowledgeItem[]>([])
49+
const selectedKnowledgeIds = ref<Array<string>>([])
50+
51+
const filteredKnowledgeList = computed(() => {
52+
const keyword = knowledgeSearch.value.trim().toLocaleLowerCase()
53+
if (!keyword) return knowledgeList.value
54+
return knowledgeList.value.filter((item) => item.name.toLocaleLowerCase().includes(keyword))
55+
})
56+
57+
const openAddKnowledgeDialog = async () => {
58+
knowledgeDialogVisible.value = true
59+
selectedKnowledgeIds.value = (formValue.value.knowledge_list || []).map((k: DynamicFormValue) => k.id)
60+
knowledgeLoading.value = true
61+
try {
62+
const res = await KnowledgeApi.getKnowledgePage({ currentPage: 1, pageSize: 100 }, { folder_id: FOLDER_ENTRY_ID.ALL })
63+
knowledgeList.value = res.records || []
64+
} finally {
65+
knowledgeLoading.value = false
66+
}
67+
}
68+
69+
const handleKnowledgeSelect = () => {
70+
const selectIds = new Set(selectedKnowledgeIds.value)
71+
const currentList = (formValue.value.knowledge_list || []).filter((k: DynamicFormValue) => selectIds.has(k.id))
72+
knowledgeList.value.forEach((item) => {
73+
if (selectIds.has(item.id) && !currentList.some((k: DynamicFormValue) => k.id === item.id)) {
74+
currentList.push({ id: item.id, name: item.name, type: item.type, embedding_model_id: item.embedding_model_id })
75+
}
76+
})
77+
formValue.value.knowledge_list = currentList
78+
if (formValue.value.default_value) {
79+
formValue.value.default_value = formValue.value.default_value.filter((id: string) => selectIds.has(id))
80+
}
81+
knowledgeDialogVisible.value = false
82+
}
83+
4084
function removeKnowledge(id: string) {
4185
formValue.value.knowledge_list = formValue.value.knowledge_list.filter((k: DynamicFormValue) => k.id !== id)
4286
if (formValue.value.default_value) {
@@ -48,17 +92,24 @@ function removeKnowledge(id: string) {
4892
<template>
4993
<el-form-item prop="knowledge_list" :rules="[{ message: '请选择可选知识库', type: 'array', min: 1 }]">
5094
<template #label>
51-
<div class="flex-between mb-12 cursor" @click="collapseData.optional_knowledge = !collapseData.optional_knowledge">
95+
<div class="flex-between mb-2 cursor" @click="collapseData.optional_knowledge = !collapseData.optional_knowledge">
5296
<div class="flex align-center">
53-
<el-icon class="mr-8 arrow-icon" :class="collapseData.optional_knowledge ? 'rotate-90' : ''">
54-
<CaretRight />
55-
</el-icon>
97+
<MkIcon
98+
:icon="CaretBottom"
99+
:size="14"
100+
class="mr-1 text-N600! transition-transform"
101+
:class="{ '-rotate-90': !collapseData.optional_knowledge }"
102+
/>
56103
<span class="lighter"
57104
>可选知识库
58-
<span class="color-danger">*</span>
105+
<span class="text-danger">*</span>
59106
</span>
60-
<span class="ml-4" v-if="formValue.knowledge_list?.length">({{ formValue.knowledge_list.length }})</span>
107+
<span class="ml-1" v-if="formValue.knowledge_list?.length">({{ formValue.knowledge_list.length }})</span>
61108
</div>
109+
<el-button type="primary" link @click.stop="openAddKnowledgeDialog">
110+
<MkIcon name="icon_add_outlined" class="mr-1" />
111+
添加
112+
</el-button>
62113
</div>
63114
</template>
64115
<div class="w-full" v-if="collapseData.optional_knowledge">
@@ -90,4 +141,27 @@ function removeKnowledge(id: string) {
90141
<Knowledge v-model="formValue.default_value" :form-field="formField" />
91142
</div>
92143
</el-form-item>
144+
145+
<MkDialog v-model="knowledgeDialogVisible" title="添加知识库" width="600px" append-to-body>
146+
<el-input v-model="knowledgeSearch" placeholder="按名称搜索" clearable class="mb-3">
147+
<template #prefix>
148+
<MkIcon name="icon_magnify_outlined" />
149+
</template>
150+
</el-input>
151+
<div v-loading="knowledgeLoading" class="max-h-[360px] overflow-auto">
152+
<el-checkbox-group v-model="selectedKnowledgeIds">
153+
<el-checkbox :value="item.id" class="-mr-2 w-full mb-2 rounded-md border border-N300 p-2" v-for="item in filteredKnowledgeList" :key="item.id">
154+
<span class="flex items-center gap-2">
155+
<KnowledgeIcon :type="item.type" :size="20" style="--el-avatar-border-radius: 6px" />
156+
<span class="truncate">{{ item.name }}</span>
157+
</span>
158+
</el-checkbox>
159+
</el-checkbox-group>
160+
<el-empty v-if="!knowledgeLoading && filteredKnowledgeList.length === 0" description="暂无知识库" :image-size="60" />
161+
</div>
162+
<template #footer>
163+
<el-button @click="knowledgeDialogVisible = false">取消</el-button>
164+
<el-button type="primary" @click="handleKnowledgeSelect">确定</el-button>
165+
</template>
166+
</MkDialog>
93167
</template>

‎ui/src/components/mk-dynamics-form/index.vue‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ const visibilityCompareHandlers: Record<VisibilityCompareOperator, (left: Dynami
111111
}
112112
113113
function getVisibilityConditionValue(condition: VisibilityCondition, values: Dict<DynamicFormValue>): DynamicFormValue {
114-
return condition.self ? values[condition.field[1]] : condition.leftValue
114+
// self 取表单自身字段值;跨节点使用后端已解析的左值(_left / leftValue)
115+
return condition.self ? values[condition.field[1]] : (condition._left ?? condition.leftValue)
115116
}
116117
117118
function evaluateVisibility(rules: VisibilityRules | null | undefined, values: Dict<DynamicFormValue>): boolean {

‎ui/src/components/mk-dynamics-form/type.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface VisibilityCondition {
2525
compare: VisibilityCompareOperator | ''
2626
value: DynamicFormValue
2727
leftValue?: DynamicFormValue
28+
_left?: DynamicFormValue
2829
}
2930

3031
export interface VisibilityConditionState extends VisibilityCondition {

‎ui/src/workflow-canvas/component/NodeMenu.vue‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const workflowComponentGroups = [
3939
label: '业务逻辑',
4040
list: [
4141
{ icon: aiChatIcon, iconClass: 'bg-purple', label: '判断器', value: WorkflowNodeType.Condition },
42+
{ icon: aiChatIcon, iconClass: 'bg-success', label: '表单收集', value: WorkflowNodeType.FormNode },
4243
{ icon: replyIcon, iconClass: 'bg-warning', label: '指定回复', value: WorkflowNodeType.Reply },
4344
],
4445
},
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<template>
2+
<el-avatar class="avatar-gradient" shape="square">
3+
<img src="@/assets/workflow/icon_ai_chat.svg" style="width: 75%" alt="" />
4+
</el-avatar>
5+
</template>
6+
<script setup lang="ts"></script>

‎ui/src/workflow-canvas/index.vue‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { initDefaultShortcut } from '@/workflow-canvas/core/shortcut'
1010
import { disconnectAll, getTeleport } from '@/workflow-canvas/core/teleport'
1111
import type { WorkflowNodeModel } from '@/workflow-canvas/core/workflow-node'
1212
import Dagre from '@/workflow-canvas/plugins/dagre'
13+
import modelAPI from '@/api/admin/workspace/model/model'
1314
import { WorkflowMode, WorkflowNodeType } from '@/workflow-canvas/types'
1415
import { type ShapeItem } from '@/workflow-canvas/types'
1516
defineOptions({ name: 'MkWorkflow' })
@@ -64,6 +65,8 @@ function renderGraphData(data: LogicFlow.GraphConfigData = props.data ?? {}) {
6465
workflowMode: workflow_mode,
6566
loopWorkflowMode: loop_workflow_mode,
6667
apiType: 'workspace',
68+
getSelectModelList: (params: { model_type: string }) => modelAPI.getModelList(params),
69+
getModelParamsForm: (modelId: string) => modelAPI.getModelParamsForm(modelId),
6770
})
6871
lf.value.graphModel.eventCenter.on('delete_edge', (edgeIds: string[]) => {
6972
edgeIds.forEach((edgeId) => lf.value?.deleteEdge(edgeId))
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import FormNodeVue from './index.vue'
2+
import { WorkflowNodeModel, WorkflowNodeView } from '@/workflow-canvas/core/workflow-node'
3+
import { WorkflowNodeType } from '@/workflow-canvas/types'
4+
5+
class FormNodeView extends WorkflowNodeView {
6+
constructor(props: ConstructorParameters<typeof WorkflowNodeView>[0]) {
7+
super(props, FormNodeVue)
8+
}
9+
}
10+
11+
export default { type: WorkflowNodeType.FormNode, model: WorkflowNodeModel, view: FormNodeView }

0 commit comments

Comments
 (0)