backend.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import json
  2. import asyncio
  3. import flask
  4. import os
  5. from flask import request, Flask
  6. from typing import AsyncGenerator, Generator
  7. from werkzeug.utils import secure_filename
  8. from g4f.image import is_allowed_extension, to_image
  9. from g4f.client.service import convert_to_provider
  10. from g4f.errors import ProviderNotFoundError
  11. from g4f.cookies import get_cookies_dir
  12. from .api import Api
  13. def safe_iter_generator(generator: Generator) -> Generator:
  14. start = next(generator)
  15. def iter_generator():
  16. yield start
  17. yield from generator
  18. return iter_generator()
  19. def to_sync_generator(gen: AsyncGenerator) -> Generator:
  20. loop = asyncio.new_event_loop()
  21. asyncio.set_event_loop(loop)
  22. gen = gen.__aiter__()
  23. async def get_next():
  24. try:
  25. obj = await gen.__anext__()
  26. return False, obj
  27. except StopAsyncIteration: return True, None
  28. while True:
  29. done, obj = loop.run_until_complete(get_next())
  30. if done:
  31. break
  32. yield obj
  33. class Backend_Api(Api):
  34. """
  35. Handles various endpoints in a Flask application for backend operations.
  36. This class provides methods to interact with models, providers, and to handle
  37. various functionalities like conversations, error handling, and version management.
  38. Attributes:
  39. app (Flask): A Flask application instance.
  40. routes (dict): A dictionary mapping API endpoints to their respective handlers.
  41. """
  42. def __init__(self, app: Flask) -> None:
  43. """
  44. Initialize the backend API with the given Flask application.
  45. Args:
  46. app (Flask): Flask application instance to attach routes to.
  47. """
  48. self.app: Flask = app
  49. self.routes = {
  50. '/backend-api/v2/models': {
  51. 'function': self.get_models,
  52. 'methods': ['GET']
  53. },
  54. '/backend-api/v2/models/<provider>': {
  55. 'function': self.get_provider_models,
  56. 'methods': ['GET']
  57. },
  58. '/backend-api/v2/image_models': {
  59. 'function': self.get_image_models,
  60. 'methods': ['GET']
  61. },
  62. '/backend-api/v2/providers': {
  63. 'function': self.get_providers,
  64. 'methods': ['GET']
  65. },
  66. '/backend-api/v2/version': {
  67. 'function': self.get_version,
  68. 'methods': ['GET']
  69. },
  70. '/backend-api/v2/conversation': {
  71. 'function': self.handle_conversation,
  72. 'methods': ['POST']
  73. },
  74. '/backend-api/v2/synthesize/<provider>': {
  75. 'function': self.handle_synthesize,
  76. 'methods': ['GET']
  77. },
  78. '/backend-api/v2/upload_cookies': {
  79. 'function': self.upload_cookies,
  80. 'methods': ['POST']
  81. },
  82. '/images/<path:name>': {
  83. 'function': self.serve_images,
  84. 'methods': ['GET']
  85. }
  86. }
  87. def upload_cookies(self):
  88. file = None
  89. if "file" in request.files:
  90. file = request.files['file']
  91. if file.filename == '':
  92. return 'No selected file', 400
  93. if file and file.filename.endswith(".json") or file.filename.endswith(".har"):
  94. filename = secure_filename(file.filename)
  95. file.save(os.path.join(get_cookies_dir(), filename))
  96. return "File saved", 200
  97. return 'Not supported file', 400
  98. def handle_conversation(self):
  99. """
  100. Handles conversation requests and streams responses back.
  101. Returns:
  102. Response: A Flask response object for streaming.
  103. """
  104. kwargs = {}
  105. if "file" in request.files:
  106. file = request.files['file']
  107. if file.filename != '' and is_allowed_extension(file.filename):
  108. kwargs['image'] = to_image(file.stream, file.filename.endswith('.svg'))
  109. kwargs['image_name'] = file.filename
  110. if "json" in request.form:
  111. json_data = json.loads(request.form['json'])
  112. else:
  113. json_data = request.json
  114. kwargs = self._prepare_conversation_kwargs(json_data, kwargs)
  115. return self.app.response_class(
  116. self._create_response_stream(
  117. kwargs,
  118. json_data.get("conversation_id"),
  119. json_data.get("provider"),
  120. json_data.get("download_images", True),
  121. ),
  122. mimetype='text/event-stream'
  123. )
  124. def handle_synthesize(self, provider: str):
  125. try:
  126. provider_handler = convert_to_provider(provider)
  127. except ProviderNotFoundError:
  128. return "Provider not found", 404
  129. if not hasattr(provider_handler, "synthesize"):
  130. return "Provider doesn't support synthesize", 500
  131. try:
  132. response_generator = provider_handler.synthesize({**request.args})
  133. if hasattr(response_generator, "__aiter__"):
  134. response_generator = to_sync_generator(response_generator)
  135. response = flask.Response(safe_iter_generator(response_generator), content_type="audio/mpeg")
  136. response.headers['Cache-Control'] = "max-age=604800"
  137. return response
  138. except Exception as e:
  139. return f"{e.__class__.__name__}: {e}", 500
  140. def get_provider_models(self, provider: str):
  141. api_key = None if request.authorization is None else request.authorization.token
  142. models = super().get_provider_models(provider, api_key)
  143. if models is None:
  144. return "Provider not found", 404
  145. return models
  146. def _format_json(self, response_type: str, content) -> str:
  147. """
  148. Formats and returns a JSON response.
  149. Args:
  150. response_type (str): The type of the response.
  151. content: The content to be included in the response.
  152. Returns:
  153. str: A JSON formatted string.
  154. """
  155. return json.dumps(super()._format_json(response_type, content)) + "\n"