Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions apps/chat/serializers/chat_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,34 @@ def auth(self, request):
_type).to_token()


class AnonymousAuthenticationV2Serializer(serializers.Serializer):
"""v2 匿名认证:application_id 不在 path,从 access_token 解出并写进 token,
供 ChatUserToken handler 收窄到该应用。"""
access_token = serializers.CharField(required=True, label=_("access_token"))

def auth(self, request, with_valid=True):
token = request.META.get('HTTP_AUTHORIZATION')
token_details = {}
try:
# 校验token
if token is not None:
token_details = signing.loads(token[7:])
except Exception as e:
pass
if with_valid:
self.is_valid(raise_exception=True)
access_token = self.data.get("access_token")
application_access_token = QuerySet(ApplicationAccessToken).filter(access_token=access_token).first()
if application_access_token is None or not application_access_token.is_active:
raise NotFound404(404, _("Invalid access_token"))
chat_user_id = token_details.get('user_id') or token_details.get('id') or str(uuid.uuid7())
_type = AuthenticationType.CHAT_USER
application_id = str(application_access_token.application_id)
return ChatToken(chat_user_id, _type, str(Operate.ANNOTATION_AUTH),
application_id=application_id).to_token(), \
FileToken(chat_user_id, _type, application_id=application_id).to_token()


class AuthProfileSerializer(serializers.Serializer):
access_token = serializers.CharField(required=True, label=_("access_token"))

Expand Down
94 changes: 63 additions & 31 deletions apps/chat/urls.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,72 @@
from django.urls import path
from django.urls import path, include

from application.views import ChatRecordDetailView, ChatRecordLinkView
from chat.views.mcp import mcp_view
from . import views
from chat.views import v2 as v2_views, v3 as v3_views

app_name = 'chat'
# @formatter:off
# fmt: off
urlpatterns = [
path('embed', views.ChatEmbedView.as_view()),
path('mcp', mcp_view),
path('auth/anonymous', views.AnonymousAuthentication.as_view()),
path('profile', views.AuthProfile.as_view()),
path('application/<str:application_id>/profile', views.ApplicationProfile.as_view(), name='profile'),
path('chat_message/<str:chat_id>', views.ChatView.as_view(), name='chat'),
path('chat_message/<str:chat_id>/cancel', views.CancelWorkflowView.as_view(), name='cancel_workflow'),
path('application/<str:application_id>/open', views.OpenView.as_view(), name='open'),
path('text_to_speech', views.TextToSpeech.as_view()),
path('speech_to_text', views.SpeechToText.as_view()),
path('captcha', views.CaptchaView.as_view(), name='captcha'),
path('<str:application_id>/chat/completions', views.OpenAIView.as_view(), name='application/chat_completions'),
path('vote/chat/<str:chat_id>/chat_record/<str:chat_record_id>', views.VoteView.as_view(), name='vote'),
path('historical_conversation', views.HistoricalConversationView.as_view(), name='historical_conversation'),
path('historical_conversation/<str:chat_id>/record/<str:chat_record_id>',views.ChatRecordView.as_view(),name='conversation_details'),
path('historical_conversation/<int:current_page>/<int:page_size>', views.HistoricalConversationView.PageView.as_view(), name='historical_conversation'),
path('historical_conversation/clear',views.HistoricalConversationView.BatchDelete.as_view(), name='historical_conversation_clear'),
path('historical_conversation/<str:chat_id>',views.HistoricalConversationView.Operate.as_view(), name='historical_conversation_operate'),
path('historical_conversation_record/<str:chat_id>', views.HistoricalConversationRecordView.as_view(), name='historical_conversation_record'),
path('historical_conversation_record/<str:chat_id>/<int:current_page>/<int:page_size>', views.HistoricalConversationRecordView.PageView.as_view(), name='historical_conversation_record'),

v3=[
# ---- application 作用域:application_id 从 path 获取 ----
path('application/<str:application_id>/', include([
path("profile",v3_views.ApplicationProfile.as_view(), name='v3_profile'),
path('open', v3_views.OpenView.as_view(), name='v3_open'),
path('text_to_speech',v3_views.TextToSpeech.as_view(),name='v3_text_to_speech'),
path('speech_to_text',v3_views.SpeechToText.as_view(),name='v3_speech_to_text'),
path('chat/completions',v3_views.OpenAIView.as_view(), name='v3_chat_completions'),
path('chat/clear',v3_views.HistoricalConversationView.BatchDelete.as_view(), name='v3_historical_conversation_clear'),
path('chat',v3_views.HistoricalConversationView.as_view(), name='v3_historical_conversation'),
path('chat/<int:current_page>/<int:page_size>',v3_views.HistoricalConversationView.PageView.as_view(),name='v3_historical_conversation_page'),
path('chat/<str:chat_id>/chat_message',v3_views.ChatView.as_view(), name='v3_chat'),
path('chat/<str:chat_id>/chat_record',v3_views.HistoricalConversationRecordView.as_view(), name='v3_historical_conversation_record'),
path('chat/<str:chat_id>/chat_record/<int:current_page>/<int:page_size>', v3_views.HistoricalConversationRecordView.PageView.as_view(), name='v3_historical_conversation_record_page'),
path('chat/<str:chat_id>/chat_record/<str:chat_record_id>',v3_views.ChatRecordView.as_view(),name='v3_conversation_details'),
path('chat/<str:chat_id>/chat_record/<str:chat_record_id>/vote',v3_views.VoteView.as_view(), name='v3_vote'),
path('chat/<str:chat_id>/share_chat',ChatRecordLinkView.as_view(),name='v3_share_chat'),
path('chat/<str:chat_id>',v3_views.HistoricalConversationView.Operate.as_view(), name='v3_historical_conversation_operate'),
])),
# ---- 全局(非 application 作用域)----
path('embed', v3_views.ChatEmbedView.as_view()),
path('mcp', v3_views.mcp_view),
path('auth/anonymous', v3_views.AnonymousAuthentication.as_view()),
path('auth/login/<str:access_token>', v3_views.LocalLoginView.as_view()),
path('auth/logout', v3_views.Logout.as_view(), name='v3_logout'),
path('profile', v3_views.AuthProfile.as_view()),
path('captcha', v3_views.CaptchaView.as_view(), name='v3_captcha'),
path('share/<str:link>', ChatRecordDetailView.as_view()),
path('chat_message/<str:chat_id>/cancel', v3_views.CancelWorkflowView.as_view(), name='v3_cancel_workflow'),
path('chat_user/profile', v3_views.ChatUserProfileView.as_view(), name='v3_chat_user_profile'),
path('chat_user/current/reset_password', v3_views.ResetCurrentUserPasswordView.as_view(), name='v3_reset_password_current'),
path('api_key', v3_views.ChatUserApiKeyView.as_view()),
path('api_key/<int:current_page>/<int:page_size>', v3_views.ChatUserApiKeyView.Page.as_view()),
path('api_key/<str:api_key_id>', v3_views.ChatUserApiKeyView.Operate.as_view()),
]
v2=[
path('embed', v2_views.ChatEmbedView.as_view()),
path('mcp', v2_views.mcp_view),
path('auth/anonymous', v2_views.AnonymousAuthentication.as_view(), name='anonymous'),
path('profile', v2_views.AuthProfile.as_view()),
path('application/profile', v2_views.ApplicationProfile.as_view(), name='profile'),
path('chat_message/<str:chat_id>', v2_views.ChatView.as_view(), name='chat'),
path('open', v2_views.OpenView.as_view(), name='open'),
path('text_to_speech', v2_views.TextToSpeech.as_view()),
path('speech_to_text', v2_views.SpeechToText.as_view()),
path('captcha', v2_views.CaptchaView.as_view(), name='captcha'),
path('<str:application_id>/chat/completions', v2_views.OpenAIView.as_view(), name='application/chat_completions'),
path('vote/chat/<str:chat_id>/chat_record/<str:chat_record_id>', v2_views.VoteView.as_view(), name='vote'),
path('historical_conversation', v2_views.HistoricalConversationView.as_view(), name='historical_conversation'),
path('historical_conversation/<str:chat_id>/record/<str:chat_record_id>',v2_views.ChatRecordView.as_view(),name='conversation_details'),
path('historical_conversation/<int:current_page>/<int:page_size>', v2_views.HistoricalConversationView.PageView.as_view(), name='historical_conversation'),
path('historical_conversation/clear',v2_views.HistoricalConversationView.BatchDelete.as_view(), name='historical_conversation_clear'),
path('historical_conversation/<str:chat_id>',v2_views.HistoricalConversationView.Operate.as_view(), name='historical_conversation_operate'),
path('historical_conversation_record/<str:chat_id>', v2_views.HistoricalConversationRecordView.as_view(), name='historical_conversation_record'),
path('historical_conversation_record/<str:chat_id>/<int:current_page>/<int:page_size>', v2_views.HistoricalConversationRecordView.PageView.as_view(), name='historical_conversation_record'),
path('share/<str:link>', ChatRecordDetailView.as_view()),
path('<str:application_id>/chat/<str:chat_id>/share_chat', ChatRecordLinkView.as_view()),
path("chat_user/current/reset_password", views.ResetCurrentUserPasswordView.as_view(), name="reset_password_current"),
path('auth/login/<str:access_token>', views.LocalLoginView.as_view()),
path('auth/logout', views.Logout.as_view(), name='logout'),
path('chat_user/profile', views.ChatUserProfileView.as_view(), name="chat_user_profile"),
path('api_key', views.ChatUserApiKeyView.as_view()),
path('api_key/<int:current_page>/<int:page_size>', views.ChatUserApiKeyView.Page.as_view()),
path('api_key/<str:api_key_id>', views.ChatUserApiKeyView.Operate.as_view())

]
urlpatterns = [
*v2,
path('v3/',include(v3))
]
6 changes: 2 additions & 4 deletions apps/chat/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,5 @@
@date:2025/5/29 16:08
@desc:
"""
from .chat_embed import *
from .chat import *
from .chat_record import *
from .chat_user_api_key import *
from . import v2
from . import v3
12 changes: 12 additions & 0 deletions apps/chat/views/v2/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# coding=utf-8
"""
@project: MaxKB
@Author:虎虎
@file: __init__.py.py
@date:2025/5/29 16:08
@desc:
"""
from .chat_embed import *
from .chat import *
from .chat_record import *
from .mcp import mcp_view
35 changes: 17 additions & 18 deletions apps/chat/views/chat.py → apps/chat/views/v2/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from chat.api.chat_authentication_api import ChatAuthenticationAPI, ChatAuthenticationProfileAPI, ChatOpenAPI, OpenAIAPI
from chat.serializers.chat import OpenChatSerializers, ChatSerializers, SpeechToTextSerializers, \
TextToSpeechSerializers, OpenAIChatSerializer
from chat.serializers.chat_authentication import AnonymousAuthenticationSerializer, ApplicationProfileSerializer, \
from chat.serializers.chat_authentication import AnonymousAuthenticationV2Serializer, ApplicationProfileSerializer, \
AuthProfileSerializer
from common.auth import ChatTokenAuth
from common.auth.authentication import has_permissions
Expand Down Expand Up @@ -97,11 +97,11 @@ class OpenAIView(APIView):
)
def post(self, request: Request, application_id: str):
ip_address = _get_ip_address(request)
if application_id != str(request.auth.application_id):
if application_id != str(request.user.kwargs.get('application_id')):
raise AppAuthenticationFailed(500, _('Secret key is invalid'))
return OpenAIChatSerializer(
data={'application_id': application_id, 'chat_user_id': request.auth.chat_user_id,
'chat_user_type': request.auth.chat_user_type,
data={'application_id': application_id, 'chat_user_id': request.user.id,
'chat_user_type': request.user.type,
'ip_address': ip_address,
'source': {"type": ChatSourceChoices.API_CALL.value}}).chat(request.data)

Expand All @@ -118,12 +118,12 @@ def options(self, request, *args, **kwargs):
description=_('Application Anonymous Certification'),
summary=_('Application Anonymous Certification'),
operation_id=_('Application Anonymous Certification'), # type: ignore
request=ChatAuthenticationAPI.get_request(),
request=AnonymousAuthenticationV2Serializer,
responses=None,
tags=[_('Chat')] # type: ignore
)
def post(self, request: Request):
token, f_token = AnonymousAuthenticationSerializer().auth(
token, f_token = AnonymousAuthenticationV2Serializer(data=request.data).auth(
request)
response = result.success(
token,
Expand Down Expand Up @@ -157,10 +157,9 @@ class ApplicationProfile(APIView):
responses=None,
tags=[_('Chat')] # type: ignore
)
@has_permissions(ChatPermissionConstants.get_aggregate_permissions())
def get(self, request: Request, application_id: str):
def get(self, request: Request):
return result.success(ApplicationProfileSerializer(
data={'application_id': application_id}).profile())
data={'application_id': request.user.kwargs.get('application_id')}).profile())


class AuthProfile(APIView):
Expand Down Expand Up @@ -194,13 +193,13 @@ class ChatView(APIView):
def post(self, request: Request, chat_id: str):
ip_address = _get_ip_address(request)
return ChatSerializers(data={'chat_id': chat_id,
'chat_user_id': request.auth.chat_user_id,
'chat_user_type': request.auth.chat_user_type,
'application_id': request.auth.application_id,
'chat_user_id': request.user.id,
'chat_user_type': request.user.type,
'application_id': request.user.kwargs.get('application_id'),
'debug': False,
'ip_address': ip_address,
'source': {
'type': ChatSourceChoices.API_CALL.value if request.auth.chat_user_type == ChatUserType.APPLICATION_API_KEY.value else ChatSourceChoices.ONLINE.value}
'type': ChatSourceChoices.API_CALL.value if request.user.type == ChatUserType.APPLICATION_API_KEY.value else ChatSourceChoices.ONLINE.value}
}
).chat(request.data)

Expand All @@ -218,10 +217,10 @@ class OpenView(APIView):
tags=[_('Chat')] # type: ignore
)
@has_permissions(ChatPermissionConstants.get_aggregate_permissions())
def get(self, request: Request, application_id: str):
def get(self, request: Request):
ip_address = _get_ip_address(request)
return result.success(OpenChatSerializers(
data={'application_id': application_id,
data={'application_id': request.user.kwargs.get('application_id'),
'chat_user_id': request.user.id, 'chat_user_type': request.user.type,
'ip_address': ip_address,
'source': {
Expand Down Expand Up @@ -283,7 +282,7 @@ class SpeechToText(APIView):
def post(self, request: Request):
return result.success(
SpeechToTextSerializers(
data={'application_id': request.auth.application_id})
data={'application_id': request.user.kwargs.get('application_id')})
.speech_to_text({'file': request.FILES.get('file')}))


Expand All @@ -301,7 +300,7 @@ class TextToSpeech(APIView):
)
def post(self, request: Request):
byte_data = TextToSpeechSerializers(
data={'application_id': request.auth.application_id}).text_to_speech(request.data)
data={'application_id': request.user.kwargs.get('application_id')}).text_to_speech(request.data)
return HttpResponse(byte_data, status=200, headers={'Content-Type': 'audio/mp3',
'Content-Disposition': 'attachment; filename="abc.mp3"'})

Expand All @@ -326,7 +325,7 @@ def post(self, request: Request, chat_id: str):
for file in files:
file_url = FileSerializer(
data={'file': file, 'meta': meta, 'source_id': chat_id, 'source_type': FileSourceType.CHAT, }).upload(
request.auth.chat_user_id)
request.user.id)
file_ids.append({'name': file.name, 'url': file_url, 'file_id': file_url.split('/')[-1]})
return result.success(file_ids)

Expand Down
File renamed without changes.
Loading
Loading