-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat: AI dialogue nodes support calling tools configured in the system #3896
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 |
|---|---|---|
|
|
@@ -56,7 +56,7 @@ div:focus { | |
| } | ||
|
|
||
| ul { | ||
| list-style: circle; | ||
| list-style: none; | ||
| margin: 0; | ||
| padding: 0; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,89 +1,227 @@ | ||
| <template> | ||
| <el-dialog | ||
| align-center | ||
| :title="$t('common.setting')" | ||
| v-model="dialogVisible" | ||
| style="width: 550px" | ||
| width="1000" | ||
| append-to-body | ||
| class="addTool-dialog" | ||
| align-center | ||
| :close-on-click-modal="false" | ||
| :close-on-press-escape="false" | ||
| > | ||
| <el-form | ||
| label-position="top" | ||
| ref="paramFormRef" | ||
| :model="form" | ||
| require-asterisk-position="right" | ||
| > | ||
| <el-form-item> | ||
| <el-select v-model="form.tool_ids" filterable multiple> | ||
| <el-option | ||
| v-for="mcpTool in toolSelectOptions" | ||
| :key="mcpTool.id" | ||
| :label="mcpTool.name" | ||
| :value="mcpTool.id" | ||
| > | ||
| <span>{{ mcpTool.name }}</span> | ||
| <el-tag v-if="mcpTool.scope === 'SHARED'" type="info" class="info-tag ml-8 mt-4"> | ||
| {{ $t('views.shared.title') }} | ||
| </el-tag> | ||
| </el-option> | ||
| </el-select> | ||
| </el-form-item> | ||
| </el-form> | ||
| <template #header="{ titleId, titleClass }"> | ||
| <div class="flex-between mb-8"> | ||
| <div class="flex"> | ||
| <h4 :id="titleId" :class="titleClass" class="mr-8"> | ||
| {{ $t('views.tool.settingTool') }} | ||
| </h4> | ||
| </div> | ||
|
|
||
| <template #footer> | ||
| <span class="dialog-footer"> | ||
| <el-button @click.prevent="dialogVisible = false">{{ $t('common.cancel') }}</el-button> | ||
| <el-button type="primary" @click="submit()" :loading="loading"> | ||
| {{ $t('common.save') }} | ||
| <el-button link class="mr-24" @click="refresh"> | ||
| <el-icon :size="18"><Refresh /></el-icon> | ||
| </el-button> | ||
| </span> | ||
| </div> | ||
| </template> | ||
| <LayoutContainer class="application-manage"> | ||
| <template #left> | ||
| <div class="p-8"> | ||
| <folder-tree | ||
| :data="folderList" | ||
| :currentNodeKey="currentFolder?.id" | ||
| @handleNodeClick="folderClickHandle" | ||
| v-loading="folderLoading" | ||
| :canOperation="false" | ||
| showShared | ||
| :shareTitle="$t('views.shared.shared_tool')" | ||
| :treeStyle="{ height: 'calc(100vh - 240px)' }" | ||
| /> | ||
| </div> | ||
| </template> | ||
| <div class="layout-bg"> | ||
| <div class="flex-between p-16 ml-8"> | ||
| <h4>{{ currentFolder?.name }}</h4> | ||
| <el-input | ||
| v-model="searchValue" | ||
| :placeholder="$t('common.search')" | ||
| prefix-icon="Search" | ||
| class="w-240 mr-8" | ||
| clearable | ||
| /> | ||
| </div> | ||
|
|
||
| <el-scrollbar> | ||
| <div class="p-16-24 pt-0" style="height: calc(100vh - 200px)"> | ||
| <el-row :gutter="12" v-loading="apiLoading" v-if="searchData.length"> | ||
| <el-col :span="12" v-for="(item, index) in searchData" :key="index" class="mb-16"> | ||
| <CardCheckbox | ||
| value-field="id" | ||
| :data="item" | ||
| v-model="checkList" | ||
| @change="changeHandle" | ||
| > | ||
| <template #icon> | ||
| <el-avatar | ||
| v-if="item?.icon" | ||
| shape="square" | ||
| :size="32" | ||
| style="background: none" | ||
| class="mr-8" | ||
| > | ||
| <img :src="resetUrl(item?.icon)" alt="" /> | ||
| </el-avatar> | ||
| <ToolIcon v-else :size="32" :type="item?.tool_type" /> | ||
| </template> | ||
| <span class="ellipsis cursor ml-12" :title="item.name"> {{ item.name }}</span> | ||
| </CardCheckbox> | ||
| </el-col> | ||
| </el-row> | ||
| <el-empty :description="$t('common.noData')" v-else /> | ||
| </div> | ||
| </el-scrollbar> | ||
| </div> | ||
| </LayoutContainer> | ||
|
|
||
| <template #footer> | ||
| <div class="flex-between"> | ||
| <div class="flex"> | ||
| <el-text type="info" class="color-secondary mr-8" v-if="checkList.length > 0"> | ||
| {{ $t('common.selected') }} {{ checkList.length }} | ||
| </el-text> | ||
| <el-button link type="primary" v-if="checkList.length > 0" @click="clearCheck"> | ||
| {{ $t('common.clear') }} | ||
| </el-button> | ||
| </div> | ||
| <span> | ||
| <el-button @click.prevent="dialogVisible = false"> | ||
| {{ $t('common.cancel') }} | ||
| </el-button> | ||
| <el-button type="primary" @click="submitHandle"> | ||
| {{ $t('common.add') }} | ||
| </el-button> | ||
| </span> | ||
| </div> | ||
| </template> | ||
| </el-dialog> | ||
| </template> | ||
| <script setup lang="ts"> | ||
| import {ref, watch} from 'vue' | ||
| import { computed, ref, watch } from 'vue' | ||
| import { useRoute } from 'vue-router' | ||
| import useStore from '@/stores' | ||
| import { loadSharedApi } from '@/utils/dynamics-api/shared-api' | ||
| import { uniqueArray } from '@/utils/array' | ||
| import { resetUrl } from '@/utils/common' | ||
| const route = useRoute() | ||
|
|
||
| const emit = defineEmits(['refresh']) | ||
|
|
||
| const paramFormRef = ref() | ||
|
|
||
| const form = ref<any>({ | ||
| tool_ids: [], | ||
| const { folder, user } = useStore() | ||
| const apiType = computed(() => { | ||
| if (route.path.includes('shared')) { | ||
| return 'systemShare' | ||
| } else if (route.path.includes('resource-management')) { | ||
| return 'systemManage' | ||
| } else { | ||
| return 'workspace' | ||
| } | ||
| }) | ||
|
|
||
| const toolSelectOptions = ref<any[]>([]) | ||
|
|
||
| const dialogVisible = ref<boolean>(false) | ||
|
|
||
| const loading = ref(false) | ||
| const checkList = ref<Array<string>>([]) | ||
| const searchValue = ref('') | ||
| const searchData = ref<Array<any>>([]) | ||
| const toolList = ref<Array<any>>([]) | ||
| const apiLoading = ref(false) | ||
|
|
||
| watch(dialogVisible, (bool) => { | ||
| if (!bool) { | ||
| form.value = { | ||
| tool_ids: [], | ||
| } | ||
| checkList.value = [] | ||
| searchValue.value = '' | ||
| searchData.value = [] | ||
| toolList.value = [] | ||
| } | ||
| }) | ||
|
|
||
| watch(searchValue, (val) => { | ||
| if (val) { | ||
| searchData.value = toolList.value.filter((v) => v.name.includes(val)) | ||
| } else { | ||
| searchData.value = toolList.value | ||
| } | ||
| }) | ||
|
|
||
| function changeHandle() {} | ||
| function clearCheck() { | ||
| checkList.value = [] | ||
| } | ||
|
|
||
| const open = (data: any, selectOptions: any) => { | ||
| form.value = {...form.value, ...data} | ||
| const open = (checked: any) => { | ||
| checkList.value = checked || [] | ||
| getFolder() | ||
| dialogVisible.value = true | ||
| toolSelectOptions.value = selectOptions | ||
| } | ||
|
|
||
| const submit = () => { | ||
| paramFormRef.value.validate().then((valid: any) => { | ||
| if (valid) { | ||
| emit('refresh', form.value) | ||
| dialogVisible.value = false | ||
| } | ||
| const submitHandle = () => { | ||
| emit('refresh', { | ||
| tool_ids: checkList.value, | ||
| }) | ||
| dialogVisible.value = false | ||
| } | ||
|
|
||
| const refresh = () => { | ||
| searchValue.value = '' | ||
| toolList.value = [] | ||
| getList() | ||
| } | ||
|
|
||
| const folderList = ref<any[]>([]) | ||
| const currentFolder = ref<any>({}) | ||
| const folderLoading = ref(false) | ||
| // 文件 | ||
| function folderClickHandle(row: any) { | ||
| if (row.id === currentFolder.value?.id) { | ||
| return | ||
| } | ||
| currentFolder.value = row | ||
| getList() | ||
| } | ||
|
|
||
| function getFolder() { | ||
| const params = {} | ||
| folder.asyncGetFolder('TOOL', params, folderLoading).then((res: any) => { | ||
| folderList.value = res.data | ||
| currentFolder.value = res.data?.[0] || {} | ||
| getList() | ||
| }) | ||
| } | ||
|
|
||
| defineExpose({open}) | ||
| function getList() { | ||
| const folder_id = currentFolder.value?.id || user.getWorkspaceId() | ||
| loadSharedApi({ | ||
| type: 'tool', | ||
| systemType: apiType.value, | ||
| }) | ||
| .getToolList({ folder_id }, apiLoading) | ||
| .then((res: any) => { | ||
| toolList.value = uniqueArray([...toolList.value, ...res.data.tools], 'id') | ||
| searchData.value = res.data.tools | ||
| }) | ||
| } | ||
|
|
||
| defineExpose({ open }) | ||
| </script> | ||
| <style lang="scss" scoped></style> | ||
| <style lang="scss"> | ||
| .addTool-dialog { | ||
| padding: 0; | ||
| .el-dialog__header { | ||
| padding: 12px 20px 4px 24px; | ||
| border-bottom: 1px solid var(--el-border-color-light); | ||
| } | ||
| .el-dialog__footer { | ||
| padding: 12px 24px 12px 24px; | ||
| border-top: 1px solid var(--el-border-color-light); | ||
| } | ||
|
|
||
| .el-dialog__headerbtn { | ||
| top: 2px; | ||
| right: 6px; | ||
| } | ||
| } | ||
| </style> | ||
|
Contributor
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 code provided has several issues and inefficiencies:
Optimization Suggestions:
Here's an improved version based on these suggestions: <template>
<el-dialog
align-center
:title="$t('views.tool.settingTool')"
v-model="dialogVisible"
width="1000"
append-to-body
align-center
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<el-card v-loading="loading" max-height="calc(100vh - 240px)">
<div class="flex-between pb-8">
<div class="flex">
<h4 class="mr-8">{{ $t('views.tool.manageTools') }}</h4>
</div>
<el-input
v-model="searchValue"
placeholder="$t('common.search')"
prefix-icon="Search"
class="w-140 ml-8 mr-32"
clearable
/>
</div>
<ToolbarButtons />
<!-- Toolbar buttons -->
<template #toolbar-btns>
<ToolbarButton @click="refresh" icon-type="Refresh" text="刷新" />
<ToolbarButton @click="openAllChecked" icon-type="CheckAll" text="全选" />
<ToolbarButton @click="clearSelection" icon-type="ClearAll" text="清除所有选择" />
</template>
<ElTree
ref="selectedItemsTreeRef"
key="all-tools-menu-list-tree-key"
node-key="id"
default-expand-all
show-checkbox
default-checked-keys=""
draggable
highlight-current
@node-drop="onDropAction"
>
<MyTreeNodeTemplate :render-content="defaultSlotRenderFn"></MyTreeNodeTemplate>
</ElTree>
<br />
<!-- Buttons below tree view. -->
<div slot="footer" style="text-align:center;">
<button @click="cancel">取消</button>
<button type="primary" class="ml-12" @click="saveSettings">保存设置</button>
</div>
</el-card>
</el-dialog>
</template>
<script setup lang="ts">
import { ref } from "vue";
import ElCard from "@/components/common/card.vue";
const selectedItemsTreeRef = ref();
// Other props / state...
function cancel() {
// ...
}
function saveSettings() {
// ...
}
function refresh() {
// ...
}
function handleItemAdd(item) {
// Handle add operation here
}
function onDropAction(e) {
console.log("dropped", e.source.key);
// Update your model after drag'n'drop action
}
function openAllChecked() {}
function clearSelection() {}
const apiLoading = ref(false);
defineExpose({
open,
});
</script>
<style scoped>
/* ... Your styles here */
</style>This improved version removes duplication, centralizes common logic using templates, simplifies state management through computed properties, and ensures clean code practices. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -247,7 +247,6 @@ const model_change = (model_id?: string) => { | |
| } | ||
| } | ||
|
|
||
| // @ts-ignore | ||
| const defaultPrompt = `${t('views.applicationWorkflow.nodes.aiChatNode.defaultPrompt')}: | ||
| {{${t('views.applicationWorkflow.nodes.searchKnowledgeNode.label')}.data}} | ||
| ${t('views.problem.title')}: | ||
|
|
@@ -364,10 +363,7 @@ function submitMcpServersDialog(config: any) { | |
|
|
||
| const toolDialogRef = ref() | ||
| function openToolDialog() { | ||
| const config = { | ||
| tool_ids: chat_data.value.tool_ids, | ||
| } | ||
| toolDialogRef.value.open(config, toolSelectOptions.value) | ||
| toolDialogRef.value.open(chat_data.value.tool_ids) | ||
| } | ||
| function submitToolDialog(config: any) { | ||
| set(props.nodeModel.properties.node_data, 'tool_ids', config.tool_ids) | ||
|
Contributor
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 a few concerns and optimizations to suggest: Code Changes & Optimization Suggestions
function closeModalWithError(error?: Error) {
setTimeout(() => {
app.config.globalProperties.$bvToast.toast('Failed to update dialog!', {
title: "Update failed",
variant: "danger",
})
}, 100)
}These suggestions should make the code more readable and maintainable while minimizing potential issues related to TypeScript types and unnecessary complexity. |
||
|
|
||
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 a few minor issues and improvements that can be made to the provided code:
toolStoreobject contains properties with duplicate names (title,createFromToolStore). Ensure they are unique.Here's an optimized version of the code:
Changes Made:
toolStoreobject is unique.This should improve readability and consistency in the codebase.