mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
feat: add template store dialog and related functionality for knowledge templates
This commit is contained in:
parent
4ec5112274
commit
515a5140d4
|
|
@ -5,6 +5,7 @@ import pickle
|
|||
from functools import reduce
|
||||
from typing import Dict, List
|
||||
|
||||
import requests
|
||||
import uuid_utils.compat as uuid
|
||||
from django.core.cache import cache
|
||||
from django.db import transaction
|
||||
|
|
@ -25,7 +26,9 @@ from common.db.search import page_search
|
|||
from common.exception.app_exception import AppApiException
|
||||
from common.field.common import UploadedFileField
|
||||
from common.result import result
|
||||
from common.utils.common import bytes_to_uploaded_file
|
||||
from common.utils.common import restricted_loads, generate_uuid
|
||||
from common.utils.logger import maxkb_logger
|
||||
from common.utils.rsa_util import rsa_long_decrypt
|
||||
from common.utils.tool_code import ToolExecutor
|
||||
from knowledge.models import KnowledgeScope, Knowledge, KnowledgeType, KnowledgeWorkflow, KnowledgeWorkflowVersion
|
||||
|
|
@ -70,6 +73,7 @@ class KnowledgeWorkflowActionListQuerySerializer(serializers.Serializer):
|
|||
user_name = serializers.CharField(required=False, label=_('Name'), allow_blank=True, allow_null=True)
|
||||
state = serializers.CharField(required=False, label=_("State"), allow_blank=True, allow_null=True)
|
||||
|
||||
|
||||
class KBWFInstance:
|
||||
|
||||
def __init__(self, knowledge_workflow: dict, function_lib_list: List[dict], version: str, tool_list: List[dict]):
|
||||
|
|
@ -81,6 +85,7 @@ class KBWFInstance:
|
|||
def get_tool_list(self):
|
||||
return [*(self.tool_list or []), *(self.function_lib_list or [])]
|
||||
|
||||
|
||||
class KnowledgeWorkflowActionSerializer(serializers.Serializer):
|
||||
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
|
||||
knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))
|
||||
|
|
@ -248,6 +253,24 @@ class KnowledgeWorkflowSerializer(serializers.Serializer):
|
|||
|
||||
knowledge_workflow.save()
|
||||
save_workflow_mapping(instance.get('work_flow', {}), ResourceType.KNOWLEDGE, str(knowledge_id))
|
||||
|
||||
# 处理 work_flow_template
|
||||
if instance.get('work_flow_template') is not None:
|
||||
template_instance = instance.get('work_flow_template')
|
||||
download_url = template_instance.get('downloadUrl')
|
||||
# 查找匹配的版本名称
|
||||
res = requests.get(download_url, timeout=5)
|
||||
KnowledgeWorkflowSerializer.Import(data={
|
||||
'user_id': self.data.get('user_id'),
|
||||
'workspace_id': self.data.get('workspace_id'),
|
||||
'knowledge_id': str(knowledge_id),
|
||||
}).import_({'file': bytes_to_uploaded_file(res.content, 'file.kbwf')}, is_import_tool=True)
|
||||
|
||||
try:
|
||||
requests.get(template_instance.get('downloadCallbackUrl'), timeout=5)
|
||||
except Exception as e:
|
||||
maxkb_logger.error(f"callback appstore tool download error: {e}")
|
||||
|
||||
return {**KnowledgeModelSerializer(knowledge).data, 'document_list': []}
|
||||
|
||||
class Import(serializers.Serializer):
|
||||
|
|
@ -255,6 +278,7 @@ class KnowledgeWorkflowSerializer(serializers.Serializer):
|
|||
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
|
||||
knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))
|
||||
|
||||
@transaction.atomic
|
||||
def import_(self, instance: dict, is_import_tool, with_valid=True):
|
||||
if with_valid:
|
||||
self.is_valid()
|
||||
|
|
@ -296,8 +320,10 @@ class KnowledgeWorkflowSerializer(serializers.Serializer):
|
|||
update_tool_map,
|
||||
)
|
||||
tool_model_list = [self.to_tool(tool, workspace_id, user_id) for tool in tool_list]
|
||||
KnowledgeWorkflow.objects.filter(workspace_id=workspace_id,knowledge_id=knowledge_id).update(
|
||||
work_flow=work_flow
|
||||
KnowledgeWorkflow.objects.filter(workspace_id=workspace_id, knowledge_id=knowledge_id).update_or_create(
|
||||
knowledge_id=knowledge_id,
|
||||
workspace_id=workspace_id,
|
||||
defaults={'work_flow': work_flow}
|
||||
)
|
||||
|
||||
if is_import_tool:
|
||||
|
|
@ -373,7 +399,6 @@ class KnowledgeWorkflowSerializer(serializers.Serializer):
|
|||
except Exception as e:
|
||||
return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
class Operate(serializers.Serializer):
|
||||
user_id = serializers.UUIDField(required=True, label=_('user id'))
|
||||
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
|
||||
|
|
@ -416,6 +441,23 @@ class KnowledgeWorkflowSerializer(serializers.Serializer):
|
|||
'work_flow': instance.get('work_flow')
|
||||
})
|
||||
return self.one()
|
||||
if instance.get("work_flow_template"):
|
||||
template_instance = instance.get('work_flow_template')
|
||||
download_url = template_instance.get('downloadUrl')
|
||||
# 查找匹配的版本名称
|
||||
res = requests.get(download_url, timeout=5)
|
||||
KnowledgeWorkflowSerializer.Import(data={
|
||||
'user_id': self.data.get('user_id'),
|
||||
'workspace_id': self.data.get('workspace_id'),
|
||||
'knowledge_id': str(self.data.get('knowledge_id')),
|
||||
}).import_({'file': bytes_to_uploaded_file(res.content, 'file.kbwf')}, is_import_tool=False)
|
||||
|
||||
try:
|
||||
requests.get(template_instance.get('downloadCallbackUrl'), timeout=5)
|
||||
except Exception as e:
|
||||
maxkb_logger.error(f"callback appstore tool download error: {e}")
|
||||
|
||||
return self.one()
|
||||
|
||||
def one(self):
|
||||
self.is_valid(raise_exception=True)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,20 @@ const getStoreToolList: (param?: any, loading?: Ref<boolean>) => Promise<Result<
|
|||
return get('/workspace/store/tool', param, loading)
|
||||
}
|
||||
|
||||
const getStoreKBList: (param?: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
param,
|
||||
loading,
|
||||
) => {
|
||||
return get('/workspace/store/knowledge_template', param, loading)
|
||||
}
|
||||
|
||||
const getStoreAppList: (param?: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
param,
|
||||
loading,
|
||||
) => {
|
||||
return get('/workspace/store/application_template', param, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具商店-添加系统内置
|
||||
*/
|
||||
|
|
@ -57,6 +71,8 @@ const addStoreTool: (
|
|||
export default {
|
||||
getInternalToolList,
|
||||
getStoreToolList,
|
||||
getStoreKBList,
|
||||
getStoreAppList,
|
||||
addInternalTool,
|
||||
addStoreTool
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,13 @@
|
|||
</el-button>
|
||||
</div>
|
||||
<div v-else-if="!route.path.includes('share/')">
|
||||
<el-button
|
||||
class="ml-8"
|
||||
v-if="permissionPrecise.create()"
|
||||
@click="openTemplateStoreDialog()"
|
||||
>
|
||||
{{ $t('模版中心') }}
|
||||
</el-button>
|
||||
<el-button @click="showPopover = !showPopover">
|
||||
<AppIcon iconName="app-add-outlined" class="mr-4" />
|
||||
{{ $t('workflow.setting.addComponent') }}
|
||||
|
|
@ -161,6 +168,7 @@
|
|||
v-click-outside="clickoutsideHistory"
|
||||
@refreshVersion="refreshVersion"
|
||||
/>
|
||||
<TemplateStoreDialog ref="templateStoreDialogRef" :api-type="apiType" source="work_flow" @refresh="getDetail"/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
|
@ -186,6 +194,7 @@ import permissionMap from '@/permission'
|
|||
import { WorkflowMode } from '@/enums/application'
|
||||
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
||||
import { knowledgeBaseNode } from '@/workflow/common/data'
|
||||
import TemplateStoreDialog from "@/views/knowledge/template-store/TemplateStoreDialog.vue";
|
||||
provide('getResourceDetail', () => detail)
|
||||
provide('workflowMode', WorkflowMode.Knowledge)
|
||||
provide('loopWorkflowMode', WorkflowMode.KnowledgeLoop)
|
||||
|
|
@ -649,6 +658,12 @@ const toImportDoc = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const templateStoreDialogRef = ref()
|
||||
function openTemplateStoreDialog() {
|
||||
templateStoreDialogRef.value?.open(folderId)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 定时保存
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -35,6 +35,13 @@
|
|||
<el-option v-for="u in user_options" :key="u.id" :value="u.id" :label="u.nick_name" />
|
||||
</el-select>
|
||||
</div>
|
||||
<el-button
|
||||
class="ml-8"
|
||||
v-if="!isShared && permissionPrecise.create()"
|
||||
@click="openTemplateStoreDialog()"
|
||||
>
|
||||
{{ $t('模版中心') }}
|
||||
</el-button>
|
||||
<el-dropdown trigger="click" v-if="!isShared && permissionPrecise.create()">
|
||||
<el-button type="primary" class="ml-8">
|
||||
{{ $t('common.create') }}
|
||||
|
|
@ -305,6 +312,7 @@
|
|||
ref="ResourceAuthorizationDrawerRef"
|
||||
v-if="apiType === 'workspace'"
|
||||
/>
|
||||
<TemplateStoreDialog ref="templateStoreDialogRef" :api-type="apiType" @refresh="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -329,6 +337,7 @@ import { i18n_name } from '@/utils/common'
|
|||
import { SourceTypeEnum } from '@/enums/common'
|
||||
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
||||
import permissionMap from '@/permission'
|
||||
import TemplateStoreDialog from "@/views/knowledge/template-store/TemplateStoreDialog.vue";
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { folder, user, knowledge } = useStore()
|
||||
|
|
@ -541,6 +550,11 @@ function refreshFolder() {
|
|||
emit('refreshFolder')
|
||||
}
|
||||
|
||||
const templateStoreDialogRef = ref()
|
||||
function openTemplateStoreDialog() {
|
||||
templateStoreDialogRef.value?.open(folder.currentFolder.id)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (apiType.value !== 'workspace') {
|
||||
folder.setCurrentFolder({
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ const dialogVisible = ref<boolean>(false)
|
|||
const currentFolder = ref<any>(null)
|
||||
|
||||
const workflowDefault = ref(knowledgeTemplate.default)
|
||||
const workflowTemplate = ref()
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
|
|
@ -59,8 +60,11 @@ watch(dialogVisible, (bool) => {
|
|||
}
|
||||
})
|
||||
|
||||
const open = (folder: string) => {
|
||||
const open = (folder: string, workflow?: any) => {
|
||||
currentFolder.value = folder
|
||||
if (workflow) {
|
||||
workflowTemplate.value = workflow
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
|
|
@ -69,6 +73,7 @@ const submitHandle = async () => {
|
|||
const obj = {
|
||||
folder_id: currentFolder.value?.id,
|
||||
work_flow: workflowDefault.value,
|
||||
work_flow_template: workflowTemplate.value,
|
||||
...BaseFormRef.value.form,
|
||||
}
|
||||
loadSharedApi({ type: 'knowledge', systemType: apiType.value })
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
<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">
|
||||
<el-avatar
|
||||
v-if="isAppIcon(toolDetail?.icon)"
|
||||
shape="square"
|
||||
:size="64"
|
||||
style="background: none"
|
||||
class="mr-8"
|
||||
>
|
||||
<img :src="toolDetail?.icon" alt="" />
|
||||
</el-avatar>
|
||||
<el-avatar
|
||||
v-else-if="toolDetail?.name"
|
||||
:name="toolDetail?.name"
|
||||
pinyinColor
|
||||
shape="square"
|
||||
:size="64"
|
||||
class="mr-8"
|
||||
/>
|
||||
<div class="ml-16">
|
||||
<h3 class="mb-8">{{ toolDetail.name }}</h3>
|
||||
<el-text type="info" v-if="toolDetail?.desc">
|
||||
{{ toolDetail.desc }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
<div @click.stop>
|
||||
<el-button type="primary" @click="addInternalTool(toolDetail)">
|
||||
{{ $t('common.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</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, numberFormat } from '@/utils/common'
|
||||
const emit = defineEmits(['refresh', 'addTool'])
|
||||
|
||||
const visibleInternalDesc = ref(false)
|
||||
const markdownContent = ref('')
|
||||
const toolDetail = ref<any>({})
|
||||
|
||||
watch(visibleInternalDesc, (bool) => {
|
||||
if (!bool) {
|
||||
markdownContent.value = ''
|
||||
}
|
||||
})
|
||||
|
||||
const open = (data: any, detail: any) => {
|
||||
toolDetail.value = detail
|
||||
if (data) {
|
||||
markdownContent.value = cloneDeep(data)
|
||||
}
|
||||
visibleInternalDesc.value = true
|
||||
}
|
||||
|
||||
const addInternalTool = (data: any) => {
|
||||
emit('addTool', data)
|
||||
visibleInternalDesc.value = false
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
})
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
<template>
|
||||
<CardBox :title="props.tool.name" :description="props.tool.desc" class="cursor tool-card">
|
||||
<template #icon>
|
||||
<el-avatar
|
||||
v-if="isAppIcon(props.tool?.icon)"
|
||||
shape="square"
|
||||
:size="32"
|
||||
style="background: none"
|
||||
>
|
||||
<img :src="resetUrl(props.tool?.icon)" alt=""/>
|
||||
</el-avatar>
|
||||
<el-avatar
|
||||
v-else-if="props.tool?.name"
|
||||
:name="props.tool?.name"
|
||||
pinyinColor
|
||||
shape="square"
|
||||
:size="32"
|
||||
/>
|
||||
</template>
|
||||
<template #title>
|
||||
<div class="flex align-center">
|
||||
<span :title="props.tool?.name" class="ellipsis"> {{ props.tool?.name }}</span>
|
||||
<el-tag v-if="props.tool?.version" class="ml-4" type="info" effect="plain">
|
||||
{{ props.tool?.version }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #tag>
|
||||
<el-tag type="info" v-if="props.tool?.label === 'knowledge_template'" class="info-tag">
|
||||
{{ $t('知识库') }}
|
||||
</el-tag>
|
||||
<el-tag type="info" class="info-tag" v-else>
|
||||
{{ $t('views.tool.title') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #subTitle>
|
||||
<el-text class="color-secondary lighter" size="small">
|
||||
{{ getSubTitle(props.tool) }}
|
||||
</el-text>
|
||||
</template>
|
||||
<template #footer>
|
||||
<span class="card-footer-left color-secondary" v-if="props.tool?.downloads != undefined">
|
||||
{{ `${$t('views.document.upload.download')}: ${numberFormat(props.tool.downloads || 0)} ` }}
|
||||
</span>
|
||||
<div class="card-footer-operation mb-8" @click.stop>
|
||||
<el-button @click="emit('handleDetail')">
|
||||
{{ $t('common.detail') }}
|
||||
</el-button>
|
||||
<el-button type="primary" :loading="props.addLoading" @click="emit('handleAdd')">
|
||||
{{ $t('common.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { isAppIcon, numberFormat, resetUrl } from '@/utils/common'
|
||||
|
||||
const props = defineProps<{
|
||||
tool: any
|
||||
getSubTitle: (v: any) => string
|
||||
addLoading: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'handleAdd'): void
|
||||
(e: 'handleDetail'): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tool-card {
|
||||
:deep(.card-footer) {
|
||||
& > div:first-of-type {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-footer-operation {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.card-footer-left {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card-footer-operation {
|
||||
display: flex !important;
|
||||
|
||||
.el-button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,307 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
width="1200"
|
||||
append-to-body
|
||||
class="tool-store-dialog"
|
||||
align-center
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<template #header="{ titleId }">
|
||||
<div class="dialog-header flex-between mb-8">
|
||||
<h4 :id="titleId" class="medium w-240 mr-8">
|
||||
{{ $t('模版中心') }}
|
||||
</h4>
|
||||
|
||||
<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"
|
||||
clearable
|
||||
@change="getList"
|
||||
/>
|
||||
<el-divider direction="vertical"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<LayoutContainer v-loading="loading" :minLeftWidth="204">
|
||||
<template #left>
|
||||
<el-anchor
|
||||
direction="vertical"
|
||||
:offset="130"
|
||||
type="default"
|
||||
container=".category-scrollbar"
|
||||
@click="handleClick"
|
||||
>
|
||||
<el-anchor-link
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
:href="`#category-${category.id}`"
|
||||
:title="category.title"
|
||||
/>
|
||||
</el-anchor>
|
||||
</template>
|
||||
|
||||
<el-scrollbar class="layout-bg" wrap-class="p-16-24 category-scrollbar">
|
||||
<template v-if="filterList === null">
|
||||
<div v-for="category in categories" :key="category.id">
|
||||
<h4
|
||||
class="title-decoration-1 mb-16 mt-8 color-text-primary"
|
||||
:id="`category-${category.id}`"
|
||||
>
|
||||
{{ category.title }}
|
||||
</h4>
|
||||
<el-row :gutter="16">
|
||||
<el-col v-for="tool in category.tools" :key="tool.id" :span="8" class="mb-16">
|
||||
<TemplateCard
|
||||
:tool="tool"
|
||||
:addLoading="addLoading"
|
||||
:get-sub-title="getSubTitle"
|
||||
@handleAdd="handleOpenAdd(tool)"
|
||||
@handleDetail="handleDetail(tool)"
|
||||
>
|
||||
</TemplateCard>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else>
|
||||
<h4 class="color-text-primary medium mb-16">
|
||||
<span class="color-primary">{{ searchValue }}</span>
|
||||
{{ t('views.tool.toolStore.searchResult', {count: filterList.length}) }}
|
||||
</h4>
|
||||
<el-row :gutter="16" v-if="filterList.length">
|
||||
<el-col v-for="tool in filterList" :key="tool.id" :span="12" class="mb-16">
|
||||
<TemplateCard
|
||||
:tool="tool"
|
||||
:addLoading="addLoading"
|
||||
:get-sub-title="getSubTitle"
|
||||
@handleAdd="handleOpenAdd(tool)"
|
||||
@handleDetail="handleDetail(tool)"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-empty v-else :description="$t('common.noData')"/>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</LayoutContainer>
|
||||
</el-dialog>
|
||||
<InternalDescDrawer ref="internalDescDrawerRef" @addTool="handleOpenAdd"/>
|
||||
<CreateWorkflowKnowledgeDialog ref="CreateKnowledgeDialogRef"/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import ToolStoreApi from '@/api/tool/store'
|
||||
import { t } from '@/locales'
|
||||
import TemplateCard from './TemplateCard.vue'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
import InternalDescDrawer from './InternalDescDrawer.vue'
|
||||
import { loadSharedApi } from '@/utils/dynamics-api/shared-api.ts'
|
||||
import useStore from '@/stores'
|
||||
import CreateWorkflowKnowledgeDialog
|
||||
from "@/views/knowledge/create-component/CreateWorkflowKnowledgeDialog.vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
const {user} = useStore()
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: {id},
|
||||
/*
|
||||
folderId 可以区分 resource-management shared还是 workspace
|
||||
*/
|
||||
} = route as any
|
||||
|
||||
interface ToolCategory {
|
||||
id: string
|
||||
title: string
|
||||
tools: any[]
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
apiType: {
|
||||
type: String as () => 'workspace' | 'systemShare' | 'systemManage' | 'workspaceShare',
|
||||
default: 'workspace',
|
||||
},
|
||||
source: {
|
||||
type: String,
|
||||
default: 'knowledge',
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const searchValue = ref('')
|
||||
const folderId = ref('')
|
||||
const categories = ref<ToolCategory[]>([])
|
||||
|
||||
const filterList = ref<any>(null)
|
||||
|
||||
function getSubTitle(tool: any) {
|
||||
return categories.value.find((i) => i.id === tool.label)?.title ?? ''
|
||||
}
|
||||
|
||||
function open(id: string) {
|
||||
folderId.value = id
|
||||
filterList.value = null
|
||||
dialogVisible.value = true
|
||||
|
||||
getList()
|
||||
}
|
||||
|
||||
async function getList() {
|
||||
filterList.value = null
|
||||
const [v1] = await Promise.all([getStoreToolList()])
|
||||
|
||||
const merged = [...v1].reduce((acc, category) => {
|
||||
const existing = acc.find((item: any) => item.id === category.id)
|
||||
if (existing) {
|
||||
existing.tools = [...existing.tools, ...category.tools]
|
||||
} else {
|
||||
acc.push({...category})
|
||||
}
|
||||
return acc
|
||||
}, [] as ToolCategory[])
|
||||
|
||||
categories.value = merged.filter((item: any) => item.tools.length > 0)
|
||||
}
|
||||
|
||||
|
||||
async function getStoreToolList() {
|
||||
try {
|
||||
const res = await ToolStoreApi.getStoreKBList({name: searchValue.value}, loading)
|
||||
const tags = res.data.additionalProperties.tags
|
||||
const storeTools = res.data.apps
|
||||
let categories = []
|
||||
//
|
||||
storeTools.forEach((tool: any) => {
|
||||
tool.desc = tool.description
|
||||
})
|
||||
if (searchValue.value.length) {
|
||||
filterList.value = [...res.data.apps, ...(filterList.value || [])]
|
||||
} else {
|
||||
filterList.value = null
|
||||
categories = tags.map((tag: any) => ({
|
||||
id: tag.key,
|
||||
title: tag.name, // 国际化
|
||||
tools: storeTools.filter((tool: any) => tool.label === tag.key),
|
||||
}))
|
||||
}
|
||||
return categories
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
const internalDescDrawerRef = ref<InstanceType<typeof InternalDescDrawer>>()
|
||||
|
||||
async function handleDetail(tool: any) {
|
||||
internalDescDrawerRef.value?.open(tool.readMe, tool)
|
||||
}
|
||||
|
||||
const CreateKnowledgeDialogRef = ref()
|
||||
|
||||
function handleOpenAdd(data?: any, isEdit?: boolean) {
|
||||
if (props.source === 'work_flow') {
|
||||
handleStoreAdd(data)
|
||||
} else {
|
||||
CreateKnowledgeDialogRef.value.open({id: folderId.value}, data)
|
||||
}
|
||||
}
|
||||
|
||||
const addLoading = ref(false)
|
||||
|
||||
function handleStoreAdd(tool: any) {
|
||||
try {
|
||||
loadSharedApi({type: 'knowledge', systemType: props.apiType})
|
||||
.putKnowledgeWorkflow(id, {work_flow_template: tool})
|
||||
.then(() => {
|
||||
emit('refresh')
|
||||
MsgSuccess(t('common.addSuccess'))
|
||||
})
|
||||
dialogVisible.value = false
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
defineExpose({open})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.tool-store-dialog {
|
||||
padding: 0;
|
||||
|
||||
.el-dialog__headerbtn {
|
||||
top: 7px;
|
||||
}
|
||||
|
||||
.el-dialog__header {
|
||||
padding: 12px 20px 4px 24px;
|
||||
border-bottom: 1px solid var(--el-border-color-light);
|
||||
|
||||
.dialog-header {
|
||||
position: relative;
|
||||
|
||||
.store-type {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-container__left {
|
||||
background-color: var(--app-layout-bg-color);
|
||||
border-radius: 0 0 0 8px;
|
||||
}
|
||||
|
||||
.layout-container__right {
|
||||
background-color: var(--app-layout-bg-color);
|
||||
border-radius: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.el-anchor {
|
||||
background-color: var(--app-layout-bg-color);
|
||||
|
||||
.el-anchor__marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-anchor__list {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.el-anchor__item {
|
||||
.el-anchor__link {
|
||||
padding: 8px 16px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-primary);
|
||||
border-radius: 6px;
|
||||
|
||||
&.is-active {
|
||||
color: var(--el-color-primary);
|
||||
background-color: #3370ff1a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.category-scrollbar {
|
||||
height: calc(100vh - 200px);
|
||||
// min-height: 500px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -164,7 +164,7 @@ async function getList() {
|
|||
return acc
|
||||
}, [] as ToolCategory[])
|
||||
|
||||
categories.value = merged
|
||||
categories.value = merged.filter((item: any) => item.tools.length > 0)
|
||||
}
|
||||
|
||||
async function getInternalToolList() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue