-
Notifications
You must be signed in to change notification settings - Fork 2.6k
fix: The knowledge base workflow data source can only be the starting node #4409
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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> | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The provided code is mostly well-structured but has a few improvements and adjustments that can be made to enhance its readability and functionality: Improvements/Adjustments
Optimizations/Suggestions
Here's an optimized version of the code based on these suggestions: @@ -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 px-12 text-sm cursor-pointer"
+ role="button"
+ @mousedown.stop="handleClick(item)"
+ data-id="`${item.id}`"
+ >
+ <app-icon-vue v-if="isAppIcon(item?.icon)" :size="20" :style="{ display: 'block', margin: '-2px auto' }"></app-icon-vue>
+ <!-- Note: AppIconVue should be defined elsewhere -->
+ <tool-icon v-else :size="20" :type="item?.tool_type" class=""></tool-icon>
+ <span class="ml-[4px] overflow-x-hidden whitespace-nowrap" title>{{ item.name }}
+ </div>
+ </template>
+
+ <template #default>
+ <div class="flex justify-between items-center pt-4">
+ <div class="flex items-center font-semibold leading-none group relative">
+ <app-icon-vue v-if="isAppIcon(item?.icon)" :size="20" :style="{ display: 'inline-block', marginRight: '6px' }"></app-icon-vue>
+ <tool-icon v-else :size="20" :type="item?.tool_type" class=""></tool-icon>
+ {{ item.name }}
+ </div>
+ <div class="text-md"><em>{{ item.desc }}</em></div>
+ </div>
+ </template>
+ </el-popover>
+ </template>
+ <el-empty v-else :description="$t('common.noData')" />
+ </div>
+</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import ToolIcon from './components/ToolIcon.vue'; // Adjust path accordingly
import AppIconVue from './components/AppIconVue.vue'; // Add appropriate import for svg icons
const props = defineProps({
list: {
type: Array,
required: true,
},
});
const emit = defineEmits(['clickNodes']);
const filterText = ref('');
const filteredItems = computed(() =>
filter(props.list, filterText.value)
);
const handleClick = (item: any) => {
emit('clickNodes', item);
};
onMounted(() => {});
</script>
<style scoped lang="scss"></style>Changes Made
Make sure to adjust the paths according to your actual project directory layout. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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> | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are several areas where this Vue component can be improved:
Here's an updated version addressing some of these points: <script setup lang="ts">
// Remove unnecessary imports after checking usage
import { ref, onMounted, computed, inject } from 'vue';
interface MenuNode {
label: string;
list: { label: string; type: string, text?: string }[];
}
interface DataField {
// Define your data field interface here
}
let search_text = ref<string>('');
const menuNodes: Array<MenuNode> =
getMenuNodes(workflowMode || WorkflowMode.Application)!
.filter((item, index) => index >= 1);
const loading = ref(false);
const activeName = ref('base');
const apiType: ComputedRef<'systemManage' | 'workspace'> = computed(() =>
route.path.includes('resource-management') ? 'systemManage' : 'workspace',
);
const permissionPrecise = computed(() =>
permissionMap['tool'][apiType.value],
);
const filter_menu_nodes = computed(() => {
if (!search_text.value.trim()) return menuNodes;
const searchTerm = search_text.value.toLowerCase();
return menuNodes.reduce<Array<{ label: string; list: MenuNode[] }>>((result, item) => {
const filteredList = item.list.filter(listItem =>
listItem.label.toLowerCase().includes(searchTerm),
);
if (filteredList.length) {
result.push({ ...item, list: filteredList });
}
return result.filter(({ list }) => list.length); // Filter out empty lists
}, []);
});
function onClickNodes(item: any, data?: any): void {
if (!data) return;
item.properties.stepName ??= data.name;
if ([...Object.values(WorkflowKind)].includes(data.tool_type as never)) {
item.properties.kind = WorkflowKind[data.tool_type];
}
const inputDataFields: { [key: string]: string | DataField[] } = {};
data.input_field_list.forEach(field => {
inputDataFields[field.source] ||= [];
if (field.source !== 'reference') {
inputDataFields[field.source].push({});
}
});
item.properties.node_data = { ...data, ...inputDataFields };
props.workflowRef.addNode(item);
emit('clickNodes', item);
emit('onmousedown', item);
}
function onMouseDown(item: any, data?: any): void {
onClickNodes(item, data);
}
const toolTreeData = ref([]);
const toolList = ref([]);
async function getToolFolder(): Promise<void> {
try {
const res = await folder.asyncGetFolder(SourceTypeEnum.TOOL, {}, loading);
toolTreeData.value = res?.data || [];
folder.setCurrentFolder(toolTreeData.value[0]! || {});
} catch (error) {
console.error(error);
}
};
async function getToolList(folderId: string = ''): Promise<void> {
try {
const toolsRes = await loadSharedApi({
type: 'tool',
isShared: folderId === 'share',
systemType: apiType.value,
}).getToolList({
folder_id: folderId || user.getWorkspaceId(),
tool_type: activeName.value.includes('DATA_SOURCE_TOOL')
? WorkflowKind.DataSource
: 'CUSTOM',
});
toolList.value = (toolsRes?.data ?? {}).tools || toolsRes.data || [];
toolList.value = toolList.value?.filter(({ isActive }) => isActive);
} catch (error) {
console.error(error);
}
};
function folderClickHandler(nodeOrFolderId: string) {
folder.setCurrentFolder(typeof nodeOrFolderId === 'string' ? { id: nodeOrFolderId } : nodeOrFolderId);
if (activeName.value.includes(['DATA_SOURCE_TOOL', 'CUSTOM_TOOL'])) updateToolList();
}
async function updateToolList(): Promise<void> {
folder.currentFolder && !folder.currentFolder.id.startsWith('share')
? getToolFolder()
: await getToolList();
await getToolList();
}
// Call handleUpdate when clicking on tab base
const handleUpdate = ():void => handleClick(activeName.value);
watch([() => props.show], () => {
if (!props.show) resetState(); // Reset state when dropdown hides
});
/**
* Resets all states associated with the component display.
*/
const resetState = (): void => {
search_text.value = '';
activeName.value = 'base'; // Set back to initial tab?
};
onMounted(async () => {
const currentUserId = await fetchCurrentUserId();
folder.setInitUser(currentUserId);
resetState();
});
</script>
<style scoped=""This refactor attempts to address some common issues by focusing on performance improvements, readability, and better handling of dynamic inputs. Remember, further analysis might be necessary based on actual performance testing and feedback. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are several minor improvements and potential optimizations:
Import of Component: Ensure that
KnowledgeDropdownInnerMenuis correctly added to the module imports section at line 4.Consistent Property Names: Use consistent property names throughout the component definition, such as using PascalCase for component properties (
show,workflowType, etc.) and camelCase for other variables (wfMode).Destructuring Assignment: Instead of assigning directly to a variable (e.g.,
const wfMode = inject('workflowMode') || WorkflowMode.Application), consider deconstructing it from an object with a more descriptive name.Here's the revised code snippet:
Additional Notes:
injectprovides the necessary context. You might need to adjust package/module paths based on your project structure.