XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 1
返回提交历史

XFEstudio/gpt4free

Increase max token in HuggingfaceAPI Restore browser instance on start up errors in nodriver Restored instances can be used as usual or to stop the browser Add demo modus to web ui for HuggingSpace Add rate limit support to web ui. Simply install flask_limiter Add home for demo with Access Token input and validation Add stripped model list for demo Add ignores for encoding error in web_search and file upload

ab04d1d8
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

17 个文件 +444 -118
Modified g4f/Provider/CablyAI.py +3 -4
@@ -1,16 +1,15 @@
1 1 from __future__ import annotations
2 2
3 3 from ..typing import AsyncResult, Messages
4 from .needs_auth import OpenaiAPI
4 from .needs_auth.OpenaiTemplate import OpenaiTemplate
5 5
6 class CablyAI(OpenaiAPI):
7 label = "CablyAI"
6 class CablyAI(OpenaiTemplate):
8 7 url = "https://cablyai.com"
9 8 login_url = None
10 9 needs_auth = False
11 10 api_base = "https://cablyai.com/v1"
12 11 working = True
13
12
14 13 default_model = "Cably-80B"
15 14 models = [default_model]
16 15 model_aliases = {"cably-80b": default_model}
Modified g4f/Provider/DeepInfraChat.py +0 -1
@@ -4,7 +4,6 @@ from ..typing import AsyncResult, Messages
4 4 from .needs_auth.OpenaiTemplate import OpenaiTemplate
5 5
6 6 class DeepInfraChat(OpenaiTemplate):
7 label = "DeepInfraChat"
8 7 url = "https://deepinfra.com/chat"
9 8 api_base = "https://api.deepinfra.com/v1/openai"
10 9 working = True
Modified g4f/Provider/mini_max/HailuoAI.py +2 -2
@@ -7,7 +7,7 @@ from aiohttp import ClientSession, FormData
7 7
8 8 from ...typing import AsyncResult, Messages
9 9 from ..base_provider import AsyncAuthedProvider, ProviderModelMixin, format_prompt
10 from ..mini_max.crypt import CallbackResults, get_browser_callback, generate_yy_header
10 from ..mini_max.crypt import CallbackResults, get_browser_callback, generate_yy_header, get_body_to_yy
11 11 from ...requests import get_args_from_nodriver, raise_for_status
12 12 from ...providers.response import AuthResult, JsonConversation, RequestLogin, TitleGeneration
13 13 from ... import debug
@@ -71,7 +71,7 @@ class HailuoAI(AsyncAuthedProvider, ProviderModelMixin):
71 71 data.add_field(name, str(value))
72 72 headers = {
73 73 "token": token,
74 "yy": generate_yy_header(auth_result.path_and_query, form_data, "POST", timestamp)
74 "yy": generate_yy_header(auth_result.path_and_query, get_body_to_yy(form_data), timestamp)
75 75 }
76 76 async with session.post(f"{cls.url}{path_and_query}", data=data, headers=headers) as response:
77 77 await raise_for_status(response)
Modified g4f/Provider/mini_max/crypt.py +5 -12
@@ -22,27 +22,17 @@ def hash_function(base_string: str) -> str:
22 22 """
23 23 return hashlib.md5(base_string.encode()).hexdigest()
24 24
25 def generate_yy_header(has_search_params_path: str, body: dict, method: str, time: int) -> str:
25 def generate_yy_header(has_search_params_path: str, body_to_yy: dict, time: int) -> str:
26 26 """
27 27 Python equivalent of the generateYYHeader function.
28 28 """
29 body_to_yy=get_body_to_yy(body)
30
31 if method and method.lower() == 'post':
32 s = body or {}
33 else:
34 s = {}
35
36 s = json.dumps(s, ensure_ascii=True, sort_keys=True)
37 if body_to_yy:
38 s = body_to_yy
39 29 # print("Encoded Path:", quote(has_search_params_path, ""))
40 30 # print("Stringified Body:", s)
41 31 # print("Hashed Time:", hash_function(str(time)))
42 32
43 33 encoded_path = quote(has_search_params_path, "")
44 34 time_hash = hash_function(str(time))
45 combined_string = f"{encoded_path}_{s}{time_hash}ooui"
35 combined_string = f"{encoded_path}_{body_to_yy}{time_hash}ooui"
46 36
47 37 # print("Combined String:", combined_string)
48 38 # print("Hashed Combined String:", hash_function(combined_string))
@@ -56,6 +46,9 @@ def get_body_to_yy(l):
56 46 # print("bodyToYY:", M)
57 47 return M
58 48
49 def get_body_json(s):
50 return json.dumps(s, ensure_ascii=True, sort_keys=True)
51
59 52 async def get_browser_callback(auth_result: CallbackResults):
60 53 async def callback(page: Tab):
61 54 while not auth_result.token:
Modified g4f/Provider/needs_auth/HuggingChat.py +4 -1
@@ -65,10 +65,13 @@ class HuggingChat(AsyncAuthedProvider, ProviderModelMixin):
65 65 "llama-3.2-11b": "meta-llama/Llama-3.2-11B-Vision-Instruct",
66 66 "mistral-nemo": "mistralai/Mistral-Nemo-Instruct-2407",
67 67 "phi-3.5-mini": "microsoft/Phi-3.5-mini-instruct",
68
69 68 ### Image ###
70 69 "flux-dev": "black-forest-labs/FLUX.1-dev",
71 70 "flux-schnell": "black-forest-labs/FLUX.1-schnell",
71 ### API ###
72 "qwen-2-vl-7b": "Qwen/Qwen2-VL-7B-Instruct",
73 "gemma-2-27b": "google/gemma-2-27b-it",
74 "qvq-72b": "Qwen/QVQ-72B-Preview"
72 75 }
73 76
74 77 @classmethod
Modified g4f/Provider/needs_auth/HuggingFaceAPI.py +8 -2
@@ -14,6 +14,8 @@ class HuggingFaceAPI(OpenaiTemplate):
14 14
15 15 default_model = "meta-llama/Llama-3.2-11B-Vision-Instruct"
16 16 default_vision_model = default_model
17 vision_models = [default_vision_model, "Qwen/Qwen2-VL-7B-Instruct"]
18 model_aliases = HuggingChat.model_aliases
17 19
18 20 @classmethod
19 21 def get_models(cls, **kwargs):
@@ -28,9 +30,13 @@ class HuggingFaceAPI(OpenaiTemplate):
28 30 model: str,
29 31 messages: Messages,
30 32 api_base: str = None,
33 max_tokens: int = 2048,
31 34 **kwargs
32 35 ):
33 36 if api_base is None:
34 api_base = f"https://api-inference.huggingface.co/models/{model}/v1"
35 async for chunk in super().create_async_generator(model, messages, api_base=api_base, **kwargs):
37 model_name = model
38 if model in cls.model_aliases:
39 model_name = cls.model_aliases[model]
40 api_base = f"https://api-inference.huggingface.co/models/{model_name}/v1"
41 async for chunk in super().create_async_generator(model, messages, api_base=api_base, max_tokens=max_tokens, **kwargs):
36 42 yield chunk
Modified g4f/api/__init__.py +13 -4
@@ -73,7 +73,7 @@ def create_app():
73 73 api.register_validation_exception_handler()
74 74
75 75 if AppConfig.gui:
76 gui_app = WSGIMiddleware(get_gui_app())
76 gui_app = WSGIMiddleware(get_gui_app(AppConfig.demo))
77 77 app.mount("/", gui_app)
78 78
79 79 # Read cookie files if not ignored
@@ -96,6 +96,12 @@ def create_app_with_gui_and_debug():
96 96 AppConfig.gui = True
97 97 return create_app()
98 98
99 def create_app_with_demo_and_debug():
100 g4f.debug.logging = True
101 AppConfig.gui = True
102 AppConfig.demo = True
103 return create_app()
104
99 105 class ErrorResponse(Response):
100 106 media_type = "application/json"
101 107
@@ -121,6 +127,7 @@ class AppConfig:
121 127 image_provider: str = None
122 128 proxy: str = None
123 129 gui: bool = False
130 demo: bool = False
124 131
125 132 @classmethod
126 133 def set_config(cls, **data):
@@ -156,7 +163,7 @@ class Api:
156 163 print(f"Register authentication key: {''.join(['*' for _ in range(len(AppConfig.g4f_api_key))])}")
157 164 @self.app.middleware("http")
158 165 async def authorization(request: Request, call_next):
159 if AppConfig.g4f_api_key is not None:
166 if AppConfig.g4f_api_key is not None or AppConfig.demo:
160 167 try:
161 168 user_g4f_api_key = await self.get_g4f_api_key(request)
162 169 except HTTPException:
@@ -167,7 +174,7 @@ class Api:
167 174 return ErrorResponse.from_message("G4F API key required", HTTP_401_UNAUTHORIZED)
168 175 if not secrets.compare_digest(AppConfig.g4f_api_key, user_g4f_api_key):
169 176 return ErrorResponse.from_message("Invalid G4F API key", HTTP_403_FORBIDDEN)
170 else:
177 elif not AppConfig.demo:
171 178 if user_g4f_api_key is not None and path.startswith("/images/"):
172 179 if not secrets.compare_digest(AppConfig.g4f_api_key, user_g4f_api_key):
173 180 return ErrorResponse.from_message("Invalid G4F API key", HTTP_403_FORBIDDEN)
@@ -562,7 +569,9 @@ def run_api(
562 569 host, port = bind.split(":")
563 570 if port is None:
564 571 port = DEFAULT_PORT
565 if AppConfig.gui and debug:
572 if AppConfig.demo and debug:
573 method = "create_app_with_demo_and_debug"
574 elif AppConfig.gui and debug:
566 575 method = "create_app_with_gui_and_debug"
567 576 else:
568 577 method = "create_app_debug" if debug else "create_app"
Modified g4f/cli.py +2 -0
@@ -28,6 +28,7 @@ def get_api_parser():
28 28 api_parser.add_argument("--cookie-browsers", nargs="+", choices=[browser.__name__ for browser in g4f.cookies.browsers],
29 29 default=[], help="List of browsers to access or retrieve cookies from. (incompatible with --reload and --workers)")
30 30 api_parser.add_argument("--reload", action="store_true", help="Enable reloading.")
31 api_parser.add_argument("--demo", action="store_true", help="Enable demo modus.")
31 32 return api_parser
32 33
33 34 def main():
@@ -57,6 +58,7 @@ def run_api_args(args):
57 58 proxy=args.proxy,
58 59 model=args.model,
59 60 gui=args.gui,
61 demo=args.demo,
60 62 )
61 63 if args.cookie_browsers:
62 64 g4f.cookies.browsers = [g4f.cookies[browser] for browser in args.cookie_browsers]
Modified g4f/gui/__init__.py +2 -1
@@ -8,10 +8,11 @@ try:
8 8 except ImportError as e:
9 9 import_error = e
10 10
11 def get_gui_app():
11 def get_gui_app(demo: bool = False):
12 12 if import_error is not None:
13 13 raise MissingRequirementsError(f'Install "gui" requirements | pip install -U g4f[gui]\n{import_error}')
14 14 app = create_app()
15 app.demo = demo
15 16
16 17 site = Website(app)
17 18 for route in site.routes:
Added g4f/gui/client/demo.html +257 -0
@@ -0,0 +1,257 @@
1
2 <!DOCTYPE html>
3 <html lang="en">
4 <head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>G4F DEMO</title>
8 <link rel="apple-touch-icon" sizes="180x180" href="/static/img/apple-touch-icon.png">
9 <link rel="icon" type="image/png" sizes="32x32" href="/static/img/favicon-32x32.png">
10 <link rel="icon" type="image/png" sizes="16x16" href="/static/img/favicon-16x16.png">
11 <link rel="manifest" href="/static/img/site.webmanifest">
12 <style>
13 :root {
14 --colour-1: #000000;
15 --colour-2: #ccc;
16 --colour-3: #e4d4ff;
17 --colour-4: #f0f0f0;
18 --colour-5: #181818;
19 --colour-6: #242424;
20 --accent: #8b3dff;
21 --gradient: #1a1a1a;
22 --background: #16101b;
23 --size: 70vw;
24 --top: 50%;
25 --blur: 40px;
26 --opacity: 0.6;
27 }
28
29 @import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap");
30
31 .gradient {
32 position: absolute;
33 z-index: -1;
34 left: 50vw;
35 border-radius: 50%;
36 background: radial-gradient(circle at center, var(--accent), var(--gradient));
37 width: var(--size);
38 height: var(--size);
39 top: var(--top);
40 transform: translate(-50%, -50%);
41 filter: blur(var(--blur)) opacity(var(--opacity));
42 animation: zoom_gradient 6s infinite alternate;
43 display: none;
44 max-height: 100%;
45 transition: max-height 0.25s ease-in;
46 }
47
48 .gradient.hidden {
49 max-height: 0;
50 transition: max-height 0.15s ease-out;
51 }
52
53 @media only screen and (min-width: 40em) {
54 body .gradient{
55 display: block;
56 }
57 }
58
59 @keyframes zoom_gradient {
60 0% {
61 transform: translate(-50%, -50%) scale(1);
62 }
63 100% {
64 transform: translate(-50%, -50%) scale(1.2);
65 }
66 }
67
68 /* Body and text color */
69 body {
70 background: var(--background);
71 color: var(--colour-3);
72 font-family: "Inter", sans-serif;
73 height: 100vh;
74 margin: 0;
75 padding: 0;
76 overflow: hidden;
77 font-weight: bold;
78 }
79
80 /* Container for the main content */
81 .container {
82 display: flex;
83 flex-direction: column;
84 justify-content: center;
85 align-items: center;
86 height: 100%;
87 text-align: center;
88 z-index: 1;
89 }
90
91 header {
92 font-size: 3rem;
93 text-transform: uppercase;
94 margin: 20px;
95 color: var(--colour-4);
96 }
97
98 iframe {
99 background: transparent;
100 width: 100%;
101 border: none;
102 }
103
104 #background {
105 height: 100%;
106 position: absolute;
107 z-index: -1;
108 }
109
110 .description, form a {
111 font-size: 1.2rem;
112 margin-bottom: 30px;
113 color: var(--colour-2);
114 }
115
116 .input-field {
117 width: 80%;
118 max-width: 400px;
119 padding: 12px;
120 margin: 10px 0;
121 border: 2px solid var(--colour-6);
122 background-color: var(--colour-5);
123 color: var(--colour-3);
124 border-radius: 8px;
125 font-size: 1.1rem;
126 }
127
128 .input-field:focus {
129 outline: none;
130 border-color: var(--accent);
131 }
132
133 .button {
134 background-color: var(--accent);
135 color: var(--colour-3);
136 border: none;
137 padding: 15px 30px;
138 font-size: 1.1rem;
139 border-radius: 8px;
140 cursor: pointer;
141 transition: background-color 0.3s ease;
142 margin-top: 15px;
143 width: 100%;
144 max-width: 400px;
145 font-weight: bold;
146 }
147
148 .button:hover {
149 background-color: #7a2ccd;
150 }
151
152 .footer {
153 margin-top: 30px;
154 font-size: 0.9rem;
155 color: var(--colour-2);
156 }
157
158 /* Animation for the gradient circle */
159 @keyframes zoom_gradient {
160 0% {
161 transform: translate(-50%, -50%) scale(1);
162 }
163 100% {
164 transform: translate(-50%, -50%) scale(1.5);
165 }
166 }
167 </style>
168 <script src="https://unpkg.com/es-module-shims@1.7.0/dist/es-module-shims.js"></script>
169 <script type="importmap">
170 {
171 "imports": {
172 "@huggingface/hub": "https://cdn.jsdelivr.net/npm/@huggingface/hub@0.21.0/+esm"
173 }
174 }
175 </script>
176 </head>
177 <body>
178 <iframe id="background"></iframe>
179
180 <!-- Gradient Background Circle -->
181 <div class="gradient"></div>
182
183 <!-- Main Content -->
184 <div class="container">
185 <header>
186 G4F DEMO
187 </header>
188 <div class="description">
189 Welcome to the G4F Web UI! <br>
190 Your AI assistant is ready to assist you.
191 </div>
192
193 <!-- Input and Button -->
194 <form action="/chat/">
195 <input type="text" name="token" class="input-field" placeholder="Enter an Access Token..." autocomplete="off">
196 <button class="button">Submit</button>
197 <p>
198 <a href="https://huggingface.co/settings/tokens" target="_blank">Get Access Token</a>
199 </p>
200 </form>
201 <script type="module">
202 import * as hub from "@huggingface/hub";
203
204 const form = document.querySelector("form");
205 const input = document.querySelector('form input[name="token"]');
206 async function check_access_token() {
207 const accessToken = input.value || localStorage.getItem("HuggingFace-api_key");
208 let user;
209 try {
210 user = await hub.whoAmI({accessToken: accessToken});
211 } catch(e) {
212 console.log(e);
213 input.setCustomValidity("Invalid Access Token.");
214 return;
215 }
216 localStorage.setItem("HuggingFace-api_key", accessToken);
217 localStorage.setItem("HuggingFace-user", JSON.stringify(user));
218 location.href = "/chat/";
219 }
220 input.addEventListener("input", () => check_access_token());
221 input.addEventListener("click", () => check_access_token());
222 form.addEventListener("submit", async (event) => {
223 event.preventDefault();
224 check_access_token();
225 });
226 </script>
227
228 <!-- Footer -->
229 <div class="footer">
230 <p>&copy; 2025 G4F. All Rights Reserved.</p>
231 <p>Powered by the G4F framework</p>
232 </div>
233 </div>
234 <script>
235 (async () => {
236 const today = new Date().toJSON().slice(0, 10);
237 const max = 5;
238 const cache_id = Math.floor(Math.random() * max);
239 let prompt;
240 if (cache_id % 2 == 0) {
241 prompt = `
242 Today is ${new Date().toJSON().slice(0, 10)}.
243 Create a single-page HTML screensaver reflecting the current season (based on the date).
244 Avoid using any text.`;
245 } else {
246 prompt = `Create a single-page HTML screensaver. Avoid using any text.`;
247 const response = await fetch(`/backend-api/v2/create?prompt=${prompt}&filter_markdown=html&cache=${cache_id}`);
248 }
249 const response = await fetch(`/backend-api/v2/create?prompt=${prompt}&filter_markdown=html&cache=${cache_id}`);
250 const text = await response.text()
251 background.src = `data:text/html;charset=utf-8,${encodeURIComponent(text)}`;
252 const gradient = document.querySelector('.gradient');
253 gradient.classList.add('hidden');
254 })();
255 </script>
256 </body>
257 </html>
Modified g4f/gui/server/api.py +3 -2
@@ -28,6 +28,7 @@ class Api:
28 28 return [{
29 29 "name": model.name,
30 30 "image": isinstance(model, models.ImageModel),
31 "vision": isinstance(model, models.VisionModel),
31 32 "providers": [
32 33 getattr(provider, "parent", provider.__name__)
33 34 for provider in providers
@@ -84,7 +85,7 @@ class Api:
84 85 return send_from_directory(os.path.abspath(images_dir), name)
85 86
86 87 def _prepare_conversation_kwargs(self, json_data: dict, kwargs: dict):
87 model = json_data.get('model') or models.default
88 model = json_data.get('model')
88 89 provider = json_data.get('provider')
89 90 messages = json_data.get('messages')
90 91 api_key = json_data.get("api_key")
@@ -180,7 +181,7 @@ class Api:
180 181 conversations[provider][conversation_id] = chunk
181 182 if isinstance(chunk, JsonConversation):
182 183 yield self._format_json("conversation", {
183 provider: chunk.get_dict()
184 provider.__name__ if isinstance(provider, type) else provider: chunk.get_dict()
184 185 })
185 186 else:
186 187 yield self._format_json("conversation_id", conversation_id)
Modified g4f/gui/server/backend_api.py +78 -49
@@ -6,12 +6,19 @@ import os
6 6 import logging
7 7 import asyncio
8 8 import shutil
9 import random
9 10 from flask import Flask, Response, request, jsonify
10 11 from typing import Generator
11 12 from pathlib import Path
12 13 from urllib.parse import quote_plus
13 14 from hashlib import sha256
14 15 from werkzeug.utils import secure_filename
16 try:
17 from flask_limiter import Limiter
18 from flask_limiter.util import get_remote_address
19 has_flask_limiter = True
20 except ImportError:
21 has_flask_limiter = False
15 22
16 23 from ...image import is_allowed_extension, to_image
17 24 from ...client.service import convert_to_provider
@@ -22,6 +29,7 @@ from ...tools.run_tools import iter_run_tools
22 29 from ...errors import ProviderNotFoundError
23 30 from ...cookies import get_cookies_dir
24 31 from ... import ChatCompletion
32 from ... import models
25 33 from .api import Api
26 34
27 35 logger = logging.getLogger(__name__)
@@ -53,45 +61,98 @@ class Backend_Api(Api):
53 61 """
54 62 self.app: Flask = app
55 63
64 if has_flask_limiter:
65 limiter = Limiter(
66 get_remote_address,
67 app=app,
68 default_limits=["200 per day", "50 per hour"],
69 storage_uri="memory://",
70 )
71 else:
72 class Dummy():
73 def limit(self, value):
74 pass
75 limiter = Dummy()
76
77 @app.route('/backend-api/v2/models', methods=['GET'])
56 78 def jsonify_models(**kwargs):
57 response = self.get_models(**kwargs)
79 response = get_demo_models() if app.demo else self.get_models(**kwargs)
58 80 if isinstance(response, list):
59 81 return jsonify(response)
60 82 return response
61 83
84 @app.route('/backend-api/v2/models/<provider>', methods=['GET'])
62 85 def jsonify_provider_models(**kwargs):
63 86 response = self.get_provider_models(**kwargs)
64 87 if isinstance(response, list):
65 88 return jsonify(response)
66 89 return response
67 90
91 @app.route('/backend-api/v2/providers', methods=['GET'])
68 92 def jsonify_providers(**kwargs):
69 93 response = self.get_providers(**kwargs)
70 94 if isinstance(response, list):
71 95 return jsonify(response)
72 96 return response
73 97
98 def get_demo_models():
99 return [{
100 "name": model.name,
101 "image": isinstance(model, models.ImageModel),
102 "vision": isinstance(model, models.VisionModel),
103 "providers": [
104 getattr(provider, "parent", provider.__name__)
105 for provider in providers
106 ],
107 "demo": True
108 }
109 for model, providers in models.demo_models.values()]
110
111 @app.route('/backend-api/v2/conversation', methods=['POST'])
112 @limiter.limit("4 per minute")
113 def handle_conversation():
114 """
115 Handles conversation requests and streams responses back.
116
117 Returns:
118 Response: A Flask response object for streaming.
119 """
120 kwargs = {}
121 if "files[]" in request.files:
122 images = []
123 for file in request.files.getlist('files[]'):
124 if file.filename != '' and is_allowed_extension(file.filename):
125 images.append((to_image(file.stream, file.filename.endswith('.svg')), file.filename))
126 kwargs['images'] = images
127 if "json" in request.form:
128 json_data = json.loads(request.form['json'])
129 else:
130 json_data = request.json
131
132 if app.demo:
133 model = json_data.get("model")
134 if model != "default" and model in models.demo_models:
135 json_data["provider"] = random.choice(models.demo_models[model][1])
136 else:
137 json_data["model"] = models.demo_models["default"][0].name
138 json_data["provider"] = random.choice(models.demo_models["default"][1])
139
140 kwargs = self._prepare_conversation_kwargs(json_data, kwargs)
141 return self.app.response_class(
142 self._create_response_stream(
143 kwargs,
144 json_data.get("conversation_id"),
145 json_data.get("provider"),
146 json_data.get("download_images", True),
147 ),
148 mimetype='text/event-stream'
149 )
150
74 151 self.routes = {
75 '/backend-api/v2/models': {
76 'function': jsonify_models,
77 'methods': ['GET']
78 },
79 '/backend-api/v2/models/<provider>': {
80 'function': jsonify_provider_models,
81 'methods': ['GET']
82 },
83 '/backend-api/v2/providers': {
84 'function': jsonify_providers,
85 'methods': ['GET']
86 },
87 152 '/backend-api/v2/version': {
88 153 'function': self.get_version,
89 154 'methods': ['GET']
90 155 },
91 '/backend-api/v2/conversation': {
92 'function': self.handle_conversation,
93 'methods': ['POST']
94 },
95 156 '/backend-api/v2/synthesize/<provider>': {
96 157 'function': self.handle_synthesize,
97 158 'methods': ['GET']
@@ -250,38 +311,6 @@ class Backend_Api(Api):
250 311 return "File saved", 200
251 312 return 'Not supported file', 400
252 313
253 def handle_conversation(self):
254 """
255 Handles conversation requests and streams responses back.
256
257 Returns:
258 Response: A Flask response object for streaming.
259 """
260
261 kwargs = {}
262 if "files[]" in request.files:
263 images = []
264 for file in request.files.getlist('files[]'):
265 if file.filename != '' and is_allowed_extension(file.filename):
266 images.append((to_image(file.stream, file.filename.endswith('.svg')), file.filename))
267 kwargs['images'] = images
268 if "json" in request.form:
269 json_data = json.loads(request.form['json'])
270 else:
271 json_data = request.json
272
273 kwargs = self._prepare_conversation_kwargs(json_data, kwargs)
274
275 return self.app.response_class(
276 self._create_response_stream(
277 kwargs,
278 json_data.get("conversation_id"),
279 json_data.get("provider"),
280 json_data.get("download_images", True),
281 ),
282 mimetype='text/event-stream'
283 )
284
285 314 def handle_synthesize(self, provider: str):
286 315 try:
287 316 provider_handler = convert_to_provider(provider)
Modified g4f/gui/server/website.py +5 -2
Modified g4f/models.py +41 -23
Modified g4f/requests/__init__.py +13 -7
Modified g4f/tools/files.py +2 -2
Modified g4f/tools/web_search.py +6 -6