chat.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. # coding=utf-8
  2. """
  3. @project: MaxKB
  4. @Author:虎虎
  5. @file: chat.py
  6. @date:2025/6/6 11:18
  7. @desc:
  8. """
  9. import requests
  10. from django.http import HttpResponse, StreamingHttpResponse
  11. from django.utils.translation import gettext_lazy as _
  12. from drf_spectacular.utils import extend_schema
  13. from rest_framework.parsers import MultiPartParser
  14. from rest_framework.request import Request
  15. from rest_framework.views import APIView
  16. from application.api.application_api import SpeechToTextAPI, TextToSpeechAPI
  17. from application.models import ChatUserType, ChatSourceChoices
  18. from chat.api.chat_api import ChatAPI
  19. from chat.api.chat_authentication_api import ChatAuthenticationAPI, ChatAuthenticationProfileAPI, ChatOpenAPI, OpenAIAPI
  20. from chat.serializers.chat import OpenChatSerializers, ChatSerializers, SpeechToTextSerializers, \
  21. TextToSpeechSerializers, OpenAIChatSerializer
  22. from chat.serializers.chat_authentication import AnonymousAuthenticationSerializer, ApplicationProfileSerializer, \
  23. AuthProfileSerializer
  24. from common.auth import ChatTokenAuth
  25. from common.constants.permission_constants import ChatAuth
  26. from common.exception.app_exception import AppAuthenticationFailed
  27. from common.log.log import _get_ip_address
  28. from common.result import result
  29. from knowledge.models import FileSourceType
  30. from oss.serializers.file import FileSerializer
  31. from users.api import CaptchaAPI
  32. from users.serializers.login import CaptchaSerializer
  33. def stream_image(response):
  34. """生成器函数,用于流式传输图片数据"""
  35. for chunk in response.iter_content(chunk_size=4096):
  36. if chunk: # 过滤掉保持连接的空块
  37. yield chunk
  38. class ResourceProxy(APIView):
  39. def get(self, request: Request):
  40. image_url = request.query_params.get("url")
  41. if not image_url:
  42. return result.error("Missing 'url' parameter")
  43. try:
  44. # 发送GET请求,流式获取图片内容
  45. response = requests.get(
  46. image_url,
  47. stream=True, # 启用流式响应
  48. allow_redirects=True,
  49. timeout=10
  50. )
  51. content_type = response.headers.get('Content-Type', '').split(';')[0]
  52. # 创建Django流式响应
  53. django_response = StreamingHttpResponse(
  54. stream_image(response), # 使用生成器
  55. content_type=content_type
  56. )
  57. return django_response
  58. except Exception as e:
  59. return result.error(f"Image request failed: {str(e)}")
  60. class OpenAIView(APIView):
  61. authentication_classes = [ChatTokenAuth]
  62. @extend_schema(
  63. methods=['POST'],
  64. description=_('OpenAI Interface Dialogue'),
  65. summary=_('OpenAI Interface Dialogue'),
  66. operation_id=_('OpenAI Interface Dialogue'), # type: ignore
  67. request=OpenAIAPI.get_request(),
  68. responses=None,
  69. tags=[_('Chat')] # type: ignore
  70. )
  71. def post(self, request: Request, application_id: str):
  72. ip_address = _get_ip_address(request)
  73. if application_id != str(request.auth.application_id):
  74. raise AppAuthenticationFailed(500, _('Secret key is invalid'))
  75. return OpenAIChatSerializer(
  76. data={'application_id': application_id, 'chat_user_id': request.auth.chat_user_id,
  77. 'chat_user_type': request.auth.chat_user_type,
  78. 'ip_address': ip_address,
  79. 'source': {"type": ChatSourceChoices.API_CALL.value}}).chat(request.data)
  80. class AnonymousAuthentication(APIView):
  81. def options(self, request, *args, **kwargs):
  82. return HttpResponse(
  83. headers={"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": "true",
  84. "Access-Control-Allow-Methods": "POST",
  85. "Access-Control-Allow-Headers": "Origin,Content-Type,Cookie,Accept,Token"}, )
  86. @extend_schema(
  87. methods=['POST'],
  88. description=_('Application Anonymous Certification'),
  89. summary=_('Application Anonymous Certification'),
  90. operation_id=_('Application Anonymous Certification'), # type: ignore
  91. request=ChatAuthenticationAPI.get_request(),
  92. responses=None,
  93. tags=[_('Chat')] # type: ignore
  94. )
  95. def post(self, request: Request):
  96. return result.success(
  97. AnonymousAuthenticationSerializer(data={'access_token': request.data.get("access_token")}).auth(
  98. request),
  99. headers={"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": "true",
  100. "Access-Control-Allow-Methods": "POST",
  101. "Access-Control-Allow-Headers": "Origin,Content-Type,Cookie,Accept,Token"}
  102. )
  103. class ApplicationProfile(APIView):
  104. authentication_classes = [ChatTokenAuth]
  105. @extend_schema(
  106. methods=['GET'],
  107. description=_("Get application related information"),
  108. summary=_("Get application related information"),
  109. operation_id=_("Get application related information"), # type: ignore
  110. request=None,
  111. responses=None,
  112. tags=[_('Chat')] # type: ignore
  113. )
  114. def get(self, request: Request):
  115. if isinstance(request.auth, ChatAuth):
  116. return result.success(ApplicationProfileSerializer(
  117. data={'application_id': request.auth.application_id}).profile())
  118. raise AppAuthenticationFailed(401, "身份异常")
  119. class AuthProfile(APIView):
  120. @extend_schema(
  121. methods=['GET'],
  122. description=_("Get application authentication information"),
  123. summary=_("Get application authentication information"),
  124. operation_id=_("Get application authentication information"), # type: ignore
  125. parameters=ChatAuthenticationProfileAPI.get_parameters(),
  126. responses=None,
  127. tags=[_('Chat')] # type: ignore
  128. )
  129. def get(self, request: Request):
  130. return result.success(
  131. AuthProfileSerializer(data={'access_token': request.query_params.get("access_token")}).profile())
  132. class ChatView(APIView):
  133. authentication_classes = [ChatTokenAuth]
  134. @extend_schema(
  135. methods=['POST'],
  136. description=_("dialogue"),
  137. summary=_("dialogue"),
  138. operation_id=_("dialogue"), # type: ignore
  139. request=ChatAPI.get_request(),
  140. parameters=ChatAPI.get_parameters(),
  141. responses=None,
  142. tags=[_('Chat')] # type: ignore
  143. )
  144. def post(self, request: Request, chat_id: str):
  145. ip_address = _get_ip_address(request)
  146. return ChatSerializers(data={'chat_id': chat_id,
  147. 'chat_user_id': request.auth.chat_user_id,
  148. 'chat_user_type': request.auth.chat_user_type,
  149. 'application_id': request.auth.application_id,
  150. 'debug': False,
  151. 'ip_address': ip_address,
  152. 'source': {
  153. 'type': ChatSourceChoices.API_CALL.value if request.auth.chat_user_type == ChatUserType.APPLICATION_API_KEY.value else ChatSourceChoices.ONLINE.value}
  154. }
  155. ).chat(request.data)
  156. class OpenView(APIView):
  157. authentication_classes = [ChatTokenAuth]
  158. @extend_schema(
  159. methods=['GET'],
  160. description=_("Get the session id according to the application id"),
  161. summary=_("Get the session id according to the application id"),
  162. operation_id=_("Get the session id according to the application id"), # type: ignore
  163. parameters=ChatOpenAPI.get_parameters(),
  164. responses=None,
  165. tags=[_('Chat')] # type: ignore
  166. )
  167. def get(self, request: Request):
  168. ip_address = _get_ip_address(request)
  169. return result.success(OpenChatSerializers(
  170. data={'application_id': request.auth.application_id,
  171. 'chat_user_id': request.auth.chat_user_id, 'chat_user_type': request.auth.chat_user_type,
  172. 'ip_address': ip_address,
  173. 'source': {
  174. 'type': ChatSourceChoices.API_CALL.value if request.auth.chat_user_type == ChatUserType.APPLICATION_API_KEY.value else ChatSourceChoices.ONLINE.value},
  175. 'debug': False}).open())
  176. class CaptchaView(APIView):
  177. @extend_schema(methods=['GET'],
  178. summary=_("Get Chat captcha"),
  179. description=_("Get Chat captcha"),
  180. operation_id=_("Get Chat captcha"), # type: ignore
  181. tags=[_("Chat")], # type: ignore
  182. responses=CaptchaAPI.get_response())
  183. def get(self, request: Request):
  184. username = request.query_params.get('username', None)
  185. accessToken = request.query_params.get('accessToken', None)
  186. return result.success(CaptchaSerializer().chat_generate(username, 'chat', accessToken))
  187. class SpeechToText(APIView):
  188. authentication_classes = [ChatTokenAuth]
  189. @extend_schema(
  190. methods=['POST'],
  191. description=_("speech to text"),
  192. summary=_("speech to text"),
  193. operation_id=_("speech to text"), # type: ignore
  194. request=SpeechToTextAPI.get_request(),
  195. responses=SpeechToTextAPI.get_response(),
  196. tags=[_('Chat')] # type: ignore
  197. )
  198. def post(self, request: Request):
  199. return result.success(
  200. SpeechToTextSerializers(
  201. data={'application_id': request.auth.application_id})
  202. .speech_to_text({'file': request.FILES.get('file')}))
  203. class TextToSpeech(APIView):
  204. authentication_classes = [ChatTokenAuth]
  205. @extend_schema(
  206. methods=['POST'],
  207. description=_("text to speech"),
  208. summary=_("text to speech"),
  209. operation_id=_("text to speech"), # type: ignore
  210. request=TextToSpeechAPI.get_request(),
  211. responses=TextToSpeechAPI.get_response(),
  212. tags=[_('Chat')] # type: ignore
  213. )
  214. def post(self, request: Request):
  215. byte_data = TextToSpeechSerializers(
  216. data={'application_id': request.auth.application_id}).text_to_speech(request.data)
  217. return HttpResponse(byte_data, status=200, headers={'Content-Type': 'audio/mp3',
  218. 'Content-Disposition': 'attachment; filename="abc.mp3"'})
  219. class UploadFile(APIView):
  220. authentication_classes = [ChatTokenAuth]
  221. parser_classes = [MultiPartParser]
  222. @extend_schema(
  223. methods=['POST'],
  224. description=_("Upload files"),
  225. summary=_("Upload files"),
  226. operation_id=_("Upload files"), # type: ignore
  227. request=TextToSpeechAPI.get_request(),
  228. responses=TextToSpeechAPI.get_response(),
  229. tags=[_('Application')] # type: ignore
  230. )
  231. def post(self, request: Request, chat_id: str):
  232. files = request.FILES.getlist('file')
  233. file_ids = []
  234. meta = {}
  235. for file in files:
  236. file_url = FileSerializer(
  237. data={'file': file, 'meta': meta, 'source_id': chat_id, 'source_type': FileSourceType.CHAT, }).upload()
  238. file_ids.append({'name': file.name, 'url': file_url, 'file_id': file_url.split('/')[-1]})
  239. return result.success(file_ids)