返回提交历史
Modified
g4f/Provider/CablyAI.py
+3
-4
Modified
g4f/Provider/DeepInfraChat.py
+0
-1
Modified
g4f/Provider/mini_max/HailuoAI.py
+2
-2
Modified
g4f/Provider/mini_max/crypt.py
+5
-12
Modified
g4f/Provider/needs_auth/HuggingChat.py
+4
-1
Modified
g4f/Provider/needs_auth/HuggingFaceAPI.py
+8
-2
Modified
g4f/api/__init__.py
+13
-4
Modified
g4f/cli.py
+2
-0
Modified
g4f/gui/__init__.py
+2
-1
Added
g4f/gui/client/demo.html
+257
-0
Modified
g4f/gui/server/api.py
+3
-2
Modified
g4f/gui/server/backend_api.py
+78
-49
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
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
代码差异
17 个文件
+444
-118
@@ -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}
@@ -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
@@ -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)
@@ -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:
@@ -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
@@ -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
@@ -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"
@@ -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]
@@ -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:
@@ -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>© 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>
@@ -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)
@@ -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)