fix: The knowledge base workflow data source can only be the starting node

This commit is contained in:
shaohuzhang1 2025-12-01 18:18:23 +08:00
parent 7e1c2c2166
commit d6ad384ae8
4 changed files with 340 additions and 3 deletions

View File

@ -6,8 +6,9 @@ import { inject } from 'vue'
import { WorkflowMode } from '@/enums/application'
import ApplicationDropdownMenu from '@/components/workflow-dropdown-menu/application/index.vue'
import KnowledgeDropdownMenu from '@/components/workflow-dropdown-menu/knowledge/index.vue'
import KnowledgeDropdownInnerMenu from '@/components/workflow-dropdown-menu/knowledge-inner/index.vue'
const workflow_mode: WorkflowMode = inject('workflowMode') || WorkflowMode.Application
defineProps({
const props = defineProps({
show: {
type: Boolean,
default: false,
@ -17,12 +18,16 @@ defineProps({
default: '',
},
workflowRef: Object,
inner: {
type: Boolean,
default: false,
},
})
const kw: any = {
[WorkflowMode.Application]: ApplicationDropdownMenu,
[WorkflowMode.ApplicationLoop]: ApplicationDropdownMenu,
[WorkflowMode.Knowledge]: KnowledgeDropdownMenu,
[WorkflowMode.KnowledgeLoop]: KnowledgeDropdownMenu,
[WorkflowMode.Knowledge]: props.inner ? KnowledgeDropdownInnerMenu : KnowledgeDropdownMenu,
[WorkflowMode.KnowledgeLoop]: props.inner ? KnowledgeDropdownInnerMenu : KnowledgeDropdownMenu,
}
</script>
<style lang="scss">

View File

@ -0,0 +1,89 @@
<template>
<el-input
v-model.trim="filterText"
:placeholder="$t('common.search')"
prefix-icon="Search"
clearable
style="padding: 12px 12px 0 12px"
/>
<div class="list flex-wrap">
<template v-if="filterList.length">
<el-popover
v-for="item in filterList"
:key="item.id"
placement="right"
:width="280"
:show-after="500"
>
<template #reference>
<div
class="list-item flex align-center border border-r-6 p-8-12 cursor"
style="width: calc(50% - 6px)"
@click.stop="emit('clickNodes', item)"
@mousedown.stop="emit('onmousedown', item)"
>
<el-avatar
v-if="isAppIcon(item?.icon)"
shape="square"
:size="20"
style="background: none"
>
<img :src="resetUrl(item?.icon, resetUrl('./favicon.ico'))" alt="" />
</el-avatar>
<ToolIcon v-else :size="20" :type="item?.tool_type" />
<span class="ml-8 ellipsis" :title="item.name">{{ item.name }}</span>
</div>
</template>
<template #default>
<div class="flex-between">
<div class="flex align-center">
<el-avatar
v-if="isAppIcon(item?.icon)"
shape="square"
:size="20"
style="background: none"
>
<img :src="resetUrl(item?.icon, resetUrl('./favicon.ico'))" alt="" />
</el-avatar>
<ToolIcon v-else :size="20" :type="item?.tool_type" />
<span class="font-medium ml-8 break-all" :title="item.name">{{ item.name }}</span>
</div>
</div>
<el-text type="info" size="small" class="mt-4">{{ item.desc }}</el-text>
</template>
</el-popover>
</template>
<el-empty v-else :description="$t('common.noData')" />
</div>
</template>
<script setup lang="ts">
import { watch, ref } from 'vue'
import { isAppIcon, resetUrl } from '@/utils/common'
const props = defineProps<{
list: any[]
}>()
const emit = defineEmits<{
(e: 'clickNodes', item: any): void
(e: 'onmousedown', item: any): void
}>()
const filterText = ref('')
const filterList = ref<any[]>([])
function filter(list: any[], filterText: string) {
if (!filterText.length) {
return list
}
return list.filter((v: any) => v.name.toLowerCase().includes(filterText.toLowerCase()))
}
watch([() => filterText.value, () => props.list], () => {
filterList.value = filter(props.list, filterText.value)
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,242 @@
<template>
<div
v-show="show"
class="workflow-dropdown-menu border border-r-6 white-bg"
:style="{ width: activeName === 'base' ? '400px' : '640px' }"
>
<el-tabs v-model="activeName" class="workflow-dropdown-tabs" @tab-change="handleClick">
<div
v-show="activeName === 'base'"
style="display: flex; width: 100%; justify-content: center"
class="mb-12 mt-12"
>
<el-input
v-model="search_text"
class="mr-12 ml-12"
:placeholder="$t('common.searchBar.placeholder')"
>
<template #suffix>
<el-icon class="el-input__icon">
<search />
</el-icon>
</template>
</el-input>
</div>
<el-tab-pane :label="$t('views.workflow.baseComponent')" name="base">
<el-scrollbar height="400">
<div v-if="filter_menu_nodes.length > 0">
<template v-for="(node, index) in filter_menu_nodes" :key="index">
<el-text type="info" size="small" class="color-secondary ml-12">{{
node.label
}}</el-text>
<div class="flex-wrap" style="gap: 12px; padding: 12px">
<template v-for="(item, index) in node.list" :key="index">
<el-popover placement="right" :width="280" :show-after="500">
<template #reference>
<div
class="list-item flex align-center border border-r-6 p-8-12 cursor"
style="width: calc(50% - 6px)"
@click.stop="clickNodes(item)"
@mousedown.stop="onmousedown(item)"
>
<component
:is="iconComponent(`${item.type}-icon`)"
class="mr-8"
:size="20"
/>
<div class="lighter">{{ item.label }}</div>
</div>
</template>
<template #default>
<div class="flex align-center mb-8">
<component
:is="iconComponent(`${item.type}-icon`)"
class="mr-8"
:size="32"
/>
<div class="lighter color-text-primary">{{ item.label }}</div>
</div>
<el-text type="info" size="small" class="color-secondary lighter">{{
item.text
}}</el-text>
</template>
</el-popover>
</template>
</div>
</template>
</div>
<div v-else class="ml-16 mt-8">
<el-text type="info">{{ $t('views.workflow.tip.noData') }}</el-text>
</div>
</el-scrollbar>
</el-tab-pane>
<!-- 工具 -->
<el-tab-pane :label="$t('views.tool.title')" name="CUSTOM_TOOL">
<LayoutContainer>
<template #left>
<folder-tree
:source="SourceTypeEnum.TOOL"
:data="toolTreeData"
:currentNodeKey="folder.currentFolder?.id"
@handleNodeClick="folderClickHandle"
:shareTitle="$t('views.shared.shared_tool')"
:showShared="permissionPrecise['is_share']()"
:canOperation="false"
:treeStyle="{ height: '400px' }"
/>
</template>
<el-scrollbar height="450">
<NodeContent
:list="toolList"
@clickNodes="(val: any) => clickNodes(toolLibNode, val)"
@onmousedown="(val: any) => onmousedown(toolLibNode, val)"
/>
</el-scrollbar>
</LayoutContainer>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed, inject } from 'vue'
import { getMenuNodes, toolLibNode, applicationNode } from '@/workflow/common/data'
import { iconComponent } from '@/workflow/icons/utils'
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
import useStore from '@/stores'
import NodeContent from './NodeContent.vue'
import { SourceTypeEnum } from '@/enums/common'
import permissionMap from '@/permission'
import { useRoute } from 'vue-router'
import { WorkflowKind, WorkflowMode } from '@/enums/application'
const workflowModel = inject('workflowMode') as WorkflowMode
const route = useRoute()
const { user, folder } = useStore()
const menuNodes = getMenuNodes(workflowModel || WorkflowMode.Application)?.filter(
(item, index) => index > 0,
)
const search_text = ref<string>('')
const props = defineProps({
show: {
type: Boolean,
default: false,
},
id: {
type: String,
default: '',
},
workflowRef: Object,
})
const emit = defineEmits(['clickNodes', 'onmousedown'])
const apiType = computed(() => {
if (route.path.includes('resource-management')) {
return 'systemManage'
} else {
return 'workspace'
}
})
const permissionPrecise = computed(() => {
return permissionMap['tool'][apiType.value]
})
const loading = ref(false)
const activeName = ref('base')
const filter_menu_nodes = computed(() => {
if (!search_text.value) return menuNodes || []
const searchTerm = search_text.value.toLowerCase()
return (menuNodes || []).reduce((result: any[], item) => {
const filteredList = item.list.filter((listItem) =>
listItem.label.toLowerCase().includes(searchTerm),
)
if (filteredList.length) {
result.push({ ...item, list: filteredList })
}
return result
}, [])
})
function clickNodes(item: any, data?: any) {
if (data) {
item['properties']['stepName'] = data.name
if (data.tool_type == 'DATA_SOURCE') {
item['properties'].kind = WorkflowKind.DataSource
}
item['properties']['node_data'] = {
...data,
tool_lib_id: data.id,
input_field_list: data.input_field_list.map((field: any) => ({
...field,
value: field.source == 'reference' ? [] : '',
})),
}
}
props.workflowRef?.addNode(item)
emit('clickNodes', item)
}
function onmousedown(item: any, data?: any) {
if (data) {
item['properties']['stepName'] = data.name
if (data.tool_type == 'DATA_SOURCE') {
item['properties'].kind = WorkflowKind.DataSource
}
item['properties']['node_data'] = {
...data,
tool_lib_id: data.id,
input_field_list: data.input_field_list.map((field: any) => ({
...field,
value: field.source == 'reference' ? [] : '',
})),
}
}
props.workflowRef?.onmousedown(item)
emit('onmousedown', item)
}
const toolTreeData = ref<any[]>([])
const toolList = ref<any[]>([])
async function getToolFolder() {
const res: any = await folder.asyncGetFolder(SourceTypeEnum.TOOL, {}, loading)
toolTreeData.value = res.data
folder.setCurrentFolder(res.data?.[0] || {})
}
async function getToolList() {
const res = await loadSharedApi({
type: 'tool',
isShared: folder.currentFolder?.id === 'share',
systemType: 'workspace',
}).getToolList({
folder_id: folder.currentFolder?.id || user.getWorkspaceId(),
tool_type: activeName.value == 'DATA_SOURCE_TOOL' ? 'DATA_SOURCE' : 'CUSTOM',
})
toolList.value = res.data?.tools || res.data || []
toolList.value = toolList.value?.filter((item: any) => item.is_active)
}
function folderClickHandle(row: any) {
folder.setCurrentFolder(row)
if (['DATA_SOURCE_TOOL', 'CUSTOM_TOOL'].includes(activeName.value)) {
getToolList()
}
}
async function handleClick(val: string) {
if (['DATA_SOURCE_TOOL', 'CUSTOM_TOOL'].includes(val)) {
await getToolFolder()
getToolList()
}
}
onMounted(() => {})
</script>
<style lang="scss" scoped></style>

View File

@ -126,6 +126,7 @@
@click.stop
@wheel="handleWheel"
:show="showAnchor"
:inner="true"
:id="id"
style="left: 100%; top: 50%; transform: translate(0, -50%)"
@clickNodes="clickNodes"