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

XFEstudio/gpt4free

Arm2 (#2414)

* Fix arm v7 build / improve api * Update stubs.py * Fix unit tests

804a80bc
H Lohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

12 个文件 +255 -226
Modified docker/Dockerfile-slim +1 -1
@@ -47,7 +47,7 @@ RUN python -m pip install --upgrade pip \
47 47 --global-option=build_ext \
48 48 --global-option=-j8 \
49 49 pydantic==${PYDANTIC_VERSION} \
50 && cat requirements.txt | xargs -n 1 pip install --no-cache-dir \
50 && cat requirements-slim.txt | xargs -n 1 pip install --no-cache-dir || true \
51 51 # Remove build packages
52 52 && pip uninstall --yes \
53 53 Cython \
Modified g4f/Provider/PollinationsAI.py +1 -2
@@ -46,8 +46,7 @@ class PollinationsAI(OpenaiAPI):
46 46 seed: str = None,
47 47 **kwargs
48 48 ) -> AsyncResult:
49 if model:
50 model = cls.get_model(model)
49 model = cls.get_model(model)
51 50 if model in cls.image_models:
52 51 if prompt is None:
53 52 prompt = messages[-1]["content"]
Modified g4f/Provider/needs_auth/Gemini.py +1 -0
@@ -313,6 +313,7 @@ class Conversation(BaseConversation):
313 313 self.conversation_id = conversation_id
314 314 self.response_id = response_id
315 315 self.choice_id = choice_id
316
316 317 async def iter_filter_base64(response_iter: AsyncIterator[bytes]) -> AsyncIterator[bytes]:
317 318 search_for = b'[["wrb.fr","XqA3Ic","[\\"'
318 319 end_with = b'\\'
Modified g4f/api/__init__.py +70 -45
@@ -8,21 +8,29 @@ import os
8 8 import shutil
9 9
10 10 import os.path
11 from fastapi import FastAPI, Response, Request, UploadFile
11 from fastapi import FastAPI, Response, Request, UploadFile, Depends
12 from fastapi.middleware.wsgi import WSGIMiddleware
12 13 from fastapi.responses import StreamingResponse, RedirectResponse, HTMLResponse, JSONResponse
13 14 from fastapi.exceptions import RequestValidationError
14 15 from fastapi.security import APIKeyHeader
15 16 from starlette.exceptions import HTTPException
16 from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN
17 from starlette.status import (
18 HTTP_200_OK,
19 HTTP_422_UNPROCESSABLE_ENTITY,
20 HTTP_404_NOT_FOUND,
21 HTTP_401_UNAUTHORIZED,
22 HTTP_403_FORBIDDEN
23 )
17 24 from fastapi.encoders import jsonable_encoder
25 from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
18 26 from fastapi.middleware.cors import CORSMiddleware
19 27 from starlette.responses import FileResponse
20 from pydantic import BaseModel
21 from typing import Union, Optional, List
28 from pydantic import BaseModel, Field
29 from typing import Union, Optional, List, Annotated
22 30
23 31 import g4f
24 32 import g4f.debug
25 from g4f.client import AsyncClient, ChatCompletion, convert_to_provider
33 from g4f.client import AsyncClient, ChatCompletion, ImagesResponse, convert_to_provider
26 34 from g4f.providers.response import BaseConversation
27 35 from g4f.client.helper import filter_none
28 36 from g4f.image import is_accepted_format, images_dir
@@ -30,6 +38,7 @@ from g4f.typing import Messages
30 38 from g4f.errors import ProviderNotFoundError
31 39 from g4f.cookies import read_cookie_files, get_cookies_dir
32 40 from g4f.Provider import ProviderType, ProviderUtils, __providers__
41 from g4f.gui import get_gui_app
33 42
34 43 logger = logging.getLogger(__name__)
35 44
@@ -50,6 +59,10 @@ def create_app(g4f_api_key: str = None):
50 59 api.register_authorization()
51 60 api.register_validation_exception_handler()
52 61
62 if AppConfig.gui:
63 gui_app = WSGIMiddleware(get_gui_app())
64 app.mount("/", gui_app)
65
53 66 # Read cookie files if not ignored
54 67 if not AppConfig.ignore_cookie_files:
55 68 read_cookie_files()
@@ -61,17 +74,17 @@ def create_app_debug(g4f_api_key: str = None):
61 74 return create_app(g4f_api_key)
62 75
63 76 class ChatCompletionsConfig(BaseModel):
64 messages: Messages
65 model: str
66 provider: Optional[str] = None
77 messages: Messages = Field(examples=[[{"role": "system", "content": ""}, {"role": "user", "content": ""}]])
78 model: str = Field(default="")
79 provider: Optional[str] = Field(examples=[None])
67 80 stream: bool = False
68 temperature: Optional[float] = None
69 max_tokens: Optional[int] = None
70 stop: Union[list[str], str, None] = None
71 api_key: Optional[str] = None
72 web_search: Optional[bool] = None
73 proxy: Optional[str] = None
74 conversation_id: str = None
81 temperature: Optional[float] = Field(examples=[None])
82 max_tokens: Optional[int] = Field(examples=[None])
83 stop: Union[list[str], str, None] = Field(examples=[None])
84 api_key: Optional[str] = Field(examples=[None])
85 web_search: Optional[bool] = Field(examples=[None])
86 proxy: Optional[str] = Field(examples=[None])
87 conversation_id: Optional[str] = Field(examples=[None])
75 88
76 89 class ImageGenerationConfig(BaseModel):
77 90 prompt: str
@@ -101,6 +114,9 @@ class ModelResponseModel(BaseModel):
101 114 created: int
102 115 owned_by: Optional[str]
103 116
117 class ErrorResponseModel(BaseModel):
118 error: str
119
104 120 class AppConfig:
105 121 ignored_providers: Optional[list[str]] = None
106 122 g4f_api_key: Optional[str] = None
@@ -109,6 +125,7 @@ class AppConfig:
109 125 provider: str = None
110 126 image_provider: str = None
111 127 proxy: str = None
128 gui: bool = False
112 129
113 130 @classmethod
114 131 def set_config(cls, **data):
@@ -129,6 +146,8 @@ class Api:
129 146 self.get_g4f_api_key = APIKeyHeader(name="g4f-api-key")
130 147 self.conversations: dict[str, dict[str, BaseConversation]] = {}
131 148
149 security = HTTPBearer(auto_error=False)
150
132 151 def register_authorization(self):
133 152 @self.app.middleware("http")
134 153 async def authorization(request: Request, call_next):
@@ -192,7 +211,7 @@ class Api:
192 211 } for model_id, model in model_list.items()]
193 212
194 213 @self.app.get("/v1/models/{model_name}")
195 async def model_info(model_name: str):
214 async def model_info(model_name: str) -> ModelResponseModel:
196 215 if model_name in g4f.models.ModelUtils.convert:
197 216 model_info = g4f.models.ModelUtils.convert[model_name]
198 217 return JSONResponse({
@@ -201,20 +220,20 @@ class Api:
201 220 'created': 0,
202 221 'owned_by': model_info.base_provider
203 222 })
204 return JSONResponse({"error": "The model does not exist."}, 404)
205
206 @self.app.post("/v1/chat/completions")
207 async def chat_completions(config: ChatCompletionsConfig, request: Request = None, provider: str = None):
223 return JSONResponse({"error": "The model does not exist."}, HTTP_404_NOT_FOUND)
224
225 @self.app.post("/v1/chat/completions", response_model=ChatCompletion)
226 async def chat_completions(
227 config: ChatCompletionsConfig,
228 credentials: Annotated[HTTPAuthorizationCredentials, Depends(Api.security)] = None,
229 provider: str = None
230 ):
208 231 try:
209 232 config.provider = provider if config.provider is None else config.provider
210 233 if config.provider is None:
211 234 config.provider = AppConfig.provider
212 if config.api_key is None and request is not None:
213 auth_header = request.headers.get("Authorization")
214 if auth_header is not None:
215 api_key = auth_header.split(None, 1)[-1]
216 if api_key and api_key != "Bearer":
217 config.api_key = api_key
235 if credentials is not None:
236 config.api_key = credentials.credentials
218 237
219 238 conversation = return_conversation = None
220 239 if config.conversation_id is not None and config.provider is not None:
@@ -242,8 +261,7 @@ class Api:
242 261 )
243 262
244 263 if not config.stream:
245 response: ChatCompletion = await response
246 return JSONResponse(response.to_json())
264 return await response
247 265
248 266 async def streaming():
249 267 try:
@@ -254,7 +272,7 @@ class Api:
254 272 self.conversations[config.conversation_id] = {}
255 273 self.conversations[config.conversation_id][config.provider] = chunk
256 274 else:
257 yield f"data: {json.dumps(chunk.to_json())}\n\n"
275 yield f"data: {chunk.json()}\n\n"
258 276 except GeneratorExit:
259 277 pass
260 278 except Exception as e:
@@ -268,15 +286,15 @@ class Api:
268 286 logger.exception(e)
269 287 return Response(content=format_exception(e, config), status_code=500, media_type="application/json")
270 288
271 @self.app.post("/v1/images/generate")
272 @self.app.post("/v1/images/generations")
273 async def generate_image(config: ImageGenerationConfig, request: Request):
274 if config.api_key is None:
275 auth_header = request.headers.get("Authorization")
276 if auth_header is not None:
277 api_key = auth_header.split(None, 1)[-1]
278 if api_key and api_key != "Bearer":
279 config.api_key = api_key
289 @self.app.post("/v1/images/generate", response_model=ImagesResponse)
290 @self.app.post("/v1/images/generations", response_model=ImagesResponse)
291 async def generate_image(
292 request: Request,
293 config: ImageGenerationConfig,
294 credentials: Annotated[HTTPAuthorizationCredentials, Depends(Api.security)] = None
295 ):
296 if credentials is not None:
297 config.api_key = credentials.credentials
280 298 try:
281 299 response = await self.client.images.generate(
282 300 prompt=config.prompt,
@@ -291,7 +309,7 @@ class Api:
291 309 for image in response.data:
292 310 if hasattr(image, "url") and image.url.startswith("/"):
293 311 image.url = f"{request.base_url}{image.url.lstrip('/')}"
294 return JSONResponse(response.to_json())
312 return response
295 313 except Exception as e:
296 314 logger.exception(e)
297 315 return Response(content=format_exception(e, config, True), status_code=500, media_type="application/json")
@@ -342,22 +360,29 @@ class Api:
342 360 file.file.close()
343 361 return response_data
344 362
345 @self.app.get("/v1/synthesize/{provider}")
363 @self.app.get("/v1/synthesize/{provider}", responses={
364 HTTP_200_OK: {"content": {"audio/*": {}}},
365 HTTP_404_NOT_FOUND: {"model": ErrorResponseModel},
366 HTTP_422_UNPROCESSABLE_ENTITY: {"model": ErrorResponseModel},
367 })
346 368 async def synthesize(request: Request, provider: str):
347 369 try:
348 370 provider_handler = convert_to_provider(provider)
349 371 except ProviderNotFoundError:
350 return Response("Provider not found", 404)
372 return JSONResponse({"error": "Provider not found"}, HTTP_404_NOT_FOUND)
351 373 if not hasattr(provider_handler, "synthesize"):
352 return Response("Provider doesn't support synthesize", 500)
374 return JSONResponse({"error": "Provider doesn't support synthesize"}, HTTP_404_NOT_FOUND)
353 375 if len(request.query_params) == 0:
354 return Response("Missing query params", 500)
376 return JSONResponse({"error": "Missing query params"}, HTTP_422_UNPROCESSABLE_ENTITY)
355 377 response_data = provider_handler.synthesize({**request.query_params})
356 378 content_type = getattr(provider_handler, "synthesize_content_type", "application/octet-stream")
357 379 return StreamingResponse(response_data, media_type=content_type)
358 380
359 @self.app.get("/images/{filename}")
360 async def get_image(filename) -> FileResponse:
381 @self.app.get("/images/{filename}", response_class=FileResponse, responses={
382 HTTP_200_OK: {"content": {"image/*": {}}},
383 HTTP_404_NOT_FOUND: {}
384 })
385 async def get_image(filename):
361 386 target = os.path.join(images_dir, filename)
362 387
363 388 if not os.path.isfile(target):
Modified g4f/cli.py +3 -1
@@ -12,6 +12,7 @@ def main():
12 12 api_parser = subparsers.add_parser("api")
13 13 api_parser.add_argument("--bind", default="0.0.0.0:1337", help="The bind string.")
14 14 api_parser.add_argument("--debug", action="store_true", help="Enable verbose logging.")
15 api_parser.add_argument("--gui", "-g", default=False, action="store_true", help="Add gui to the api.")
15 16 api_parser.add_argument("--model", default=None, help="Default model for chat completion. (incompatible with --reload and --workers)")
16 17 api_parser.add_argument("--provider", choices=[provider.__name__ for provider in Provider.__providers__ if provider.working],
17 18 default=None, help="Default provider for chat completion. (incompatible with --reload and --workers)")
@@ -48,7 +49,8 @@ def run_api_args(args):
48 49 provider=args.provider,
49 50 image_provider=args.image_provider,
50 51 proxy=args.proxy,
51 model=args.model
52 model=args.model,
53 gui=args.gui,
52 54 )
53 55 g4f.cookies.browsers = [g4f.cookies[browser] for browser in args.cookie_browsers]
54 56 run_api(
Modified g4f/client/__init__.py +10 -10
@@ -73,7 +73,7 @@ def iter_response(
73 73 finish_reason = "stop"
74 74
75 75 if stream:
76 yield ChatCompletionChunk(chunk, None, completion_id, int(time.time()))
76 yield ChatCompletionChunk.model_construct(chunk, None, completion_id, int(time.time()))
77 77
78 78 if finish_reason is not None:
79 79 break
@@ -83,12 +83,12 @@ def iter_response(
83 83 finish_reason = "stop" if finish_reason is None else finish_reason
84 84
85 85 if stream:
86 yield ChatCompletionChunk(None, finish_reason, completion_id, int(time.time()))
86 yield ChatCompletionChunk.model_construct(None, finish_reason, completion_id, int(time.time()))
87 87 else:
88 88 if response_format is not None and "type" in response_format:
89 89 if response_format["type"] == "json_object":
90 90 content = filter_json(content)
91 yield ChatCompletion(content, finish_reason, completion_id, int(time.time()))
91 yield ChatCompletion.model_construct(content, finish_reason, completion_id, int(time.time()))
92 92
93 93 # Synchronous iter_append_model_and_provider function
94 94 def iter_append_model_and_provider(response: ChatCompletionResponseType) -> ChatCompletionResponseType:
@@ -137,7 +137,7 @@ async def async_iter_response(
137 137 finish_reason = "stop"
138 138
139 139 if stream:
140 yield ChatCompletionChunk(chunk, None, completion_id, int(time.time()))
140 yield ChatCompletionChunk.model_construct(chunk, None, completion_id, int(time.time()))
141 141
142 142 if finish_reason is not None:
143 143 break
@@ -145,12 +145,12 @@ async def async_iter_response(
145 145 finish_reason = "stop" if finish_reason is None else finish_reason
146 146
147 147 if stream:
148 yield ChatCompletionChunk(None, finish_reason, completion_id, int(time.time()))
148 yield ChatCompletionChunk.model_construct(None, finish_reason, completion_id, int(time.time()))
149 149 else:
150 150 if response_format is not None and "type" in response_format:
151 151 if response_format["type"] == "json_object":
152 152 content = filter_json(content)
153 yield ChatCompletion(content, finish_reason, completion_id, int(time.time()))
153 yield ChatCompletion.model_construct(content, finish_reason, completion_id, int(time.time()))
154 154 finally:
155 155 if hasattr(response, 'aclose'):
156 156 await safe_aclose(response)
@@ -394,13 +394,13 @@ class Images:
394 394 if response_format == "b64_json":
395 395 with open(os.path.join(images_dir, os.path.basename(image_file)), "rb") as file:
396 396 image_data = base64.b64encode(file.read()).decode()
397 return Image(url=image_file, b64_json=image_data, revised_prompt=response.alt)
398 return Image(url=image_file, revised_prompt=response.alt)
397 return Image.model_construct(url=image_file, b64_json=image_data, revised_prompt=response.alt)
398 return Image.model_construct(url=image_file, revised_prompt=response.alt)
399 399 images = await asyncio.gather(*[process_image_item(image) for image in images])
400 400 else:
401 images = [Image(url=image, revised_prompt=response.alt) for image in response.get_list()]
401 images = [Image.model_construct(url=image, revised_prompt=response.alt) for image in response.get_list()]
402 402 last_provider = get_last_provider(True)
403 return ImagesResponse(
403 return ImagesResponse.model_construct(
404 404 images,
405 405 model=last_provider.get("model") if model is None else model,
406 406 provider=last_provider.get("name") if provider is None else provider
Modified g4f/client/stubs.py +128 -108
@@ -1,130 +1,150 @@
1 1 from __future__ import annotations
2 2
3 from typing import Union
3 from typing import Optional, List, Dict
4 4 from time import time
5 5
6 class Model():
7 ...
6 from .helper import filter_none
7
8 try:
9 from pydantic import BaseModel, Field
10 except ImportError:
11 class BaseModel():
12 @classmethod
13 def model_construct(cls, **data):
14 new = cls()
15 for key, value in data.items():
16 setattr(new, key, value)
17 return new
18 class Field():
19 def __init__(self, **config):
20 pass
21
22 class ChatCompletionChunk(BaseModel):
23 id: str
24 object: str
25 created: int
26 model: str
27 provider: Optional[str]
28 choices: List[ChatCompletionDeltaChoice]
8 29
9 class ChatCompletion(Model):
10 def __init__(
11 self,
30 @classmethod
31 def model_construct(
32 cls,
12 33 content: str,
13 34 finish_reason: str,
14 35 completion_id: str = None,
15 36 created: int = None
16 37 ):
17 self.id: str = f"chatcmpl-{completion_id}" if completion_id else None
18 self.object: str = "chat.completion"
19 self.created: int = created
20 self.model: str = None
21 self.provider: str = None
22 self.choices = [ChatCompletionChoice(ChatCompletionMessage(content), finish_reason)]
23 self.usage: dict[str, int] = {
24 "prompt_tokens": 0, #prompt_tokens,
25 "completion_tokens": 0, #completion_tokens,
26 "total_tokens": 0, #prompt_tokens + completion_tokens,
27 }
28
29 def to_json(self):
30 return {
31 **self.__dict__,
32 "choices": [choice.to_json() for choice in self.choices]
33 }
34
35 class ChatCompletionChunk(Model):
36 def __init__(
37 self,
38 return super().model_construct(
39 id=f"chatcmpl-{completion_id}" if completion_id else None,
40 object="chat.completion.cunk",
41 created=created,
42 model=None,
43 provider=None,
44 choices=[ChatCompletionDeltaChoice.model_construct(
45 ChatCompletionDelta.model_construct(content),
46 finish_reason
47 )]
48 )
49
50 class ChatCompletionMessage(BaseModel):
51 role: str
52 content: str
53
54 @classmethod
55 def model_construct(cls, content: str):
56 return super().model_construct(role="assistant", content=content)
57
58 class ChatCompletionChoice(BaseModel):
59 index: int
60 message: ChatCompletionMessage
61 finish_reason: str
62
63 @classmethod
64 def model_construct(cls, message: ChatCompletionMessage, finish_reason: str):
65 return super().model_construct(index=0, message=message, finish_reason=finish_reason)
66
67 class ChatCompletion(BaseModel):
68 id: str
69 object: str
70 created: int
71 model: str
72 provider: Optional[str]
73 choices: List[ChatCompletionChoice]
74 usage: Dict[str, int] = Field(examples=[{
75 "prompt_tokens": 0, #prompt_tokens,
76 "completion_tokens": 0, #completion_tokens,
77 "total_tokens": 0, #prompt_tokens + completion_tokens,
78 }])
79
80 @classmethod
81 def model_construct(
82 cls,
38 83 content: str,
39 84 finish_reason: str,
40 85 completion_id: str = None,
41 86 created: int = None
42 87 ):
43 self.id: str = f"chatcmpl-{completion_id}" if completion_id else None
44 self.object: str = "chat.completion.chunk"
45 self.created: int = created
46 self.model: str = None
47 self.provider: str = None
48 self.choices = [ChatCompletionDeltaChoice(ChatCompletionDelta(content), finish_reason)]
49
50 def to_json(self):
51 return {
52 **self.__dict__,
53 "choices": [choice.to_json() for choice in self.choices]
54 }
55
56 class ChatCompletionMessage(Model):
57 def __init__(self, content: Union[str, None]):
58 self.role = "assistant"
59 self.content = content
60
61 def to_json(self):
62 return self.__dict__
63
64 class ChatCompletionChoice(Model):
65 def __init__(self, message: ChatCompletionMessage, finish_reason: str):
66 self.index = 0
67 self.message = message
68 self.finish_reason = finish_reason
69
70 def to_json(self):
71 return {
72 **self.__dict__,
73 "message": self.message.to_json()
74 }
75
76 class ChatCompletionDelta(Model):
77 content: Union[str, None] = None
78
79 def __init__(self, content: Union[str, None]):
80 if content is not None:
81 self.content = content
82 self.role = "assistant"
83
84 def to_json(self):
85 return self.__dict__
86
87 class ChatCompletionDeltaChoice(Model):
88 def __init__(self, delta: ChatCompletionDelta, finish_reason: Union[str, None]):
89 self.index = 0
90 self.delta = delta
91 self.finish_reason = finish_reason
92
93 def to_json(self):
94 return {
95 **self.__dict__,
96 "delta": self.delta.to_json()
97 }
98
99 class Image(Model):
100 def __init__(self, url: str = None, b64_json: str = None, revised_prompt: str = None) -> None:
101 if url is not None:
102 self.url = url
103 if b64_json is not None:
104 self.b64_json = b64_json
105 if revised_prompt is not None:
106 self.revised_prompt = revised_prompt
107
108 def to_json(self):
109 return self.__dict__
110
111 class ImagesResponse(Model):
88 return super().model_construct(
89 id=f"chatcmpl-{completion_id}" if completion_id else None,
90 object="chat.completion",
91 created=created,
92 model=None,
93 provider=None,
94 choices=[ChatCompletionChoice.model_construct(
95 ChatCompletionMessage.model_construct(content),
96 finish_reason
97 )],
98 usage={
99 "prompt_tokens": 0, #prompt_tokens,
100 "completion_tokens": 0, #completion_tokens,
101 "total_tokens": 0, #prompt_tokens + completion_tokens,
102 }
103 )
104
105 class ChatCompletionDelta(BaseModel):
106 role: str
107 content: str
108
109 @classmethod
110 def model_construct(cls, content: Optional[str]):
111 return super().model_construct(role="assistant", content=content)
112
113 class ChatCompletionDeltaChoice(BaseModel):
114 index: int
115 delta: ChatCompletionDelta
116 finish_reason: Optional[str]
117
118 @classmethod
119 def model_construct(cls, delta: ChatCompletionDelta, finish_reason: Optional[str]):
120 return super().model_construct(index=0, delta=delta, finish_reason=finish_reason)
121
122 class Image(BaseModel):
123 url: Optional[str]
124 b64_json: Optional[str]
125 revised_prompt: Optional[str]
126
127 @classmethod
128 def model_construct(cls, url: str = None, b64_json: str = None, revised_prompt: str = None):
129 return super().model_construct(**filter_none(
130 url=url,
131 b64_json=b64_json,
132 revised_prompt=revised_prompt
133 ))
134
135 class ImagesResponse(BaseModel):
112 136 data: list[Image]
113 137 model: str
114 138 provider: str
115 139 created: int
116 140
117 def __init__(self, data: list[Image], created: int = None, model: str = None, provider: str = None) -> None:
118 self.data = data
141 @classmethod
142 def model_construct(cls, data: list[Image], created: int = None, model: str = None, provider: str = None):
119 143 if created is None:
120 144 created = int(time())
121 self.model = model
122 if provider is not None:
123 self.provider = provider
124 self.created = created
125
126 def to_json(self):
127 return {
128 **self.__dict__,
129 "data": [image.to_json() for image in self.data]
130 }
145 return super().model_construct(
146 data=data,
147 model=model,
148 provider=provider,
149 created=created
150 )
Modified g4f/gui/__init__.py +16 -12
@@ -8,22 +8,13 @@ try:
8 8 except ImportError as e:
9 9 import_error = e
10 10
11 def run_gui(host: str = '0.0.0.0', port: int = 8080, debug: bool = False) -> None:
12 if import_error is not None:
13 raise MissingRequirementsError(f'Install "gui" requirements | pip install -U g4f[gui]\n{import_error}')
14
15 config = {
16 'host' : host,
17 'port' : port,
18 'debug': debug
19 }
20
11 def get_gui_app():
21 12 site = Website(app)
22 13 for route in site.routes:
23 14 app.add_url_rule(
24 15 route,
25 view_func = site.routes[route]['function'],
26 methods = site.routes[route]['methods'],
16 view_func=site.routes[route]['function'],
17 methods=site.routes[route]['methods'],
27 18 )
28 19
29 20 backend_api = Backend_Api(app)
@@ -33,6 +24,19 @@ def run_gui(host: str = '0.0.0.0', port: int = 8080, debug: bool = False) -> Non
33 24 view_func = backend_api.routes[route]['function'],
34 25 methods = backend_api.routes[route]['methods'],
35 26 )
27 return app
28
29 def run_gui(host: str = '0.0.0.0', port: int = 8080, debug: bool = False) -> None:
30 if import_error is not None:
31 raise MissingRequirementsError(f'Install "gui" requirements | pip install -U g4f[gui]\n{import_error}')
32
33 config = {
34 'host' : host,
35 'port' : port,
36 'debug': debug
37 }
38
39 get_gui_app()
36 40
37 41 print(f"Running on port {config['port']}")
38 42 app.run(**config)
Modified g4f/gui/server/api.py +4 -36
@@ -22,11 +22,11 @@ conversations: dict[dict[str, BaseConversation]] = {}
22 22
23 23 class Api:
24 24 @staticmethod
25 def get_models() -> list[str]:
25 def get_models():
26 26 return models._all_models
27 27
28 28 @staticmethod
29 def get_provider_models(provider: str, api_key: str = None) -> list[dict]:
29 def get_provider_models(provider: str, api_key: str = None):
30 30 if provider in __map__:
31 31 provider: ProviderType = __map__[provider]
32 32 if issubclass(provider, ProviderModelMixin):
@@ -46,39 +46,7 @@ class Api:
46 46 return []
47 47
48 48 @staticmethod
49 def get_image_models() -> list[dict]:
50 image_models = []
51 index = []
52 for provider in __providers__:
53 if hasattr(provider, "image_models"):
54 if hasattr(provider, "get_models"):
55 provider.get_models()
56 parent = provider
57 if hasattr(provider, "parent"):
58 parent = __map__[provider.parent]
59 if parent.__name__ not in index:
60 for model in provider.image_models:
61 image_models.append({
62 "provider": parent.__name__,
63 "url": parent.url,
64 "label": parent.label if hasattr(parent, "label") else None,
65 "image_model": model,
66 "vision_model": getattr(parent, "default_vision_model", None)
67 })
68 index.append(parent.__name__)
69 elif hasattr(provider, "default_vision_model") and provider.__name__ not in index:
70 image_models.append({
71 "provider": provider.__name__,
72 "url": provider.url,
73 "label": provider.label if hasattr(provider, "label") else None,
74 "image_model": None,
75 "vision_model": provider.default_vision_model
76 })
77 index.append(provider.__name__)
78 return image_models
79
80 @staticmethod
81 def get_providers() -> list[str]:
49 def get_providers() -> dict[str, str]:
82 50 return {
83 51 provider.__name__: (provider.label if hasattr(provider, "label") else provider.__name__)
84 52 + (" (Image Generation)" if getattr(provider, "image_models", None) else "")
@@ -90,7 +58,7 @@ class Api:
90 58 }
91 59
92 60 @staticmethod
93 def get_version():
61 def get_version() -> dict:
94 62 try:
95 63 current_version = version.utils.current_version
96 64 except VersionNotFoundError:
Modified g4f/gui/server/backend.py +16 -7
@@ -3,7 +3,7 @@ import flask
3 3 import os
4 4 import logging
5 5 import asyncio
6 from flask import request, Flask
6 from flask import Flask, request, jsonify
7 7 from typing import Generator
8 8 from werkzeug.utils import secure_filename
9 9
@@ -42,17 +42,26 @@ class Backend_Api(Api):
42 42 app (Flask): Flask application instance to attach routes to.
43 43 """
44 44 self.app: Flask = app
45
46 def jsonify_models(**kwargs):
47 response = self.get_models(**kwargs)
48 if isinstance(response, list):
49 return jsonify(response)
50 return response
51
52 def jsonify_provider_models(**kwargs):
53 response = self.get_provider_models(**kwargs)
54 if isinstance(response, list):
55 return jsonify(response)
56 return response
57
45 58 self.routes = {
46 59 '/backend-api/v2/models': {
47 'function': self.get_models,
60 'function': jsonify_models,
48 61 'methods': ['GET']
49 62 },
50 63 '/backend-api/v2/models/<provider>': {
51 'function': self.get_provider_models,
52 'methods': ['GET']
53 },
54 '/backend-api/v2/image_models': {
55 'function': self.get_image_models,
64 'function': jsonify_provider_models,
56 65 'methods': ['GET']
57 66 },
58 67 '/backend-api/v2/providers': {
Modified g4f/gui/server/website.py +4 -3
@@ -1,11 +1,12 @@
1 1 import uuid
2 2 from flask import render_template, redirect
3 3
4 def redirect_home():
5 return redirect('/chat')
6
4 7 class Website:
5 8 def __init__(self, app) -> None:
6 9 self.app = app
7 def redirect_home():
8 return redirect('/chat')
9 10 self.routes = {
10 11 '/': {
11 12 'function': redirect_home,
@@ -35,7 +36,7 @@ class Website:
35 36
36 37 def _chat(self, conversation_id):
37 38 if '-' not in conversation_id:
38 return redirect('/chat')
39 return redirect_home()
39 40 return render_template('index.html', chat_id=conversation_id)
40 41
41 42 def _index(self):
Modified g4f/requests/raise_for_status.py +1 -1
@@ -11,7 +11,7 @@ class CloudflareError(ResponseStatusError):
11 11 ...
12 12
13 13 def is_cloudflare(text: str) -> bool:
14 if "Generated by cloudfront" in text:
14 if "Generated by cloudfront" in text or '<p id="cf-spinner-please-wait">' in text:
15 15 return True
16 16 elif "<title>Attention Required! | Cloudflare</title>" in text or 'id="cf-cloudflare-status"' in text:
17 17 return True