diff --git a/apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py b/apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py index 4f5399f6b..ba1b78af6 100644 --- a/apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py +++ b/apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py @@ -16,6 +16,7 @@ from application.flow.i_step_node import NodeResult from application.flow.step_node.function_lib_node.i_function_lib_node import IFunctionLibNode from common.exception.app_exception import AppApiException from common.util.function_code import FunctionExecutor +from common.util.rsa_util import rsa_long_decrypt from function_lib.models.function import FunctionLib from smartdoc.const import CONFIG @@ -107,8 +108,11 @@ class BaseFunctionLibNodeNode(IFunctionLibNode): ), **field} for field in function_lib.input_field_list]} + self.context['params'] = params - result = function_executor.exec_code(function_lib.code, params) + # 合并初始化参数 + all_params = json.loads(rsa_long_decrypt(function_lib.init_params)) | params + result = function_executor.exec_code(function_lib.code, all_params) return NodeResult({'result': result}, {}, _write_context=write_context) def get_details(self, index: int, **kwargs): diff --git a/apps/application/serializers/application_serializers.py b/apps/application/serializers/application_serializers.py index 543f53ae2..7ea5b011c 100644 --- a/apps/application/serializers/application_serializers.py +++ b/apps/application/serializers/application_serializers.py @@ -45,7 +45,7 @@ from common.util.file_util import get_file_content from dataset.models import DataSet, Document, Image from dataset.serializers.common_serializers import list_paragraph, get_embedding_model_by_dataset_id_list from embedding.models import SearchMode -from function_lib.models.function import FunctionLib, PermissionType +from function_lib.models.function import FunctionLib, PermissionType, FunctionType from function_lib.serializers.function_lib_serializer import FunctionLibSerializer, FunctionLibModelSerializer from setting.models import AuthOperate, TeamMemberPermission from setting.models.model_management import Model @@ -810,8 +810,10 @@ class ApplicationSerializer(serializers.Serializer): if with_valid: self.is_valid(raise_exception=True) application = QuerySet(Application).filter(id=self.data.get("application_id")).first() - return FunctionLibSerializer.Query(data={'user_id': application.user_id, 'is_active': True}).list( - with_valid=True) + return FunctionLibSerializer.Query( + data={'user_id': application.user_id, 'is_active': True, + 'function_type': FunctionType.PUBLIC} + ).list(with_valid=True) def get_function_lib(self, function_lib_id, with_valid=True): if with_valid: diff --git a/apps/function_lib/migrations/0003_functionlib_function_type_functionlib_icon_and_more.py b/apps/function_lib/migrations/0003_functionlib_function_type_functionlib_icon_and_more.py new file mode 100644 index 000000000..28f9c288c --- /dev/null +++ b/apps/function_lib/migrations/0003_functionlib_function_type_functionlib_icon_and_more.py @@ -0,0 +1,107 @@ +# Generated by Django 4.2.15 on 2025-03-13 07:21 + +from django.db import migrations, models + +function_template = ''' +INSERT INTO function_lib (create_time, update_time, id, name, "desc", code, input_field_list, user_id, is_active, permission_type, function_type, icon, init_field_list, init_params, template_id) VALUES ('2025-03-10 06:20:35.945414 +00:00', '2025-03-10 09:19:23.608026 +00:00', 'c75cb48e-fd77-11ef-84d2-5618c4394482', '博查AI', '从博查搜索任何信息和网页URL', e'def bocha_search(query, apikey): + import requests + import json + url = "https://api.bochaai.com/v1/web-search" + payload = json.dumps({ + "query": query, + "Boolean": "true", + "count": 8 + }) + + headers = { + "Authorization": "Bearer " + apikey, #鉴权参数,示例:Bearer xxxxxx,API KEY请先前往博查AI开放平台(https://open.bochaai.com)> API KEY 管理中获取。 + "Content-Type": "application/json" + } + + response = requests.request("POST", url, headers=headers, data=payload) + if response.status_code == 200: + return response.json() + else: + raise Exception(f"API请求失败: {response.status_code}, 错误信息: {response.text}") + return (response.text)', '{"{\\"name\\": \\"query\\", \\"type\\": \\"string\\", \\"source\\": \\"reference\\", \\"is_required\\": true}"}', 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', TRUE, 'PUBLIC', 'INTERNAL', '/src/assets/fx/bochaai/icon.png', '[{"attrs": {"type": "password", "maxlength": 200, "minlength": 1, "show-password": true, "show-word-limit": true}, "field": "apikey", "label": "apikey", "required": true, "input_type": "PasswordInput", "props_info": {"rules": [{"message": "apikey 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "apikey长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}]', '', NULL); +INSERT INTO function_lib (create_time, update_time, id, name, "desc", code, input_field_list, user_id, is_active, permission_type, function_type, icon, init_field_list, init_params, template_id) VALUES ('2025-02-26 03:36:48.187286 +00:00', '2025-03-11 07:23:46.123972 +00:00', 'e89ad2ae-f3f2-11ef-ad09-0242ac110002', 'Google Search', 'Google Web Search', e'def google_search(query, apikey, cx): + import requests + import json + url = "https://customsearch.googleapis.com/customsearch/v1" + params = { + "q": query, + "key": apikey, + "cx": cx, + "num": 10, # 每次最多返回10条 + } + + response = requests.get(url, params=params) + if response.status_code == 200: + return response.json() + else: + raise Exception(f"API请求失败: {response.status_code}, 错误信息: {response.text}") + return (response.text)', '{"{\\"name\\": \\"query\\", \\"type\\": \\"string\\", \\"source\\": \\"reference\\", \\"is_required\\": true}"}', 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', TRUE, 'PUBLIC', 'INTERNAL', '/src/assets/fx/google_search/icon.png', '[{"attrs": {"type": "password", "maxlength": 200, "minlength": 1, "show-password": true, "show-word-limit": true}, "field": "apikey", "label": "apikey", "required": true, "input_type": "PasswordInput", "props_info": {"rules": [{"message": "apikey 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "apikey长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}, {"attrs": {"maxlength": 200, "minlength": 1, "show-word-limit": true}, "field": "cx", "label": "cx", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "cx 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "cx长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}]', '', NULL); +INSERT INTO function_lib (create_time, update_time, id, name, "desc", code, input_field_list, user_id, is_active, permission_type, function_type, icon, init_field_list, init_params, template_id) VALUES ('2025-02-25 07:44:40.141515 +00:00', '2025-03-11 06:33:53.248495 +00:00', '5e912f00-f34c-11ef-8a9c-5618c4394482', 'LangSearch API', e'A Web Search API supporting natural language search +', e' +def langsearch(query, apikey): + import json + import requests + + url = "https://api.langsearch.com/v1/web-search" + payload = json.dumps({ + "query": query, + "summary": True, + "freshness": "noLimit", + "livecrawl": True, + "count": 20 + }) + headers = { + "Authorization": apikey, + "Content-Type": "application/json" + } + # key从官网申请 https://langsearch.com/ + response = requests.request("POST", url, headers=headers, data=payload) + if response.status_code == 200: + return response.json() + else: + raise Exception(f"API请求失败: {response.status_code}, 错误信息: {response.text}") + return (response.text)', '{"{\\"name\\": \\"query\\", \\"type\\": \\"string\\", \\"source\\": \\"reference\\", \\"is_required\\": true}"}', 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', TRUE, 'PUBLIC', 'INTERNAL', '/src/assets/fx/langsearch/icon.png', '[{"attrs": {"type": "password", "maxlength": 200, "minlength": 1, "show-password": true, "show-word-limit": true}, "field": "apikey", "label": "apikey", "required": true, "input_type": "PasswordInput", "props_info": {"rules": [{"message": "apikey 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "apikey长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}]', '', NULL); + +''' + + +class Migration(migrations.Migration): + dependencies = [ + ('function_lib', '0002_functionlib_is_active_functionlib_permission_type'), + ] + + operations = [ + migrations.AddField( + model_name='functionlib', + name='function_type', + field=models.CharField(choices=[('INTERNAL', '内置'), ('PUBLIC', '公开')], + default='PUBLIC', max_length=20, verbose_name='函数类型'), + ), + migrations.AddField( + model_name='functionlib', + name='icon', + field=models.CharField(default='/ui/favicon.ico', max_length=256, + verbose_name='函数库icon'), + ), + migrations.AddField( + model_name='functionlib', + name='init_field_list', + field=models.JSONField(default=list, verbose_name='启动字段列表'), + ), + migrations.AddField( + model_name='functionlib', + name='init_params', + field=models.CharField(max_length=102400, null=True, verbose_name='初始化参数'), + ), + migrations.AddField( + model_name='functionlib', + name='template_id', + field=models.UUIDField(default=None, null=True, verbose_name='模版id'), + ), + migrations.RunSQL(function_template) + ] diff --git a/apps/function_lib/migrations/0003_functionlib_icon_functionlib_init_field_list.py b/apps/function_lib/migrations/0003_functionlib_icon_functionlib_init_field_list.py deleted file mode 100644 index 3ea3a4637..000000000 --- a/apps/function_lib/migrations/0003_functionlib_icon_functionlib_init_field_list.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.2.15 on 2025-03-07 10:31 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('function_lib', '0002_functionlib_is_active_functionlib_permission_type'), - ] - - operations = [ - migrations.AddField( - model_name='functionlib', - name='icon', - field=models.CharField(default='/ui/favicon.ico', max_length=256, verbose_name='函数库icon'), - ), - migrations.AddField( - model_name='functionlib', - name='init_field_list', - field=models.JSONField(default=list, verbose_name='启动字段列表'), - ), - ] diff --git a/apps/function_lib/models/function.py b/apps/function_lib/models/function.py index a95339545..037f50995 100644 --- a/apps/function_lib/models/function.py +++ b/apps/function_lib/models/function.py @@ -19,6 +19,10 @@ class PermissionType(models.TextChoices): PUBLIC = "PUBLIC", '公开' PRIVATE = "PRIVATE", "私有" +class FunctionType(models.TextChoices): + INTERNAL = "INTERNAL", '内置' + PUBLIC = "PUBLIC", "公开" + class FunctionLib(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id") @@ -34,6 +38,10 @@ class FunctionLib(AppModelMixin): is_active = models.BooleanField(default=True) permission_type = models.CharField(max_length=20, verbose_name='权限类型', choices=PermissionType.choices, default=PermissionType.PRIVATE) + function_type = models.CharField(max_length=20, verbose_name='函数类型', choices=FunctionType.choices, + default=FunctionType.PUBLIC) + template_id = models.UUIDField(max_length=128, verbose_name="模版id", null=True, default=None) + init_params = models.CharField(max_length=102400, verbose_name="初始化参数", null=True) class Meta: db_table = "function_lib" diff --git a/apps/function_lib/serializers/__init__.py b/apps/function_lib/serializers/__init__.py new file mode 100644 index 000000000..a68550e90 --- /dev/null +++ b/apps/function_lib/serializers/__init__.py @@ -0,0 +1,8 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎 + @file: __init__.py.py + @date:2024/8/2 14:55 + @desc: +""" diff --git a/apps/function_lib/serializers/function_lib_serializer.py b/apps/function_lib/serializers/function_lib_serializer.py index a007cc287..52ff5a002 100644 --- a/apps/function_lib/serializers/function_lib_serializer.py +++ b/apps/function_lib/serializers/function_lib_serializer.py @@ -13,7 +13,7 @@ import uuid from django.core import validators from django.db import transaction -from django.db.models import QuerySet, Q +from django.db.models import QuerySet, Q, OuterRef, Exists from django.http import HttpResponse from django.utils.translation import gettext_lazy as _ from rest_framework import serializers, status @@ -24,8 +24,9 @@ from common.field.common import UploadedFileField, UploadedImageField from common.response import result from common.util.field_message import ErrMessage from common.util.function_code import FunctionExecutor -from dataset.models import Image -from function_lib.models.function import FunctionLib +from common.util.rsa_util import rsa_long_decrypt, rsa_long_encrypt +from dataset.models import File +from function_lib.models.function import FunctionLib, PermissionType, FunctionType from smartdoc.const import CONFIG function_executor = FunctionExecutor(CONFIG.get('SANDBOX')) @@ -35,11 +36,33 @@ class FlibInstance: self.function_lib = function_lib self.version = version +def encryption(message: str): + """ + 加密敏感字段数据 加密方式是 如果密码是 1234567890 那么给前端则是 123******890 + :param message: + :return: + """ + max_pre_len = 8 + max_post_len = 4 + message_len = len(message) + pre_len = int(message_len / 5 * 2) + post_len = int(message_len / 5 * 1) + pre_str = "".join([message[index] for index in + range(0, + max_pre_len if pre_len > max_pre_len else 1 if pre_len <= 0 else int( + pre_len))]) + end_str = "".join( + [message[index] for index in + range(message_len - (int(post_len) if pre_len < max_post_len else max_post_len), + message_len)]) + content = "***************" + return pre_str + content + end_str + class FunctionLibModelSerializer(serializers.ModelSerializer): class Meta: model = FunctionLib - fields = ['id', 'name', 'icon', 'desc', 'code', 'input_field_list','init_field_list', 'permission_type', 'is_active', 'user_id', + fields = ['id', 'name', 'icon', 'desc', 'code', 'input_field_list','init_field_list', 'init_params', 'permission_type', 'is_active', 'user_id', 'template_id', 'create_time', 'update_time'] @@ -66,6 +89,7 @@ class DebugInstance(serializers.Serializer): debug_field_list = DebugField(required=True, many=True) input_field_list = FunctionLibInputField(required=True, many=True) init_field_list = serializers.ListField(required=False, default=list) + init_params = serializers.JSONField(required=False, default=dict) code = serializers.CharField(required=True, error_messages=ErrMessage.char(_('function content'))) @@ -116,6 +140,8 @@ class FunctionLibSerializer(serializers.Serializer): user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_('user id'))) select_user_id = serializers.CharField(required=False, allow_null=True, allow_blank=True) + function_type = serializers.CharField(required=False, allow_null=True, allow_blank=True) + def get_query_set(self): query_set = QuerySet(FunctionLib).filter( @@ -128,19 +154,38 @@ class FunctionLibSerializer(serializers.Serializer): query_set = query_set.filter(is_active=self.data.get('is_active')) if self.data.get('select_user_id') is not None: query_set = query_set.filter(user_id=self.data.get('select_user_id')) + if self.data.get('function_type') is not None: + query_set = query_set.filter(function_type=self.data.get('function_type')) query_set = query_set.order_by("-create_time") + + subquery = FunctionLib.objects.filter(template_id=OuterRef('id')) + subquery = subquery.filter(user_id=self.data.get('user_id')) + query_set = query_set.annotate(added=Exists(subquery)) + return query_set def list(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) - return [FunctionLibModelSerializer(item).data for item in self.get_query_set()] + rs = [] + for item in self.get_query_set(): + data = {**FunctionLibModelSerializer(item).data, 'init_params': None} + rs.append(data) + return rs def page(self, current_page: int, page_size: int, with_valid=True): if with_valid: self.is_valid(raise_exception=True) + + def post_records_handler(row): + return { + **FunctionLibModelSerializer(row).data, + 'added': row.added, + 'init_params': None + } + return page_search(current_page, page_size, self.get_query_set(), - post_records_handler=lambda row: FunctionLibModelSerializer(row).data) + post_records_handler=post_records_handler) class Create(serializers.Serializer): user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_('user id'))) @@ -169,8 +214,7 @@ class FunctionLibSerializer(serializers.Serializer): input_field_list = debug_instance.get('input_field_list') code = debug_instance.get('code') debug_field_list = debug_instance.get('debug_field_list') - init_field_list = debug_instance.get('init_field_list') - init_params = {field.get('field'): field.get('value') if field.get('value', None) is not None else field.get('default_value') for field in init_field_list} + init_params = debug_instance.get('init_params') params = {field.get('name'): self.convert_value(field.get('name'), field.get('value'), field.get('type'), field.get('is_required')) for field in @@ -227,6 +271,9 @@ class FunctionLibSerializer(serializers.Serializer): def delete(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) + fun = QuerySet(FunctionLib).filter(id=self.data.get('id')).first() + if fun.template_id is None and fun.icon != '/ui/favicon.ico': + QuerySet(File).filter(id=fun.icon.split('/')[-1]).delete() QuerySet(FunctionLib).filter(id=self.data.get('id')).delete() return True @@ -234,9 +281,19 @@ class FunctionLibSerializer(serializers.Serializer): if with_valid: self.is_valid(raise_exception=True) EditFunctionLib(data=instance).is_valid(raise_exception=True) - edit_field_list = ['name', 'desc', 'code', 'input_field_list', 'init_field_list', 'permission_type', 'is_active'] + edit_field_list = ['name', 'desc', 'code', 'icon', 'input_field_list', 'init_field_list', 'init_params', 'permission_type', 'is_active'] edit_dict = {field: instance.get(field) for field in edit_field_list if ( field in instance and instance.get(field) is not None)} + + function_lib = QuerySet(FunctionLib).filter(id=self.data.get('id')).first() + if 'init_params' in edit_dict: + if function_lib.init_params: + old_init_params = json.loads(rsa_long_decrypt(function_lib.init_params)) + for key in edit_dict['init_params']: + if edit_dict['init_params'][key] == encryption(old_init_params[key]): + edit_dict['init_params'][key] = old_init_params[key] + + edit_dict['init_params'] = rsa_long_encrypt(json.dumps(edit_dict['init_params'])) QuerySet(FunctionLib).filter(id=self.data.get('id')).update(**edit_dict) return self.one(False) @@ -247,7 +304,15 @@ class FunctionLibSerializer(serializers.Serializer): Q(user_id=self.data.get('user_id')) | Q(permission_type='PUBLIC')).exists(): raise AppApiException(500, _('Function does not exist')) function_lib = QuerySet(FunctionLib).filter(id=self.data.get('id')).first() - return FunctionLibModelSerializer(function_lib).data + if function_lib.init_params: + function_lib.init_params = json.loads(rsa_long_decrypt(function_lib.init_params)) + if function_lib.init_field_list: + password_fields = [i["field"] for i in function_lib.init_field_list if i.get("input_type") == "PasswordInput"] + if function_lib.init_params: + for k in function_lib.init_params: + if k in password_fields: + function_lib.init_params[k] = encryption(function_lib.init_params[k]) + return {**FunctionLibModelSerializer(function_lib).data, 'init_params': function_lib.init_params} def export(self, with_valid=True): try: @@ -284,6 +349,7 @@ class FunctionLibSerializer(serializers.Serializer): code=function_lib.get('code'), user_id=user_id, input_field_list=function_lib.get('input_field_list'), + init_field_list=function_lib.get('init_field_list'), permission_type='PRIVATE', is_active=function_lib.get('is_active')) function_lib_model.save() @@ -300,10 +366,54 @@ class FunctionLibSerializer(serializers.Serializer): functionLib = QuerySet(FunctionLib).filter(id=self.data.get('id')).first() if functionLib is None: raise AppApiException(500, _('Function does not exist')) - image_id = uuid.uuid1() - image = Image(id=image_id, image=self.data.get('image').read(), image_name=self.data.get('image').name) - image.save() - functionLib.icon = f'/api/image/{image_id}' + # 删除旧的图片 + if functionLib.icon != '/ui/favicon.ico': + QuerySet(File).filter(id=functionLib.icon.split('/')[-1]).delete() + if self.data.get('image') is None: + functionLib.icon = '/ui/favicon.ico' + else: + meta = { + 'debug': False + } + file_id = uuid.uuid1() + file = File(id=file_id, file_name=self.data.get('image').name, meta=meta) + file.save(self.data.get('image').read()) + + functionLib.icon = f'/api/file/{file_id}' functionLib.save() return functionLib.icon + + class InternalFunction(serializers.Serializer): + id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("function ID"))) + user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID"))) + + def add(self, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + + if QuerySet(FunctionLib).filter(template_id=self.data.get('id')).filter( + user_id=self.data.get('user_id')).exists(): + raise AppApiException(500, _('Function already exists')) + + internal_function_lib = QuerySet(FunctionLib).filter(id=self.data.get('id')).first() + if internal_function_lib is None: + raise AppApiException(500, _('Function does not exist')) + + function_lib = FunctionLib( + id=uuid.uuid1(), + name=internal_function_lib.name, + desc=internal_function_lib.desc, + code=internal_function_lib.code, + user_id=self.data.get('user_id'), + input_field_list=internal_function_lib.input_field_list, + init_field_list=internal_function_lib.init_field_list, + permission_type=PermissionType.PRIVATE, + template_id=internal_function_lib.id, + function_type=FunctionType.PUBLIC, + icon=internal_function_lib.icon, + is_active=False + ) + function_lib.save() + + return FunctionLibModelSerializer(function_lib).data diff --git a/apps/function_lib/swagger_api/__init__.py b/apps/function_lib/swagger_api/__init__.py new file mode 100644 index 000000000..a68550e90 --- /dev/null +++ b/apps/function_lib/swagger_api/__init__.py @@ -0,0 +1,8 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎 + @file: __init__.py.py + @date:2024/8/2 14:55 + @desc: +""" diff --git a/apps/function_lib/urls.py b/apps/function_lib/urls.py index d0c89f17a..036d68926 100644 --- a/apps/function_lib/urls.py +++ b/apps/function_lib/urls.py @@ -9,6 +9,7 @@ urlpatterns = [ path('function_lib//export', views.FunctionLibView.Export.as_view()), path('function_lib/import', views.FunctionLibView.Import.as_view()), path('function_lib//edit_icon', views.FunctionLibView.EditIcon.as_view()), + path('function_lib//add_internal_fun', views.FunctionLibView.AddInternalFun.as_view()), path('function_lib/pylint', views.PyLintView.as_view()), path('function_lib/', views.FunctionLibView.Operate.as_view()), path("function_lib//", views.FunctionLibView.Page.as_view(), diff --git a/apps/function_lib/views/function_lib_views.py b/apps/function_lib/views/function_lib_views.py index 51c15ea02..c0213b65c 100644 --- a/apps/function_lib/views/function_lib_views.py +++ b/apps/function_lib/views/function_lib_views.py @@ -34,6 +34,7 @@ class FunctionLibView(APIView): FunctionLibSerializer.Query( data={'name': request.query_params.get('name'), 'desc': request.query_params.get('desc'), + 'function_type': request.query_params.get('function_type'), 'user_id': request.user.id}).list()) @action(methods=['POST'], detail=False) @@ -107,6 +108,7 @@ class FunctionLibView(APIView): FunctionLibSerializer.Query( data={'name': request.query_params.get('name'), 'desc': request.query_params.get('desc'), + 'function_type': request.query_params.get('function_type'), 'user_id': request.user.id, 'select_user_id': request.query_params.get('select_user_id')}).page( current_page, page_size)) @@ -148,4 +150,14 @@ class FunctionLibView(APIView): return result.success( FunctionLibSerializer.IconOperate( data={'id': id, 'user_id': request.user.id, - 'image': request.FILES.get('file')}).edit(request.data)) \ No newline at end of file + 'image': request.FILES.get('file')}).edit(request.data)) + + class AddInternalFun(APIView): + authentication_classes = [TokenAuth] + + @action(methods=['GET'], detail=False) + @has_permissions(RoleConstants.ADMIN, RoleConstants.USER) + def get(self, request: Request, id: str): + return result.success( + FunctionLibSerializer.InternalFunction( + data={'id': id, 'user_id': request.user.id}).add()) \ No newline at end of file diff --git a/ui/src/api/function-lib.ts b/ui/src/api/function-lib.ts index 7fdbad8b0..004cd7217 100644 --- a/ui/src/api/function-lib.ts +++ b/ui/src/api/function-lib.ts @@ -120,6 +120,13 @@ const putFunctionLibIcon: ( return put(`${prefix}/${id}/edit_icon`, data, undefined, loading) } +const addInternalFunction: ( + id: string, + loading?: Ref +) => Promise> = (id, loading) => { + return get(`${prefix}/${id}/add_internal_fun`, undefined, loading) +} + const importFunctionLib: (data: any, loading?: Ref) => Promise> = ( data, loading @@ -137,5 +144,6 @@ export default { exportFunctionLib, importFunctionLib, pylint, - putFunctionLibIcon + putFunctionLibIcon, + addInternalFunction } diff --git a/ui/src/assets/fx/bochaai/icon.png b/ui/src/assets/fx/bochaai/icon.png new file mode 100644 index 000000000..530a086ed Binary files /dev/null and b/ui/src/assets/fx/bochaai/icon.png differ diff --git a/ui/src/assets/fx/bochaai/index.vue b/ui/src/assets/fx/bochaai/index.vue new file mode 100644 index 000000000..c03b178d7 --- /dev/null +++ b/ui/src/assets/fx/bochaai/index.vue @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/ui/src/assets/fx/google_search/icon.png b/ui/src/assets/fx/google_search/icon.png new file mode 100644 index 000000000..7b903159b Binary files /dev/null and b/ui/src/assets/fx/google_search/icon.png differ diff --git a/ui/src/assets/fx/google_search/index.vue b/ui/src/assets/fx/google_search/index.vue new file mode 100644 index 000000000..c03b178d7 --- /dev/null +++ b/ui/src/assets/fx/google_search/index.vue @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/ui/src/assets/fx/langsearch/icon.png b/ui/src/assets/fx/langsearch/icon.png new file mode 100644 index 000000000..72ca125f3 Binary files /dev/null and b/ui/src/assets/fx/langsearch/icon.png differ diff --git a/ui/src/assets/fx/langsearch/index.vue b/ui/src/assets/fx/langsearch/index.vue new file mode 100644 index 000000000..c03b178d7 --- /dev/null +++ b/ui/src/assets/fx/langsearch/index.vue @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/ui/src/locales/lang/en-US/common.ts b/ui/src/locales/lang/en-US/common.ts index 12f83c7fe..b00021ee0 100644 --- a/ui/src/locales/lang/en-US/common.ts +++ b/ui/src/locales/lang/en-US/common.ts @@ -35,6 +35,7 @@ export default { private: 'Private', paramSetting: 'Parameter Settings', creator: 'Creator', + author: 'Author', debug: 'Debug', required: 'Required', noData: 'No data', diff --git a/ui/src/locales/lang/en-US/views/function-lib.ts b/ui/src/locales/lang/en-US/views/function-lib.ts index 2f56c9456..1ca7103b3 100644 --- a/ui/src/locales/lang/en-US/views/function-lib.ts +++ b/ui/src/locales/lang/en-US/views/function-lib.ts @@ -1,5 +1,7 @@ export default { title: 'Function', + internalTitle: 'Internal Function', + added: 'Added', createFunction: 'Create Function', editFunction: 'Edit Function', copyFunction: 'Copy Function', diff --git a/ui/src/locales/lang/zh-CN/common.ts b/ui/src/locales/lang/zh-CN/common.ts index da89789ee..ba790f282 100644 --- a/ui/src/locales/lang/zh-CN/common.ts +++ b/ui/src/locales/lang/zh-CN/common.ts @@ -35,6 +35,7 @@ export default { private: '私有', paramSetting: '参数设置', creator: '创建者', + author: '作者', debug: '调试', required: '必填', noData: '暂无数据', diff --git a/ui/src/locales/lang/zh-CN/views/function-lib.ts b/ui/src/locales/lang/zh-CN/views/function-lib.ts index 2ad28bad3..ac6c00dce 100644 --- a/ui/src/locales/lang/zh-CN/views/function-lib.ts +++ b/ui/src/locales/lang/zh-CN/views/function-lib.ts @@ -1,5 +1,7 @@ export default { title: '函数库', + internalTitle: '内置函数', + added: '已添加', createFunction: '创建函数', editFunction: '编辑函数', copyFunction: '复制函数', diff --git a/ui/src/locales/lang/zh-Hant/common.ts b/ui/src/locales/lang/zh-Hant/common.ts index ea4bbe118..5775f5813 100644 --- a/ui/src/locales/lang/zh-Hant/common.ts +++ b/ui/src/locales/lang/zh-Hant/common.ts @@ -35,6 +35,7 @@ export default { private: '私有', paramSetting: '參數設定', creator: '建立者', + author: '作者', debug: '調試', required: '必填', noData: '暂无数据', diff --git a/ui/src/locales/lang/zh-Hant/views/function-lib.ts b/ui/src/locales/lang/zh-Hant/views/function-lib.ts index f7c8eab49..6b4a9b339 100644 --- a/ui/src/locales/lang/zh-Hant/views/function-lib.ts +++ b/ui/src/locales/lang/zh-Hant/views/function-lib.ts @@ -1,5 +1,7 @@ export default { title: '函數庫', + internalTitle: '內置函數', + added: '已新增', createFunction: '建立函數', editFunction: '編輯函數', copyFunction: '複製函數', diff --git a/ui/src/views/function-lib/component/EditAvatarDialog.vue b/ui/src/views/function-lib/component/EditAvatarDialog.vue index 0dd3189eb..21c96c4cd 100644 --- a/ui/src/views/function-lib/component/EditAvatarDialog.vue +++ b/ui/src/views/function-lib/component/EditAvatarDialog.vue @@ -111,11 +111,8 @@ const onChange = (file: any) => { function submit() { if (radioType.value === 'default') { - // application.asyncPutApplication(props.id as string, { icon: defaultIcon }, loading).then((res: any) => { - // emit('refresh') - // MsgSuccess(t('views.applicationOverview.appInfo.EditAvatarDialog.setSuccess')) - // dialogVisible.value = false - // }) + emit('refresh', '/ui/favicon.ico') + dialogVisible.value = false } else if (radioType.value === 'custom' && iconFile.value) { let fd = new FormData() fd.append('file', iconFile.value.raw) diff --git a/ui/src/views/function-lib/component/FunctionDebugDrawer.vue b/ui/src/views/function-lib/component/FunctionDebugDrawer.vue index 84f81aff3..45e234790 100644 --- a/ui/src/views/function-lib/component/FunctionDebugDrawer.vue +++ b/ui/src/views/function-lib/component/FunctionDebugDrawer.vue @@ -16,9 +16,9 @@ {{ $t('common.param.initParam') }} -
{{ item.name }} *{{ item.name }} * {{ item.type }}
@@ -114,18 +114,19 @@ import type { FormInstance } from 'element-plus' import DynamicsForm from '@/components/dynamics-form/index.vue' const FormRef = ref() +const dynamicsFormRef = ref() const loading = ref(false) const debugVisible = ref(false) const showResult = ref(false) const isSuccess = ref(false) const result = ref('') -const init_form_data = ref({}) const form = ref({ debug_field_list: [], code: '', input_field_list: [], - init_field_list: [] + init_field_list: [], + init_params: {} }) watch(debugVisible, (bool) => { @@ -137,14 +138,15 @@ watch(debugVisible, (bool) => { debug_field_list: [], code: '', input_field_list: [], - init_field_list: [] + init_field_list: [], + init_params: {} } } }) const submit = async (formEl: FormInstance | undefined) => { const validate = formEl ? formEl.validate() : Promise.resolve() - validate.then(() => { + Promise.all([dynamicsFormRef.value?.validate(), validate]).then(() => { functionLibApi.postFunctionLibDebug(form.value, loading).then((res) => { if (res.code === 500) { showResult.value = true diff --git a/ui/src/views/function-lib/component/FunctionFormDrawer.vue b/ui/src/views/function-lib/component/FunctionFormDrawer.vue index 95d748b00..0e075608a 100644 --- a/ui/src/views/function-lib/component/FunctionFormDrawer.vue +++ b/ui/src/views/function-lib/component/FunctionFormDrawer.vue @@ -26,13 +26,13 @@ @mouseleave="showEditIcon = false" > - + +

diff --git a/ui/src/views/function-lib/component/InitParamDrawer.vue b/ui/src/views/function-lib/component/InitParamDrawer.vue index f5923ca00..cbc895a09 100644 --- a/ui/src/views/function-lib/component/InitParamDrawer.vue +++ b/ui/src/views/function-lib/component/InitParamDrawer.vue @@ -11,10 +11,10 @@

-
+
({}) const form = ref({ - init_field_list: [] + init_params: {} }) watch(debugVisible, (bool) => { @@ -63,17 +62,13 @@ watch(debugVisible, (bool) => { isSuccess.value = false result.value = '' form.value = { - init_field_list: [] + init_params: {} } } }) const submit = async () => { dynamicsFormRef.value.validate().then(() => { - form.value.init_field_list.forEach((item: any) => { - item.value = init_form_data.value[item.field] - }) - // console.log(init_form_data.value) functionLibApi.putFunctionLib(form.value?.id as string, form.value, loading) .then((res) => { MsgSuccess(t('common.editSuccess')) @@ -87,17 +82,15 @@ const open = (data: any) => { if (data) { form.value = cloneDeep(data) } - init_form_data.value = form.value.init_field_list + const init_params = form.value.init_field_list .map((item: any) => { - if (item.value) { - return { [item.field]: item.value } - } if (item.show_default_value === false) { return { [item.field]: undefined } } return { [item.field]: item.default_value } }) .reduce((x: any, y: any) => ({ ...x, ...y }), {}) + form.value.init_params = { ...init_params, ...form.value.init_params } debugVisible.value = true } diff --git a/ui/src/views/function-lib/index.vue b/ui/src/views/function-lib/index.vue index 25ba3a03a..d58165ebd 100644 --- a/ui/src/views/function-lib/index.vue +++ b/ui/src/views/function-lib/index.vue @@ -1,7 +1,11 @@