Skip to content

Commit 515a514

Browse files
committed
feat: add template store dialog and related functionality for knowledge templates
1 parent 4ec5112 commit 515a514

File tree

9 files changed

+595
-5
lines changed

9 files changed

+595
-5
lines changed

apps/knowledge/serializers/knowledge_workflow.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from functools import reduce
66
from typing import Dict, List
77

8+
import requests
89
import uuid_utils.compat as uuid
910
from django.core.cache import cache
1011
from django.db import transaction
@@ -25,7 +26,9 @@
2526
from common.exception.app_exception import AppApiException
2627
from common.field.common import UploadedFileField
2728
from common.result import result
29+
from common.utils.common import bytes_to_uploaded_file
2830
from common.utils.common import restricted_loads, generate_uuid
31+
from common.utils.logger import maxkb_logger
2932
from common.utils.rsa_util import rsa_long_decrypt
3033
from common.utils.tool_code import ToolExecutor
3134
from knowledge.models import KnowledgeScope, Knowledge, KnowledgeType, KnowledgeWorkflow, KnowledgeWorkflowVersion
@@ -70,6 +73,7 @@ class KnowledgeWorkflowActionListQuerySerializer(serializers.Serializer):
7073
user_name = serializers.CharField(required=False, label=_('Name'), allow_blank=True, allow_null=True)
7174
state = serializers.CharField(required=False, label=_("State"), allow_blank=True, allow_null=True)
7275

76+
7377
class KBWFInstance:
7478

7579
def __init__(self, knowledge_workflow: dict, function_lib_list: List[dict], version: str, tool_list: List[dict]):
@@ -81,6 +85,7 @@ def __init__(self, knowledge_workflow: dict, function_lib_list: List[dict], vers
8185
def get_tool_list(self):
8286
return [*(self.tool_list or []), *(self.function_lib_list or [])]
8387

88+
8489
class KnowledgeWorkflowActionSerializer(serializers.Serializer):
8590
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
8691
knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))
@@ -248,13 +253,32 @@ def save_workflow(self, instance: Dict):
248253

249254
knowledge_workflow.save()
250255
save_workflow_mapping(instance.get('work_flow', {}), ResourceType.KNOWLEDGE, str(knowledge_id))
256+
257+
# 处理 work_flow_template
258+
if instance.get('work_flow_template') is not None:
259+
template_instance = instance.get('work_flow_template')
260+
download_url = template_instance.get('downloadUrl')
261+
# 查找匹配的版本名称
262+
res = requests.get(download_url, timeout=5)
263+
KnowledgeWorkflowSerializer.Import(data={
264+
'user_id': self.data.get('user_id'),
265+
'workspace_id': self.data.get('workspace_id'),
266+
'knowledge_id': str(knowledge_id),
267+
}).import_({'file': bytes_to_uploaded_file(res.content, 'file.kbwf')}, is_import_tool=True)
268+
269+
try:
270+
requests.get(template_instance.get('downloadCallbackUrl'), timeout=5)
271+
except Exception as e:
272+
maxkb_logger.error(f"callback appstore tool download error: {e}")
273+
251274
return {**KnowledgeModelSerializer(knowledge).data, 'document_list': []}
252275

253276
class Import(serializers.Serializer):
254277
user_id = serializers.UUIDField(required=True, label=_('user id'))
255278
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
256279
knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))
257280

281+
@transaction.atomic
258282
def import_(self, instance: dict, is_import_tool, with_valid=True):
259283
if with_valid:
260284
self.is_valid()
@@ -296,8 +320,10 @@ def import_(self, instance: dict, is_import_tool, with_valid=True):
296320
update_tool_map,
297321
)
298322
tool_model_list = [self.to_tool(tool, workspace_id, user_id) for tool in tool_list]
299-
KnowledgeWorkflow.objects.filter(workspace_id=workspace_id,knowledge_id=knowledge_id).update(
300-
work_flow=work_flow
323+
KnowledgeWorkflow.objects.filter(workspace_id=workspace_id, knowledge_id=knowledge_id).update_or_create(
324+
knowledge_id=knowledge_id,
325+
workspace_id=workspace_id,
326+
defaults={'work_flow': work_flow}
301327
)
302328

303329
if is_import_tool:
@@ -373,7 +399,6 @@ def export(self, with_valid=True):
373399
except Exception as e:
374400
return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR)
375401

376-
377402
class Operate(serializers.Serializer):
378403
user_id = serializers.UUIDField(required=True, label=_('user id'))
379404
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
@@ -416,6 +441,23 @@ def edit(self, instance: Dict):
416441
'work_flow': instance.get('work_flow')
417442
})
418443
return self.one()
444+
if instance.get("work_flow_template"):
445+
template_instance = instance.get('work_flow_template')
446+
download_url = template_instance.get('downloadUrl')
447+
# 查找匹配的版本名称
448+
res = requests.get(download_url, timeout=5)
449+
KnowledgeWorkflowSerializer.Import(data={
450+
'user_id': self.data.get('user_id'),
451+
'workspace_id': self.data.get('workspace_id'),
452+
'knowledge_id': str(self.data.get('knowledge_id')),
453+
}).import_({'file': bytes_to_uploaded_file(res.content, 'file.kbwf')}, is_import_tool=False)
454+
455+
try:
456+
requests.get(template_instance.get('downloadCallbackUrl'), timeout=5)
457+
except Exception as e:
458+
maxkb_logger.error(f"callback appstore tool download error: {e}")
459+
460+
return self.one()
419461

420462
def one(self):
421463
self.is_valid(raise_exception=True)

ui/src/api/tool/store.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@ const getStoreToolList: (param?: any, loading?: Ref<boolean>) => Promise<Result<
3232
return get('/workspace/store/tool', param, loading)
3333
}
3434

35+
const getStoreKBList: (param?: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
36+
param,
37+
loading,
38+
) => {
39+
return get('/workspace/store/knowledge_template', param, loading)
40+
}
41+
42+
const getStoreAppList: (param?: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
43+
param,
44+
loading,
45+
) => {
46+
return get('/workspace/store/application_template', param, loading)
47+
}
48+
3549
/**
3650
* 工具商店-添加系统内置
3751
*/
@@ -57,6 +71,8 @@ const addStoreTool: (
5771
export default {
5872
getInternalToolList,
5973
getStoreToolList,
74+
getStoreKBList,
75+
getStoreAppList,
6076
addInternalTool,
6177
addStoreTool
6278
}

ui/src/views/knowledge-workflow/index.vue

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@
2626
</el-button>
2727
</div>
2828
<div v-else-if="!route.path.includes('share/')">
29+
<el-button
30+
class="ml-8"
31+
v-if="permissionPrecise.create()"
32+
@click="openTemplateStoreDialog()"
33+
>
34+
{{ $t('模版中心') }}
35+
</el-button>
2936
<el-button @click="showPopover = !showPopover">
3037
<AppIcon iconName="app-add-outlined" class="mr-4" />
3138
{{ $t('workflow.setting.addComponent') }}
@@ -161,6 +168,7 @@
161168
v-click-outside="clickoutsideHistory"
162169
@refreshVersion="refreshVersion"
163170
/>
171+
<TemplateStoreDialog ref="templateStoreDialogRef" :api-type="apiType" source="work_flow" @refresh="getDetail"/>
164172
</div>
165173
</template>
166174
<script setup lang="ts">
@@ -186,6 +194,7 @@ import permissionMap from '@/permission'
186194
import { WorkflowMode } from '@/enums/application'
187195
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
188196
import { knowledgeBaseNode } from '@/workflow/common/data'
197+
import TemplateStoreDialog from "@/views/knowledge/template-store/TemplateStoreDialog.vue";
189198
provide('getResourceDetail', () => detail)
190199
provide('workflowMode', WorkflowMode.Knowledge)
191200
provide('loopWorkflowMode', WorkflowMode.KnowledgeLoop)
@@ -649,6 +658,12 @@ const toImportDoc = () => {
649658
}
650659
}
651660
661+
const templateStoreDialogRef = ref()
662+
function openTemplateStoreDialog() {
663+
templateStoreDialogRef.value?.open(folderId)
664+
}
665+
666+
652667
/**
653668
* 定时保存
654669
*/

ui/src/views/knowledge/component/KnowledgeListContainer.vue

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@
3535
<el-option v-for="u in user_options" :key="u.id" :value="u.id" :label="u.nick_name" />
3636
</el-select>
3737
</div>
38+
<el-button
39+
class="ml-8"
40+
v-if="!isShared && permissionPrecise.create()"
41+
@click="openTemplateStoreDialog()"
42+
>
43+
{{ $t('模版中心') }}
44+
</el-button>
3845
<el-dropdown trigger="click" v-if="!isShared && permissionPrecise.create()">
3946
<el-button type="primary" class="ml-8">
4047
{{ $t('common.create') }}
@@ -305,6 +312,7 @@
305312
ref="ResourceAuthorizationDrawerRef"
306313
v-if="apiType === 'workspace'"
307314
/>
315+
<TemplateStoreDialog ref="templateStoreDialogRef" :api-type="apiType" @refresh="getList" />
308316
</template>
309317

310318
<script lang="ts" setup>
@@ -329,6 +337,7 @@ import { i18n_name } from '@/utils/common'
329337
import { SourceTypeEnum } from '@/enums/common'
330338
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
331339
import permissionMap from '@/permission'
340+
import TemplateStoreDialog from "@/views/knowledge/template-store/TemplateStoreDialog.vue";
332341
const router = useRouter()
333342
const route = useRoute()
334343
const { folder, user, knowledge } = useStore()
@@ -541,6 +550,11 @@ function refreshFolder() {
541550
emit('refreshFolder')
542551
}
543552
553+
const templateStoreDialogRef = ref()
554+
function openTemplateStoreDialog() {
555+
templateStoreDialogRef.value?.open(folder.currentFolder.id)
556+
}
557+
544558
onMounted(() => {
545559
if (apiType.value !== 'workspace') {
546560
folder.setCurrentFolder({

ui/src/views/knowledge/create-component/CreateWorkflowKnowledgeDialog.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,19 @@ const dialogVisible = ref<boolean>(false)
5252
const currentFolder = ref<any>(null)
5353
5454
const workflowDefault = ref(knowledgeTemplate.default)
55+
const workflowTemplate = ref()
5556
5657
watch(dialogVisible, (bool) => {
5758
if (!bool) {
5859
currentFolder.value = null
5960
}
6061
})
6162
62-
const open = (folder: string) => {
63+
const open = (folder: string, workflow?: any) => {
6364
currentFolder.value = folder
65+
if (workflow) {
66+
workflowTemplate.value = workflow
67+
}
6468
dialogVisible.value = true
6569
}
6670
@@ -69,6 +73,7 @@ const submitHandle = async () => {
6973
const obj = {
7074
folder_id: currentFolder.value?.id,
7175
work_flow: workflowDefault.value,
76+
work_flow_template: workflowTemplate.value,
7277
...BaseFormRef.value.form,
7378
}
7479
loadSharedApi({ type: 'knowledge', systemType: apiType.value })
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<template>
2+
<el-drawer v-model="visibleInternalDesc" size="60%" :append-to-body="true">
3+
<template #header>
4+
<div class="flex align-center" style="margin-left: -8px">
5+
<el-button class="cursor mr-4" link @click.prevent="visibleInternalDesc = false">
6+
<el-icon :size="20">
7+
<Back />
8+
</el-icon>
9+
</el-button>
10+
<h4>详情</h4>
11+
</div>
12+
</template>
13+
14+
<div>
15+
<div class="card-header">
16+
<div class="flex-between">
17+
<div class="title flex align-center">
18+
<el-avatar
19+
v-if="isAppIcon(toolDetail?.icon)"
20+
shape="square"
21+
:size="64"
22+
style="background: none"
23+
class="mr-8"
24+
>
25+
<img :src="toolDetail?.icon" alt="" />
26+
</el-avatar>
27+
<el-avatar
28+
v-else-if="toolDetail?.name"
29+
:name="toolDetail?.name"
30+
pinyinColor
31+
shape="square"
32+
:size="64"
33+
class="mr-8"
34+
/>
35+
<div class="ml-16">
36+
<h3 class="mb-8">{{ toolDetail.name }}</h3>
37+
<el-text type="info" v-if="toolDetail?.desc">
38+
{{ toolDetail.desc }}
39+
</el-text>
40+
</div>
41+
</div>
42+
<div @click.stop>
43+
<el-button type="primary" @click="addInternalTool(toolDetail)">
44+
{{ $t('common.add') }}
45+
</el-button>
46+
</div>
47+
</div>
48+
</div>
49+
<MdPreview
50+
ref="editorRef"
51+
editorId="preview-only"
52+
:modelValue="markdownContent"
53+
style="background: none"
54+
noImgZoomIn
55+
/>
56+
</div>
57+
</el-drawer>
58+
</template>
59+
60+
<script setup lang="ts">
61+
import { ref, watch } from 'vue'
62+
import { cloneDeep } from 'lodash'
63+
import { isAppIcon, numberFormat } from '@/utils/common'
64+
const emit = defineEmits(['refresh', 'addTool'])
65+
66+
const visibleInternalDesc = ref(false)
67+
const markdownContent = ref('')
68+
const toolDetail = ref<any>({})
69+
70+
watch(visibleInternalDesc, (bool) => {
71+
if (!bool) {
72+
markdownContent.value = ''
73+
}
74+
})
75+
76+
const open = (data: any, detail: any) => {
77+
toolDetail.value = detail
78+
if (data) {
79+
markdownContent.value = cloneDeep(data)
80+
}
81+
visibleInternalDesc.value = true
82+
}
83+
84+
const addInternalTool = (data: any) => {
85+
emit('addTool', data)
86+
visibleInternalDesc.value = false
87+
}
88+
89+
defineExpose({
90+
open,
91+
})
92+
</script>
93+
<style lang="scss"></style>

0 commit comments

Comments
 (0)