Compare commits

...

6 Commits

Author SHA1 Message Date
wangdan-fit2cloud 4cecf86543 feat: template center
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-12-25 15:55:47 +08:00
shaohuzhang1 c362b4ed4e
feat: [Application] User input component types support multi line text boxes, single line tabs, and single line multi select card components (#4566) 2025-12-25 14:49:05 +08:00
shaohuzhang1 c0f63e9e3d
fix: Application workflow node display error (#4565) 2025-12-25 14:32:53 +08:00
zhangzhanwei bd9faa89bd fix: Folder auth permission 2025-12-25 11:14:27 +08:00
CaptainB c067479643 chore: add AppApiException for missing application key in chat steps 2025-12-25 10:56:02 +08:00
shaohuzhang1 71c63adb0b
fix: Remove video tags during AI conversation voice playback (#4562) 2025-12-25 10:50:48 +08:00
23 changed files with 244 additions and 156 deletions

View File

@ -26,6 +26,7 @@ from application.chat_pipeline.pipeline_manage import PipelineManage
from application.chat_pipeline.step.chat_step.i_chat_step import IChatStep, PostResponseHandler
from application.flow.tools import Reasoning, mcp_response_generator
from application.models import ApplicationChatUserStats, ChatUserType, Application, ApplicationApiKey
from common.exception.app_exception import AppApiException
from common.utils.logger import maxkb_logger
from common.utils.rsa_util import rsa_long_decrypt
from common.utils.tool_code import ToolExecutor
@ -273,7 +274,10 @@ class BaseChatStep(IChatStep):
if app_key is not None:
api_key = app_key.secret_key
else:
continue
raise AppApiException(
500,
_('Application Key is required for application tool 【{name}').format(name=app.name)
)
executor = ToolExecutor()
app_config = executor.get_app_mcp_config(api_key)
mcp_servers_config[app.name] = app_config

View File

@ -13,6 +13,7 @@ from functools import reduce
from typing import List, Dict
from django.db.models import QuerySet
from django.utils.translation import gettext as _
from langchain.schema import HumanMessage, SystemMessage
from langchain_core.messages import BaseMessage, AIMessage
@ -20,6 +21,7 @@ from application.flow.i_step_node import NodeResult, INode
from application.flow.step_node.ai_chat_step_node.i_chat_node import IChatNode
from application.flow.tools import Reasoning, mcp_response_generator
from application.models import Application, ApplicationApiKey
from common.exception.app_exception import AppApiException
from common.utils.rsa_util import rsa_long_decrypt
from common.utils.tool_code import ToolExecutor
from models_provider.models import Model
@ -259,7 +261,10 @@ class BaseChatNode(IChatNode):
if app_key is not None:
api_key = app_key.secret_key
else:
continue
raise AppApiException(
500,
_('Application Key is required for application tool 【{name}').format(name=app.name)
)
executor = ToolExecutor()
app_config = executor.get_app_mcp_config(api_key)
mcp_servers_config[app.name] = app_config

View File

@ -558,7 +558,7 @@ class PermissionConstants(Enum):
)
TOOL_FOLDER_READ = Permission(
group=Group.TOOL_FOLDER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER],
parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],
parent_group=[UserGroup.TOOL],
resource_permission_group_list=[ResourcePermissionConst.TOOL_VIEW]
)
TOOL_FOLDER_CREATE = Permission(
@ -629,7 +629,7 @@ class PermissionConstants(Enum):
KNOWLEDGE_FOLDER_READ = Permission(
group=Group.KNOWLEDGE_FOLDER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER],
resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW],
parent_group = [WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]
parent_group = [UserGroup.KNOWLEDGE]
)
KNOWLEDGE_FOLDER_CREATE = Permission(
group=Group.KNOWLEDGE_FOLDER, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],
@ -961,7 +961,7 @@ class PermissionConstants(Enum):
)
APPLICATION_FOLDER_READ = Permission(group=Group.APPLICATION_FOLDER, operate=Operate.READ,
role_list=[RoleConstants.ADMIN, RoleConstants.USER],
parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],
parent_group=[UserGroup.APPLICATION],
resource_permission_group_list=[ResourcePermissionConst.APPLICATION_VIEW]
)
APPLICATION_FOLDER_CREATE = Permission(group=Group.APPLICATION_FOLDER, operate=Operate.CREATE,

View File

@ -10,7 +10,8 @@ from rest_framework import serializers
from application.models.application import Application, ApplicationFolder
from application.serializers.application import ApplicationOperateSerializer
from application.serializers.application_folder import ApplicationFolderTreeSerializer
from common.constants.permission_constants import Group, ResourcePermission, ResourcePermissionRole
from common.constants.permission_constants import Group, ResourcePermission, ResourcePermissionRole, RoleConstants
from common.database_model_manage.database_model_manage import DatabaseModelManage
from common.exception.app_exception import AppApiException
from folders.api.folder import FolderCreateRequest
from knowledge.models import KnowledgeFolder, Knowledge
@ -300,30 +301,52 @@ class FolderTreeSerializer(serializers.Serializer):
return True # 需要重建
return False
@staticmethod
def _having_read_permission_by_role(user_id: str, workspace_id: str, source: str):
workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping")
role_permission_mapping_model = DatabaseModelManage.get_model("role_permission_mapping_model")
is_x_pack_ee = workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None
if is_x_pack_ee:
return QuerySet(workspace_user_role_mapping_model).select_related('role', 'user').filter(
workspace_id=workspace_id, user_id=user_id,
role__type=RoleConstants.USER.value.__str__(),
role__rolepermission__permission_id=f"{source}_FOLDER:READ"
).exists()
return False
def get_folder_tree(self,
current_user, name=None):
self.is_valid(raise_exception=True)
Folder = get_folder_type(self.data.get('source')) # noqa
user_id = current_user.id
workspace_id = self.data.get('workspace_id')
source = self.data.get('source')
Folder = get_folder_type(source) # noqa
# 检查特定工作空间的树结构完整性
workspace_folders = Folder.objects.filter(workspace_id=self.data.get('workspace_id'))
workspace_folders = Folder.objects.filter(workspace_id=workspace_id)
# 如果发现数据不一致,重建整个表(这是 MPTT 的限制)
if self._check_tree_integrity(workspace_folders):
Folder.objects.rebuild()
workspace_manage = is_workspace_manage(current_user.id, self.data.get('workspace_id'))
workspace_manage = is_workspace_manage(user_id, workspace_id)
base_q = Q(workspace_id=self.data.get('workspace_id'))
base_q = Q(workspace_id=workspace_id)
if name is not None:
base_q &= Q(name__contains=name)
if not workspace_manage:
having_read_permission_by_role = self._having_read_permission_by_role(user_id, workspace_id, source)
permission_condition = ['VIEW']
if having_read_permission_by_role:
permission_condition = ['VIEW', 'ROLE']
base_q &= (Q(id__in=WorkspaceUserResourcePermission.objects.filter(user_id=current_user.id,
auth_target_type=self.data.get('source'),
workspace_id=self.data.get(
'workspace_id'),
permission_list__contains=['VIEW'])
permission_list__overlap=permission_condition)
.values_list(
'target', flat=True)) | Q(id=self.data.get('workspace_id')))
@ -332,4 +355,5 @@ class FolderTreeSerializer(serializers.Serializer):
TreeSerializer = get_folder_tree_serializer(self.data.get('source')) # noqa
serializer = TreeSerializer(nodes, many=True)
return [d for d in serializer.data if d.get('id') == d.get('workspace_id')] if name is None else serializer.data # 这是可序列化的字典
return [d for d in serializer.data if
d.get('id') == d.get('workspace_id')] if name is None else serializer.data # 这是可序列化的字典

View File

@ -120,16 +120,16 @@ class WorkspaceResourceUserPermissionView(APIView):
resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE"),
lambda r, kwargs: Permission(group=Group(kwargs.get('resource')),
operate=Operate.AUTH,
resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource')}/{kwargs.get('target')}"),
resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}"),
ViewPermission([RoleConstants.USER.get_workspace_role()],
[lambda r, kwargs: Permission(group=Group(kwargs.get('resource')),
operate=Operate.SELF,
resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource')}/{kwargs.get('target')}")],
resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}")],
CompareConstants.AND),
RoleConstants.WORKSPACE_MANAGE.get_workspace_role())
def get(self, request: Request, workspace_id: str, target: str, resource: str):
return result.success(ResourceUserPermissionSerializer(
data={'workspace_id': workspace_id, "target": target, 'auth_target_type': resource,
data={'workspace_id': workspace_id, "target": target, 'auth_target_type': resource.replace('_FOLDER',''),
}).list(
{'username': request.query_params.get("username"), 'nick_name': request.query_params.get("nick_name"),
'permission': request.query_params.getlist("permission[]")
@ -154,16 +154,16 @@ class WorkspaceResourceUserPermissionView(APIView):
resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE"),
lambda r, kwargs: Permission(group=Group(kwargs.get('resource')),
operate=Operate.AUTH,
resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource')}/{kwargs.get('target')}"),
resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}"),
ViewPermission([RoleConstants.USER.get_workspace_role()],
[lambda r, kwargs: Permission(group=Group(kwargs.get('resource')),
operate=Operate.SELF,
resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource')}/{kwargs.get('target')}")],
resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}")],
CompareConstants.AND),
RoleConstants.WORKSPACE_MANAGE.get_workspace_role())
def put(self, request: Request, workspace_id: str, target: str, resource: str):
return result.success(ResourceUserPermissionSerializer(
data={'workspace_id': workspace_id, "target": target, 'auth_target_type': resource, })
data={'workspace_id': workspace_id, "target": target, 'auth_target_type': resource.replace('_FOLDER',''), })
.edit(instance=request.data, current_user_id=request.user.id))
class Page(APIView):
@ -184,17 +184,17 @@ class WorkspaceResourceUserPermissionView(APIView):
resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE"),
lambda r, kwargs: Permission(group=Group(kwargs.get('resource')),
operate=Operate.AUTH,
resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource')}/{kwargs.get('target')}"),
resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}"),
ViewPermission([RoleConstants.USER.get_workspace_role()],
[lambda r, kwargs: Permission(group=Group(kwargs.get('resource')),
operate=Operate.SELF,
resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource')}/{kwargs.get('target')}")],
resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}")],
CompareConstants.AND),
RoleConstants.WORKSPACE_MANAGE.get_workspace_role())
def get(self, request: Request, workspace_id: str, target: str, resource: str, current_page: int,
page_size: int):
return result.success(ResourceUserPermissionSerializer(
data={'workspace_id': workspace_id, "target": target, 'auth_target_type': resource, }
data={'workspace_id': workspace_id, "target": target, 'auth_target_type': resource.replace('_FOLDER',''), }
).page({'username': request.query_params.get("username"),
'role': request.query_params.get("role"),
'nick_name': request.query_params.get("nick_name"),

View File

@ -219,6 +219,8 @@ function markdownToPlainText(md: string) {
.replace(/`(.*?)`/g, '$1')
// ```code```
.replace(/```[\s\S]*?```/g, '')
// video
.replace(/<video>[\s\S]*?<\/video>/g, '')
// html
.replace(/<[^>]+>/g, '')
//

View File

@ -208,4 +208,25 @@ export default {
])
},
},
'app-template-center': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 1024 1024',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg',
},
[
h('path', {
d: 'M213.333333 128h469.333334v107.52a21.333333 21.333333 0 0 0 21.333333 21.333333H810.666667V896H213.333333V128z m515.626667-85.333333H170.666667a42.666667 42.666667 0 0 0-42.666667 42.666666v853.333334a42.666667 42.666667 0 0 0 42.666667 42.666666h682.666666a42.666667 42.666667 0 0 0 42.666667-42.666666V209.749333a42.666667 42.666667 0 0 0-12.501333-30.208l-124.330667-124.373333A42.666667 42.666667 0 0 0 729.002667 42.666667zM320 341.333333a21.333333 21.333333 0 0 0-21.333333 21.333334v42.666666a21.333333 21.333333 0 0 0 21.333333 21.333334h384a21.333333 21.333333 0 0 0 21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 0-21.333333-21.333334h-384z m149.333333 192a21.333333 21.333333 0 0 1 21.333334-21.333333h213.333333a21.333333 21.333333 0 0 1 21.333333 21.333333v213.333334a21.333333 21.333333 0 0 1-21.333333 21.333333h-213.333333a21.333333 21.333333 0 0 1-21.333334-21.333333v-213.333334zM320 512a21.333333 21.333333 0 0 0-21.333333 21.333333v213.333334a21.333333 21.333333 0 0 0 21.333333 21.333333h42.666667a21.333333 21.333333 0 0 0 21.333333-21.333333v-213.333334a21.333333 21.333333 0 0 0-21.333333-21.333333h-42.666667z',
fill: 'currentColor',
}),
],
),
])
},
},
}

View File

@ -251,6 +251,48 @@ export const iconMap: any = {
])
},
},
'app-download': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 16 16',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg',
},
[
h('path', {
d: 'M14 12.3333V14C14 14.3681 13.7015 14.6666 13.3333 14.6666H2.66667C2.29848 14.6666 2 14.3681 2 14V12.3333C2 12.1492 2.14924 12 2.33333 12H3C3.18409 12 3.33333 12.1492 3.33333 12.3333V13.3333H12.6667V12.3333C12.6667 12.1492 12.8159 12 13 12H13.6667C13.8508 12 14 12.1492 14 12.3333ZM8.66667 9.3571L10.6736 7.35013C10.8038 7.21995 11.0149 7.21995 11.1451 7.35013L11.6165 7.82153C11.7466 7.9517 11.7466 8.16276 11.6165 8.29293L8.31663 11.5928C8.25154 11.6579 8.16623 11.6904 8.08092 11.6904C7.99562 11.6904 7.91031 11.6579 7.84522 11.5928L4.54539 8.29293C4.41521 8.16276 4.41521 7.9517 4.54539 7.82153L5.01679 7.35013C5.14697 7.21995 5.35802 7.21995 5.4882 7.35013L7.33334 9.19526V1.99996C7.33334 1.81586 7.48257 1.66663 7.66667 1.66663H8.33334C8.51743 1.66663 8.66667 1.81586 8.66667 1.99996V9.3571Z',
fill: 'currentColor',
}),
],
),
])
},
},
'app-upload': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 1024 1024',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg',
},
[
h('path', {
d: 'M896 789.333333V896a42.666667 42.666667 0 0 1-42.666667 42.666667H170.666667a42.666667 42.666667 0 0 1-42.666667-42.666667v-106.666667a21.333333 21.333333 0 0 1 21.333333-21.333333h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333333V853.333333h597.333334v-64a21.333333 21.333333 0 0 1 21.333333-21.333333h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333333z m-341.333333-512l128.426666 128.426667a21.333333 21.333333 0 0 0 30.208 0l30.165334-30.165333a21.333333 21.333333 0 0 0 0-30.165334l-211.2-211.2a21.248 21.248 0 0 0-30.165334 0l-211.2 211.2a21.333333 21.333333 0 0 0 0 30.165334l30.165334 30.165333a21.333333 21.333333 0 0 0 30.165333 0L469.333333 287.701333v460.501334a21.333333 21.333333 0 0 0 21.333334 21.333333h42.666666a21.333333 21.333333 0 0 0 21.333334-21.333333V277.333333z',
fill: 'currentColor',
}),
],
),
])
},
},
'app-404': {
iconReader: () => {
return h('i', [

View File

@ -141,4 +141,5 @@ export default {
prev: 'Previous',
next: 'Next',
},
use: 'Use',
}

View File

@ -25,6 +25,7 @@ export default {
debug: 'Run',
exit: 'Exit',
exitSave: 'Save & Exit',
templateCenter: 'Template Center',
},
tip: {
noData: 'No related results found',

View File

@ -141,4 +141,5 @@ export default {
prev: '上一步',
next: '下一步',
},
use: '使用',
}

View File

@ -24,6 +24,7 @@ export default {
copyParam: '复制参数',
exit: '直接退出',
exitSave: '保存并退出',
templateCenter: '模板中心',
},
tip: {
noData: '没有找到相关结果',

View File

@ -140,4 +140,5 @@ export default {
prev: '上一步',
next: '下一步',
},
use: '使用',
}

View File

@ -24,6 +24,7 @@ export default {
copyParam: '複製參數',
exit: '直接退出',
exitSave: '保存並退出',
templateCenter: '模板中心',
},
tip: {
noData: '沒有找到相關結果',

View File

@ -524,9 +524,7 @@
@click.stop="downloadDocument(row)"
v-if="permissionPrecise.doc_download(id)"
>
<el-icon class="color-secondary">
<Download />
</el-icon>
<AppIcon iconName="app-download" class="color-secondary" />
{{ $t('views.document.setting.download') }}
</el-dropdown-item>
<el-upload
@ -539,9 +537,7 @@
:on-change="(file: any, fileList: any) => replaceDocument(file, row)"
>
<el-dropdown-item>
<el-icon class="color-secondary">
<Upload />
</el-icon>
<AppIcon iconName="app-upload" class="color-secondary" />
{{ $t('views.document.setting.replace') }}
</el-dropdown-item>
</el-upload>

View File

@ -31,7 +31,8 @@
v-if="permissionPrecise.create()"
@click="openTemplateStoreDialog()"
>
{{ $t('模版中心') }}
<AppIcon iconName="app-template-center" class="mr-4" />
{{ $t('workflow.setting.templateCenter') }}
</el-button>
<el-button @click="showPopover = !showPopover">
<AppIcon iconName="app-add-outlined" class="mr-4" />
@ -169,7 +170,12 @@
v-click-outside="clickoutsideHistory"
@refreshVersion="refreshVersion"
/>
<TemplateStoreDialog ref="templateStoreDialogRef" :api-type="apiType" source="work_flow" @refresh="getDetail"/>
<TemplateStoreDialog
ref="templateStoreDialogRef"
:api-type="apiType"
source="work_flow"
@refresh="getDetail"
/>
</div>
</template>
<script setup lang="ts">
@ -183,7 +189,6 @@ import PublishHistory from '@/views/knowledge-workflow/component/PublishHistory.
import { isAppIcon, resetUrl } from '@/utils/common'
import { MsgSuccess, MsgError, MsgConfirm } from '@/utils/message'
import { datetimeFormat } from '@/utils/time'
import { mapToUrlParams } from '@/utils/application'
import useStore from '@/stores'
import { KnowledgeWorkFlowInstance } from '@/workflow/common/validate'
import { hasPermission } from '@/utils/permission'
@ -195,7 +200,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";
import TemplateStoreDialog from '@/views/knowledge/template-store/TemplateStoreDialog.vue'
provide('getResourceDetail', () => detail)
provide('workflowMode', WorkflowMode.Knowledge)
provide('loopWorkflowMode', WorkflowMode.KnowledgeLoop)
@ -664,7 +669,6 @@ function openTemplateStoreDialog() {
templateStoreDialogRef.value?.open(folderId)
}
/**
* 定时保存
*/

View File

@ -40,7 +40,8 @@
v-if="!isShared && permissionPrecise.create()"
@click="openTemplateStoreDialog()"
>
{{ $t('模版中心') }}
<AppIcon iconName="app-template-center" class="mr-4" />
{{ $t('workflow.setting.templateCenter') }}
</el-button>
<el-dropdown trigger="click" v-if="!isShared && permissionPrecise.create()">
<el-button type="primary" class="ml-8">
@ -337,7 +338,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";
import TemplateStoreDialog from '@/views/knowledge/template-store/TemplateStoreDialog.vue'
const router = useRouter()
const route = useRoute()
const { folder, user, knowledge } = useStore()

View File

@ -7,41 +7,34 @@
<Back />
</el-icon>
</el-button>
<h4>详情</h4>
<h4>{{ $t('common.detail') }}</h4>
</div>
</template>
<div>
<div class="card-header">
<div class="flex-between">
<div class="border-b">
<div class="flex-between mb-24">
<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 shape="square" :size="64" style="background: none">
<img src="@/assets/knowledge/icon_basic_template.svg" 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>
<span
class="color-secondary flex align-center mt-8"
v-if="toolDetail?.downloads != undefined"
>
<AppIcon iconName="app-download" class="mr-4" />
<span> {{ numberFormat(toolDetail.downloads || 0) }} </span>
</span>
</div>
</div>
<div @click.stop>
<el-button type="primary" @click="addInternalTool(toolDetail)">
{{ $t('common.add') }}
{{ $t('common.use') }}
</el-button>
</div>
</div>

View File

@ -1,53 +1,38 @@
<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 shape="square" :size="32" style="background: none">
<img src="@/assets/knowledge/icon_basic_template.svg" 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>
<span :title="props.tool?.name" class="ellipsis"> {{ props.tool?.name }}</span>
</template>
<template #tag>
<!-- <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>
</template> -->
<!-- <template #subTitle>
<el-text class="color-secondary lighter" size="small">
{{ getSubTitle(props.tool) }}
</el-text>
</template>
</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 class="card-footer-left color-secondary flex align-center" v-if="props.tool?.downloads != undefined">
<AppIcon iconName="app-download" class="mr-4" />
<span> {{ numberFormat(props.tool.downloads || 0) }} </span>
</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') }}
{{ $t('common.use') }}
</el-button>
</div>
</template>

View File

@ -1,7 +1,7 @@
<template>
<el-dialog
v-model="dialogVisible"
width="1200"
width="1000"
append-to-body
class="tool-store-dialog"
align-center
@ -11,7 +11,7 @@
<template #header="{ titleId }">
<div class="dialog-header flex-between mb-8">
<h4 :id="titleId" class="medium w-240 mr-8">
{{ $t('模版中心') }}
{{ $t('workflow.setting.templateCenter') }}
</h4>
<div class="flex align-center" style="margin-right: 28px">
@ -23,12 +23,12 @@
clearable
@change="getList"
/>
<el-divider direction="vertical"/>
<el-divider direction="vertical" />
</div>
</div>
</template>
<LayoutContainer v-loading="loading" :minLeftWidth="204">
<!-- <LayoutContainer v-loading="loading" :minLeftWidth="204">
<template #left>
<el-anchor
direction="vertical"
@ -44,54 +44,54 @@
:title="category.title"
/>
</el-anchor>
</template>
</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
<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">
</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>
<el-empty v-else :description="$t('common.noData')"/>
</div>
</el-scrollbar>
</LayoutContainer>
</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"/>
<InternalDescDrawer ref="internalDescDrawerRef" @addTool="handleOpenAdd" />
<CreateWorkflowKnowledgeDialog ref="CreateKnowledgeDialogRef" />
</template>
<script setup lang="ts">
@ -99,18 +99,17 @@ 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 { MsgSuccess, MsgConfirm } 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";
import CreateWorkflowKnowledgeDialog from '@/views/knowledge/create-component/CreateWorkflowKnowledgeDialog.vue'
import { useRoute } from 'vue-router'
const {user} = useStore()
const { user } = useStore()
const route = useRoute()
const {
params: {id},
params: { id },
/*
folderId 可以区分 resource-management shared还是 workspace
*/
@ -163,7 +162,7 @@ async function getList() {
if (existing) {
existing.tools = [...existing.tools, ...category.tools]
} else {
acc.push({...category})
acc.push({ ...category })
}
return acc
}, [] as ToolCategory[])
@ -171,10 +170,9 @@ async function getList() {
categories.value = merged.filter((item: any) => item.tools.length > 0)
}
async function getStoreToolList() {
try {
const res = await ToolStoreApi.getStoreKBList({name: searchValue.value}, loading)
const res = await ToolStoreApi.getStoreKBList({ name: searchValue.value }, loading)
const tags = res.data.additionalProperties.tags
const storeTools = res.data.apps
let categories = []
@ -213,9 +211,20 @@ const CreateKnowledgeDialogRef = ref()
function handleOpenAdd(data?: any, isEdit?: boolean) {
if (props.source === 'work_flow') {
handleStoreAdd(data)
MsgConfirm(
t('common.tip'),
`${t('views.application.tip.confirmUse')} ${data.name} ${t('views.application.tip.overwrite')}?`,
{
confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'),
},
)
.then(() => {
handleStoreAdd(data)
})
.catch(() => {})
} else {
CreateKnowledgeDialogRef.value.open({id: folderId.value}, data)
CreateKnowledgeDialogRef.value.open({ id: folderId.value }, data)
}
}
@ -223,8 +232,8 @@ const addLoading = ref(false)
function handleStoreAdd(tool: any) {
try {
loadSharedApi({type: 'knowledge', systemType: props.apiType})
.putKnowledgeWorkflow(id, {work_flow_template: tool})
loadSharedApi({ type: 'knowledge', systemType: props.apiType })
.putKnowledgeWorkflow(id, { work_flow_template: tool })
.then(() => {
emit('refresh')
MsgSuccess(t('common.addSuccess'))
@ -235,8 +244,7 @@ function handleStoreAdd(tool: any) {
}
}
defineExpose({open})
defineExpose({ open })
</script>
<style lang="scss">
.tool-store-dialog {

View File

@ -7,7 +7,7 @@
<Back />
</el-icon>
</el-button>
<h4>详情</h4>
<h4>{{ $t('common.detail') }}</h4>
</div>
</template>

View File

@ -781,14 +781,7 @@ export const menuNodes = [
},
{
label: t('views.knowledge.title'),
list: [
searchKnowledgeNode,
searchDocumentNode,
rerankerNode,
documentExtractNode,
documentSplitNode,
knowledgeWriteNode,
],
list: [searchKnowledgeNode, searchDocumentNode, rerankerNode, documentExtractNode],
},
{
label: t('workflow.nodes.classify.businessLogic'),

View File

@ -1,10 +1,6 @@
<template>
<el-dialog
:title="
isEdit
? $t('common.param.editParam')
: $t('common.param.addParam')
"
:title="isEdit ? $t('common.param.editParam') : $t('common.param.addParam')"
v-model="dialogVisible"
:close-on-click-modal="false"
:close-on-press-escape="false"
@ -59,13 +55,13 @@ const currentRow = computed(() => {
input_type: 'TextInput',
label: row.label || row.name,
default_value: row.default_value,
required: row.required != undefined ? row.required : row.is_required
required: row.required != undefined ? row.required : row.is_required,
}
case 'select':
if (
check_field(
['field', 'input_type', 'label', 'required', 'option_list'],
currentItem.value
currentItem.value,
)
) {
return currentItem.value
@ -81,7 +77,7 @@ const currentRow = computed(() => {
? row.option_list
: row.optionList.map((o: any) => {
return { key: o, value: o }
})
}),
}
case 'date':
@ -94,9 +90,9 @@ const currentRow = computed(() => {
'required',
'attrs.format',
'attrs.value-format',
'attrs.type'
'attrs.type',
],
currentItem.value
currentItem.value,
)
) {
return currentItem.value
@ -110,14 +106,19 @@ const currentRow = computed(() => {
attrs: {
format: 'YYYY-MM-DD HH:mm:ss',
'value-format': 'YYYY-MM-DD HH:mm:ss',
type: 'datetime'
}
type: 'datetime',
},
}
default:
return currentItem.value
}
} else {
return { input_type: 'TextInput', required: false, attrs: { maxlength: 200, minlength: 0 }, show_default_value: true }
return {
input_type: 'TextInput',
required: false,
attrs: { maxlength: 200, minlength: 0 },
show_default_value: true,
}
}
})
const currentIndex = ref(null)
@ -129,6 +130,9 @@ const inputTypeList = ref([
{ label: t('dynamicsForm.input_type_list.RadioCard'), value: 'RadioCardConstructor' },
{ label: t('dynamicsForm.input_type_list.DatePicker'), value: 'DatePickerConstructor' },
{ label: t('dynamicsForm.input_type_list.SwitchInput'), value: 'SwitchInputConstructor' },
{ label: t('dynamicsForm.input_type_list.RadioRow'), value: 'RadioRowConstructor' },
{ label: t('dynamicsForm.input_type_list.TextareaInput'), value: 'TextareaInputConstructor' },
{ label: t('dynamicsForm.input_type_list.MultiRow'), value: 'MultiRowConstructor' },
])
const dialogVisible = ref<boolean>(false)