diff --git a/apps/application/serializers/chat_serializers.py b/apps/application/serializers/chat_serializers.py index d8a3e648b..cedfffbf0 100644 --- a/apps/application/serializers/chat_serializers.py +++ b/apps/application/serializers/chat_serializers.py @@ -56,6 +56,18 @@ class ChatSerializers(serializers.Serializer): QuerySet(Chat).filter(id=self.data.get('chat_id'), application_id=self.data.get('application_id')).delete() return True + class ClientChatHistory(serializers.Serializer): + application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id")) + client_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("客户端id")) + + def page(self, current_page: int, page_size: int, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + queryset = QuerySet(Chat).filter(client_id=self.data.get('client_id'), + application_id=self.data.get('application_id')) + queryset = queryset.order_by('-create_time') + return page_search(current_page, page_size, queryset, lambda row: ChatSerializerModel(row).data) + class Query(serializers.Serializer): abstract = serializers.CharField(required=False, error_messages=ErrMessage.char("摘要")) history_day = serializers.IntegerField(required=True, error_messages=ErrMessage.integer("历史天数")) @@ -282,6 +294,12 @@ class ChatRecordSerializerModel(serializers.ModelSerializer): 'create_time', 'update_time'] +class ChatSerializerModel(serializers.ModelSerializer): + class Meta: + model = Chat + fields = ['id', 'application_id', 'abstract', 'client_id'] + + class ChatRecordSerializer(serializers.Serializer): class Operate(serializers.Serializer): chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("对话id")) @@ -319,13 +337,16 @@ class ChatRecordSerializer(serializers.Serializer): class Query(serializers.Serializer): application_id = serializers.UUIDField(required=True) chat_id = serializers.UUIDField(required=True) + order_asc = serializers.BooleanField(required=False) def list(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id')) + order_by = 'create_time' if self.data.get('order_asc') is None or self.data.get( + 'order_asc') else '-create_time' return [ChatRecordSerializerModel(chat_record).data for chat_record in - QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id')).order_by("create_time")] + QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id')).order_by(order_by)] @staticmethod def reset_chat_record(chat_record): @@ -354,8 +375,10 @@ class ChatRecordSerializer(serializers.Serializer): def page(self, current_page: int, page_size: int, with_valid=True): if with_valid: self.is_valid(raise_exception=True) + order_by = 'create_time' if self.data.get('order_asc') is None or self.data.get( + 'order_asc') else '-create_time' page = page_search(current_page, page_size, - QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id')).order_by("create_time"), + QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id')).order_by(order_by), post_records_handler=lambda chat_record: self.reset_chat_record(chat_record)) return page diff --git a/apps/application/swagger_api/chat_api.py b/apps/application/swagger_api/chat_api.py index 29f60c3d0..9c56cd21e 100644 --- a/apps/application/swagger_api/chat_api.py +++ b/apps/application/swagger_api/chat_api.py @@ -12,6 +12,17 @@ from application.swagger_api.application_api import ApplicationApi from common.mixins.api_mixin import ApiMixin +class ChatClientHistoryApi(ApiMixin): + @staticmethod + def get_request_params_api(): + return [openapi.Parameter(name='application_id', + in_=openapi.IN_PATH, + type=openapi.TYPE_STRING, + required=True, + description='应用id') + ] + + class ChatApi(ApiMixin): @staticmethod def get_request_body_api(): @@ -80,7 +91,7 @@ class ChatApi(ApiMixin): 'problem_optimization'], properties={ 'id': openapi.Schema(type=openapi.TYPE_STRING, title="应用id", - description="应用id,修改的时候传,创建的时候不传"), + description="应用id,修改的时候传,创建的时候不传"), 'model_id': openapi.Schema(type=openapi.TYPE_STRING, title="模型id", description="模型id"), 'dataset_id_list': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING), diff --git a/apps/application/template/embed.js b/apps/application/template/embed.js index 33545af47..ec4e6b9f2 100644 --- a/apps/application/template/embed.js +++ b/apps/application/template/embed.js @@ -53,14 +53,17 @@ const chatButtonHtml= const getChatContainerHtml=(protocol,host,token)=>{ return `
- -
+ +
+ +
- -
-
- - + + +
+
+ +
` } @@ -94,6 +97,7 @@ const initChat=(root)=>{ const closeviewport=root.querySelector('.maxkb-closeviewport') const close_func=()=>{ chat_container.style['display']=chat_container.style['display']=='block'?'none':'block' + chat_button.style['display']=chat_container.style['display']=='block'?'none':'block' } close_icon=chat_container.querySelector('.maxkb-chat-close') chat_button.onclick = close_func @@ -250,12 +254,12 @@ function initMaxkbStyle(root){ border: 1px solid #ffffff; background: linear-gradient(188deg, rgba(235, 241, 255, 0.20) 39.6%, rgba(231, 249, 255, 0.20) 94.3%), #EFF0F1; box-shadow: 0px 4px 8px 0px rgba(31, 35, 41, 0.10); - position: fixed;bottom: 20px;right: 45px;overflow: hidden; + position: fixed;bottom: 16px;right: 16px;overflow: hidden; } #maxkb #maxkb-chat-container .maxkb-operate{ - top: 15px; - right: 10px; + top: 18px; + right: 15px; position: absolute; display: flex; align-items: center; diff --git a/apps/application/urls.py b/apps/application/urls.py index 30866c81a..624b5b45b 100644 --- a/apps/application/urls.py +++ b/apps/application/urls.py @@ -30,6 +30,8 @@ urlpatterns = [ path('application//', views.Application.Page.as_view(), name='application_page'), path('application//chat/open', views.ChatView.Open.as_view()), path("application/chat/open", views.ChatView.OpenTemp.as_view()), + path("application//chat/client//", + views.ChatView.ClientChatHistoryPage.as_view()), path('application//chat/export', views.ChatView.Export.as_view(), name='export'), path('application//chat', views.ChatView.as_view(), name='chats'), path('application//chat//', views.ChatView.Page.as_view()), diff --git a/apps/application/views/chat_views.py b/apps/application/views/chat_views.py index 2d6ef10f1..3bae2b877 100644 --- a/apps/application/views/chat_views.py +++ b/apps/application/views/chat_views.py @@ -13,7 +13,8 @@ from rest_framework.views import APIView from application.serializers.chat_message_serializers import ChatMessageSerializer from application.serializers.chat_serializers import ChatSerializers, ChatRecordSerializer -from application.swagger_api.chat_api import ChatApi, VoteApi, ChatRecordApi, ImproveApi, ChatRecordImproveApi +from application.swagger_api.chat_api import ChatApi, VoteApi, ChatRecordApi, ImproveApi, ChatRecordImproveApi, \ + ChatClientHistoryApi from common.auth import TokenAuth, has_permissions from common.constants.authentication_type import AuthenticationType from common.constants.permission_constants import Permission, Group, Operate, \ @@ -137,6 +138,28 @@ class ChatView(APIView): data={'application_id': application_id, 'user_id': request.user.id, 'chat_id': chat_id}).delete()) + class ClientChatHistoryPage(APIView): + authentication_classes = [TokenAuth] + + @action(methods=['GET'], detail=False) + @swagger_auto_schema(operation_summary="分页获取客户端对话列表", + operation_id="分页获取客户端对话列表", + manual_parameters=result.get_page_request_params( + ChatClientHistoryApi.get_request_params_api()), + responses=result.get_page_api_response(ChatApi.get_response_body_api()), + tags=["应用/对话日志"] + ) + @has_permissions( + ViewPermission([RoleConstants.APPLICATION_ACCESS_TOKEN], + [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE, + dynamic_tag=keywords.get('application_id'))]) + ) + def get(self, request: Request, application_id: str, current_page: int, page_size: int): + return result.success(ChatSerializers.ClientChatHistory( + data={'client_id': request.auth.client_id, 'application_id': application_id}).page( + current_page=current_page, + page_size=page_size)) + class Page(APIView): authentication_classes = [TokenAuth] @@ -198,7 +221,7 @@ class ChatView(APIView): def get(self, request: Request, application_id: str, chat_id: str): return result.success(ChatRecordSerializer.Query( data={'application_id': application_id, - 'chat_id': chat_id}).list()) + 'chat_id': chat_id, 'order_asc': request.query_params.get('order_asc')}).list()) class Page(APIView): authentication_classes = [TokenAuth] @@ -219,7 +242,8 @@ class ChatView(APIView): def get(self, request: Request, application_id: str, chat_id: str, current_page: int, page_size: int): return result.success(ChatRecordSerializer.Query( data={'application_id': application_id, - 'chat_id': chat_id}).page(current_page, page_size)) + 'chat_id': chat_id, 'order_asc': request.query_params.get('order_asc')}).page(current_page, + page_size)) class Vote(APIView): authentication_classes = [TokenAuth] diff --git a/ui/public/embeb.js b/ui/public/embeb.js deleted file mode 100644 index 23517cf59..000000000 --- a/ui/public/embeb.js +++ /dev/null @@ -1,307 +0,0 @@ -function auth(token, protocol, host) { - const XML = new XMLHttpRequest() - XML.open('POST', `${protocol}//${host}/api/application/authentication`, false) - XML.setRequestHeader('Content-Type', 'application/json') - res = XML.send(JSON.stringify({ access_token: token })) - return XML.status == 200 -} - -const guideHtml=` -
-
-
-
-
- - - -
- -
🌟 遇见问题,不再有障碍!
-

你好,我是你的智能小助手。
- 点我,开启高效解答模式,让问题变成过去式。

-
- -
- -
-` -const chatButtonHtml= -`
- - - - - - - - - - - - - - - - - - - - - - - -
` - - - -const getChatContainerHtml=(protocol,host,token)=>{ - return `
- -
-
- -
-
- - -
` -} -/** - * 初始化引导 - * @param {*} root - */ -const initGuide=(root)=>{ - root.insertAdjacentHTML("beforeend",guideHtml) - const button=root.querySelector(".maxkb-button") - const close_icon=root.querySelector('.maxkb-close') - const close_func=()=>{ - root.removeChild(root.querySelector('.maxkb-tips')) - root.removeChild(root.querySelector('.maxkb-mask')) - localStorage.setItem('maxkbMaskTip',true) - } - button.onclick=close_func - close_icon.onclick=close_func -} -const initChat=(root)=>{ - // 添加对话icon - root.insertAdjacentHTML("beforeend",chatButtonHtml) - // 添加对话框 - root.insertAdjacentHTML('beforeend',getChatContainerHtml(window.maxkbChatConfig.protocol,window.maxkbChatConfig.host,window.maxkbChatConfig.token)) - // 按钮元素 - const chat_button=root.querySelector('.maxkb-chat-button') - // 对话框元素 - const chat_container=root.querySelector('#maxkb-chat-container') - - const viewport=root.querySelector('.maxkb-openviewport') - const closeviewport=root.querySelector('.maxkb-closeviewport') - const close_func=()=>{ - chat_container.style['display']=chat_container.style['display']=='block'?'none':'block' - } - close_icon=chat_container.querySelector('.maxkb-close') - chat_button.onclick = close_func - close_icon.onclick=close_func - const viewport_func=()=>{ - if(chat_container.classList.contains('maxkb-enlarge')){ - chat_container.classList.remove("maxkb-enlarge"); - viewport.classList.remove('maxkb-viewportnone') - closeviewport.classList.add('maxkb-viewportnone') - }else{ - chat_container.classList.add("maxkb-enlarge"); - viewport.classList.add('maxkb-viewportnone') - closeviewport.classList.remove('maxkb-viewportnone') - } - } - viewport.onclick=viewport_func - closeviewport.onclick=viewport_func -} -/** - * 第一次进来的引导提示 - */ -function initMaxkb(){ - const maxkb=document.createElement('div') - const root=document.createElement('div') - root.id="maxkb" - initMaxkbStyle(maxkb) - maxkb.appendChild(root) - document.body.appendChild(maxkb) - const maxkbMaskTip=localStorage.getItem('maxkbMaskTip') - if(maxkbMaskTip==null){ - initGuide(root) - } - initChat(root) -} - - -// 初始化全局样式 -function initMaxkbStyle(root){ - style=document.createElement('style') - style.type='text/css' - style.innerText= ` - /* 放大 */ - #maxkb .maxkb-enlarge { - width: 50%!important; - height: 100%!important; - bottom: 0!important; - right: 0 !important; - } - @media only screen and (max-width: 768px){ - #maxkb .maxkb-enlarge { - width: 100%!important; - height: 100%!important; - right: 0 !important; - bottom: 0!important; - } - } - - /* 引导 */ - - #maxkb .maxkb-mask { - position: fixed; - z-index: 999; - background-color: transparent; - height: 100%; - width: 100%; - top: 0; - left: 0; - } - #maxkb .maxkb-mask .maxkb-content { - width: 45px; - height: 50px; - box-shadow: 1px 1px 1px 2000px rgba(0,0,0,.6); - border-radius: 50% 0 0 50%; - position: absolute; - right: 0; - bottom: 42px; - z-index: 1000; - } - #maxkb .maxkb-tips { - position: fixed; - bottom: 30px; - right: 60px; - padding: 22px 24px 24px; - border-radius: 6px; - color: #ffffff; - font-size: 14px; - background: #3370FF; - z-index: 1000; - } - #maxkb .maxkb-tips .maxkb-arrow { - position: absolute; - background: #3370FF; - width: 10px; - height: 10px; - pointer-events: none; - transform: rotate(45deg); - box-sizing: border-box; - /* left */ - right: -5px; - bottom: 33px; - border-left-color: transparent; - border-bottom-color: transparent - } - #maxkb .maxkb-tips .maxkb-title { - font-size: 20px; - font-weight: 500; - margin-bottom: 8px; - } - #maxkb .maxkb-tips .maxkb-button { - text-align: right; - margin-top: 24px; - } - #maxkb .maxkb-tips .maxkb-button button { - border-radius: 4px; - background: #FFF; - padding: 3px 12px; - color: #3370FF; - cursor: pointer; - outline: none; - border: none; - } - #maxkb .maxkb-tips .maxkb-button button::after{ - border: none; - } - #maxkb .maxkb-tips .maxkb-close { - position: absolute; - right: 20px; - top: 20px; - cursor: pointer; - - } - #maxkb-chat-container { - width: 420px; - height: 600px; - display:none; - } - @media only screen and (max-width: 768px) { - #maxkb-chat-container { - width: 100%; - height: 70%; - right: 0 !important; - } - } - - #maxkb .maxkb-chat-button{ - position: fixed; - bottom: 30px; - right: 0; - cursor: pointer; - } - #maxkb #maxkb-chat-container{ - z-index:10000;position: relative; - border-radius: 8px; - border: 1px solid var(--N300, #DEE0E3); - background: linear-gradient(188deg, rgba(235, 241, 255, 0.20) 39.6%, rgba(231, 249, 255, 0.20) 94.3%), #EFF0F1; - box-shadow: 0px 4px 8px 0px rgba(31, 35, 41, 0.10); - position: fixed;bottom: 20px;right: 45px;overflow: hidden; - } - #maxkb #maxkb-chat-container .maxkb-chat-close{ - position: absolute; - top: 15px; - right: 10px; - cursor: pointer; - } - #maxkb #maxkb-chat-container .maxkb-openviewport{ - position: absolute; - top: 15px; - right: 50px; - cursor: pointer; - } - #maxkb #maxkb-chat-container .maxkb-closeviewport{ - position: absolute; - top: 15px; - right: 50px; - cursor: pointer; - } - #maxkb #maxkb-chat-container .maxkb-viewportnone{ - display:none; - } - #maxkb #maxkb-chat-container #maxkb-chat{ - height:100%; - width:100%; - border: none; -} - #maxkb #maxkb-chat-container { - animation: appear .4s ease-in-out; - } - @keyframes appear { - from { - height: 0;; - } - - to { - height: 600px; - } - }` - root.appendChild(style) -} - -function embedChatbot() { - const t = window.maxkbChatConfig - check = auth(t.token, t.protocol, t.host) - if (t && t.token && t.protocol && t.host && check) { - // 初始化maxkb智能小助手 - initMaxkb() - } else console.error('invalid parameter') -} -window.onload = embedChatbot diff --git a/ui/src/api/log.ts b/ui/src/api/log.ts index 1904e96b0..67b88ab8f 100644 --- a/ui/src/api/log.ts +++ b/ui/src/api/log.ts @@ -1,5 +1,5 @@ import { Result } from '@/request/Result' -import { get, post, del, put, exportExcel } from '@/request/index' +import { get, del, put, exportExcel } from '@/request/index' import type { pageRequest } from '@/api/type/common' import { type Ref } from 'vue' @@ -64,11 +64,12 @@ const getChatRecordLog: ( application_id: String, chart_id: String, page: pageRequest, - loading?: Ref -) => Promise> = (application_id, chart_id, page, loading) => { + loading?: Ref, + order_asc?: boolean +) => Promise> = (application_id, chart_id, page, loading, order_asc) => { return get( `${prefix}/${application_id}/chat/${chart_id}/chat_record/${page.current_page}/${page.page_size}`, - undefined, + { order_asc: order_asc !== undefined ? order_asc : true }, loading ) } @@ -173,6 +174,18 @@ const getRecordDetail: ( ) } +const getChatLogClient: ( + application_id: String, + page: pageRequest, + loading?: Ref +) => Promise> = (application_id, page, loading) => { + return get( + `${prefix}/${application_id}/chat/client/${page.current_page}/${page.page_size}`, + null, + loading + ) +} + export default { getChatLog, delChatLog, @@ -181,5 +194,6 @@ export default { getMarkRecord, getRecordDetail, delMarkRecord, - exportChatLog + exportChatLog, + getChatLogClient } diff --git a/ui/src/components/ai-chat/LogOperationButton.vue b/ui/src/components/ai-chat/LogOperationButton.vue index 59cc48634..7a6dcb74d 100644 --- a/ui/src/components/ai-chat/LogOperationButton.vue +++ b/ui/src/components/ai-chat/LogOperationButton.vue @@ -41,7 +41,7 @@
diff --git a/ui/src/views/chat/embed/index.vue b/ui/src/views/chat/embed/index.vue new file mode 100644 index 000000000..01740ad45 --- /dev/null +++ b/ui/src/views/chat/embed/index.vue @@ -0,0 +1,285 @@ + + + diff --git a/ui/src/views/chat/index.vue b/ui/src/views/chat/index.vue index f04d44242..16f77d8fb 100644 --- a/ui/src/views/chat/index.vue +++ b/ui/src/views/chat/index.vue @@ -1,144 +1,23 @@ - +onMounted(() => {}) + + diff --git a/ui/src/views/chat/pc/index.vue b/ui/src/views/chat/pc/index.vue new file mode 100644 index 000000000..6d8dcd10b --- /dev/null +++ b/ui/src/views/chat/pc/index.vue @@ -0,0 +1,279 @@ + + + diff --git a/ui/src/views/dataset/component/SyncWebDialog.vue b/ui/src/views/dataset/component/SyncWebDialog.vue index 08ccc9674..60dda86e7 100644 --- a/ui/src/views/dataset/component/SyncWebDialog.vue +++ b/ui/src/views/dataset/component/SyncWebDialog.vue @@ -35,8 +35,6 @@