Skip to content

Commit f29a8c3

Browse files
authored
fix: The knowledge base workflow data source can only be the starting node (#4409)
1 parent 7e9c440 commit f29a8c3

File tree

4 files changed

+340
-3
lines changed

4 files changed

+340
-3
lines changed

ui/src/components/workflow-dropdown-menu/index.vue

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import { inject } from 'vue'
66
import { WorkflowMode } from '@/enums/application'
77
import ApplicationDropdownMenu from '@/components/workflow-dropdown-menu/application/index.vue'
88
import KnowledgeDropdownMenu from '@/components/workflow-dropdown-menu/knowledge/index.vue'
9+
import KnowledgeDropdownInnerMenu from '@/components/workflow-dropdown-menu/knowledge-inner/index.vue'
910
const workflow_mode: WorkflowMode = inject('workflowMode') || WorkflowMode.Application
10-
defineProps({
11+
const props = defineProps({
1112
show: {
1213
type: Boolean,
1314
default: false,
@@ -17,12 +18,16 @@ defineProps({
1718
default: '',
1819
},
1920
workflowRef: Object,
21+
inner: {
22+
type: Boolean,
23+
default: false,
24+
},
2025
})
2126
const kw: any = {
2227
[WorkflowMode.Application]: ApplicationDropdownMenu,
2328
[WorkflowMode.ApplicationLoop]: ApplicationDropdownMenu,
24-
[WorkflowMode.Knowledge]: KnowledgeDropdownMenu,
25-
[WorkflowMode.KnowledgeLoop]: KnowledgeDropdownMenu,
29+
[WorkflowMode.Knowledge]: props.inner ? KnowledgeDropdownInnerMenu : KnowledgeDropdownMenu,
30+
[WorkflowMode.KnowledgeLoop]: props.inner ? KnowledgeDropdownInnerMenu : KnowledgeDropdownMenu,
2631
}
2732
</script>
2833
<style lang="scss">
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<template>
2+
<el-input
3+
v-model.trim="filterText"
4+
:placeholder="$t('common.search')"
5+
prefix-icon="Search"
6+
clearable
7+
style="padding: 12px 12px 0 12px"
8+
/>
9+
<div class="list flex-wrap">
10+
<template v-if="filterList.length">
11+
<el-popover
12+
v-for="item in filterList"
13+
:key="item.id"
14+
placement="right"
15+
:width="280"
16+
:show-after="500"
17+
>
18+
<template #reference>
19+
<div
20+
class="list-item flex align-center border border-r-6 p-8-12 cursor"
21+
style="width: calc(50% - 6px)"
22+
@click.stop="emit('clickNodes', item)"
23+
@mousedown.stop="emit('onmousedown', item)"
24+
>
25+
<el-avatar
26+
v-if="isAppIcon(item?.icon)"
27+
shape="square"
28+
:size="20"
29+
style="background: none"
30+
>
31+
<img :src="resetUrl(item?.icon, resetUrl('./favicon.ico'))" alt="" />
32+
</el-avatar>
33+
<ToolIcon v-else :size="20" :type="item?.tool_type" />
34+
<span class="ml-8 ellipsis" :title="item.name">{{ item.name }}</span>
35+
</div>
36+
</template>
37+
38+
<template #default>
39+
<div class="flex-between">
40+
<div class="flex align-center">
41+
<el-avatar
42+
v-if="isAppIcon(item?.icon)"
43+
shape="square"
44+
:size="20"
45+
style="background: none"
46+
>
47+
<img :src="resetUrl(item?.icon, resetUrl('./favicon.ico'))" alt="" />
48+
</el-avatar>
49+
<ToolIcon v-else :size="20" :type="item?.tool_type" />
50+
<span class="font-medium ml-8 break-all" :title="item.name">{{ item.name }}</span>
51+
</div>
52+
</div>
53+
<el-text type="info" size="small" class="mt-4">{{ item.desc }}</el-text>
54+
</template>
55+
</el-popover>
56+
</template>
57+
<el-empty v-else :description="$t('common.noData')" />
58+
</div>
59+
</template>
60+
61+
<script setup lang="ts">
62+
import { watch, ref } from 'vue'
63+
import { isAppIcon, resetUrl } from '@/utils/common'
64+
65+
const props = defineProps<{
66+
list: any[]
67+
}>()
68+
69+
const emit = defineEmits<{
70+
(e: 'clickNodes', item: any): void
71+
(e: 'onmousedown', item: any): void
72+
}>()
73+
74+
const filterText = ref('')
75+
const filterList = ref<any[]>([])
76+
77+
function filter(list: any[], filterText: string) {
78+
if (!filterText.length) {
79+
return list
80+
}
81+
return list.filter((v: any) => v.name.toLowerCase().includes(filterText.toLowerCase()))
82+
}
83+
84+
watch([() => filterText.value, () => props.list], () => {
85+
filterList.value = filter(props.list, filterText.value)
86+
})
87+
</script>
88+
89+
<style lang="scss" scoped></style>
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
<template>
2+
<div
3+
v-show="show"
4+
class="workflow-dropdown-menu border border-r-6 white-bg"
5+
:style="{ width: activeName === 'base' ? '400px' : '640px' }"
6+
>
7+
<el-tabs v-model="activeName" class="workflow-dropdown-tabs" @tab-change="handleClick">
8+
<div
9+
v-show="activeName === 'base'"
10+
style="display: flex; width: 100%; justify-content: center"
11+
class="mb-12 mt-12"
12+
>
13+
<el-input
14+
v-model="search_text"
15+
class="mr-12 ml-12"
16+
:placeholder="$t('common.searchBar.placeholder')"
17+
>
18+
<template #suffix>
19+
<el-icon class="el-input__icon">
20+
<search />
21+
</el-icon>
22+
</template>
23+
</el-input>
24+
</div>
25+
26+
<el-tab-pane :label="$t('views.workflow.baseComponent')" name="base">
27+
<el-scrollbar height="400">
28+
<div v-if="filter_menu_nodes.length > 0">
29+
<template v-for="(node, index) in filter_menu_nodes" :key="index">
30+
<el-text type="info" size="small" class="color-secondary ml-12">{{
31+
node.label
32+
}}</el-text>
33+
<div class="flex-wrap" style="gap: 12px; padding: 12px">
34+
<template v-for="(item, index) in node.list" :key="index">
35+
<el-popover placement="right" :width="280" :show-after="500">
36+
<template #reference>
37+
<div
38+
class="list-item flex align-center border border-r-6 p-8-12 cursor"
39+
style="width: calc(50% - 6px)"
40+
@click.stop="clickNodes(item)"
41+
@mousedown.stop="onmousedown(item)"
42+
>
43+
<component
44+
:is="iconComponent(`${item.type}-icon`)"
45+
class="mr-8"
46+
:size="20"
47+
/>
48+
<div class="lighter">{{ item.label }}</div>
49+
</div>
50+
</template>
51+
<template #default>
52+
<div class="flex align-center mb-8">
53+
<component
54+
:is="iconComponent(`${item.type}-icon`)"
55+
class="mr-8"
56+
:size="32"
57+
/>
58+
<div class="lighter color-text-primary">{{ item.label }}</div>
59+
</div>
60+
<el-text type="info" size="small" class="color-secondary lighter">{{
61+
item.text
62+
}}</el-text>
63+
</template>
64+
</el-popover>
65+
</template>
66+
</div>
67+
</template>
68+
</div>
69+
<div v-else class="ml-16 mt-8">
70+
<el-text type="info">{{ $t('views.workflow.tip.noData') }}</el-text>
71+
</div>
72+
</el-scrollbar>
73+
</el-tab-pane>
74+
<!-- 工具 -->
75+
<el-tab-pane :label="$t('views.tool.title')" name="CUSTOM_TOOL">
76+
<LayoutContainer>
77+
<template #left>
78+
<folder-tree
79+
:source="SourceTypeEnum.TOOL"
80+
:data="toolTreeData"
81+
:currentNodeKey="folder.currentFolder?.id"
82+
@handleNodeClick="folderClickHandle"
83+
:shareTitle="$t('views.shared.shared_tool')"
84+
:showShared="permissionPrecise['is_share']()"
85+
:canOperation="false"
86+
:treeStyle="{ height: '400px' }"
87+
/>
88+
</template>
89+
<el-scrollbar height="450">
90+
<NodeContent
91+
:list="toolList"
92+
@clickNodes="(val: any) => clickNodes(toolLibNode, val)"
93+
@onmousedown="(val: any) => onmousedown(toolLibNode, val)"
94+
/>
95+
</el-scrollbar>
96+
</LayoutContainer>
97+
</el-tab-pane>
98+
</el-tabs>
99+
</div>
100+
</template>
101+
<script setup lang="ts">
102+
import { ref, onMounted, computed, inject } from 'vue'
103+
import { getMenuNodes, toolLibNode, applicationNode } from '@/workflow/common/data'
104+
import { iconComponent } from '@/workflow/icons/utils'
105+
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
106+
import useStore from '@/stores'
107+
import NodeContent from './NodeContent.vue'
108+
import { SourceTypeEnum } from '@/enums/common'
109+
import permissionMap from '@/permission'
110+
import { useRoute } from 'vue-router'
111+
import { WorkflowKind, WorkflowMode } from '@/enums/application'
112+
const workflowModel = inject('workflowMode') as WorkflowMode
113+
const route = useRoute()
114+
const { user, folder } = useStore()
115+
116+
const menuNodes = getMenuNodes(workflowModel || WorkflowMode.Application)?.filter(
117+
(item, index) => index > 0,
118+
)
119+
const search_text = ref<string>('')
120+
const props = defineProps({
121+
show: {
122+
type: Boolean,
123+
default: false,
124+
},
125+
id: {
126+
type: String,
127+
default: '',
128+
},
129+
workflowRef: Object,
130+
})
131+
132+
const emit = defineEmits(['clickNodes', 'onmousedown'])
133+
134+
const apiType = computed(() => {
135+
if (route.path.includes('resource-management')) {
136+
return 'systemManage'
137+
} else {
138+
return 'workspace'
139+
}
140+
})
141+
const permissionPrecise = computed(() => {
142+
return permissionMap['tool'][apiType.value]
143+
})
144+
145+
const loading = ref(false)
146+
const activeName = ref('base')
147+
148+
const filter_menu_nodes = computed(() => {
149+
if (!search_text.value) return menuNodes || []
150+
const searchTerm = search_text.value.toLowerCase()
151+
152+
return (menuNodes || []).reduce((result: any[], item) => {
153+
const filteredList = item.list.filter((listItem) =>
154+
listItem.label.toLowerCase().includes(searchTerm),
155+
)
156+
157+
if (filteredList.length) {
158+
result.push({ ...item, list: filteredList })
159+
}
160+
161+
return result
162+
}, [])
163+
})
164+
function clickNodes(item: any, data?: any) {
165+
if (data) {
166+
item['properties']['stepName'] = data.name
167+
168+
if (data.tool_type == 'DATA_SOURCE') {
169+
item['properties'].kind = WorkflowKind.DataSource
170+
}
171+
item['properties']['node_data'] = {
172+
...data,
173+
tool_lib_id: data.id,
174+
input_field_list: data.input_field_list.map((field: any) => ({
175+
...field,
176+
value: field.source == 'reference' ? [] : '',
177+
})),
178+
}
179+
}
180+
props.workflowRef?.addNode(item)
181+
182+
emit('clickNodes', item)
183+
}
184+
185+
function onmousedown(item: any, data?: any) {
186+
if (data) {
187+
item['properties']['stepName'] = data.name
188+
if (data.tool_type == 'DATA_SOURCE') {
189+
item['properties'].kind = WorkflowKind.DataSource
190+
}
191+
item['properties']['node_data'] = {
192+
...data,
193+
tool_lib_id: data.id,
194+
input_field_list: data.input_field_list.map((field: any) => ({
195+
...field,
196+
value: field.source == 'reference' ? [] : '',
197+
})),
198+
}
199+
}
200+
props.workflowRef?.onmousedown(item)
201+
emit('onmousedown', item)
202+
}
203+
204+
const toolTreeData = ref<any[]>([])
205+
const toolList = ref<any[]>([])
206+
207+
async function getToolFolder() {
208+
const res: any = await folder.asyncGetFolder(SourceTypeEnum.TOOL, {}, loading)
209+
toolTreeData.value = res.data
210+
folder.setCurrentFolder(res.data?.[0] || {})
211+
}
212+
213+
async function getToolList() {
214+
const res = await loadSharedApi({
215+
type: 'tool',
216+
isShared: folder.currentFolder?.id === 'share',
217+
systemType: 'workspace',
218+
}).getToolList({
219+
folder_id: folder.currentFolder?.id || user.getWorkspaceId(),
220+
tool_type: activeName.value == 'DATA_SOURCE_TOOL' ? 'DATA_SOURCE' : 'CUSTOM',
221+
})
222+
toolList.value = res.data?.tools || res.data || []
223+
toolList.value = toolList.value?.filter((item: any) => item.is_active)
224+
}
225+
226+
function folderClickHandle(row: any) {
227+
folder.setCurrentFolder(row)
228+
if (['DATA_SOURCE_TOOL', 'CUSTOM_TOOL'].includes(activeName.value)) {
229+
getToolList()
230+
}
231+
}
232+
233+
async function handleClick(val: string) {
234+
if (['DATA_SOURCE_TOOL', 'CUSTOM_TOOL'].includes(val)) {
235+
await getToolFolder()
236+
getToolList()
237+
}
238+
}
239+
240+
onMounted(() => {})
241+
</script>
242+
<style lang="scss" scoped></style>

ui/src/workflow/common/NodeContainer.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
@click.stop
127127
@wheel="handleWheel"
128128
:show="showAnchor"
129+
:inner="true"
129130
:id="id"
130131
style="left: 100%; top: 50%; transform: translate(0, -50%)"
131132
@clickNodes="clickNodes"

0 commit comments

Comments
 (0)