Skip to content

Pr@main@image #226

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions apps/application/migrations/0003_application_icon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.1.13 on 2024-04-23 11:16

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('application', '0002_chat_client_id'),
]

operations = [
migrations.AddField(
model_name='application',
name='icon',
field=models.CharField(default='/ui/favicon.ico', max_length=256, verbose_name='应用icon'),
),
]
1 change: 1 addition & 0 deletions apps/application/models/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class Application(AppModelMixin):
dataset_setting = models.JSONField(verbose_name="数据集参数设置", default=get_dataset_setting_dict)
model_setting = models.JSONField(verbose_name="模型参数相关设置", default=get_model_setting_dict)
problem_optimization = models.BooleanField(verbose_name="问题优化", default=False)
icon = models.CharField(max_length=256, verbose_name="应用icon", default="/ui/favicon.ico")

@staticmethod
def get_default_model_prompt():
Expand Down
29 changes: 25 additions & 4 deletions apps/application/serializers/application_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@
from common.db.search import get_dynamics_model, native_search, native_page_search
from common.db.sql_execute import select_list
from common.exception.app_exception import AppApiException, NotFound404
from common.field.common import UploadedImageField
from common.util.field_message import ErrMessage
from common.util.file_util import get_file_content
from dataset.models import DataSet, Document
from dataset.models import DataSet, Document, Image
from dataset.serializers.common_serializers import list_paragraph
from embedding.models import SearchMode
from setting.models import AuthOperate
Expand Down Expand Up @@ -251,6 +252,7 @@ class Edit(serializers.Serializer):
# 问题补全
problem_optimization = serializers.BooleanField(required=False, allow_null=True,
error_messages=ErrMessage.boolean("问题补全"))
icon = serializers.CharField(required=False, allow_null=True, error_messages=ErrMessage.char("icon图标"))

class Create(serializers.Serializer):
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
Expand Down Expand Up @@ -380,7 +382,8 @@ def list(self, with_valid=True):
def reset_application(application: Dict):
application['multiple_rounds_dialogue'] = True if application.get('dialogue_number') > 0 else False
del application['dialogue_number']
application['dataset_setting'] = {**application['dataset_setting'], 'search_mode': 'embedding'}
if 'dataset_setting' in application:
application['dataset_setting'] = {**application['dataset_setting'], 'search_mode': 'embedding'}
return application

def page(self, current_page: int, page_size: int, with_valid=True):
Expand All @@ -393,7 +396,25 @@ def page(self, current_page: int, page_size: int, with_valid=True):
class ApplicationModel(serializers.ModelSerializer):
class Meta:
model = Application
fields = ['id', 'name', 'desc', 'prologue', 'dialogue_number']
fields = ['id', 'name', 'desc', 'prologue', 'dialogue_number', 'icon']

class IconOperate(serializers.Serializer):
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
image = UploadedImageField(required=True, error_messages=ErrMessage.image("图片"))

def edit(self, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
application = QuerySet(Application).filter(id=self.data.get('application_id')).first()
if application is None:
raise AppApiException(500, '不存在的应用id')
image_id = uuid.uuid1()
image = Image(id=image_id, image=self.data.get('image').read(), image_name=self.data.get('image').name)
image.save()
application.icon = f'/api/image/{image_id}'
application.save()
return {**ApplicationSerializer.Query.reset_application(ApplicationSerializerModel(application).data)}

class Operate(serializers.Serializer):
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
Expand Down Expand Up @@ -457,7 +478,7 @@ def edit(self, instance: Dict, with_valid=True):

update_keys = ['name', 'desc', 'model_id', 'multiple_rounds_dialogue', 'prologue', 'status',
'dataset_setting', 'model_setting', 'problem_optimization',
'api_key_is_active']
'api_key_is_active', 'icon']
for update_key in update_keys:
if update_key in instance and instance.get(update_key) is not None:
if update_key == 'multiple_rounds_dialogue':
Expand Down
15 changes: 14 additions & 1 deletion apps/application/swagger_api/application_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@


class ApplicationApi(ApiMixin):
class EditApplicationIcon(ApiMixin):
@staticmethod
def get_request_params_api():
return [
openapi.Parameter(name='file',
in_=openapi.IN_FORM,
type=openapi.TYPE_FILE,
required=True,
description='上传文件')
]

class Authentication(ApiMixin):
@staticmethod
def get_request_body_api():
Expand Down Expand Up @@ -143,7 +154,9 @@ def get_request_body_api():
'dataset_setting': ApplicationApi.DatasetSetting.get_request_body_api(),
'model_setting': ApplicationApi.ModelSetting.get_request_body_api(),
'problem_optimization': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="问题优化",
description="是否开启问题优化", default=True)
description="是否开启问题优化", default=True),
'icon': openapi.Schema(type=openapi.TYPE_STRING, title="icon",
description="icon", default="/ui/favicon.ico")

}
)
Expand Down
1 change: 1 addition & 0 deletions apps/application/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
path('application/profile', views.Application.Profile.as_view()),
path('application/embed', views.Application.Embed.as_view()),
path('application/authentication', views.Application.Authentication.as_view()),
path('application/<str:application_id>/edit_icon', views.Application.EditIcon.as_view()),
path('application/<str:application_id>/statistics/customer_count',
views.ApplicationStatistics.CustomerCount.as_view()),
path('application/<str:application_id>/statistics/customer_count_trend',
Expand Down
23 changes: 23 additions & 0 deletions apps/application/views/application_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from django.http import HttpResponse
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.parsers import MultiPartParser
from rest_framework.request import Request
from rest_framework.views import APIView

Expand Down Expand Up @@ -131,6 +132,28 @@ def get(self, request: Request, application_id: str):
class Application(APIView):
authentication_classes = [TokenAuth]

class EditIcon(APIView):
authentication_classes = [TokenAuth]
parser_classes = [MultiPartParser]

@action(methods=['PUT'], detail=False)
@swagger_auto_schema(operation_summary="修改应用icon",
operation_id="修改应用icon",
tags=['应用'],
manual_parameters=ApplicationApi.EditApplicationIcon.get_request_params_api(),
request_body=ApplicationApi.Operate.get_request_body_api())
@has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND), PermissionConstants.APPLICATION_EDIT,
compare=CompareConstants.AND)
def put(self, request: Request, application_id: str):
return result.success(
ApplicationSerializer.IconOperate(
data={'application_id': application_id, 'user_id': request.user.id,
'image': request.FILES.get('file')}).edit(request.data))

class Embed(APIView):
@action(methods=["GET"], detail=False)
@swagger_auto_schema(operation_summary="获取嵌入js",
Expand Down
8 changes: 8 additions & 0 deletions apps/common/field/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,11 @@ def to_internal_value(self, data):

def to_representation(self, value):
return value


class UploadedImageField(serializers.ImageField):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def to_representation(self, value):
return value
13 changes: 10 additions & 3 deletions apps/common/middleware/static_headers_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ def process_response(self, request, response):
if request.path.startswith('/ui/chat/'):
access_token = request.path.replace('/ui/chat/', '')
application_access_token = QuerySet(ApplicationAccessToken).filter(access_token=access_token).first()
if application_access_token is not None and application_access_token.white_active:
# 添加自定义的响应头
response['Content-Security-Policy'] = f'frame-ancestors {" ".join(application_access_token.white_list)}'
if application_access_token is not None:
if application_access_token.white_active:
# 添加自定义的响应头
response[
'Content-Security-Policy'] = f'frame-ancestors {" ".join(application_access_token.white_list)}'
response.content = (response.content.decode('utf-8').replace(
'<link rel="icon" href="/ui/favicon.ico" />',
f'<link rel="icon" href="{application_access_token.application.icon}" />')
.replace('<title>MaxKB</title>', f'<title>{application_access_token.application.name}</title>').encode(
"utf-8"))
return response
9 changes: 9 additions & 0 deletions apps/common/util/field_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,12 @@ def date(field: str):
'invalid': gettext_lazy('【%s】日期格式错误。请改用以下格式之一: {format}。'),
'datetime': gettext_lazy('【%s】应为日期,但得到的是日期时间。')
}

@staticmethod
def image(field: str):
return {
'required': gettext_lazy('【%s】此字段必填。' % field),
'null': gettext_lazy('【%s】此字段不能为null。' % field),
'invalid_image': gettext_lazy('【%s】上载有效的图像。您上载的文件不是图像或图像已损坏。' % field),
'max_length': gettext_lazy('请确保此文件名最多包含 {max_length} 个字符(长度为 {length})。')
}
27 changes: 27 additions & 0 deletions apps/dataset/migrations/0002_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 4.1.13 on 2024-04-22 19:31

from django.db import migrations, models
import uuid


class Migration(migrations.Migration):

dependencies = [
('dataset', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='Image',
fields=[
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
('id', models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
('image', models.BinaryField(verbose_name='图片数据')),
('image_name', models.CharField(default='', max_length=256, verbose_name='图片名称')),
],
options={
'db_table': 'image',
},
),
]
9 changes: 9 additions & 0 deletions apps/dataset/models/data_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,12 @@ class ProblemParagraphMapping(AppModelMixin):

class Meta:
db_table = "problem_paragraph_mapping"


class Image(AppModelMixin):
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id")
image = models.BinaryField(verbose_name="图片数据")
image_name = models.CharField(max_length=256, verbose_name="图片名称", default="")

class Meta:
db_table = "image"
42 changes: 42 additions & 0 deletions apps/dataset/serializers/image_serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# coding=utf-8
"""
@project: maxkb
@Author:虎
@file: image_serializers.py
@date:2024/4/22 16:36
@desc:
"""
import uuid

from django.db.models import QuerySet
from django.http import HttpResponse
from rest_framework import serializers

from common.exception.app_exception import NotFound404
from common.field.common import UploadedImageField
from common.util.field_message import ErrMessage
from dataset.models import Image


class ImageSerializer(serializers.Serializer):
image = UploadedImageField(required=True, error_messages=ErrMessage.image("图片"))

def upload(self, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
image_id = uuid.uuid1()
image = Image(id=image_id, image=self.data.get('image').read(), image_name=self.data.get('image').name)
image.save()
return f'/api/image/{image_id}'

class Operate(serializers.Serializer):
id = serializers.UUIDField(required=True)

def get(self, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
image_id = self.data.get('id')
image = QuerySet(Image).filter(id=image_id).first()
if image is None:
raise NotFound404(404, "不存在的图片")
return HttpResponse(image.image, status=200, headers={'Content-Type': 'image/png'})
22 changes: 22 additions & 0 deletions apps/dataset/swagger_api/image_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# coding=utf-8
"""
@project: maxkb
@Author:虎
@file: image_api.py
@date:2024/4/23 11:23
@desc:
"""
from drf_yasg import openapi

from common.mixins.api_mixin import ApiMixin


class ImageApi(ApiMixin):
@staticmethod
def get_request_params_api():
return [openapi.Parameter(name='file',
in_=openapi.IN_FORM,
type=openapi.TYPE_FILE,
required=True,
description='上传图片文件')
]
3 changes: 2 additions & 1 deletion apps/dataset/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@
path('dataset/<str:dataset_id>/problem/<int:current_page>/<int:page_size>', views.Problem.Page.as_view()),
path('dataset/<str:dataset_id>/problem/<str:problem_id>', views.Problem.Operate.as_view()),
path('dataset/<str:dataset_id>/problem/<str:problem_id>/paragraph', views.Problem.Paragraph.as_view()),

path('image/<str:image_id>', views.Image.Operate.as_view()),
path('image', views.Image.as_view())
]
1 change: 1 addition & 0 deletions apps/dataset/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
from .document import *
from .paragraph import *
from .problem import *
from .image import *
43 changes: 43 additions & 0 deletions apps/dataset/views/image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# coding=utf-8
"""
@project: maxkb
@Author:虎
@file: image.py
@date:2024/4/22 16:23
@desc:
"""
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.parsers import MultiPartParser
from rest_framework.views import APIView
from rest_framework.views import Request

from common.auth import TokenAuth
from common.response import result
from dataset.serializers.image_serializers import ImageSerializer


class Image(APIView):
authentication_classes = [TokenAuth]
parser_classes = [MultiPartParser]

@action(methods=['POST'], detail=False)
@swagger_auto_schema(operation_summary="上传图片",
operation_id="上传图片",
manual_parameters=[openapi.Parameter(name='file',
in_=openapi.IN_FORM,
type=openapi.TYPE_FILE,
required=True,
description='上传文件')],
tags=["图片"])
def post(self, request: Request):
return result.success(ImageSerializer(data={'image': request.FILES.get('file')}).upload())

class Operate(APIView):
@action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary="获取图片",
operation_id="获取图片",
tags=["图片"])
def get(self, request: Request, image_id: str):
return ImageSerializer.Operate(data={'id': image_id}).get()
16 changes: 15 additions & 1 deletion ui/src/api/application-overview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,24 @@ const getStatistics: (
return get(`${prefix}/${application_id}/statistics/chat_record_aggregate_trend`, data, loading)
}

/**
* 修改应用icon
* @param 参数 application_id
* data: file
*/
const putAppIcon: (
application_id: string,
data: any,
loading?: Ref<boolean>
) => Promise<Result<any>> = (application_id, data, loading) => {
return put(`${prefix}/${application_id}/edit_icon`, data, undefined, loading)
}

export default {
getAPIKey,
postAPIKey,
delAPIKey,
putAPIKey,
getStatistics
getStatistics,
putAppIcon
}
Loading