feat: add Appstore tool retrieval and store tool API endpoint

This commit is contained in:
CaptainB 2025-09-09 10:32:59 +08:00 committed by 刘瑞斌
parent a5d046c26b
commit e988cbca91
17 changed files with 468 additions and 10 deletions

View File

@ -0,0 +1,23 @@
# Generated by Django 5.2.4 on 2025-09-09 04:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tools', '0002_alter_tool_tool_type'),
]
operations = [
migrations.AlterField(
model_name='tool',
name='template_id',
field=models.CharField(db_index=True, default=None, max_length=128, null=True, verbose_name='模版id'),
),
migrations.AddField(
model_name='tool',
name='version',
field=models.CharField(default=None, max_length=64, null=True, verbose_name='版本号'),
),
]

View File

@ -48,11 +48,12 @@ class Tool(AppModelMixin):
default=ToolScope.WORKSPACE, db_index=True)
tool_type = models.CharField(max_length=20, verbose_name='工具类型', choices=ToolType.choices,
default=ToolType.CUSTOM, db_index=True)
template_id = models.UUIDField(max_length=128, verbose_name="模版id", null=True, default=None, db_index=True)
template_id = models.CharField(max_length=128, verbose_name="模版id", null=True, default=None, db_index=True)
folder = models.ForeignKey(ToolFolder, on_delete=models.DO_NOTHING, verbose_name="文件夹id", default='default')
workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True)
init_params = models.CharField(max_length=102400, verbose_name="初始化参数", null=True)
label = models.CharField(max_length=128, verbose_name="标签", null=True, db_index=True)
version = models.CharField(max_length=64, verbose_name="版本号", null=True, default=None)
class Meta:
db_table = "tool"

View File

@ -5,6 +5,9 @@ import json
import os
import pickle
import re
import requests
import tempfile
import zipfile
from typing import Dict
import uuid_utils.compat as uuid
@ -124,7 +127,7 @@ class ToolModelSerializer(serializers.ModelSerializer):
model = Tool
fields = ['id', 'name', 'icon', 'desc', 'code', 'input_field_list', 'init_field_list', 'init_params',
'scope', 'is_active', 'user_id', 'template_id', 'workspace_id', 'folder_id', 'tool_type', 'label',
'create_time', 'update_time']
'version', 'create_time', 'update_time']
class ToolExportModelSerializer(serializers.ModelSerializer):
@ -705,6 +708,7 @@ class ToolSerializer(serializers.Serializer):
tool_type=ToolType.CUSTOM,
folder_id=instance.get('folder_id', self.data.get('workspace_id')),
template_id=internal_tool.id,
label=internal_tool.label,
is_active=False
)
tool.save()
@ -718,6 +722,140 @@ class ToolSerializer(serializers.Serializer):
return ToolModelSerializer(tool).data
class StoreTool(serializers.Serializer):
user_id = serializers.UUIDField(required=True, label=_("User ID"))
name = serializers.CharField(required=False, label=_("tool name"), allow_null=True, allow_blank=True)
def get_appstore_tools(self):
self.is_valid(raise_exception=True)
# 下载zip文件
try:
res = requests.get('https://apps-assets.fit2cloud.com/stable/maxkb.json.zip', timeout=5)
res.raise_for_status()
# 创建临时文件保存zip
with tempfile.NamedTemporaryFile(delete=False, suffix='.zip') as temp_zip:
temp_zip.write(res.content)
temp_zip_path = temp_zip.name
try:
# 解压zip文件
with zipfile.ZipFile(temp_zip_path, 'r') as zip_ref:
# 获取zip中的第一个文件假设只有一个json文件
json_filename = zip_ref.namelist()[0]
json_content = zip_ref.read(json_filename)
# 将json转换为字典
tool_store = json.loads(json_content.decode('utf-8'))
tag_dict = {tag['name']: tag['key'] for tag in tool_store['additionalProperties']['tags']}
filter_apps = []
for tool in tool_store['apps']:
if self.data.get('name', '') != '':
if self.data.get('name').lower() not in tool.get('name', '').lower():
continue
versions = tool.get('versions', [])
tool['label'] = tag_dict[tool.get('tags')[0]] if tool.get('tags') else ''
tool['version'] = next(
(version.get('name') for version in versions if version.get('downloadUrl') == tool['downloadUrl']),
)
filter_apps.append(tool)
tool_store['apps'] = filter_apps
return tool_store
finally:
# 清理临时文件
os.unlink(temp_zip_path)
except requests.RequestException as e:
maxkb_logger.error(f"fetch appstore tools error: {e}")
return []
class AddStoreTool(serializers.Serializer):
user_id = serializers.UUIDField(required=True, label=_("User ID"))
workspace_id = serializers.CharField(required=True, label=_("workspace id"))
tool_id = serializers.CharField(required=True, label=_("tool id"))
def add(self, instance: Dict, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
AddInternalToolRequest(data=instance).is_valid(raise_exception=True)
versions = instance.get('versions', [])
download_url = instance.get('download_url')
# 查找匹配的版本名称
version_name = next(
(version.get('name') for version in versions if version.get('downloadUrl') == download_url),
)
res = requests.get(download_url, timeout=5)
tool_data = RestrictedUnpickler(io.BytesIO(res.content)).load().tool
tool_id = uuid.uuid7()
tool = Tool(
id=tool_id,
name=tool_data.get('name'),
desc=tool_data.get('desc'),
code=tool_data.get('code'),
user_id=self.data.get('user_id'),
icon=instance.get('icon', ''),
workspace_id=self.data.get('workspace_id'),
input_field_list=tool_data.get('input_field_list', []),
init_field_list=tool_data.get('init_field_list', []),
scope=ToolScope.WORKSPACE,
tool_type=ToolType.CUSTOM,
folder_id=instance.get('folder_id', self.data.get('workspace_id')),
template_id=self.data.get('tool_id'),
label=instance.get('label'),
version=version_name,
is_active=False
)
tool.save()
# 自动授权给创建者
UserResourcePermissionSerializer(data={
'workspace_id': self.data.get('workspace_id'),
'user_id': self.data.get('user_id'),
'auth_target_type': AuthTargetType.TOOL.value
}).auth_resource(str(tool_id))
try:
requests.get(instance.get('download_callback_url'), timeout=5)
except Exception as e:
maxkb_logger.error(f"callback appstore tool download error: {e}")
return ToolModelSerializer(tool).data
class UpdateStoreTool(serializers.Serializer):
user_id = serializers.UUIDField(required=True, label=_("User ID"))
workspace_id = serializers.CharField(required=True, label=_("workspace id"))
tool_id = serializers.UUIDField(required=True, label=_("tool id"))
download_url = serializers.CharField(required=True, label=_("download url"))
download_callback_url = serializers.CharField(required=True, label=_("download callback url"))
icon = serializers.CharField(required=True, label=_("icon"), allow_null=True, allow_blank=True)
versions = serializers.ListField(required=True, label=_("versions"), child=serializers.DictField())
def update_tool(self, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
tool = QuerySet(Tool).filter(id=self.data.get('tool_id')).first()
if tool is None:
raise AppApiException(500, _('Tool does not exist'))
# 查找匹配的版本名称
version_name = next(
(version.get('name') for version in self.data.get('versions') if version.get('downloadUrl') == self.data.get('download_url')),
)
res = requests.get(self.data.get('download_url'), timeout=5)
tool_data = RestrictedUnpickler(io.BytesIO(res.content)).load().tool
tool.name = tool_data.get('name')
tool.desc = tool_data.get('desc')
tool.code = tool_data.get('code')
tool.input_field_list = tool_data.get('input_field_list', [])
tool.init_field_list = tool_data.get('init_field_list', [])
tool.icon = self.data.get('icon', tool.icon)
tool.version = version_name
# tool.is_active = False
tool.save()
try:
requests.get(self.data.get('download_callback_url'), timeout=5)
except Exception as e:
maxkb_logger.error(f"callback appstore tool download error: {e}")
return ToolModelSerializer(tool).data
class ToolTreeSerializer(serializers.Serializer):
class Query(serializers.Serializer):

View File

@ -16,6 +16,7 @@ from (select tool."id"::text,
tool."update_time",
tool.init_field_list,
tool.input_field_list,
tool.version,
tool."is_active"
from tool
left join "user" on "user".id = user_id ${tool_query_set}
@ -37,6 +38,7 @@ from (select tool."id"::text,
tool_folder."update_time",
'[]'::jsonb as init_field_list,
'[]'::jsonb as input_field_list,
'' as version,
'true' as "is_active"
from tool_folder
left join "user" on "user".id = user_id ${folder_query_set}) temp

View File

@ -16,6 +16,7 @@ FROM (SELECT tool."id"::text,
tool."update_time",
tool.init_field_list,
tool.input_field_list,
tool.version,
tool."is_active"
FROM (SELECT tool.*
FROM tool tool ${tool_query_set}
@ -43,6 +44,7 @@ FROM (SELECT tool."id"::text,
tool_folder."update_time",
'[]'::jsonb AS init_field_list,
'[]'::jsonb AS input_field_list,
'' AS version,
'true' AS "is_active"
FROM tool_folder
LEFT JOIN "user" ON "user".id = user_id ${folder_query_set}) temp

View File

@ -16,6 +16,7 @@ FROM (SELECT tool."id"::text,
tool."update_time",
tool.init_field_list,
tool.input_field_list,
tool.version,
tool."is_active"
FROM (SELECT tool.*
FROM tool tool ${tool_query_set}
@ -53,6 +54,7 @@ FROM (SELECT tool."id"::text,
tool_folder."update_time",
'[]'::jsonb AS init_field_list,
'[]'::jsonb AS input_field_list,
'' AS version,
'true' AS "is_active"
FROM tool_folder
LEFT JOIN "user" ON "user".id = user_id ${folder_query_set}) temp

View File

@ -6,6 +6,7 @@ app_name = "tool"
# @formatter:off
urlpatterns = [
path('workspace/internal/tool', views.ToolView.InternalTool.as_view()),
path('workspace/store/tool', views.ToolView.StoreTool.as_view()),
path('workspace/<str:workspace_id>/tool', views.ToolView.as_view()),
path('workspace/<str:workspace_id>/tool/import', views.ToolView.Import.as_view()),
path('workspace/<str:workspace_id>/tool/pylint', views.ToolView.Pylint.as_view()),
@ -15,5 +16,7 @@ urlpatterns = [
path('workspace/<str:workspace_id>/tool/<str:tool_id>/edit_icon', views.ToolView.EditIcon.as_view()),
path('workspace/<str:workspace_id>/tool/<str:tool_id>/export', views.ToolView.Export.as_view()),
path('workspace/<str:workspace_id>/tool/<str:tool_id>/add_internal_tool', views.ToolView.AddInternalTool.as_view()),
path('workspace/<str:workspace_id>/tool/<str:tool_id>/add_store_tool', views.ToolView.AddStoreTool.as_view()),
path('workspace/<str:workspace_id>/tool/<str:tool_id>/update_store_tool', views.ToolView.UpdateStoreTool.as_view()),
path('workspace/<str:workspace_id>/tool/<int:current_page>/<int:page_size>', views.ToolView.Page.as_view()),
]

View File

@ -407,3 +407,84 @@ class ToolView(APIView):
'user_id': request.user.id,
'workspace_id': workspace_id
}).add(request.data))
class StoreTool(APIView):
authentication_classes = [TokenAuth]
@extend_schema(
methods=['GET'],
description=_("Get Appstore tools"),
summary=_("Get Appstore tools"),
operation_id=_("Get Appstore tools"), # type: ignore
responses=GetInternalToolAPI.get_response(),
tags=[_("Tool")] # type: ignore
)
def get(self, request: Request):
return result.success(ToolSerializer.StoreTool(data={
'user_id': request.user.id,
'name': request.query_params.get('name', ''),
}).get_appstore_tools())
class AddStoreTool(APIView):
authentication_classes = [TokenAuth]
@extend_schema(
methods=['POST'],
description=_("Add Appstore tool"),
summary=_("Add Appstore tool"),
operation_id=_("Add Appstore tool"), # type: ignore
parameters=AddInternalToolAPI.get_parameters(),
request=AddInternalToolAPI.get_request(),
responses=AddInternalToolAPI.get_response(),
tags=[_("Tool")] # type: ignore
)
@has_permissions(
PermissionConstants.TOOL_CREATE.get_workspace_permission(),
PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(),
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
RoleConstants.USER.get_workspace_role(),
)
@log(
menu='Tool', operate="Add Appstore tool",
get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')),
)
def post(self, request: Request, tool_id: str, workspace_id: str):
return result.success(ToolSerializer.AddStoreTool(data={
'tool_id': tool_id,
'user_id': request.user.id,
'workspace_id': workspace_id,
}).add(request.data))
class UpdateStoreTool(APIView):
authentication_classes = [TokenAuth]
@extend_schema(
methods=['POST'],
description=_("Update Appstore tool"),
summary=_("Update Appstore tool"),
operation_id=_("Update Appstore tool"), # type: ignore
parameters=AddInternalToolAPI.get_parameters(),
request=AddInternalToolAPI.get_request(),
responses=AddInternalToolAPI.get_response(),
tags=[_("Tool")] # type: ignore
)
@has_permissions(
PermissionConstants.TOOL_CREATE.get_workspace_permission(),
PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(),
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
RoleConstants.USER.get_workspace_role(),
)
@log(
menu='Tool', operate="Update Appstore tool",
get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')),
)
def post(self, request: Request, tool_id: str, workspace_id: str):
return result.success(ToolSerializer.UpdateStoreTool(data={
'tool_id': tool_id,
'user_id': request.user.id,
'workspace_id': workspace_id,
'download_url': request.data.get('download_url'),
'download_callback_url': request.data.get('download_callback_url'),
'icon': request.data.get('icon'),
'versions': request.data.get('versions'),
}).update_tool(request.data))

View File

@ -142,6 +142,24 @@ const addInternalTool: (
return post(`${prefix}/${tool_id}/add_internal_tool`, param, undefined, loading)
}
/**
*
*/
const addStoreTool: (
tool_id: string,
param: AddInternalToolParam,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (tool_id, param, loading) => {
return post(`${prefix}/${tool_id}/add_store_tool`, param, undefined, loading)
}
const updateStoreTool: (
tool_id: string,
param: AddInternalToolParam,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (tool_id, param, loading) => {
return post(`${prefix}/${tool_id}/update_store_tool`, param, undefined, loading)
}
export default {
getToolList,
@ -156,5 +174,7 @@ export default {
exportTool,
putToolIcon,
delTool,
addInternalTool
addInternalTool,
addStoreTool,
updateStoreTool
}

View File

@ -22,6 +22,16 @@ const getInternalToolList: (param?: any, loading?: Ref<boolean>) => Promise<Resu
return get('/workspace/internal/tool', param, loading)
}
/**
*
*/
const getStoreToolList: (param?: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
param,
loading,
) => {
return get('/workspace/store/tool', param, loading)
}
/**
* -
*/
@ -33,7 +43,20 @@ const addInternalTool: (
return post(`${prefix.value}/${tool_id}/add_internal_tool`, param, undefined, loading)
}
/**
* -
*/
const addStoreTool: (
tool_id: string,
param: AddInternalToolParam,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (tool_id, param, loading) => {
return post(`${prefix.value}/${tool_id}/add_store_tool`, param, undefined, loading)
}
export default {
getInternalToolList,
getStoreToolList,
addInternalTool,
addStoreTool
}

View File

@ -148,6 +148,27 @@ const addInternalTool: (
return post(`${prefix.value}/${tool_id}/add_internal_tool`, param, undefined, loading)
}
/**
* -
*/
const addStoreTool: (
tool_id: string,
param: AddInternalToolParam,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (tool_id, param, loading) => {
return post(`${prefix.value}/${tool_id}/add_store_tool`, param, undefined, loading)
}
const updateStoreTool: (
tool_id: string,
param: AddInternalToolParam,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (tool_id, param, loading) => {
return post(`${prefix.value}/${tool_id}/update_store_tool`, param, undefined, loading)
}
export default {
getToolList,
getAllToolList,
@ -162,4 +183,6 @@ export default {
putToolIcon,
delTool,
addInternalTool,
addStoreTool,
updateStoreTool
}

View File

@ -20,6 +20,8 @@ export default {
developer: 'Developer',
communication: 'Communication',
searchResult: '{count} search results for',
confirmTip: 'Are you sure to update tool: ',
updateStoreToolMessage: 'Updating tools may affect resources in use, so proceed with caution.',
},
searchBar: {
placeholder: 'Search by tool name',

View File

@ -20,6 +20,8 @@ export default {
developer: '开发者',
communication: '通信',
searchResult: '的搜索结果 {count} 个',
confirmTip: '是否更新工具:',
updateStoreToolMessage: '更新工具可能会影响正在使用的资源,请谨慎操作。',
},
delete: {
confirmTitle: '是否刪除工具',

View File

@ -20,6 +20,8 @@ export default {
developer: '開發者',
communication: '通信',
searchResult: '的搜索結果 {count} 個',
confirmTip: '是否更新工具:',
updateStoreToolMessage: '更新工具可能會影響正在使用的資源,請謹慎操作。',
},
searchBar: {
placeholder: '按工具名稱搜尋',

View File

@ -72,6 +72,7 @@
<el-table-column prop="tool_type" :label="$t('views.system.resource_management.type')">
<template #default="scope">
<span v-if="scope.row.tool_type === 'MCP'"> MCP </span>
<span v-else-if="scope.row.version">{{ $t('views.tool.toolStore.title') }}</span>
<span v-else>
{{
$t(

View File

@ -176,6 +176,14 @@
</el-avatar>
<ToolIcon v-else :size="32" :type="item?.tool_type" />
</template>
<template #title>
<div>
{{ item.name }}
<el-tag v-if="item.version" class="ml-4">
{{ item.version }}
</el-tag>
</div>
</template>
<template #subTitle>
<el-text class="color-secondary lighter" size="small">
{{ $t('common.creator') }}: {{ item.nick_name }}
@ -185,6 +193,12 @@
<el-tag v-if="isShared" type="info" class="info-tag">
{{ t('views.shared.title') }}
</el-tag>
<el-button text @click.stop v-if="
showUpdateStoreTool(item) && !isShared && permissionPrecise.edit(item.id)
" @click="updateStoreTool(item)"
>
<el-icon><Refresh /></el-icon>
</el-button>
</template>
<template #footer>
@ -353,6 +367,7 @@ import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
import permissionMap from '@/permission'
import useStore from '@/stores'
import { t } from '@/locales'
import ToolStoreApi from "@/api/tool/store.ts";
const route = useRoute()
const { folder, user, tool } = useStore()
onBeforeRouteLeave((to, from) => {
@ -626,6 +641,57 @@ function confirmAddInternalTool(data?: any, isEdit?: boolean) {
}
}
const storeTools = ref<any[]>([])
function getStoreToolList() {
ToolStoreApi.getStoreToolList({ name: '' }, loading)
.then((res: any) => {
storeTools.value = res.data.apps
})
}
function showUpdateStoreTool(item: any) {
for (const tool of storeTools.value) {
if (tool.id === item.template_id && tool.version !== item.version) {
item.downloadUrl = tool.downloadUrl
item.downloadCallbackUrl = tool.downloadCallbackUrl
item.icon = tool.icon
item.versions = tool.versions
item.label = tool.label
return true
}
}
}
function updateStoreTool(item: any) {
MsgConfirm(t('views.tool.toolStore.confirmTip') + item.name,
t('views.tool.toolStore.updateStoreToolMessage'), {
cancelButtonText: t('common.cancel'),
confirmButtonText: t('common.confirm'),
})
.then(() => {
const obj = {
download_url: item.downloadUrl,
download_callback_url: item.downloadCallbackUrl,
icon: item.icon,
versions: item.versions,
label: item.label
}
loadSharedApi({type: 'tool', systemType: apiType.value})
.updateStoreTool(item.id, obj, loading)
.then(async (res: any) => {
if (res?.data) {
tool.setToolList([])
return user.profile()
}
})
.then(() => {
getList()
})
})
.catch(() => {
})
}
const elUploadRef = ref()
function importTool(file: any) {
const formData = new FormData()
@ -745,6 +811,7 @@ onMounted(() => {
.then((res: any) => {
user_options.value = res.data
})
getStoreToolList()
})
</script>

View File

@ -6,8 +6,10 @@
<h4 :id="titleId" class="medium">
{{ $t('views.tool.toolStore.title') }}
</h4>
<!-- <el-tag class="store-type default-tag">{{t('views.tool.toolStore.internal')}}</el-tag> -->
<el-radio-group v-model="toolType" @change="radioChange" class="app-radio-button-group">
<el-radio-button value="INTERNAL">{{ $t('views.tool.toolStore.internal') }}</el-radio-button>
<el-radio-button value="APPSTORE">{{ $t('views.tool.toolStore.title') }}</el-radio-button>
</el-radio-group>
<div class="flex align-center" style="margin-right: 28px;">
<el-input v-model="searchValue" :placeholder="$t('common.search')" prefix-icon="Search" class="w-240 mr-8"
@ -88,6 +90,7 @@ const dialogVisible = ref(false)
const loading = ref(false)
const searchValue = ref('')
const folderId = ref('')
const toolType = ref('INTERNAL')
const categories = ref<ToolCategory[]>([
//
@ -139,6 +142,14 @@ onBeforeMount(() => {
})
async function getList() {
if (toolType.value === 'INTERNAL') {
await getInternalToolList()
} else {
await getStoreToolList()
}
}
async function getInternalToolList() {
try {
const res = await ToolStoreApi.getInternalToolList({ name: searchValue.value }, loading)
if (searchValue.value.length) {
@ -158,16 +169,36 @@ async function getList() {
}
}
async function getStoreToolList() {
try {
const res = await ToolStoreApi.getStoreToolList({ name: searchValue.value }, loading)
const tags = res.data.additionalProperties.tags
const storeTools = res.data.apps
categories.value = tags.map((tag: any) => ({
id: tag.key,
title: tag.name, //
tools: storeTools.filter((tool: any) => tool.label === tag.key)
}))
} catch (error) {
console.error(error)
}
}
const handleClick = (e: MouseEvent) => {
e.preventDefault()
}
const internalDescDrawerRef = ref<InstanceType<typeof InternalDescDrawer>>()
async function handleDetail(tool: any) {
const index = tool.icon.replace('icon.png', 'detail.md')
const response = await fetch(index)
const content = await response.text()
internalDescDrawerRef.value?.open(content, tool)
if (toolType.value === 'INTERNAL') {
const index = tool.icon.replace('icon.png', 'detail.md')
const response = await fetch(index)
const content = await response.text()
internalDescDrawerRef.value?.open(content, tool)
} else {
internalDescDrawerRef.value?.open(tool.readMe, tool)
}
}
const addInternalToolDialogRef = ref<InstanceType<typeof AddInternalToolDialog>>()
@ -177,13 +208,20 @@ function handleOpenAdd(data?: any, isEdit?: boolean) {
const addLoading = ref(false)
async function handleAdd(tool: any) {
if (toolType.value === 'INTERNAL') {
await handleInternalAdd(tool)
} else {
await handleStoreAdd(tool)
}
}
async function handleInternalAdd(tool: any) {
try {
await loadSharedApi({ type: 'tool', systemType: props.apiType })
.addInternalTool(tool.id, { name: tool.name, folder_id: folderId.value }, addLoading)
.then(() => {
return user.profile()
})
// await ToolStoreApi.addInternalTool(tool.id, { name: tool.name, folder_id: folderId.value }, addLoading)
emit('refresh')
MsgSuccess(t('common.addSuccess'))
dialogVisible.value = false
@ -192,6 +230,34 @@ async function handleAdd(tool: any) {
}
}
async function handleStoreAdd(tool: any) {
try {
const obj = {
name: tool.name,
folder_id: folderId.value,
download_url: tool.downloadUrl,
download_callback_url: tool.downloadCallbackUrl,
icon: tool.icon,
versions: tool.versions,
label: tool.label
}
await loadSharedApi({ type: 'tool', systemType: props.apiType })
.addStoreTool(tool.id, obj, addLoading)
.then(() => {
return user.profile()
})
emit('refresh')
MsgSuccess(t('common.addSuccess'))
dialogVisible.value = false
} catch (error) {
console.error(error)
}
}
function radioChange() {
getList()
}
defineExpose({ open })
</script>
<style lang="scss">