feat: 对话增加历史记录 (#485)
This commit is contained in:
parent
5a4971645d
commit
a3dd967c61
@ -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
|
||||
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -53,14 +53,17 @@ const chatButtonHtml=
|
||||
|
||||
const getChatContainerHtml=(protocol,host,token)=>{
|
||||
return `<div id="maxkb-chat-container">
|
||||
<iframe id="maxkb-chat" src=${protocol}://${host}/ui/chat/${token}></iframe>
|
||||
<div class="maxkb-operate"><div class="maxkb-closeviewport maxkb-viewportnone"><svg style="vertical-align: middle;overflow: hidden;" t="1710214539671" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="rgb(100, 106, 115)" width="16" height="16"><path d="M85.333333 384c25.6 0 42.666667-17.066667 42.666667-42.666667V128h213.333333c25.6 0 42.666667-17.066667 42.666667-42.666667s-17.066667-42.666667-42.666667-42.666666H85.333333c-25.6 0-42.666667 17.066667-42.666666 42.666666v256c0 25.6 17.066667 42.666667 42.666666 42.666667zM938.666667 640c-25.6 0-42.666667 17.066667-42.666667 42.666667v213.333333h-213.333333c-25.6 0-42.666667 17.066667-42.666667 42.666667s17.066667 42.666667 42.666667 42.666666h256c25.6 0 42.666667-17.066667 42.666666-42.666666v-256c0-25.6-17.066667-42.666667-42.666666-42.666667zM601.6 401.066667c4.266667 8.533333 12.8 17.066667 21.333333 21.333333 4.266667 4.266667 12.8 4.266667 17.066667 4.266667h256c25.6 0 42.666667-17.066667 42.666667-42.666667s-17.066667-42.666667-42.666667-42.666667h-153.6l226.133333-226.133333c17.066667-17.066667 17.066667-42.666667 0-59.733333-8.533333-8.533333-17.066667-12.8-29.866666-12.8s-21.333333 4.266667-29.866667 12.8L682.666667 281.6V128c0-25.6-17.066667-42.666667-42.666667-42.666667s-42.666667 17.066667-42.666667 42.666667v256c0 4.266667 0 12.8 4.266667 17.066667zM115.2 968.533333L341.333333 742.4V896c0 25.6 17.066667 42.666667 42.666667 42.666667s42.666667-17.066667 42.666667-42.666667v-256c0-4.266667 0-12.8-4.266667-17.066667-4.266667-8.533333-12.8-17.066667-21.333333-21.333333-4.266667-4.266667-12.8-4.266667-17.066667-4.266667H128c-25.6 0-42.666667 17.066667-42.666667 42.666667s17.066667 42.666667 42.666667 42.666667h153.6l-226.133333 226.133333c-17.066667 17.066667-17.066667 42.666667 0 59.733333s42.666667 17.066667 59.733333 0z" p-id="10189"></path></svg></div>
|
||||
<iframe id="maxkb-chat" src=${protocol}://${host}/ui/chat/${token}?mode=embed></iframe>
|
||||
<div class="maxkb-operate"><div class="maxkb-closeviewport maxkb-viewportnone"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M7.507 11.6645C7.73712 11.6645 7.94545 11.7578 8.09625 11.9086C8.24706 12.0594 8.34033 12.2677 8.34033 12.4978V16.7976C8.34033 17.0277 8.15378 17.2143 7.92366 17.2143H7.09033C6.86021 17.2143 6.67366 17.0277 6.67366 16.7976V14.5812L3.41075 17.843C3.24803 18.0057 2.98421 18.0057 2.82149 17.843L2.23224 17.2537C2.06952 17.091 2.06952 16.8272 2.23224 16.6645L5.56668 13.3311H3.19634C2.96622 13.3311 2.77967 13.1446 2.77967 12.9145V12.0811C2.77967 11.851 2.96622 11.6645 3.19634 11.6645H7.507ZM16.5991 2.1572C16.7619 1.99448 17.0257 1.99448 17.1884 2.1572L17.7777 2.74645C17.9404 2.90917 17.9404 3.17299 17.7777 3.33571L14.4432 6.66904H16.8136C17.0437 6.66904 17.2302 6.85559 17.2302 7.08571V7.91904C17.2302 8.14916 17.0437 8.33571 16.8136 8.33571H12.5029C12.2728 8.33571 12.0644 8.24243 11.9136 8.09163C11.7628 7.94082 11.6696 7.73249 11.6696 7.50237V3.20257C11.6696 2.97245 11.8561 2.7859 12.0862 2.7859H12.9196C13.1497 2.7859 13.3362 2.97245 13.3362 3.20257V5.419L16.5991 2.1572Z" fill="#646A73"/>
|
||||
</svg></div>
|
||||
<div class="maxkb-openviewport">
|
||||
<svg t="1710150885892" style="vertical-align: middle;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="rgb(100, 106, 115)" width="16" height="16" ><path d="M85.333333 384c25.6 0 42.666667-17.066667 42.666667-42.666667V128h213.333333c25.6 0 42.666667-17.066667 42.666667-42.666667s-17.066667-42.666667-42.666667-42.666666H85.333333c-25.6 0-42.666667 17.066667-42.666666 42.666666v256c0 25.6 17.066667 42.666667 42.666666 42.666667zM938.666667 640c-25.6 0-42.666667 17.066667-42.666667 42.666667v213.333333h-213.333333c-25.6 0-42.666667 17.066667-42.666667 42.666667s17.066667 42.666667 42.666667 42.666666h256c25.6 0 42.666667-17.066667 42.666666-42.666666v-256c0-25.6-17.066667-42.666667-42.666666-42.666667zM977.066667 68.266667c-4.266667-8.533333-12.8-17.066667-21.333334-21.333334-4.266667-4.266667-12.8-4.266667-17.066666-4.266666h-256c-25.6 0-42.666667 17.066667-42.666667 42.666666s17.066667 42.666667 42.666667 42.666667h153.6l-226.133334 226.133333c-17.066667 17.066667-17.066667 42.666667 0 59.733334 8.533333 8.533333 17.066667 12.8 29.866667 12.8s21.333333-4.266667 29.866667-12.8L896 187.733333V341.333333c0 25.6 17.066667 42.666667 42.666667 42.666667s42.666667-17.066667 42.666666-42.666667V85.333333c0-4.266667 0-12.8-4.266666-17.066666zM354.133333 610.133333L128 836.266667V682.666667c0-25.6-17.066667-42.666667-42.666667-42.666667s-42.666667 17.066667-42.666666 42.666667v256c0 4.266667 0 12.8 4.266666 17.066666 4.266667 8.533333 12.8 17.066667 21.333334 21.333334 4.266667 4.266667 12.8 4.266667 17.066666 4.266666h256c25.6 0 42.666667-17.066667 42.666667-42.666666s-17.066667-42.666667-42.666667-42.666667H187.733333l226.133334-226.133333c17.066667-17.066667 17.066667-42.666667 0-59.733334s-42.666667-17.066667-59.733334 0z" p-id="8645"></path></svg>
|
||||
</div>
|
||||
<div class="maxkb-chat-close"><svg style="vertical-align: middle;overflow: hidden;" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M9.95317 8.73169L15.5511 3.13376C15.7138 2.97104 15.9776 2.97104 16.1403 3.13376L16.7296 3.72301C16.8923 3.88573 16.8923 4.14955 16.7296 4.31227L11.1317 9.9102L16.7296 15.5081C16.8923 15.6708 16.8923 15.9347 16.7296 16.0974L16.1403 16.6866C15.9776 16.8494 15.7138 16.8494 15.5511 16.6866L9.95317 11.0887L4.35524 16.6866C4.19252 16.8494 3.9287 16.8494 3.76598 16.6866L3.17673 16.0974C3.01401 15.9347 3.01401 15.6708 3.17673 15.5081L8.77465 9.9102L3.17673 4.31227C3.01401 4.14955 3.01401 3.88573 3.17673 3.72301L3.76598 3.13376C3.9287 2.97104 4.19252 2.97104 4.35524 3.13376L9.95317 8.73169Z" fill="#646A73"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M7.15209 11.5968C7.31481 11.4341 7.57862 11.4341 7.74134 11.5968L8.3306 12.186C8.49332 12.3487 8.49332 12.6126 8.3306 12.7753L4.99615 16.1086H7.3665C7.59662 16.1086 7.78316 16.2952 7.78316 16.5253V17.3586C7.78316 17.5887 7.59662 17.7753 7.3665 17.7753H3.05584C2.82572 17.7753 2.61738 17.682 2.46658 17.5312C2.31578 17.3804 2.2225 17.1721 2.2225 16.9419V12.6421C2.2225 12.412 2.40905 12.2255 2.63917 12.2255H3.4725C3.70262 12.2255 3.88917 12.412 3.88917 12.6421V14.8586L7.15209 11.5968ZM16.937 2.22217C17.1671 2.22217 17.3754 2.31544 17.5262 2.46625C17.677 2.61705 17.7703 2.82538 17.7703 3.0555V7.35531C17.7703 7.58543 17.5837 7.77198 17.3536 7.77198H16.5203C16.2902 7.77198 16.1036 7.58543 16.1036 7.35531V5.13888L12.8407 8.40068C12.678 8.5634 12.4142 8.5634 12.2515 8.40068L11.6622 7.81142C11.4995 7.64871 11.4995 7.38489 11.6622 7.22217L14.9966 3.88883H12.6263C12.3962 3.88883 12.2096 3.70229 12.2096 3.47217V2.63883C12.2096 2.40872 12.3962 2.22217 12.6263 2.22217H16.937Z" fill="#646A73"/>
|
||||
</svg></div>
|
||||
<div class="maxkb-chat-close"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M9.95317 8.73169L15.5511 3.13376C15.7138 2.97104 15.9776 2.97104 16.1403 3.13376L16.7296 3.72301C16.8923 3.88573 16.8923 4.14955 16.7296 4.31227L11.1317 9.9102L16.7296 15.5081C16.8923 15.6708 16.8923 15.9347 16.7296 16.0974L16.1403 16.6866C15.9776 16.8494 15.7138 16.8494 15.5511 16.6866L9.95317 11.0887L4.35524 16.6866C4.19252 16.8494 3.9287 16.8494 3.76598 16.6866L3.17673 16.0974C3.01401 15.9347 3.01401 15.6708 3.17673 15.5081L8.77465 9.9102L3.17673 4.31227C3.01401 4.14955 3.01401 3.88573 3.17673 3.72301L3.76598 3.13376C3.9287 2.97104 4.19252 2.97104 4.35524 3.13376L9.95317 8.73169Z" fill="#646A73"/>
|
||||
</svg>
|
||||
</div></div>
|
||||
`
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -30,6 +30,8 @@ urlpatterns = [
|
||||
path('application/<int:current_page>/<int:page_size>', views.Application.Page.as_view(), name='application_page'),
|
||||
path('application/<str:application_id>/chat/open', views.ChatView.Open.as_view()),
|
||||
path("application/chat/open", views.ChatView.OpenTemp.as_view()),
|
||||
path("application/<str:application_id>/chat/client/<int:current_page>/<int:page_size>",
|
||||
views.ChatView.ClientChatHistoryPage.as_view()),
|
||||
path('application/<str:application_id>/chat/export', views.ChatView.Export.as_view(), name='export'),
|
||||
path('application/<str:application_id>/chat', views.ChatView.as_view(), name='chats'),
|
||||
path('application/<str:application_id>/chat/<int:current_page>/<int:page_size>', views.ChatView.Page.as_view()),
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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=`
|
||||
<div class="maxkb-mask">
|
||||
<div class="maxkb-content"></div>
|
||||
</div>
|
||||
<div class="maxkb-tips">
|
||||
<div class="maxkb-close">
|
||||
<svg style="vertical-align: middle;overflow: hidden;" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M9.95317 8.73169L15.5511 3.13376C15.7138 2.97104 15.9776 2.97104 16.1403 3.13376L16.7296 3.72301C16.8923 3.88573 16.8923 4.14955 16.7296 4.31227L11.1317 9.9102L16.7296 15.5081C16.8923 15.6708 16.8923 15.9347 16.7296 16.0974L16.1403 16.6866C15.9776 16.8494 15.7138 16.8494 15.5511 16.6866L9.95317 11.0887L4.35524 16.6866C4.19252 16.8494 3.9287 16.8494 3.76598 16.6866L3.17673 16.0974C3.01401 15.9347 3.01401 15.6708 3.17673 15.5081L8.77465 9.9102L3.17673 4.31227C3.01401 4.14955 3.01401 3.88573 3.17673 3.72301L3.76598 3.13376C3.9287 2.97104 4.19252 2.97104 4.35524 3.13376L9.95317 8.73169Z" fill="#ffffff"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="maxkb-title"> 🌟 遇见问题,不再有障碍!</div>
|
||||
<p>你好,我是你的智能小助手。<br/>
|
||||
点我,开启高效解答模式,让问题变成过去式。</p>
|
||||
<div class="maxkb-button">
|
||||
<button>我知道了</button>
|
||||
</div>
|
||||
<span class="maxkb-arrow" ></span>
|
||||
</div>
|
||||
`
|
||||
const chatButtonHtml=
|
||||
`<div class="maxkb-chat-button"><svg style="vertical-align: middle;overflow: hidden;" xmlns="http://www.w3.org/2000/svg" width="48" height="56" viewBox="0 0 48 56" fill="none">
|
||||
<g filter="url(#filter0_d_349_49711)">
|
||||
<path d="M8 24C8 12.9543 16.9543 4 28 4H48V44H28C16.9543 44 8 35.0457 8 24Z" fill="url(#paint0_linear_349_49711)"/>
|
||||
</g>
|
||||
<path d="M31.6667 15.6665H28.3333V18.1665H29.1667V19.8332H24.5833C23.6629 19.8332 22.9167 20.5794 22.9167 21.4998V30.6665C22.9167 31.587 23.6629 32.3332 24.5833 32.3332H35.4167C36.3371 32.3332 37.0833 31.587 37.0833 30.6665V21.4998C37.0833 20.5794 36.3371 19.8332 35.4167 19.8332H30.8333V18.1665H31.6667V15.6665ZM25.8333 24.8332H28.3333V27.3332H25.8333V24.8332ZM34.1667 24.8332V27.3332H31.6667V24.8332H34.1667Z" fill="white"/>
|
||||
<path d="M21.6667 23.9998H20V28.1665H21.6667V23.9998Z" fill="white"/>
|
||||
<path d="M38.3333 23.9998H40V28.1665H38.3333V23.9998Z" fill="white"/>
|
||||
<defs>
|
||||
<filter id="filter0_d_349_49711" x="0" y="0" width="56" height="56" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="4"/>
|
||||
<feGaussianBlur stdDeviation="4"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.168627 0 0 0 0 0.372549 0 0 0 0 0.85098 0 0 0 0.24 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_349_49711"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_349_49711" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_349_49711" x1="48" y1="25.6667" x2="8" y2="25.6667" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9258F7"/>
|
||||
<stop offset="1" stop-color="#3370FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>`
|
||||
|
||||
|
||||
|
||||
const getChatContainerHtml=(protocol,host,token)=>{
|
||||
return `<div id="maxkb-chat-container">
|
||||
<iframe id="maxkb-chat" src=${protocol}//${host}/ui/chat/${token}></iframe>
|
||||
<div class="maxkb-closeviewport maxkb-viewportnone"><svg style="vertical-align: middle;overflow: hidden;" t="1710214539671" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="rgb(100, 106, 115)" width="16" height="16"><path d="M85.333333 384c25.6 0 42.666667-17.066667 42.666667-42.666667V128h213.333333c25.6 0 42.666667-17.066667 42.666667-42.666667s-17.066667-42.666667-42.666667-42.666666H85.333333c-25.6 0-42.666667 17.066667-42.666666 42.666666v256c0 25.6 17.066667 42.666667 42.666666 42.666667zM938.666667 640c-25.6 0-42.666667 17.066667-42.666667 42.666667v213.333333h-213.333333c-25.6 0-42.666667 17.066667-42.666667 42.666667s17.066667 42.666667 42.666667 42.666666h256c25.6 0 42.666667-17.066667 42.666666-42.666666v-256c0-25.6-17.066667-42.666667-42.666666-42.666667zM601.6 401.066667c4.266667 8.533333 12.8 17.066667 21.333333 21.333333 4.266667 4.266667 12.8 4.266667 17.066667 4.266667h256c25.6 0 42.666667-17.066667 42.666667-42.666667s-17.066667-42.666667-42.666667-42.666667h-153.6l226.133333-226.133333c17.066667-17.066667 17.066667-42.666667 0-59.733333-8.533333-8.533333-17.066667-12.8-29.866666-12.8s-21.333333 4.266667-29.866667 12.8L682.666667 281.6V128c0-25.6-17.066667-42.666667-42.666667-42.666667s-42.666667 17.066667-42.666667 42.666667v256c0 4.266667 0 12.8 4.266667 17.066667zM115.2 968.533333L341.333333 742.4V896c0 25.6 17.066667 42.666667 42.666667 42.666667s42.666667-17.066667 42.666667-42.666667v-256c0-4.266667 0-12.8-4.266667-17.066667-4.266667-8.533333-12.8-17.066667-21.333333-21.333333-4.266667-4.266667-12.8-4.266667-17.066667-4.266667H128c-25.6 0-42.666667 17.066667-42.666667 42.666667s17.066667 42.666667 42.666667 42.666667h153.6l-226.133333 226.133333c-17.066667 17.066667-17.066667 42.666667 0 59.733333s42.666667 17.066667 59.733333 0z" p-id="10189"></path></svg></div>
|
||||
<div class="maxkb-openviewport">
|
||||
<svg style="vertical-align: middle;overflow: hidden;" t="1710150885892" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="rgb(100, 106, 115)" width="16" height="16" ><path d="M85.333333 384c25.6 0 42.666667-17.066667 42.666667-42.666667V128h213.333333c25.6 0 42.666667-17.066667 42.666667-42.666667s-17.066667-42.666667-42.666667-42.666666H85.333333c-25.6 0-42.666667 17.066667-42.666666 42.666666v256c0 25.6 17.066667 42.666667 42.666666 42.666667zM938.666667 640c-25.6 0-42.666667 17.066667-42.666667 42.666667v213.333333h-213.333333c-25.6 0-42.666667 17.066667-42.666667 42.666667s17.066667 42.666667 42.666667 42.666666h256c25.6 0 42.666667-17.066667 42.666666-42.666666v-256c0-25.6-17.066667-42.666667-42.666666-42.666667zM977.066667 68.266667c-4.266667-8.533333-12.8-17.066667-21.333334-21.333334-4.266667-4.266667-12.8-4.266667-17.066666-4.266666h-256c-25.6 0-42.666667 17.066667-42.666667 42.666666s17.066667 42.666667 42.666667 42.666667h153.6l-226.133334 226.133333c-17.066667 17.066667-17.066667 42.666667 0 59.733334 8.533333 8.533333 17.066667 12.8 29.866667 12.8s21.333333-4.266667 29.866667-12.8L896 187.733333V341.333333c0 25.6 17.066667 42.666667 42.666667 42.666667s42.666667-17.066667 42.666666-42.666667V85.333333c0-4.266667 0-12.8-4.266666-17.066666zM354.133333 610.133333L128 836.266667V682.666667c0-25.6-17.066667-42.666667-42.666667-42.666667s-42.666667 17.066667-42.666666 42.666667v256c0 4.266667 0 12.8 4.266666 17.066666 4.266667 8.533333 12.8 17.066667 21.333334 21.333334 4.266667 4.266667 12.8 4.266667 17.066666 4.266666h256c25.6 0 42.666667-17.066667 42.666667-42.666666s-17.066667-42.666667-42.666667-42.666667H187.733333l226.133334-226.133333c17.066667-17.066667 17.066667-42.666667 0-59.733334s-42.666667-17.066667-59.733334 0z" p-id="8645"></path></svg>
|
||||
</div>
|
||||
<div class="chat_close"><svg style="vertical-align: middle;overflow: hidden;" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M9.95317 8.73169L15.5511 3.13376C15.7138 2.97104 15.9776 2.97104 16.1403 3.13376L16.7296 3.72301C16.8923 3.88573 16.8923 4.14955 16.7296 4.31227L11.1317 9.9102L16.7296 15.5081C16.8923 15.6708 16.8923 15.9347 16.7296 16.0974L16.1403 16.6866C15.9776 16.8494 15.7138 16.8494 15.5511 16.6866L9.95317 11.0887L4.35524 16.6866C4.19252 16.8494 3.9287 16.8494 3.76598 16.6866L3.17673 16.0974C3.01401 15.9347 3.01401 15.6708 3.17673 15.5081L8.77465 9.9102L3.17673 4.31227C3.01401 4.14955 3.01401 3.88573 3.17673 3.72301L3.76598 3.13376C3.9287 2.97104 4.19252 2.97104 4.35524 3.13376L9.95317 8.73169Z" fill="#646A73"/>
|
||||
</svg>
|
||||
</div>`
|
||||
}
|
||||
/**
|
||||
* 初始化引导
|
||||
* @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
|
||||
@ -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<boolean>
|
||||
) => Promise<Result<any>> = (application_id, chart_id, page, loading) => {
|
||||
loading?: Ref<boolean>,
|
||||
order_asc?: boolean
|
||||
) => Promise<Result<any>> = (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<boolean>
|
||||
) => Promise<Result<any>> = (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
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, watch, onMounted } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { copyClick } from '@/utils/clipboard'
|
||||
import EditContentDialog from '@/views/log/component/EditContentDialog.vue'
|
||||
import EditMarkDialog from '@/views/log/component/EditMarkDialog.vue'
|
||||
@ -65,7 +65,6 @@ const EditContentDialogRef = ref()
|
||||
const EditMarkDialogRef = ref()
|
||||
|
||||
const buttonData = ref(props.data)
|
||||
const loading = ref(false)
|
||||
|
||||
function editContent(data: any) {
|
||||
EditContentDialogRef.value.open(data)
|
||||
|
||||
@ -56,7 +56,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, watch, onMounted } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { copyClick } from '@/utils/clipboard'
|
||||
import applicationApi from '@/api/application'
|
||||
const props = defineProps({
|
||||
@ -68,7 +68,7 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
chartId: {
|
||||
chatId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
@ -86,8 +86,8 @@ function regeneration() {
|
||||
|
||||
function voteHandle(val: string) {
|
||||
applicationApi
|
||||
.putChatVote(props.applicationId, props.chartId, props.data.record_id, val, loading)
|
||||
.then((res) => {
|
||||
.putChatVote(props.applicationId, props.chatId, props.data.record_id, val, loading)
|
||||
.then(() => {
|
||||
buttonData.value['vote_status'] = val
|
||||
emit('update:data', buttonData.value)
|
||||
})
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div ref="aiChatRef" class="ai-chat" :class="log ? 'chart-log' : ''">
|
||||
<el-scrollbar ref="scrollDiv" @scroll="handleScrollTop">
|
||||
<div ref="dialogScrollbar" class="ai-chat__content p-24">
|
||||
<div class="item-content mb-16" v-if="!props.available || props.data?.prologue">
|
||||
<div class="item-content mb-16" v-if="!props.available || (props.data?.prologue && !log)">
|
||||
<div class="avatar">
|
||||
<AppAvatar class="avatar-gradient">
|
||||
<img src="@/assets/icon_robot.svg" style="width: 75%" alt="" />
|
||||
@ -73,9 +73,7 @@
|
||||
|
||||
<el-card v-else shadow="always" class="dialog-card">
|
||||
<MdRenderer :source="item.answer_text"></MdRenderer>
|
||||
<div
|
||||
v-if="(id && item.write_ed) || (props.data?.show_source && item.write_ed) || log"
|
||||
>
|
||||
<div v-if="showSource(item)">
|
||||
<el-divider> <el-text type="info">知识来源</el-text> </el-divider>
|
||||
<div class="mb-8">
|
||||
<el-space wrap>
|
||||
@ -132,7 +130,7 @@
|
||||
<OperationButton
|
||||
:data="item"
|
||||
:applicationId="appId"
|
||||
:chartId="chartOpenId"
|
||||
:chatId="chartOpenId"
|
||||
@regeneration="regenerationChart(item)"
|
||||
/>
|
||||
</div>
|
||||
@ -187,7 +185,6 @@ import { randomId } from '@/utils/utils'
|
||||
import useStore from '@/stores'
|
||||
import MdRenderer from '@/components/markdown-renderer/MdRenderer.vue'
|
||||
import { MdPreview } from 'md-editor-v3'
|
||||
import { MsgError } from '@/utils/message'
|
||||
import { debounce } from 'lodash'
|
||||
defineOptions({ name: 'AiChat' })
|
||||
const route = useRoute()
|
||||
@ -209,8 +206,15 @@ const props = defineProps({
|
||||
available: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
chatId: {
|
||||
type: String,
|
||||
default: ''
|
||||
} // 历史记录Id
|
||||
})
|
||||
|
||||
const emit = defineEmits(['refresh', 'scroll'])
|
||||
|
||||
const { application } = useStore()
|
||||
|
||||
const ParagraphSourceDialogRef = ref()
|
||||
@ -249,6 +253,18 @@ const prologueList = computed(() => {
|
||||
return arr
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.chatId,
|
||||
(val) => {
|
||||
if (val && val !== 'new') {
|
||||
chartOpenId.value = val
|
||||
} else {
|
||||
chartOpenId.value = ''
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
@ -260,15 +276,25 @@ watch(
|
||||
watch(
|
||||
() => props.record,
|
||||
(value) => {
|
||||
if (props.log) {
|
||||
chatList.value = value
|
||||
}
|
||||
chatList.value = value
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
function showSource(row: any) {
|
||||
if (props.log) {
|
||||
return true
|
||||
} else if (row.write_ed) {
|
||||
if (id || props.data?.show_source) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function openParagraph(row: any, id?: string) {
|
||||
ParagraphSourceDialogRef.value.open(row, id)
|
||||
}
|
||||
@ -446,7 +472,7 @@ function chatMessage(chat?: any, problem?: string, re_chat?: boolean) {
|
||||
})
|
||||
}
|
||||
if (!chartOpenId.value) {
|
||||
getChartOpenId(chat).catch((e) => {
|
||||
getChartOpenId(chat).catch(() => {
|
||||
errorWrite(chat)
|
||||
})
|
||||
} else {
|
||||
@ -464,7 +490,7 @@ function chatMessage(chat?: any, problem?: string, re_chat?: boolean) {
|
||||
.then(() => {
|
||||
chatMessage(chat)
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(() => {
|
||||
errorWrite(chat)
|
||||
})
|
||||
} else if (response.status === 460) {
|
||||
@ -487,6 +513,9 @@ function chatMessage(chat?: any, problem?: string, re_chat?: boolean) {
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
if (props.chatId === 'new') {
|
||||
emit('refresh', chartOpenId.value)
|
||||
}
|
||||
return (id || props.data?.show_source) && getSourceDetail(chat)
|
||||
})
|
||||
.finally(() => {
|
||||
@ -535,6 +564,7 @@ const handleScrollTop = ($event: any) => {
|
||||
} else {
|
||||
scorll.value = false
|
||||
}
|
||||
emit('scroll', { ...$event, dialogScrollbar: dialogScrollbar.value, scrollDiv: scrollDiv.value })
|
||||
}
|
||||
|
||||
const handleScroll = () => {
|
||||
@ -549,6 +579,11 @@ const handleScroll = () => {
|
||||
}
|
||||
}
|
||||
|
||||
function setScrollBottom() {
|
||||
// 将滚动条滚动到最下面
|
||||
scrollDiv.value.setScrollTop(getMaxHeight())
|
||||
}
|
||||
|
||||
watch(
|
||||
chatList,
|
||||
() => {
|
||||
@ -556,6 +591,10 @@ watch(
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
|
||||
defineExpose({
|
||||
setScrollBottom
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ai-chat {
|
||||
|
||||
@ -4,18 +4,20 @@
|
||||
<template v-for="(item, index) in data" :key="index">
|
||||
<li
|
||||
@click.prevent="clickHandle(item, index)"
|
||||
:class="current === index ? 'active' : ''"
|
||||
:class="current === item[props.valueKey] ? 'active' : ''"
|
||||
class="cursor"
|
||||
>
|
||||
<slot :row="item" :index="index"> </slot>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
<el-empty description="暂无数据" v-else />
|
||||
<slot name="empty" v-else>
|
||||
<el-empty description="暂无数据" />
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, useSlots } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
defineOptions({ name: 'CommonList' })
|
||||
|
||||
@ -23,29 +25,29 @@ const props = withDefaults(
|
||||
defineProps<{
|
||||
data: Array<any>
|
||||
defaultActive?: string
|
||||
valueKey?: string // 唯一标识的键名
|
||||
}>(),
|
||||
{
|
||||
data: () => [],
|
||||
defaultActive: ''
|
||||
defaultActive: '',
|
||||
valueKey: 'id'
|
||||
}
|
||||
)
|
||||
|
||||
const current = ref<Number | String>(0)
|
||||
|
||||
watch(
|
||||
() => props.defaultActive,
|
||||
(val) => {
|
||||
if (val) {
|
||||
current.value = props.data.findIndex((v) => v.id === val)
|
||||
}
|
||||
current.value = val
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
const current = ref(0)
|
||||
|
||||
function clickHandle(row: any, index: number) {
|
||||
current.value = index
|
||||
current.value = row[props.valueKey]
|
||||
emit('click', row)
|
||||
}
|
||||
</script>
|
||||
@ -54,10 +56,12 @@ function clickHandle(row: any, index: number) {
|
||||
.common-list {
|
||||
li {
|
||||
padding: 10px 16px;
|
||||
font-weight: 400;
|
||||
&.active {
|
||||
background: var(--el-color-primary-light-9);
|
||||
border-radius: 4px;
|
||||
color: var(--el-color-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -823,5 +823,55 @@ export const iconMap: any = {
|
||||
)
|
||||
])
|
||||
}
|
||||
},
|
||||
'app-chat-record': {
|
||||
iconReader: () => {
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
style: { height: '100%', width: '100%' },
|
||||
viewBox: '0 0 16 16',
|
||||
version: '1.1',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
h('path', {
|
||||
d: 'M11.3333 7.33334C11.3333 6.96515 11.6318 6.66667 12 6.66667H14.6667C15.0349 6.66667 15.3333 6.96515 15.3333 7.33334V12.6667C15.3333 13.0349 15.0349 13.3333 14.6667 13.3333H13.2761L12.4714 14.1381C12.2111 14.3984 11.7889 14.3984 11.5286 14.1381L10.7239 13.3333H7.33334C6.96515 13.3333 6.66667 13.0349 6.66667 12.6667V10C6.66667 9.63182 6.96515 9.33334 7.33334 9.33334H11.3333V7.33334ZM12.6667 8.00001V10C12.6667 10.3682 12.3682 10.6667 12 10.6667H8.00001V12H11C11.1768 12 11.3464 12.0702 11.4714 12.1953L12 12.7239L12.5286 12.1953C12.6536 12.0702 12.8232 12 13 12H14V8.00001H12.6667Z',
|
||||
fill: 'currentColor'
|
||||
}),
|
||||
h('path', {
|
||||
d: 'M1.33334 1.33333C0.965149 1.33333 0.666672 1.63181 0.666672 1.99999V10C0.666672 10.3682 0.965149 10.6667 1.33334 10.6667H2.72386L3.86193 11.8047C4.12228 12.0651 4.54439 12.0651 4.80474 11.8047L5.94281 10.6667H12C12.3682 10.6667 12.6667 10.3682 12.6667 10V1.99999C12.6667 1.63181 12.3682 1.33333 12 1.33333H1.33334ZM4.66667 5.99999C4.66667 6.36818 4.36819 6.66666 4.00001 6.66666C3.63182 6.66666 3.33334 6.36818 3.33334 5.99999C3.33334 5.6318 3.63182 5.33333 4.00001 5.33333C4.36819 5.33333 4.66667 5.6318 4.66667 5.99999ZM7.33334 5.99999C7.33334 6.36818 7.03486 6.66666 6.66667 6.66666C6.29848 6.66666 6 6.36818 6 5.99999C6 5.6318 6.29848 5.33333 6.66667 5.33333C7.03486 5.33333 7.33334 5.6318 7.33334 5.99999ZM10 5.99999C10 6.36818 9.70153 6.66666 9.33334 6.66666C8.96515 6.66666 8.66667 6.36818 8.66667 5.99999C8.66667 5.6318 8.96515 5.33333 9.33334 5.33333C9.70153 5.33333 10 5.6318 10 5.99999Z',
|
||||
fill: 'currentColor'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
},
|
||||
'app-history-outlined': {
|
||||
iconReader: () => {
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
style: { height: '100%', width: '100%' },
|
||||
viewBox: '0 0 20 20',
|
||||
version: '1.1',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
h('path', {
|
||||
d: 'M18.6667 10.0001C18.6667 14.6025 14.9358 18.3334 10.3334 18.3334C7.68359 18.3334 5.32266 17.0967 3.79633 15.1689L5.12054 14.1563C6.3421 15.6864 8.22325 16.6667 10.3334 16.6667C14.0153 16.6667 17 13.682 17 10.0001C17 6.31818 14.0153 3.33341 10.3334 3.33341C7.03005 3.33341 4.28786 5.73596 3.75889 8.88897H4.3469C4.70187 8.88897 4.9136 9.28459 4.7167 9.57995L3.32493 11.6676C3.14901 11.9315 2.76125 11.9315 2.58533 11.6676L1.19356 9.57995C0.996651 9.28459 1.20838 8.88897 1.56336 8.88897H2.07347C2.61669 4.8119 6.10774 1.66675 10.3334 1.66675C14.9358 1.66675 18.6667 5.39771 18.6667 10.0001Z',
|
||||
fill: 'currentColor'
|
||||
}),
|
||||
h('path', {
|
||||
d: 'M10.8334 9.7223V7.11119C10.8334 6.86573 10.6344 6.66675 10.3889 6.66675H9.61115C9.36569 6.66675 9.16671 6.86573 9.16671 7.11119V10.9445C9.16671 11.19 9.36569 11.389 9.61115 11.389H13.1667C13.4122 11.389 13.6112 11.19 13.6112 10.9445V10.1667C13.6112 9.92129 13.4122 9.7223 13.1667 9.7223H10.8334Z',
|
||||
fill: 'currentColor'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
151
ui/src/directives/infiniteScrollUp.ts
Normal file
151
ui/src/directives/infiniteScrollUp.ts
Normal file
@ -0,0 +1,151 @@
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import { throttle } from 'lodash-unified'
|
||||
import { getScrollContainer } from 'element-plus/es/utils/index'
|
||||
import type { App } from 'vue'
|
||||
export const SCOPE = 'InfiniteScrollUP'
|
||||
export const CHECK_INTERVAL = 50
|
||||
export const DEFAULT_DELAY = 200
|
||||
export const DEFAULT_DISTANCE = 0
|
||||
|
||||
const attributes = {
|
||||
delay: {
|
||||
type: Number,
|
||||
default: DEFAULT_DELAY
|
||||
},
|
||||
distance: {
|
||||
type: Number,
|
||||
default: DEFAULT_DISTANCE
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
immediate: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
}
|
||||
|
||||
type Attrs = typeof attributes
|
||||
type ScrollOptions = { [K in keyof Attrs]: Attrs[K]['default'] }
|
||||
type InfiniteScrollCallback = () => void
|
||||
type InfiniteScrollEl = HTMLElement & {
|
||||
[SCOPE]: {
|
||||
container: HTMLElement | Window
|
||||
containerEl: HTMLElement
|
||||
instance: any
|
||||
delay: number // export for test
|
||||
lastScrollTop: number
|
||||
cb: InfiniteScrollCallback
|
||||
onScroll: () => void
|
||||
observer?: MutationObserver
|
||||
}
|
||||
}
|
||||
|
||||
const getScrollOptions = (el: HTMLElement, instance: any): ScrollOptions => {
|
||||
return Object.entries(attributes).reduce((acm: any, [name, option]) => {
|
||||
const { type, default: defaultValue } = option
|
||||
const attrVal: any = el.getAttribute(`infinite-scroll-up-${name}`)
|
||||
let value = instance[attrVal] ?? attrVal ?? defaultValue
|
||||
value = value === 'false' ? false : value
|
||||
value = type(value)
|
||||
acm[name] = Number.isNaN(value) ? defaultValue : value
|
||||
return acm
|
||||
}, {} as ScrollOptions)
|
||||
}
|
||||
|
||||
const destroyObserver = (el: InfiniteScrollEl) => {
|
||||
const { observer } = el[SCOPE]
|
||||
|
||||
if (observer) {
|
||||
observer.disconnect()
|
||||
delete el[SCOPE].observer
|
||||
}
|
||||
}
|
||||
|
||||
const handleScroll = (el: InfiniteScrollEl, cb: InfiniteScrollCallback) => {
|
||||
const { container, containerEl, instance, observer, lastScrollTop } = el[SCOPE]
|
||||
const { disabled } = getScrollOptions(el, instance)
|
||||
const { scrollTop } = containerEl
|
||||
|
||||
el[SCOPE].lastScrollTop = scrollTop
|
||||
|
||||
// trigger only if full check has done and not disabled and scroll down
|
||||
|
||||
if (observer || disabled || scrollTop > 0) return
|
||||
|
||||
if (scrollTop == 0) {
|
||||
cb.call(instance)
|
||||
}
|
||||
}
|
||||
|
||||
function checkFull(el: InfiniteScrollEl, cb: InfiniteScrollCallback) {
|
||||
const { containerEl, instance } = el[SCOPE]
|
||||
const { disabled } = getScrollOptions(el, instance)
|
||||
|
||||
if (disabled || containerEl.clientHeight == 0) return
|
||||
|
||||
if (containerEl.scrollTop <= 0) {
|
||||
cb.call(instance)
|
||||
} else {
|
||||
destroyObserver(el)
|
||||
}
|
||||
}
|
||||
|
||||
const InfiniteScroll = {
|
||||
async mounted(el: any, binding: any) {
|
||||
const { instance, value: cb } = binding
|
||||
|
||||
// ensure parentNode mounted
|
||||
await nextTick()
|
||||
|
||||
const { delay, immediate } = getScrollOptions(el, instance)
|
||||
const container = getScrollContainer(el, true)
|
||||
const containerEl = container === window ? document.documentElement : (container as HTMLElement)
|
||||
const onScroll = throttle(handleScroll.bind(null, el, cb), delay)
|
||||
|
||||
if (!container) return
|
||||
|
||||
el[SCOPE] = {
|
||||
instance,
|
||||
container,
|
||||
containerEl,
|
||||
delay,
|
||||
cb,
|
||||
onScroll,
|
||||
lastScrollTop: containerEl.scrollTop
|
||||
}
|
||||
|
||||
if (immediate) {
|
||||
const observer = new MutationObserver(throttle(checkFull.bind(null, el, cb), CHECK_INTERVAL))
|
||||
el[SCOPE].observer = observer
|
||||
observer.observe(el, { childList: true, subtree: true })
|
||||
checkFull(el, cb)
|
||||
}
|
||||
|
||||
container.addEventListener('scroll', onScroll)
|
||||
},
|
||||
unmounted(el: any) {
|
||||
if (!el[SCOPE]) return
|
||||
const { container, onScroll } = el[SCOPE]
|
||||
|
||||
container?.removeEventListener('scroll', onScroll)
|
||||
destroyObserver(el)
|
||||
},
|
||||
async updated(el: any) {
|
||||
if (!el[SCOPE]) {
|
||||
await nextTick()
|
||||
} else {
|
||||
const { containerEl, cb, observer } = el[SCOPE]
|
||||
if (containerEl.clientHeight && observer) {
|
||||
checkFull(el, cb)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export default {
|
||||
install: (app: App) => {
|
||||
app.directive('infinite-scroll-up', InfiniteScroll)
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,7 @@ const settingRouter = {
|
||||
path: '/setting',
|
||||
name: 'setting',
|
||||
meta: { icon: 'Setting', title: '系统设置', permission: 'SETTING:READ' },
|
||||
redirect: (to: any) => {
|
||||
redirect: () => {
|
||||
if (hasPermission(new Role('ADMIN'), 'AND')) {
|
||||
return '/user'
|
||||
}
|
||||
|
||||
@ -10,16 +10,7 @@ export const routes: Array<RouteRecordRaw> = [
|
||||
name: 'home',
|
||||
component: () => import('@/layout/app-layout/index.vue'),
|
||||
redirect: '/application',
|
||||
children: [
|
||||
// TODO 待处理
|
||||
// {
|
||||
// path: '/first',
|
||||
// name: 'first',
|
||||
// meta: { icon: 'House', title: '首页' },
|
||||
// component: () => import('@/views/first/index.vue')
|
||||
// },
|
||||
...rolesRoutes
|
||||
]
|
||||
children: [...rolesRoutes]
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
@ -18,6 +18,36 @@ const useLogStore = defineStore({
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
async asyncChatRecordLog(
|
||||
id: string,
|
||||
chatId: string,
|
||||
page: pageRequest,
|
||||
loading?: Ref<boolean>,
|
||||
order_asc?: boolean
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
logApi
|
||||
.getChatRecordLog(id, chatId, page, loading, order_asc)
|
||||
.then((data) => {
|
||||
resolve(data)
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
async asyncGetChatLogClient(id: string, page: pageRequest, loading?: Ref<boolean>) {
|
||||
return new Promise((resolve, reject) => {
|
||||
logApi
|
||||
.getChatLogClient(id, page, loading)
|
||||
.then((data) => {
|
||||
resolve(data)
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -184,6 +184,9 @@ h4 {
|
||||
.p-24 {
|
||||
padding: calc(var(--app-base-px) * 3);
|
||||
}
|
||||
.p-16-24 {
|
||||
padding: calc(var(--app-base-px) * 2) calc(var(--app-base-px) * 3);
|
||||
}
|
||||
|
||||
.pt-0 {
|
||||
padding-top: 0;
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { h } from 'vue'
|
||||
import { ElMessageBox, ElMessage, ElIcon } from 'element-plus'
|
||||
import { WarningFilled } from '@element-plus/icons-vue'
|
||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||
|
||||
export const MsgSuccess = (message: string) => {
|
||||
ElMessage.success({
|
||||
@ -52,21 +50,3 @@ export const MsgConfirm = (title: string, description: string, options?: any) =>
|
||||
}
|
||||
return ElMessageBox.confirm(description, title, defaultOptions)
|
||||
}
|
||||
|
||||
// export const MsgConfirm = ({ title, description }: any, options?: any) => {
|
||||
// const message: any = h('div', { class: 'app-confirm' }, [
|
||||
// h('h4', { class: 'app-confirm-title flex align-center' }, [
|
||||
// h(ElIcon, { class: 'icon' }, [h(WarningFilled)]),
|
||||
// h('span', { class: 'ml-16' }, title)
|
||||
// ]),
|
||||
// h('div', { class: 'app-confirm-description mt-8' }, description)
|
||||
// ])
|
||||
|
||||
// const defaultOptions: Object = {
|
||||
// showCancelButton: true,
|
||||
// confirmButtonText: '确定',
|
||||
// cancelButtonText: '取消',
|
||||
// ...options
|
||||
// }
|
||||
// return ElMessageBox({ message, ...defaultOptions })
|
||||
// }
|
||||
|
||||
@ -92,7 +92,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive, computed } from 'vue'
|
||||
import { ref, onMounted, reactive } from 'vue'
|
||||
import applicationApi from '@/api/application'
|
||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
import { isAppIcon } from '@/utils/application'
|
||||
@ -122,7 +122,7 @@ function searchHandle() {
|
||||
}
|
||||
function getAccessToken(id: string) {
|
||||
application.asyncGetAccessToken(id, loading).then((res: any) => {
|
||||
window.open(application.location + res?.data?.access_token)
|
||||
window.open(application.location + res?.data?.access_token + '?mode=pc')
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
106
ui/src/views/chat/base/index.vue
Normal file
106
ui/src/views/chat/base/index.vue
Normal file
@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div class="chat" v-loading="loading">
|
||||
<div class="chat__header">
|
||||
<div class="chat-width">
|
||||
<h2 class="ml-24">{{ applicationDetail?.name }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat__main chat-width">
|
||||
<AiChat
|
||||
v-model:data="applicationDetail"
|
||||
:available="applicationAvailable"
|
||||
:appId="applicationDetail?.id"
|
||||
></AiChat>
|
||||
</div>
|
||||
<div class="chat__footer"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import applicationApi from '@/api/application'
|
||||
import useStore from '@/stores'
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { accessToken }
|
||||
} = route as any
|
||||
|
||||
const { application, user } = useStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const applicationDetail = ref<any>({})
|
||||
const applicationAvailable = ref<boolean>(true)
|
||||
|
||||
function getAccessToken(token: string) {
|
||||
application
|
||||
.asyncAppAuthentication(token, loading)
|
||||
.then(() => {
|
||||
getProfile()
|
||||
})
|
||||
.catch(() => {
|
||||
applicationAvailable.value = false
|
||||
})
|
||||
}
|
||||
function getProfile() {
|
||||
applicationApi
|
||||
.getProfile(loading)
|
||||
.then((res) => {
|
||||
applicationDetail.value = res.data
|
||||
})
|
||||
.catch(() => {
|
||||
applicationAvailable.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
user.changeUserType(2)
|
||||
getAccessToken(accessToken)
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.chat {
|
||||
background-color: var(--app-layout-bg-color);
|
||||
overflow: hidden;
|
||||
&__header {
|
||||
background: var(--app-header-bg-color);
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
height: var(--app-header-height);
|
||||
line-height: var(--app-header-height);
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
}
|
||||
&__main {
|
||||
padding-top: calc(var(--app-header-height) + 24px);
|
||||
height: calc(100vh - var(--app-header-height) - 24px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
background: #f3f7f9;
|
||||
height: 80px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px !important;
|
||||
&:before {
|
||||
background: linear-gradient(0deg, #f3f7f9 0%, rgba(243, 247, 249, 0) 100%);
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: -16px;
|
||||
left: 0;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
.chat-width {
|
||||
max-width: var(--app-chat-width, 860px);
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
285
ui/src/views/chat/embed/index.vue
Normal file
285
ui/src/views/chat/embed/index.vue
Normal file
@ -0,0 +1,285 @@
|
||||
<template>
|
||||
<div class="chat-embed" v-loading="loading" @click="closePopover($event)">
|
||||
<div class="chat-embed__header">
|
||||
<div class="chat-width">
|
||||
<h4 class="ml-24">{{ applicationDetail?.name }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-embed__main chat-width">
|
||||
<AiChat
|
||||
ref="AiChatRef"
|
||||
v-model:data="applicationDetail"
|
||||
:available="applicationAvailable"
|
||||
:appId="applicationDetail?.id"
|
||||
:record="currentRecordList"
|
||||
:chatId="currentChatId"
|
||||
@refresh="refresh"
|
||||
@scroll="handleScroll"
|
||||
></AiChat>
|
||||
</div>
|
||||
|
||||
<el-button type="primary" link class="new-chat-button" @click="newChat">
|
||||
<el-icon><Plus /></el-icon><span class="ml-4">新建对话</span>
|
||||
</el-button>
|
||||
<!-- 历史记录弹出层 -->
|
||||
<div @click.prevent.stop="show = !show" class="chat-popover-button cursor color-secondary">
|
||||
<AppIcon iconName="app-history-outlined"></AppIcon>
|
||||
</div>
|
||||
|
||||
<el-collapse-transition>
|
||||
<div v-show="show" class="chat-popover w-full" id="chat-popover">
|
||||
<div class="border-b p-16-24">
|
||||
<span>历史记录</span>
|
||||
</div>
|
||||
|
||||
<el-scrollbar>
|
||||
<div class="p-8">
|
||||
<common-list
|
||||
:data="chatLogeData"
|
||||
v-loading="left_loading"
|
||||
:defaultActive="currentChatId"
|
||||
@click="clickListHandle"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<auto-tooltip :content="row.abstract">
|
||||
{{ row.abstract }}
|
||||
</auto-tooltip>
|
||||
</template>
|
||||
<template #empty>
|
||||
<div class="text-center">
|
||||
<el-text type="info">暂无历史记录</el-text>
|
||||
</div>
|
||||
</template>
|
||||
</common-list>
|
||||
</div>
|
||||
<div v-if="chatLogeData.length" class="gradient-divider lighter mt-8">
|
||||
<span>仅显示最近 20 条对话</span>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
<div class="chat-popover-mask" v-show="show"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive, nextTick } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import applicationApi from '@/api/application'
|
||||
import useStore from '@/stores'
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { accessToken }
|
||||
} = route as any
|
||||
|
||||
const { application, user, log } = useStore()
|
||||
|
||||
const AiChatRef = ref()
|
||||
const loading = ref(false)
|
||||
const left_loading = ref(false)
|
||||
const applicationDetail = ref<any>({})
|
||||
const applicationAvailable = ref<boolean>(true)
|
||||
const chatLogeData = ref<any[]>([])
|
||||
const show = ref(false)
|
||||
|
||||
const paginationConfig = reactive({
|
||||
current_page: 1,
|
||||
page_size: 20,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const currentRecordList = ref<any>([])
|
||||
const currentChatId = ref('new') // 当前历史记录Id 默认为'new'
|
||||
|
||||
function handleScroll(event: any) {
|
||||
if (
|
||||
currentChatId.value !== 'new' &&
|
||||
event.scrollTop === 0 &&
|
||||
paginationConfig.total > currentRecordList.value.length
|
||||
) {
|
||||
const history_height = event.dialogScrollbar.offsetHeight
|
||||
paginationConfig.current_page += 1
|
||||
getChatRecord().then(() => {
|
||||
event.scrollDiv.setScrollTop(event.dialogScrollbar.offsetHeight - history_height)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function closePopover(event: any) {
|
||||
const popover = document.getElementById('chat-popover')
|
||||
if (popover) {
|
||||
if (!popover.contains(event.target)) {
|
||||
show.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function newChat() {
|
||||
paginationConfig.current_page = 1
|
||||
currentRecordList.value = []
|
||||
currentChatId.value = 'new'
|
||||
}
|
||||
|
||||
function getAccessToken(token: string) {
|
||||
application
|
||||
.asyncAppAuthentication(token, loading)
|
||||
.then(() => {
|
||||
getProfile()
|
||||
})
|
||||
.catch(() => {
|
||||
applicationAvailable.value = false
|
||||
})
|
||||
}
|
||||
function getProfile() {
|
||||
applicationApi
|
||||
.getProfile(loading)
|
||||
.then((res) => {
|
||||
applicationDetail.value = res.data
|
||||
getChatLog(applicationDetail.value.id)
|
||||
})
|
||||
.catch(() => {
|
||||
applicationAvailable.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function getChatLog(id: string) {
|
||||
const page = {
|
||||
current_page: 1,
|
||||
page_size: 20
|
||||
}
|
||||
|
||||
log.asyncGetChatLogClient(id, page, left_loading).then((res: any) => {
|
||||
chatLogeData.value = res.data.records
|
||||
})
|
||||
}
|
||||
|
||||
function getChatRecord() {
|
||||
return log
|
||||
.asyncChatRecordLog(
|
||||
applicationDetail.value.id,
|
||||
currentChatId.value,
|
||||
paginationConfig,
|
||||
loading,
|
||||
false
|
||||
)
|
||||
.then((res: any) => {
|
||||
paginationConfig.total = res.data.total
|
||||
const list = res.data.records
|
||||
list.map((v: any) => {
|
||||
v['write_ed'] = true
|
||||
})
|
||||
currentRecordList.value = [...list, ...currentRecordList.value].sort((a, b) =>
|
||||
a.create_time.localeCompare(b.create_time)
|
||||
)
|
||||
if (paginationConfig.current_page === 1) {
|
||||
nextTick(() => {
|
||||
// 将滚动条滚动到最下面
|
||||
AiChatRef.value.setScrollBottom()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const clickListHandle = (item: any) => {
|
||||
if (item.id !== currentChatId.value) {
|
||||
paginationConfig.current_page = 1
|
||||
currentRecordList.value = []
|
||||
currentChatId.value = item.id
|
||||
if (currentChatId.value !== 'new') {
|
||||
getChatRecord()
|
||||
}
|
||||
show.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function refresh(id: string) {
|
||||
getChatLog(applicationDetail.value.id)
|
||||
currentChatId.value = id
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
user.changeUserType(2)
|
||||
getAccessToken(accessToken)
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.chat-embed {
|
||||
background-color: var(--app-layout-bg-color);
|
||||
overflow: hidden;
|
||||
&__header {
|
||||
background: var(--app-header-bg-color);
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
height: var(--app-header-height);
|
||||
line-height: var(--app-header-height);
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
}
|
||||
&__main {
|
||||
padding-top: calc(var(--app-header-height) + 24px);
|
||||
height: calc(100vh - var(--app-header-height) - 24px);
|
||||
overflow: hidden;
|
||||
}
|
||||
.new-chat-button {
|
||||
position: absolute;
|
||||
bottom: 84px;
|
||||
left: 18px;
|
||||
z-index: 11;
|
||||
}
|
||||
// 历史对话弹出层
|
||||
.chat-popover {
|
||||
position: absolute;
|
||||
top: var(--app-header-height);
|
||||
background: #ffffff;
|
||||
padding-bottom: 24px;
|
||||
z-index: 2009;
|
||||
}
|
||||
.chat-popover-button {
|
||||
z-index: 2009;
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 85px;
|
||||
font-size: 22px;
|
||||
}
|
||||
.chat-popover-mask {
|
||||
background-color: var(--el-overlay-color-lighter);
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: var(--app-header-height);
|
||||
z-index: 2008;
|
||||
}
|
||||
.gradient-divider {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
color: var(--el-color-info);
|
||||
::before {
|
||||
content: '';
|
||||
width: 17%;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, rgba(222, 224, 227, 0) 0%, #dee0e3 100%);
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
}
|
||||
::after {
|
||||
content: '';
|
||||
width: 17%;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, #dee0e3 0%, rgba(222, 224, 227, 0) 100%);
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 50%;
|
||||
}
|
||||
}
|
||||
.chat-width {
|
||||
max-width: var(--app-chat-width, 860px);
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,144 +1,23 @@
|
||||
<template>
|
||||
<div class="chat" v-loading="loading">
|
||||
<div class="chat__header">
|
||||
<div class="chat-width">
|
||||
<h2 class="ml-24">{{ applicationDetail?.name }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat__main chat-width">
|
||||
<AiChat
|
||||
v-model:data="applicationDetail"
|
||||
:available="applicationAvailable"
|
||||
:appId="applicationDetail?.id"
|
||||
></AiChat>
|
||||
</div>
|
||||
<div class="chat__footer"></div>
|
||||
</div>
|
||||
<component :is="currentTemplate" :key="route.fullPath" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, watch, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import applicationApi from '@/api/application'
|
||||
import useStore from '@/stores'
|
||||
import { onMounted, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const components: any = import.meta.glob('@/views/chat/**/index.vue', {
|
||||
eager: true
|
||||
})
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { accessToken }
|
||||
query: { mode }
|
||||
} = route as any
|
||||
|
||||
const { application, user, log } = useStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const applicationDetail = ref<any>({})
|
||||
const applicationAvailable = ref<boolean>(true)
|
||||
const chatLogeData = ref<any[]>([])
|
||||
|
||||
function getAccessToken(token: string) {
|
||||
application
|
||||
.asyncAppAuthentication(token, loading)
|
||||
.then((res) => {
|
||||
getProfile()
|
||||
})
|
||||
.catch(() => {
|
||||
applicationAvailable.value = false
|
||||
})
|
||||
}
|
||||
function getProfile() {
|
||||
applicationApi
|
||||
.getProfile(loading)
|
||||
.then((res) => {
|
||||
applicationDetail.value = res.data
|
||||
})
|
||||
.catch(() => {
|
||||
applicationAvailable.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function getChatLog(id: string) {
|
||||
const page = {
|
||||
current_page: 1,
|
||||
page_size: 20
|
||||
}
|
||||
const param = {
|
||||
history_day: 183
|
||||
}
|
||||
|
||||
log.asyncGetChatLog(id, page, param, loading).then((res: any) => {
|
||||
chatLogeData.value = res.data.records
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
user.changeUserType(2)
|
||||
getAccessToken(accessToken)
|
||||
const currentTemplate = computed(() => {
|
||||
const name = `/src/views/chat/${mode || 'pc'}/index.vue`
|
||||
return components[name].default
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.chat {
|
||||
background-color: var(--app-layout-bg-color);
|
||||
overflow: hidden;
|
||||
&__header {
|
||||
background: var(--app-header-bg-color);
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
height: var(--app-header-height);
|
||||
line-height: var(--app-header-height);
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
}
|
||||
&__main {
|
||||
padding-top: calc(var(--app-header-height) + 24px);
|
||||
height: calc(100vh - var(--app-header-height) - 24px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
background: #f3f7f9;
|
||||
height: 80px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px !important;
|
||||
&:before {
|
||||
background: linear-gradient(0deg, #f3f7f9 0%, rgba(243, 247, 249, 0) 100%);
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: -16px;
|
||||
left: 0;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
.gradient-divider {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
color: var(--el-color-info);
|
||||
::before {
|
||||
content: '';
|
||||
width: 17%;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, rgba(222, 224, 227, 0) 0%, #dee0e3 100%);
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
}
|
||||
::after {
|
||||
content: '';
|
||||
width: 17%;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, #dee0e3 0%, rgba(222, 224, 227, 0) 100%);
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 50%;
|
||||
}
|
||||
}
|
||||
.chat-width {
|
||||
max-width: var(--app-chat-width, 860px);
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
onMounted(() => {})
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
|
||||
279
ui/src/views/chat/pc/index.vue
Normal file
279
ui/src/views/chat/pc/index.vue
Normal file
@ -0,0 +1,279 @@
|
||||
<template>
|
||||
<div class="chat-pc" v-loading="loading">
|
||||
<div class="chat-pc__header">
|
||||
<h4 class="ml-24">{{ applicationDetail?.name }}</h4>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="chat-pc__left border-r">
|
||||
<div class="p-24 pb-0">
|
||||
<el-button class="add-button w-full primary" @click="newChat">
|
||||
<el-icon><Plus /></el-icon><span class="ml-4">新建对话</span>
|
||||
</el-button>
|
||||
<p class="mt-20 mb-8">历史记录</p>
|
||||
</div>
|
||||
<div class="left-height pt-0">
|
||||
<el-scrollbar>
|
||||
<div class="p-8 pt-0">
|
||||
<common-list
|
||||
:data="chatLogeData"
|
||||
class="mt-8"
|
||||
v-loading="left_loading"
|
||||
:defaultActive="currentChatId"
|
||||
@click="clickListHandle"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<auto-tooltip :content="row.abstract">
|
||||
{{ row.abstract }}
|
||||
</auto-tooltip>
|
||||
</template>
|
||||
<template #empty>
|
||||
<div class="text-center">
|
||||
<el-text type="info">暂无历史记录</el-text>
|
||||
</div>
|
||||
</template>
|
||||
</common-list>
|
||||
</div>
|
||||
<div v-if="chatLogeData.length" class="gradient-divider lighter mt-8">
|
||||
<span>仅显示最近 20 条对话</span>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-pc__right">
|
||||
<div class="right-header border-b mb-24 p-16-24 flex-between">
|
||||
<h4>{{ currentChatName }}</h4>
|
||||
<span v-if="currentRecordList.length" class="flex align-center">
|
||||
<AppIcon iconName="app-chat-record" class="info mr-8" style="font-size: 16px"></AppIcon>
|
||||
<span class="lighter"> {{ paginationConfig.total }} 条提问 </span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="right-height">
|
||||
<!-- 对话 -->
|
||||
<AiChat
|
||||
ref="AiChatRef"
|
||||
v-model:data="applicationDetail"
|
||||
:available="applicationAvailable"
|
||||
:appId="applicationDetail?.id"
|
||||
:record="currentRecordList"
|
||||
:chatId="currentChatId"
|
||||
@refresh="refresh"
|
||||
@scroll="handleScroll"
|
||||
></AiChat>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, nextTick } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import applicationApi from '@/api/application'
|
||||
import useStore from '@/stores'
|
||||
const route = useRoute()
|
||||
|
||||
const {
|
||||
params: { accessToken }
|
||||
} = route as any
|
||||
|
||||
const { application, user, log } = useStore()
|
||||
|
||||
const newObj = {
|
||||
id: 'new',
|
||||
abstract: '新建对话'
|
||||
}
|
||||
|
||||
const AiChatRef = ref()
|
||||
const loading = ref(false)
|
||||
const left_loading = ref(false)
|
||||
const applicationDetail = ref<any>({})
|
||||
const applicationAvailable = ref<boolean>(true)
|
||||
const chatLogeData = ref<any[]>([])
|
||||
|
||||
const paginationConfig = reactive({
|
||||
current_page: 1,
|
||||
page_size: 20,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const currentRecordList = ref<any>([])
|
||||
const currentChatId = ref('new') // 当前历史记录Id 默认为'new'
|
||||
const currentChatName = ref('新建对话')
|
||||
|
||||
function handleScroll(event: any) {
|
||||
if (
|
||||
currentChatId.value !== 'new' &&
|
||||
event.scrollTop === 0 &&
|
||||
paginationConfig.total > currentRecordList.value.length
|
||||
) {
|
||||
const history_height = event.dialogScrollbar.offsetHeight
|
||||
paginationConfig.current_page += 1
|
||||
getChatRecord().then(() => {
|
||||
event.scrollDiv.setScrollTop(event.dialogScrollbar.offsetHeight - history_height)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getAccessToken(token: string) {
|
||||
application
|
||||
.asyncAppAuthentication(token, loading)
|
||||
.then(() => {
|
||||
getProfile()
|
||||
})
|
||||
.catch(() => {
|
||||
applicationAvailable.value = false
|
||||
})
|
||||
}
|
||||
function getProfile() {
|
||||
applicationApi
|
||||
.getProfile(loading)
|
||||
.then((res) => {
|
||||
applicationDetail.value = res.data
|
||||
getChatLog(applicationDetail.value.id)
|
||||
})
|
||||
.catch(() => {
|
||||
applicationAvailable.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function newChat() {
|
||||
if (!chatLogeData.value.some((v) => v.id === 'new')) {
|
||||
paginationConfig.current_page = 1
|
||||
currentRecordList.value = []
|
||||
chatLogeData.value.unshift(newObj)
|
||||
} else {
|
||||
paginationConfig.current_page = 1
|
||||
currentRecordList.value = []
|
||||
}
|
||||
currentChatId.value = 'new'
|
||||
currentChatName.value = '新建对话'
|
||||
}
|
||||
|
||||
function getChatLog(id: string) {
|
||||
const page = {
|
||||
current_page: 1,
|
||||
page_size: 20
|
||||
}
|
||||
|
||||
log.asyncGetChatLogClient(id, page, left_loading).then((res: any) => {
|
||||
chatLogeData.value = res.data.records
|
||||
})
|
||||
}
|
||||
|
||||
function getChatRecord() {
|
||||
return log
|
||||
.asyncChatRecordLog(
|
||||
applicationDetail.value.id,
|
||||
currentChatId.value,
|
||||
paginationConfig,
|
||||
loading,
|
||||
false
|
||||
)
|
||||
.then((res: any) => {
|
||||
paginationConfig.total = res.data.total
|
||||
const list = res.data.records
|
||||
list.map((v: any) => {
|
||||
v['write_ed'] = true
|
||||
})
|
||||
currentRecordList.value = [...list, ...currentRecordList.value].sort((a, b) =>
|
||||
a.create_time.localeCompare(b.create_time)
|
||||
)
|
||||
if (paginationConfig.current_page === 1) {
|
||||
nextTick(() => {
|
||||
// 将滚动条滚动到最下面
|
||||
AiChatRef.value.setScrollBottom()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
const clickListHandle = (item: any) => {
|
||||
if (item.id !== currentChatId.value) {
|
||||
paginationConfig.current_page = 1
|
||||
currentRecordList.value = []
|
||||
currentChatId.value = item.id
|
||||
currentChatName.value = item.abstract
|
||||
if (currentChatId.value !== 'new') {
|
||||
getChatRecord()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function refresh(id: string) {
|
||||
getChatLog(applicationDetail.value.id)
|
||||
currentChatId.value = id
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
user.changeUserType(2)
|
||||
getAccessToken(accessToken)
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.chat-pc {
|
||||
background-color: var(--app-layout-bg-color);
|
||||
overflow: hidden;
|
||||
&__header {
|
||||
background: var(--app-header-bg-color);
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
height: var(--app-header-height);
|
||||
line-height: var(--app-header-height);
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
}
|
||||
&__left {
|
||||
padding-top: calc(var(--app-header-height) - 8px);
|
||||
background: #ffffff;
|
||||
width: 280px;
|
||||
.add-button {
|
||||
border: 1px solid var(--el-color-primary);
|
||||
}
|
||||
.left-height {
|
||||
height: calc(100vh - var(--app-header-height) - 135px);
|
||||
}
|
||||
}
|
||||
&__right {
|
||||
width: calc(100% - 280px);
|
||||
padding-top: calc(var(--app-header-height));
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
.right-header {
|
||||
background: #ffffff;
|
||||
}
|
||||
.right-height {
|
||||
height: calc(100vh - var(--app-header-height) * 2 - 24px);
|
||||
overflow: scroll;
|
||||
}
|
||||
}
|
||||
|
||||
.gradient-divider {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
color: var(--el-color-info);
|
||||
::before {
|
||||
content: '';
|
||||
width: 17%;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, rgba(222, 224, 227, 0) 0%, #dee0e3 100%);
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
}
|
||||
::after {
|
||||
content: '';
|
||||
width: 17%;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, #dee0e3 0%, rgba(222, 224, 227, 0) 100%);
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 50%;
|
||||
}
|
||||
}
|
||||
.chat-width {
|
||||
max-width: var(--app-chat-width, 860px);
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -35,8 +35,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
|
||||
import useStore from '@/stores'
|
||||
const { dataset } = useStore()
|
||||
|
||||
|
||||
@ -83,7 +83,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import documentApi from '@/api/document'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
import { hitHandlingMethod } from '../utils'
|
||||
@ -156,14 +156,14 @@ const open = (row: any, list: Array<string>) => {
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid, fields) => {
|
||||
await formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
if (isImport.value) {
|
||||
const obj = {
|
||||
source_url_list: form.value.source_url.split('\n'),
|
||||
selector: form.value.selector
|
||||
}
|
||||
documentApi.postWebDocument(id, obj, loading).then((res: any) => {
|
||||
documentApi.postWebDocument(id, obj, loading).then(() => {
|
||||
MsgSuccess('导入成功')
|
||||
emit('refresh')
|
||||
dialogVisible.value = false
|
||||
@ -178,7 +178,7 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
selector: form.value.selector
|
||||
}
|
||||
}
|
||||
documentApi.putDocument(id, documentId.value, obj, loading).then((res) => {
|
||||
documentApi.putDocument(id, documentId.value, obj, loading).then(() => {
|
||||
MsgSuccess('设置成功')
|
||||
emit('refresh')
|
||||
dialogVisible.value = false
|
||||
@ -190,7 +190,7 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
directly_return_similarity: form.value.directly_return_similarity || 0.9,
|
||||
id_list: documentList.value
|
||||
}
|
||||
documentApi.batchEditHitHandling(id, obj, loading).then((res: any) => {
|
||||
documentApi.batchEditHitHandling(id, obj, loading).then(() => {
|
||||
MsgSuccess('设置成功')
|
||||
emit('refresh')
|
||||
dialogVisible.value = false
|
||||
|
||||
@ -243,11 +243,11 @@ const { common, dataset, document } = useStore()
|
||||
|
||||
const storeKey = 'documents'
|
||||
|
||||
onBeforeRouteUpdate((to: any, from: any) => {
|
||||
onBeforeRouteUpdate(() => {
|
||||
common.savePage(storeKey, null)
|
||||
common.saveCondition(storeKey, null)
|
||||
})
|
||||
onBeforeRouteLeave((to: any, from: any) => {
|
||||
onBeforeRouteLeave((to: any) => {
|
||||
if (to.name !== 'Paragraph') {
|
||||
common.savePage(storeKey, null)
|
||||
common.saveCondition(storeKey, null)
|
||||
@ -351,7 +351,7 @@ function refreshDocument(row: any) {
|
||||
confirmButtonClass: 'danger'
|
||||
})
|
||||
.then(() => {
|
||||
documentApi.putDocumentRefresh(row.dataset_id, row.id).then((res) => {
|
||||
documentApi.putDocumentRefresh(row.dataset_id, row.id).then(() => {
|
||||
getList()
|
||||
})
|
||||
})
|
||||
@ -365,7 +365,7 @@ function refreshDocument(row: any) {
|
||||
.catch(() => {})
|
||||
}
|
||||
} else {
|
||||
documentApi.putDocumentRefresh(row.dataset_id, row.id).then((res) => {
|
||||
documentApi.putDocumentRefresh(row.dataset_id, row.id).then(() => {
|
||||
getList()
|
||||
})
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import logApi from '@/api/log'
|
||||
import { type chatType } from '@/api/type/application'
|
||||
@ -43,7 +43,7 @@ const props = withDefaults(
|
||||
/**
|
||||
* 对话 记录id
|
||||
*/
|
||||
chartId: string
|
||||
chatId: string
|
||||
currentAbstract: string
|
||||
/**
|
||||
* 下一条
|
||||
@ -61,7 +61,7 @@ const props = withDefaults(
|
||||
{}
|
||||
)
|
||||
|
||||
const emit = defineEmits(['update:chartId', 'update:currentAbstract', 'refresh'])
|
||||
const emit = defineEmits(['update:chatId', 'update:currentAbstract', 'refresh'])
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
@ -84,8 +84,8 @@ function closeHandle() {
|
||||
}
|
||||
|
||||
function getChatRecord() {
|
||||
if (props.chartId && visible.value) {
|
||||
logApi.getChatRecordLog(id as string, props.chartId, paginationConfig, loading).then((res) => {
|
||||
if (props.chatId && visible.value) {
|
||||
logApi.getChatRecordLog(id as string, props.chatId, paginationConfig, loading).then((res) => {
|
||||
paginationConfig.total = res.data.total
|
||||
recordList.value = [...recordList.value, ...res.data.records]
|
||||
})
|
||||
@ -93,7 +93,7 @@ function getChatRecord() {
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.chartId,
|
||||
() => props.chatId,
|
||||
() => {
|
||||
recordList.value = []
|
||||
paginationConfig.total = 0
|
||||
@ -104,7 +104,7 @@ watch(
|
||||
|
||||
watch(visible, (bool) => {
|
||||
if (!bool) {
|
||||
emit('update:chartId', '')
|
||||
emit('update:chatId', '')
|
||||
emit('update:currentAbstract', '')
|
||||
emit('refresh')
|
||||
}
|
||||
|
||||
@ -92,13 +92,6 @@ import useStore from '@/stores'
|
||||
|
||||
const { application, document } = useStore()
|
||||
|
||||
const props = defineProps({
|
||||
chartId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id }
|
||||
@ -147,7 +140,6 @@ watch(dialogVisible, (bool) => {
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
function changeDataset(id: string) {
|
||||
form.value.document_id = ''
|
||||
getDocument(id)
|
||||
@ -176,7 +168,7 @@ const open = (data: any) => {
|
||||
}
|
||||
const submitForm = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid, fields) => {
|
||||
await formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
const obj = {
|
||||
title: form.value.title,
|
||||
|
||||
@ -121,7 +121,7 @@
|
||||
:next="nextChatRecord"
|
||||
:pre="preChatRecord"
|
||||
ref="ChatRecordRef"
|
||||
v-model:chartId="currentChatId"
|
||||
v-model:chatId="currentChatId"
|
||||
v-model:currentAbstract="currentAbstract"
|
||||
:application="detail"
|
||||
:pre_disable="pre_disable"
|
||||
@ -131,11 +131,11 @@
|
||||
</LayoutContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive, watch, computed } from 'vue'
|
||||
import { ref, onMounted, reactive, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import ChatRecordDrawer from './component/ChatRecordDrawer.vue'
|
||||
import { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'
|
||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
import logApi from '@/api/log'
|
||||
import { datetimeFormat } from '@/utils/time'
|
||||
import useStore from '@/stores'
|
||||
@ -253,7 +253,7 @@ const preChatRecord = () => {
|
||||
return
|
||||
}
|
||||
paginationConfig.current_page = paginationConfig.current_page - 1
|
||||
getList().then((ok) => {
|
||||
getList().then(() => {
|
||||
index = paginationConfig.page_size - 1
|
||||
currentChatId.value = tableData.value[index].id
|
||||
currentAbstract.value = tableData.value[index].abstract
|
||||
|
||||
@ -12,16 +12,6 @@
|
||||
<span class="lighter" v-else>{{ form.title || '-' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="分段内容" prop="content">
|
||||
<!-- <el-input
|
||||
v-if="isEdit"
|
||||
v-model="form.content"
|
||||
placeholder="请输入分段内容"
|
||||
maxlength="4096"
|
||||
show-word-limit
|
||||
:rows="8"
|
||||
type="textarea"
|
||||
>
|
||||
</el-input>-->
|
||||
<MarkdownEditor
|
||||
v-if="isEdit"
|
||||
v-model="form.content"
|
||||
@ -39,12 +29,11 @@
|
||||
:modelValue="form.content"
|
||||
class="maxkb-md"
|
||||
/>
|
||||
<!-- <span v-else class="break-all lighter">{{ form.content }}</span> -->
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onUnmounted, watch, nextTick } from 'vue'
|
||||
import { ref, reactive, onUnmounted, watch } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { MdPreview } from 'md-editor-v3'
|
||||
import MarkdownEditor from '@/components/markdown-editor/index.vue'
|
||||
|
||||
@ -3,7 +3,13 @@
|
||||
<div class="template-manage flex main-calc-height">
|
||||
<div class="template-manage__left p-8 border-r">
|
||||
<h4 class="p-16" style="padding-bottom: 8px">供应商</h4>
|
||||
<common-list :data="provider_list" v-loading="loading" @click="clickListHandle">
|
||||
<common-list
|
||||
:data="provider_list"
|
||||
v-loading="loading"
|
||||
@click="clickListHandle"
|
||||
value-key="provider"
|
||||
default-active=""
|
||||
>
|
||||
<template #default="{ row, index }">
|
||||
<div class="flex" v-if="index === 0">
|
||||
<AppIcon
|
||||
|
||||
Loading…
Reference in New Issue
Block a user