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 4f5399f6b8a..ba1b78af658 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.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 @@ def execute(self, function_lib_id, input_field_list, **kwargs) -> NodeResult: ), **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 543f53ae240..7ea5b011c66 100644 --- a/apps/application/serializers/application_serializers.py +++ b/apps/application/serializers/application_serializers.py @@ -45,7 +45,7 @@ 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 @@ def list_function_lib(self, with_valid=True): 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 00000000000..28f9c288c1b --- /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/models/function.py b/apps/function_lib/models/function.py index 49a0e981bb5..037f5099527 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") @@ -29,9 +33,15 @@ class FunctionLib(AppModelMixin): input_field_list = ArrayField(verbose_name="输入字段列表", base_field=models.JSONField(verbose_name="输入字段", default=dict) , default=list) + init_field_list = models.JSONField(verbose_name="启动字段列表", default=list) + icon = models.CharField(max_length=256, verbose_name="函数库icon", default="/ui/favicon.ico") 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 00000000000..a68550e90ef --- /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 cccdfcdc3da..52ff5a0027b 100644 --- a/apps/function_lib/serializers/function_lib_serializer.py +++ b/apps/function_lib/serializers/function_lib_serializer.py @@ -10,23 +10,24 @@ import pickle import re import uuid -from typing import List 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 from common.db.search import page_search from common.exception.app_exception import AppApiException -from common.field.common import UploadedFileField +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 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 -from django.utils.translation import gettext_lazy as _ function_executor = FunctionExecutor(CONFIG.get('SANDBOX')) @@ -35,11 +36,33 @@ def __init__(self, function_lib: dict, version: str): 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', 'desc', 'code', 'input_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'] @@ -65,6 +88,8 @@ class DebugField(serializers.Serializer): 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'))) @@ -80,6 +105,8 @@ class EditFunctionLib(serializers.Serializer): input_field_list = FunctionLibInputField(required=False, many=True) + init_field_list = serializers.ListField(required=False, default=list) + is_active = serializers.BooleanField(required=False, error_messages=ErrMessage.char(_('Is active'))) @@ -93,6 +120,8 @@ class CreateFunctionLib(serializers.Serializer): input_field_list = FunctionLibInputField(required=True, many=True) + init_field_list = serializers.ListField(required=False, default=list) + permission_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_('permission')), validators=[ validators.RegexValidator(regex=re.compile("^PUBLIC|PRIVATE$"), message="权限只支持PUBLIC|PRIVATE", code=500) @@ -111,6 +140,8 @@ class Query(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( @@ -123,19 +154,38 @@ def get_query_set(self): 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'))) @@ -148,6 +198,7 @@ def insert(self, instance, with_valid=True): code=instance.get('code'), user_id=self.data.get('user_id'), input_field_list=instance.get('input_field_list'), + init_field_list=instance.get('init_field_list'), permission_type=instance.get('permission_type'), is_active=instance.get('is_active', True)) function_lib.save() @@ -163,12 +214,15 @@ def debug(self, debug_instance, with_valid=True): input_field_list = debug_instance.get('input_field_list') code = debug_instance.get('code') debug_field_list = debug_instance.get('debug_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 [{'value': self.get_field_value(debug_field_list, field.get('name'), field.get('is_required')), **field} for field in input_field_list]} + # 合并初始化参数 + params = init_params | params return function_executor.exec_code(code, params) @staticmethod @@ -217,6 +271,9 @@ def is_valid(self, *, raise_exception=False): 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 @@ -224,9 +281,19 @@ def edit(self, instance, with_valid=True): 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', '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) @@ -237,7 +304,15 @@ def one(self, with_valid=True): 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: @@ -274,7 +349,71 @@ def import_(self, with_valid=True): 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() - return True \ No newline at end of file + return True + + class IconOperate(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"))) + image = UploadedImageField(required=True, error_messages=ErrMessage.image(_("picture"))) + + def edit(self, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + functionLib = QuerySet(FunctionLib).filter(id=self.data.get('id')).first() + if functionLib is None: + raise AppApiException(500, _('Function does not exist')) + # 删除旧的图片 + 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 00000000000..a68550e90ef --- /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 5bcf70bd975..036d6892601 100644 --- a/apps/function_lib/urls.py +++ b/apps/function_lib/urls.py @@ -8,6 +8,8 @@ path('function_lib/debug', views.FunctionLibView.Debug.as_view()), 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 40d1b1084b3..c0213b65c06 100644 --- a/apps/function_lib/views/function_lib_views.py +++ b/apps/function_lib/views/function_lib_views.py @@ -34,6 +34,7 @@ def get(self, request: Request): 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 @@ def get(self, request: Request, current_page: int, page_size: int): 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)) @@ -136,4 +138,26 @@ class Export(APIView): @has_permissions(RoleConstants.ADMIN, RoleConstants.USER) def get(self, request: Request, id: str): return FunctionLibSerializer.Operate( - data={'id': id, 'user_id': request.user.id}).export() \ No newline at end of file + data={'id': id, 'user_id': request.user.id}).export() + + class EditIcon(APIView): + authentication_classes = [TokenAuth] + parser_classes = [MultiPartParser] + + @action(methods=['PUT'], detail=False) + @has_permissions(RoleConstants.ADMIN, RoleConstants.USER) + def put(self, request: Request, id: str): + return result.success( + FunctionLibSerializer.IconOperate( + data={'id': id, 'user_id': request.user.id, + '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 41a90a96ca0..004cd7217cc 100644 --- a/ui/src/api/function-lib.ts +++ b/ui/src/api/function-lib.ts @@ -112,6 +112,21 @@ const exportFunctionLib = ( ) } +const putFunctionLibIcon: ( + id: string, + data: any, + loading?: Ref +) => Promise> = (id, data, loading) => { + 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 @@ -128,5 +143,7 @@ export default { getFunctionLibById, exportFunctionLib, importFunctionLib, - pylint + pylint, + putFunctionLibIcon, + addInternalFunction } diff --git a/ui/src/api/type/function-lib.ts b/ui/src/api/type/function-lib.ts index 2c5efe25481..0f51764e09d 100644 --- a/ui/src/api/type/function-lib.ts +++ b/ui/src/api/type/function-lib.ts @@ -1,10 +1,12 @@ interface functionLibData { id?: String name?: String + icon?: String desc?: String code?: String permission_type?: 'PRIVATE' | 'PUBLIC' input_field_list?: Array + init_field_list?: Array is_active?: Boolean } diff --git a/ui/src/assets/fx/bochaai/icon.png b/ui/src/assets/fx/bochaai/icon.png new file mode 100644 index 00000000000..530a086ed20 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 00000000000..c03b178d72b --- /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 00000000000..7b903159b0c 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 00000000000..c03b178d72b --- /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 00000000000..72ca125f366 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 00000000000..c03b178d72b --- /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 e8fbb87e9b4..b00021ee03b 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', @@ -52,7 +53,8 @@ export default { }, param: { outputParam: 'Output Parameters', - inputParam: 'Input Parameters' + inputParam: 'Input Parameters', + initParam: 'Startup Parameters', }, inputPlaceholder: 'Please input', 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 2f56c94567d..1ca7103b3ed 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 cdcde3d6072..ba790f2822a 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: '暂无数据', @@ -55,7 +56,8 @@ export default { content: '内容', param: { outputParam: '输出参数', - inputParam:'输入参数' + inputParam: '输入参数', + initParam: '启动参数', }, rename:'重命名' } 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 2ad28bad36d..ac6c00dce66 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 3281456de96..5775f5813b0 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: '暂无数据', @@ -55,7 +56,8 @@ export default { content: '内容', param: { outputParam: '輸出參數', - inputParam: '輸入參數' + inputParam: '輸入參數', + initParam: '啟動參數', }, rename: '重命名' } 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 f7c8eab4980..6b4a9b3392d 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 new file mode 100644 index 00000000000..21c96c4cd77 --- /dev/null +++ b/ui/src/views/function-lib/component/EditAvatarDialog.vue @@ -0,0 +1,131 @@ + + + diff --git a/ui/src/views/function-lib/component/FunctionDebugDrawer.vue b/ui/src/views/function-lib/component/FunctionDebugDrawer.vue index 44856ac820a..45e2347904f 100644 --- a/ui/src/views/function-lib/component/FunctionDebugDrawer.vue +++ b/ui/src/views/function-lib/component/FunctionDebugDrawer.vue @@ -11,6 +11,22 @@
+
+

+ {{ $t('common.param.initParam') }} +

+ + + + +

{{ $t('common.param.inputParam') }} @@ -37,7 +53,7 @@ @@ -177,6 +282,10 @@ import { MsgSuccess, MsgConfirm } from '@/utils/message' import { cloneDeep } from 'lodash' import { PermissionType, PermissionDesc } from '@/enums/model' import { t } from '@/locales' +import UserFieldFormDialog from '@/workflow/nodes/base-node/component/UserFieldFormDialog.vue' +import {isAppIcon} from "@/utils/application"; +import EditAvatarDialog from "./EditAvatarDialog.vue"; + const props = defineProps({ title: String }) @@ -184,6 +293,8 @@ const props = defineProps({ const emit = defineEmits(['refresh']) const FieldFormDialogRef = ref() const FunctionDebugDrawerRef = ref() +const UserFieldFormDialogRef = ref() +const EditAvatarDialogRef = ref() const FormRef = ref() @@ -192,12 +303,15 @@ const loading = ref(false) const visible = ref(false) const showEditor = ref(false) const currentIndex = ref(null) +const showEditIcon = ref(false) const form = ref({ name: '', desc: '', code: '', + icon: '', input_field_list: [], + init_field_list: [], permission_type: 'PRIVATE' }) @@ -210,7 +324,9 @@ watch(visible, (bool) => { name: '', desc: '', code: '', + icon: '', input_field_list: [], + init_field_list: [], permission_type: 'PRIVATE' } FormRef.value?.clearValidate() @@ -286,10 +402,44 @@ function refreshFieldList(data: any) { currentIndex.value = null } + +function openAddInitDialog(data?: any, index?: any) { + if (typeof index !== 'undefined') { + currentIndex.value = index + } + + UserFieldFormDialogRef.value.open(data) +} + +function refreshInitFieldList(data: any) { + if (currentIndex.value !== null) { + form.value.init_field_list?.splice(currentIndex.value, 1, data) + } else { + form.value.init_field_list?.push(data) + } + currentIndex.value = null + UserFieldFormDialogRef.value.close() +} + +function refreshFunctionLib(data: any) { + form.value.icon = data + // console.log(data) +} + +function deleteInitField(index: any) { + form.value.init_field_list?.splice(index, 1) +} + +function openEditAvatar() { + EditAvatarDialogRef.value.open(form.value) +} + + const submit = async (formEl: FormInstance | undefined) => { if (!formEl) return await formEl.validate((valid: any) => { if (valid) { + // console.log(form.value) if (isEdit.value) { functionLibApi.putFunctionLib(form.value?.id as string, form.value, loading).then((res) => { MsgSuccess(t('common.editSuccess')) diff --git a/ui/src/views/function-lib/component/InitParamDrawer.vue b/ui/src/views/function-lib/component/InitParamDrawer.vue new file mode 100644 index 00000000000..cbc895a0946 --- /dev/null +++ b/ui/src/views/function-lib/component/InitParamDrawer.vue @@ -0,0 +1,101 @@ + + + + diff --git a/ui/src/views/function-lib/component/PermissionDialog.vue b/ui/src/views/function-lib/component/PermissionDialog.vue new file mode 100644 index 00000000000..ca9104e9a2f --- /dev/null +++ b/ui/src/views/function-lib/component/PermissionDialog.vue @@ -0,0 +1,104 @@ + + + diff --git a/ui/src/views/function-lib/index.vue b/ui/src/views/function-lib/index.vue index 732b2c9de67..d58165ebd22 100644 --- a/ui/src/views/function-lib/index.vue +++ b/ui/src/views/function-lib/index.vue @@ -1,7 +1,11 @@