MCP node and internal function style

* feat: function

* feat: MCP node and internal  function style
This commit is contained in:
wangdan-fit2cloud 2025-03-25 16:36:16 +08:00 committed by GitHub
parent f19316639e
commit 440e2ba695
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 240 additions and 199 deletions

View File

@ -0,0 +1,16 @@
## 概述
博查工具是一个支持自然语言搜索的 Web Search API从近百亿网页和生态内容源中搜索高质量世界知识包括新闻、图片、视频、百科、机酒、学术等.
## 配置
1. 获取API Key 
在[博查开放平台](https://open.bochaai.com/overview) 上申请 API 密钥。
![API Key](/src/assets/fx/img/bocha_APIKey.jpg)
1. 在函数库中配置
在函数库的博查函数面板中,点击 … > 启用参数,填写 API 密钥,并启用博查函数。
![启动参数](/src/assets/fx/img/bocha_setting.jpg)
1. 在应用中使用
在高级编排应用中,点击添加组件->函数库->博查,设置使用参数。
![应用中使用](/src/assets/fx/img/bocha_app_used.jpg)

View File

@ -1,29 +0,0 @@
<template>
<el-drawer v-model="visible" size="40%" :append-to-body="true">
<template #header>
<div class="flex align-center" style="margin-left: -8px">
<el-button class="cursor mr-4" link @click.prevent="visible = false">
<el-icon :size="20">
<Back />
</el-icon>
</el-button>
<h4>详情</h4>
</div>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { t } from '@/locales'
const visible = ref(false)
const open = (data: any) => {
visible.value = true
}
defineExpose({
open
})
</script>

View File

@ -0,0 +1,23 @@
## 概述
Google 搜索工具是一个实时 API可提取搜索引擎结果提供来自 Google 的结构化数据。它支持各种搜索类型,包括 Web、图像、新闻和地图。
## 配置
1. 创建 Google Custom Search Engine
在[Programmable Search Engine]https://programmablesearchengine.google.com/)中 添加 Search Engine
![google 创建引擎](/src/assets/fx/img/google_AddSearchEngine.jpg)
2. 获取cx参数
进入添加引擎在【基本】菜单中获取搜索引擎的ID即cx。
![google cx ](/src/assets/fx/img/google_cx.jpg)
3.获取 API Key
打开 https://developers.google.com/custom-search/v1/overview?hl=zh-cn获取API Key。
![google API Key](/src/assets/fx/img/google_APIKey.jpg)
4. 配置启动参数
在Google搜索函数的启动参数中填写配置以上参数。
![启动参数](/src/assets/fx/img/google_setting.jpg)
5. 在应用中使用
在高级编排应用中,点击添加组件->函数库->博查,设置使用参数。
![应用中使用](/src/assets/fx/img/google_app_used.jpg)

View File

@ -1,29 +0,0 @@
<template>
<el-drawer v-model="visible" size="40%" :append-to-body="true">
<template #header>
<div class="flex align-center" style="margin-left: -8px">
<el-button class="cursor mr-4" link @click.prevent="visible = false">
<el-icon :size="20">
<Back />
</el-icon>
</el-button>
<h4>详情</h4>
</div>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { t } from '@/locales'
const visible = ref(false)
const open = (data: any) => {
visible.value = true
}
defineExpose({
open
})
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

View File

@ -0,0 +1,17 @@
## 概述
博查工具是一个支持自然语言搜索的 Web Search API从近百亿网页和生态内容源中搜索高质量世界知识包括新闻、图片、视频、百科、机酒、学术等.
## 配置
1. 获取API Key 
在[博查开放平台](https://open.bochaai.com/overview) 上申请 API 密钥。
![API Key](/src/assets/fx/img/langsearch_APIKey.jpg)
2. 在函数库中配置
在函数库的博查函数面板中,点击 … > 启用参数,填写 API 密钥,并启用博查函数。
![启动参数](/src/assets/fx/img/langsearch_setting.jpg)
1. 在应用中使用
在高级编排应用中,点击添加组件->函数库->博查,设置使用参数。
![应用中使用](/src/assets/fx/img/langsearch_app_used.jpg)

View File

@ -1,29 +0,0 @@
<template>
<el-drawer v-model="visible" size="40%" :append-to-body="true">
<template #header>
<div class="flex align-center" style="margin-left: -8px">
<el-button class="cursor mr-4" link @click.prevent="visible = false">
<el-icon :size="20">
<Back />
</el-icon>
</el-button>
<h4>详情</h4>
</div>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { t } from '@/locales'
const visible = ref(false)
const open = (data: any) => {
visible.value = true
}
defineExpose({
open
})
</script>

View File

@ -1,29 +0,0 @@
<template>
<el-drawer v-model="visible" size="40%" :append-to-body="true">
<template #header>
<div class="flex align-center" style="margin-left: -8px">
<el-button class="cursor mr-4" link @click.prevent="visible = false">
<el-icon :size="20">
<Back />
</el-icon>
</el-button>
<h4>详情</h4>
</div>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { t } from '@/locales'
const visible = ref(false)
const open = (data: any) => {
visible.value = true
}
defineExpose({
open
})
</script>

View File

@ -1,29 +0,0 @@
<template>
<el-drawer v-model="visible" size="40%" :append-to-body="true">
<template #header>
<div class="flex align-center" style="margin-left: -8px">
<el-button class="cursor mr-4" link @click.prevent="visible = false">
<el-icon :size="20">
<Back />
</el-icon>
</el-button>
<h4>详情</h4>
</div>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { t } from '@/locales'
const visible = ref(false)
const open = (data: any) => {
visible.value = true
}
defineExpose({
open
})
</script>

View File

@ -0,0 +1,4 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.8888 3.44443V5.22221H18.9999C19.4908 5.22221 19.8888 5.62018 19.8888 6.1111V9.22221H15.4443V8.33332H13.6666V9.22221H8.33324V8.33332H6.55546V9.22221H2.11102V6.1111C2.11102 5.62018 2.50898 5.22221 2.9999 5.22221H6.11102V3.44443C6.11102 2.95351 6.50898 2.55554 6.9999 2.55554H14.9999C15.4908 2.55554 15.8888 2.95351 15.8888 3.44443ZM7.88879 5.22221H14.111V4.33332H7.88879V5.22221Z" fill="white"/>
<path d="M2.11102 11H6.55546V11.8889H8.33324V11H13.6666V11.8889H15.4443V11H19.8888V18.5555C19.8888 19.0465 19.4908 19.4444 18.9999 19.4444H2.9999C2.50898 19.4444 2.11102 19.0465 2.11102 18.5555V11Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 764 B

View File

@ -55,7 +55,13 @@
@click.stop="clickNodes(functionLibNode, item, 'function')"
@mousedown.stop="onmousedown(functionLibNode, item, 'function')"
>
<component :is="iconComponent(`function-lib-node-icon`)" class="mr-8" :size="32" />
<component
:is="iconComponent(`function-lib-node-icon`)"
class="mr-8"
:size="32"
:item="item"
/>
<div class="pre-wrap">
<div class="lighter ellipsis-1" :title="item.name">{{ item.name }}</div>
<p>
@ -127,7 +133,7 @@ import { ref, onMounted, computed } from 'vue'
import { menuNodes, functionLibNode, functionNode, applicationNode } from '@/workflow/common/data'
import { iconComponent } from '@/workflow/icons/utils'
import applicationApi from '@/api/application'
import { isWorkFlow } from '@/utils/application'
import { isWorkFlow, isAppIcon } from '@/utils/application'
const search_text = ref<string>('')
const props = defineProps({
show: {

View File

@ -0,0 +1,100 @@
<template>
<el-drawer v-model="visibleInternalDesc" size="60%" :append-to-body="true">
<template #header>
<div class="flex align-center" style="margin-left: -8px">
<el-button class="cursor mr-4" link @click.prevent="visibleInternalDesc = false">
<el-icon :size="20">
<Back />
</el-icon>
</el-button>
<h4>详情</h4>
</div>
</template>
<div>
<div class="card-header">
<div class="flex-between">
<div class="title flex align-center">
<AppAvatar
v-if="isAppIcon(functionDetail?.icon)"
shape="square"
:size="64"
style="background: none"
class="mr-8"
>
<img :src="functionDetail?.icon" alt="" />
</AppAvatar>
<AppAvatar
v-else-if="functionDetail?.name"
:name="functionDetail?.name"
pinyinColor
shape="square"
:size="64"
class="mr-8"
/>
<div class="ml-16">
<h3 class="mb-8">{{ functionDetail.name }}</h3>
<el-text type="info" v-if="functionDetail?.desc">
{{ functionDetail.desc }}
</el-text>
</div>
</div>
<div @click.stop>
<el-button type="primary" @click="addInternalFunction(functionDetail)">
{{ $t('common.add') }}
</el-button>
</div>
</div>
<div class="mt-16">
<el-text type="info" v-if="functionDetail?.desc">
<div>{{ $t('common.author') }}: MaxKB</div>
</el-text>
</div>
</div>
<MdPreview
ref="editorRef"
editorId="preview-only"
:modelValue="markdownContent"
style="background: none"
noImgZoomIn
/>
</div>
</el-drawer>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { cloneDeep } from 'lodash'
import { isAppIcon } from '@/utils/application'
const emit = defineEmits(['refresh', 'addFunction'])
const visibleInternalDesc = ref(false)
const markdownContent = ref('')
const functionDetail = ref<any>({})
watch(visibleInternalDesc, (bool) => {
if (!bool) {
markdownContent.value = ''
}
})
const open = (data: any, detail: any) => {
functionDetail.value = detail
if (data) {
markdownContent.value = cloneDeep(data)
}
visibleInternalDesc.value = true
}
const addInternalFunction = (data: any) => {
emit('addFunction', data)
visibleInternalDesc.value = false
}
defineExpose({
open
})
</script>
<style lang="scss"></style>

View File

@ -105,7 +105,7 @@
style="background: none"
class="mr-8"
>
<img :src="getImageUrl(item?.icon)" alt="" />
<img :src="item?.icon" alt="" />
</AppAvatar>
<AppAvatar
v-else-if="item?.name"
@ -218,7 +218,7 @@
style="background: none"
class="mr-8"
>
<img :src="getImageUrl(item?.icon)" alt="" />
<img :src="item?.icon" alt="" />
</AppAvatar>
<AppAvatar
v-else-if="item?.name"
@ -229,9 +229,7 @@
class="mr-8"
/>
</template>
<div class="status-button">
</div>
<div class="status-button"></div>
<template #footer>
<div class="footer-content flex-between">
<div>{{ $t('common.author') }}: MaxKB</div>
@ -249,9 +247,12 @@
</div>
<FunctionFormDrawer ref="FunctionFormDrawerRef" @refresh="refresh" :title="title" />
<PermissionDialog ref="PermissionDialogRef" @refresh="refresh" />
<AddInternalFunctionDialog ref="AddInternalFunctionDialogRef" @refresh="confirmAddInternalFunction" />
<AddInternalFunctionDialog
ref="AddInternalFunctionDialogRef"
@refresh="confirmAddInternalFunction"
/>
<InitParamDrawer ref="InitParamDrawerRef" @refresh="refresh" />
<component :is="internalDescComponent" ref="internalDescRef" />
<InternalDescDrawer ref="InternalDescDrawerRef" @addFunction="addInternalFunction" />
</div>
</template>
<script setup lang="ts">
@ -265,21 +266,21 @@ import applicationApi from '@/api/application'
import { t } from '@/locales'
import PermissionDialog from '@/views/function-lib/component/PermissionDialog.vue'
import InitParamDrawer from '@/views/function-lib/component/InitParamDrawer.vue'
import InternalDescDrawer from '@/views/function-lib/component/InternalDescDrawer.vue'
import { isAppIcon } from '@/utils/application'
import InfiniteScroll from '@/components/infinite-scroll/index.vue'
import CardBox from '@/components/card-box/index.vue'
import type { Dict } from '@/api/type/common'
import AddInternalFunctionDialog from '@/views/function-lib/component/AddInternalFunctionDialog.vue'
const internalIcons: Dict<any> = import.meta.glob('@/assets/fx/*/*.png', { eager: true })
let internalDesc: Dict<any> = import.meta.glob('@/assets/fx/*/index.vue', { eager: true })
const internalDescRef = ref()
const internalDescComponent = ref()
const internalDesc: Record<string, any> = import.meta.glob('@/assets/fx/*/detail.md', {
eager: true,
as: 'raw'
})
const { user } = useStore()
const loading = ref(false)
const InternalDescDrawerRef = ref()
const FunctionFormDrawerRef = ref()
const PermissionDialogRef = ref()
const AddInternalFunctionDialogRef = ref()
@ -342,19 +343,9 @@ function openCreateDialog(data?: any) {
}
}
function getImageUrl(name: string) {
if (name.startsWith('/src/assets/fx/')) {
return internalIcons[name]?.default
}
return name
}
function openDescDrawer(row: any) {
const index = row.icon.replace('icon.png', 'index.vue')
internalDescComponent.value = internalDesc[index].default
nextTick(() => {
internalDescRef.value?.open(row)
})
const index = row.icon.replace('icon.png', 'detail.md')
InternalDescDrawerRef.value.open(internalDesc[index], row)
}
function addInternalFunction(data?: any) {
@ -362,10 +353,12 @@ function addInternalFunction(data?: any) {
}
function confirmAddInternalFunction(data?: any) {
functionLibApi.addInternalFunction(data.id, {name: data.name}, changeStateloading).then((res) => {
MsgSuccess(t('common.submitSuccess'))
searchHandle()
})
functionLibApi
.addInternalFunction(data.id, { name: data.name }, changeStateloading)
.then((res) => {
MsgSuccess(t('common.submitSuccess'))
searchHandle()
})
}
function searchHandle() {

View File

@ -7,7 +7,7 @@
>
<div v-resize="resizeStepContainer">
<div class="flex-between">
<div class="flex align-center" style="width: 70%;">
<div class="flex align-center" style="width: 70%">
<component
:is="iconComponent(`${nodeModel.type}-icon`)"
class="mr-8"

View File

@ -1,6 +1,25 @@
<template>
<AppAvatar shape="square" style="background: #34c724">
<AppAvatar
v-if="isAppIcon(item?.icon)"
shape="square"
:size="32"
style="background: none"
class="mr-8"
>
<img :src="item?.icon" alt="" />
</AppAvatar>
<AppAvatar v-else shape="square" style="background: #34c724">
<img src="@/assets/icon_function_outlined.svg" style="width: 75%" alt="" />
</AppAvatar>
</template>
<script setup lang="ts">
import { isAppIcon } from '@/utils/application'
const props = defineProps<{
item: {
name: string
icon: string
}
}>()
</script>
<script setup lang="ts"></script>

View File

@ -1,6 +1,6 @@
<template>
<AppAvatar shape="square" class="avatar-blue">
<img src="@/assets/icon_assigner.svg" style="width: 65%" alt="" />
<AppAvatar shape="square" style="background: #34c724">
<img src="@/assets/icon_mcp.svg" style="width: 65%" alt="" />
</AppAvatar>
</template>
<script setup lang="ts"></script>

View File

@ -25,14 +25,14 @@
<div class="flex-between">
<span>{{ $t('views.applicationWorkflow.nodes.mcpNode.tool') }}</span>
<el-button type="primary" link @click="getTools()">
<el-icon class="mr-4">
<Plus />
</el-icon>
{{ $t('views.applicationWorkflow.nodes.mcpNode.getTool') }}
</el-button>
</div>
</template>
<el-select
v-model="form_data.mcp_tool"
@change="changeTool"
>
<el-select v-model="form_data.mcp_tool" @change="changeTool">
<el-option
v-for="item in form_data.mcp_tools"
:key="item.value"
@ -55,8 +55,12 @@
</el-form>
</div>
<h5 class="title-decoration-1 mb-8">
{{ $t('views.applicationWorkflow.nodes.mcpNode.toolParam') }}</h5>
<div class="border-r-4 p-8-12 mb-8 layout-bg lighter" v-if="form_data.tool_params[form_data.params_nested]">
{{ $t('views.applicationWorkflow.nodes.mcpNode.toolParam') }}
</h5>
<div
class="border-r-4 p-8-12 mb-8 layout-bg lighter"
v-if="form_data.tool_params[form_data.params_nested]"
>
<DynamicsForm
v-if="form_data.mcp_tool"
v-model="form_data.tool_params[form_data.params_nested]"
@ -116,7 +120,6 @@ const form = {
params_nested: ''
}
function submitDialog(val: string) {
set(props.nodeModel.properties.node_data, 'mcp_servers', val)
}
@ -132,17 +135,23 @@ function getTools() {
MsgError(t('views.applicationWorkflow.nodes.mcpNode.mcpServerTip'))
return
}
applicationApi.getMcpTools({ mcp_servers: form_data.value.mcp_servers }, loading).then((res: any) => {
form_data.value.mcp_tools = res.data
MsgSuccess(t('views.applicationWorkflow.nodes.mcpNode.getToolsSuccess'))
})
applicationApi
.getMcpTools({ mcp_servers: form_data.value.mcp_servers }, loading)
.then((res: any) => {
form_data.value.mcp_tools = res.data
MsgSuccess(t('views.applicationWorkflow.nodes.mcpNode.getToolsSuccess'))
})
}
function changeTool() {
form_data.value.mcp_server = form_data.value.mcp_tools.filter((item: any) => item.name === form_data.value.mcp_tool)[0].server
form_data.value.mcp_server = form_data.value.mcp_tools.filter(
(item: any) => item.name === form_data.value.mcp_tool
)[0].server
// console.log(form_data.value.mcp_server)
const args_schema = form_data.value.mcp_tools.filter((item: any) => item.name === form_data.value.mcp_tool)[0].args_schema
const args_schema = form_data.value.mcp_tools.filter(
(item: any) => item.name === form_data.value.mcp_tool
)[0].args_schema
form_data.value.tool_form_field = []
for (const item in args_schema.properties) {
let params = args_schema.properties[item].properties
@ -201,7 +210,10 @@ function changeTool() {
//
if (form_data.value.params_nested) {
form_data.value.tool_params = { [form_data.value.params_nested]: {} }
dynamicsFormRef.value?.render(form_data.value.tool_form_field, form_data.value.tool_params[form_data.value.params_nested])
dynamicsFormRef.value?.render(
form_data.value.tool_form_field,
form_data.value.tool_params[form_data.value.params_nested]
)
} else {
form_data.value.tool_params = {}
dynamicsFormRef.value?.render(form_data.value.tool_form_field, form_data.value.tool_params)
@ -223,14 +235,10 @@ const form_data = computed({
}
})
const replyNodeFormRef = ref()
const validate = async () => {
let ps = [
replyNodeFormRef.value?.validate(),
dynamicsFormRef.value?.validate()
]
let ps = [replyNodeFormRef.value?.validate(), dynamicsFormRef.value?.validate()]
return Promise.all(ps).catch((err: any) => {
return Promise.reject({ node: props.nodeModel, errMessage: err })
})