* Fix arm v7 build / improve api

* Update stubs.py

* Fix unit tests
This commit is contained in:
H Lohaus
2024-11-24 17:43:45 +01:00
committed by GitHub
parent 4744d0b77d
commit 804a80bc7c
12 changed files with 248 additions and 219 deletions

View File

@@ -47,7 +47,7 @@ RUN python -m pip install --upgrade pip \
--global-option=build_ext \ --global-option=build_ext \
--global-option=-j8 \ --global-option=-j8 \
pydantic==${PYDANTIC_VERSION} \ pydantic==${PYDANTIC_VERSION} \
&& cat requirements.txt | xargs -n 1 pip install --no-cache-dir \ && cat requirements-slim.txt | xargs -n 1 pip install --no-cache-dir || true \
# Remove build packages # Remove build packages
&& pip uninstall --yes \ && pip uninstall --yes \
Cython \ Cython \

View File

@@ -46,7 +46,6 @@ class PollinationsAI(OpenaiAPI):
seed: str = None, seed: str = None,
**kwargs **kwargs
) -> AsyncResult: ) -> AsyncResult:
if model:
model = cls.get_model(model) model = cls.get_model(model)
if model in cls.image_models: if model in cls.image_models:
if prompt is None: if prompt is None:

View File

@@ -313,6 +313,7 @@ class Conversation(BaseConversation):
self.conversation_id = conversation_id self.conversation_id = conversation_id
self.response_id = response_id self.response_id = response_id
self.choice_id = choice_id self.choice_id = choice_id
async def iter_filter_base64(response_iter: AsyncIterator[bytes]) -> AsyncIterator[bytes]: async def iter_filter_base64(response_iter: AsyncIterator[bytes]) -> AsyncIterator[bytes]:
search_for = b'[["wrb.fr","XqA3Ic","[\\"' search_for = b'[["wrb.fr","XqA3Ic","[\\"'
end_with = b'\\' end_with = b'\\'

View File

@@ -8,21 +8,29 @@ import os
import shutil import shutil
import os.path import os.path
from fastapi import FastAPI, Response, Request, UploadFile from fastapi import FastAPI, Response, Request, UploadFile, Depends
from fastapi.middleware.wsgi import WSGIMiddleware
from fastapi.responses import StreamingResponse, RedirectResponse, HTMLResponse, JSONResponse from fastapi.responses import StreamingResponse, RedirectResponse, HTMLResponse, JSONResponse
from fastapi.exceptions import RequestValidationError from fastapi.exceptions import RequestValidationError
from fastapi.security import APIKeyHeader from fastapi.security import APIKeyHeader
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN from starlette.status import (
HTTP_200_OK,
HTTP_422_UNPROCESSABLE_ENTITY,
HTTP_404_NOT_FOUND,
HTTP_401_UNAUTHORIZED,
HTTP_403_FORBIDDEN
)
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from starlette.responses import FileResponse from starlette.responses import FileResponse
from pydantic import BaseModel from pydantic import BaseModel, Field
from typing import Union, Optional, List from typing import Union, Optional, List, Annotated
import g4f import g4f
import g4f.debug import g4f.debug
from g4f.client import AsyncClient, ChatCompletion, convert_to_provider from g4f.client import AsyncClient, ChatCompletion, ImagesResponse, convert_to_provider
from g4f.providers.response import BaseConversation from g4f.providers.response import BaseConversation
from g4f.client.helper import filter_none from g4f.client.helper import filter_none
from g4f.image import is_accepted_format, images_dir from g4f.image import is_accepted_format, images_dir
@@ -30,6 +38,7 @@ from g4f.typing import Messages
from g4f.errors import ProviderNotFoundError from g4f.errors import ProviderNotFoundError
from g4f.cookies import read_cookie_files, get_cookies_dir from g4f.cookies import read_cookie_files, get_cookies_dir
from g4f.Provider import ProviderType, ProviderUtils, __providers__ from g4f.Provider import ProviderType, ProviderUtils, __providers__
from g4f.gui import get_gui_app
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -50,6 +59,10 @@ def create_app(g4f_api_key: str = None):
api.register_authorization() api.register_authorization()
api.register_validation_exception_handler() api.register_validation_exception_handler()
if AppConfig.gui:
gui_app = WSGIMiddleware(get_gui_app())
app.mount("/", gui_app)
# Read cookie files if not ignored # Read cookie files if not ignored
if not AppConfig.ignore_cookie_files: if not AppConfig.ignore_cookie_files:
read_cookie_files() read_cookie_files()
@@ -61,17 +74,17 @@ def create_app_debug(g4f_api_key: str = None):
return create_app(g4f_api_key) return create_app(g4f_api_key)
class ChatCompletionsConfig(BaseModel): class ChatCompletionsConfig(BaseModel):
messages: Messages messages: Messages = Field(examples=[[{"role": "system", "content": ""}, {"role": "user", "content": ""}]])
model: str model: str = Field(default="")
provider: Optional[str] = None provider: Optional[str] = Field(examples=[None])
stream: bool = False stream: bool = False
temperature: Optional[float] = None temperature: Optional[float] = Field(examples=[None])
max_tokens: Optional[int] = None max_tokens: Optional[int] = Field(examples=[None])
stop: Union[list[str], str, None] = None stop: Union[list[str], str, None] = Field(examples=[None])
api_key: Optional[str] = None api_key: Optional[str] = Field(examples=[None])
web_search: Optional[bool] = None web_search: Optional[bool] = Field(examples=[None])
proxy: Optional[str] = None proxy: Optional[str] = Field(examples=[None])
conversation_id: str = None conversation_id: Optional[str] = Field(examples=[None])
class ImageGenerationConfig(BaseModel): class ImageGenerationConfig(BaseModel):
prompt: str prompt: str
@@ -101,6 +114,9 @@ class ModelResponseModel(BaseModel):
created: int created: int
owned_by: Optional[str] owned_by: Optional[str]
class ErrorResponseModel(BaseModel):
error: str
class AppConfig: class AppConfig:
ignored_providers: Optional[list[str]] = None ignored_providers: Optional[list[str]] = None
g4f_api_key: Optional[str] = None g4f_api_key: Optional[str] = None
@@ -109,6 +125,7 @@ class AppConfig:
provider: str = None provider: str = None
image_provider: str = None image_provider: str = None
proxy: str = None proxy: str = None
gui: bool = False
@classmethod @classmethod
def set_config(cls, **data): def set_config(cls, **data):
@@ -129,6 +146,8 @@ class Api:
self.get_g4f_api_key = APIKeyHeader(name="g4f-api-key") self.get_g4f_api_key = APIKeyHeader(name="g4f-api-key")
self.conversations: dict[str, dict[str, BaseConversation]] = {} self.conversations: dict[str, dict[str, BaseConversation]] = {}
security = HTTPBearer(auto_error=False)
def register_authorization(self): def register_authorization(self):
@self.app.middleware("http") @self.app.middleware("http")
async def authorization(request: Request, call_next): async def authorization(request: Request, call_next):
@@ -192,7 +211,7 @@ class Api:
} for model_id, model in model_list.items()] } for model_id, model in model_list.items()]
@self.app.get("/v1/models/{model_name}") @self.app.get("/v1/models/{model_name}")
async def model_info(model_name: str): async def model_info(model_name: str) -> ModelResponseModel:
if model_name in g4f.models.ModelUtils.convert: if model_name in g4f.models.ModelUtils.convert:
model_info = g4f.models.ModelUtils.convert[model_name] model_info = g4f.models.ModelUtils.convert[model_name]
return JSONResponse({ return JSONResponse({
@@ -201,20 +220,20 @@ class Api:
'created': 0, 'created': 0,
'owned_by': model_info.base_provider 'owned_by': model_info.base_provider
}) })
return JSONResponse({"error": "The model does not exist."}, 404) return JSONResponse({"error": "The model does not exist."}, HTTP_404_NOT_FOUND)
@self.app.post("/v1/chat/completions") @self.app.post("/v1/chat/completions", response_model=ChatCompletion)
async def chat_completions(config: ChatCompletionsConfig, request: Request = None, provider: str = None): async def chat_completions(
config: ChatCompletionsConfig,
credentials: Annotated[HTTPAuthorizationCredentials, Depends(Api.security)] = None,
provider: str = None
):
try: try:
config.provider = provider if config.provider is None else config.provider config.provider = provider if config.provider is None else config.provider
if config.provider is None: if config.provider is None:
config.provider = AppConfig.provider config.provider = AppConfig.provider
if config.api_key is None and request is not None: if credentials is not None:
auth_header = request.headers.get("Authorization") config.api_key = credentials.credentials
if auth_header is not None:
api_key = auth_header.split(None, 1)[-1]
if api_key and api_key != "Bearer":
config.api_key = api_key
conversation = return_conversation = None conversation = return_conversation = None
if config.conversation_id is not None and config.provider is not None: if config.conversation_id is not None and config.provider is not None:
@@ -242,8 +261,7 @@ class Api:
) )
if not config.stream: if not config.stream:
response: ChatCompletion = await response return await response
return JSONResponse(response.to_json())
async def streaming(): async def streaming():
try: try:
@@ -254,7 +272,7 @@ class Api:
self.conversations[config.conversation_id] = {} self.conversations[config.conversation_id] = {}
self.conversations[config.conversation_id][config.provider] = chunk self.conversations[config.conversation_id][config.provider] = chunk
else: else:
yield f"data: {json.dumps(chunk.to_json())}\n\n" yield f"data: {chunk.json()}\n\n"
except GeneratorExit: except GeneratorExit:
pass pass
except Exception as e: except Exception as e:
@@ -268,15 +286,15 @@ class Api:
logger.exception(e) logger.exception(e)
return Response(content=format_exception(e, config), status_code=500, media_type="application/json") return Response(content=format_exception(e, config), status_code=500, media_type="application/json")
@self.app.post("/v1/images/generate") @self.app.post("/v1/images/generate", response_model=ImagesResponse)
@self.app.post("/v1/images/generations") @self.app.post("/v1/images/generations", response_model=ImagesResponse)
async def generate_image(config: ImageGenerationConfig, request: Request): async def generate_image(
if config.api_key is None: request: Request,
auth_header = request.headers.get("Authorization") config: ImageGenerationConfig,
if auth_header is not None: credentials: Annotated[HTTPAuthorizationCredentials, Depends(Api.security)] = None
api_key = auth_header.split(None, 1)[-1] ):
if api_key and api_key != "Bearer": if credentials is not None:
config.api_key = api_key config.api_key = credentials.credentials
try: try:
response = await self.client.images.generate( response = await self.client.images.generate(
prompt=config.prompt, prompt=config.prompt,
@@ -291,7 +309,7 @@ class Api:
for image in response.data: for image in response.data:
if hasattr(image, "url") and image.url.startswith("/"): if hasattr(image, "url") and image.url.startswith("/"):
image.url = f"{request.base_url}{image.url.lstrip('/')}" image.url = f"{request.base_url}{image.url.lstrip('/')}"
return JSONResponse(response.to_json()) return response
except Exception as e: except Exception as e:
logger.exception(e) logger.exception(e)
return Response(content=format_exception(e, config, True), status_code=500, media_type="application/json") return Response(content=format_exception(e, config, True), status_code=500, media_type="application/json")
@@ -342,22 +360,29 @@ class Api:
file.file.close() file.file.close()
return response_data return response_data
@self.app.get("/v1/synthesize/{provider}") @self.app.get("/v1/synthesize/{provider}", responses={
HTTP_200_OK: {"content": {"audio/*": {}}},
HTTP_404_NOT_FOUND: {"model": ErrorResponseModel},
HTTP_422_UNPROCESSABLE_ENTITY: {"model": ErrorResponseModel},
})
async def synthesize(request: Request, provider: str): async def synthesize(request: Request, provider: str):
try: try:
provider_handler = convert_to_provider(provider) provider_handler = convert_to_provider(provider)
except ProviderNotFoundError: except ProviderNotFoundError:
return Response("Provider not found", 404) return JSONResponse({"error": "Provider not found"}, HTTP_404_NOT_FOUND)
if not hasattr(provider_handler, "synthesize"): if not hasattr(provider_handler, "synthesize"):
return Response("Provider doesn't support synthesize", 500) return JSONResponse({"error": "Provider doesn't support synthesize"}, HTTP_404_NOT_FOUND)
if len(request.query_params) == 0: if len(request.query_params) == 0:
return Response("Missing query params", 500) return JSONResponse({"error": "Missing query params"}, HTTP_422_UNPROCESSABLE_ENTITY)
response_data = provider_handler.synthesize({**request.query_params}) response_data = provider_handler.synthesize({**request.query_params})
content_type = getattr(provider_handler, "synthesize_content_type", "application/octet-stream") content_type = getattr(provider_handler, "synthesize_content_type", "application/octet-stream")
return StreamingResponse(response_data, media_type=content_type) return StreamingResponse(response_data, media_type=content_type)
@self.app.get("/images/{filename}") @self.app.get("/images/{filename}", response_class=FileResponse, responses={
async def get_image(filename) -> FileResponse: HTTP_200_OK: {"content": {"image/*": {}}},
HTTP_404_NOT_FOUND: {}
})
async def get_image(filename):
target = os.path.join(images_dir, filename) target = os.path.join(images_dir, filename)
if not os.path.isfile(target): if not os.path.isfile(target):

View File

@@ -12,6 +12,7 @@ def main():
api_parser = subparsers.add_parser("api") api_parser = subparsers.add_parser("api")
api_parser.add_argument("--bind", default="0.0.0.0:1337", help="The bind string.") api_parser.add_argument("--bind", default="0.0.0.0:1337", help="The bind string.")
api_parser.add_argument("--debug", action="store_true", help="Enable verbose logging.") api_parser.add_argument("--debug", action="store_true", help="Enable verbose logging.")
api_parser.add_argument("--gui", "-g", default=False, action="store_true", help="Add gui to the api.")
api_parser.add_argument("--model", default=None, help="Default model for chat completion. (incompatible with --reload and --workers)") api_parser.add_argument("--model", default=None, help="Default model for chat completion. (incompatible with --reload and --workers)")
api_parser.add_argument("--provider", choices=[provider.__name__ for provider in Provider.__providers__ if provider.working], api_parser.add_argument("--provider", choices=[provider.__name__ for provider in Provider.__providers__ if provider.working],
default=None, help="Default provider for chat completion. (incompatible with --reload and --workers)") default=None, help="Default provider for chat completion. (incompatible with --reload and --workers)")
@@ -48,7 +49,8 @@ def run_api_args(args):
provider=args.provider, provider=args.provider,
image_provider=args.image_provider, image_provider=args.image_provider,
proxy=args.proxy, proxy=args.proxy,
model=args.model model=args.model,
gui=args.gui,
) )
g4f.cookies.browsers = [g4f.cookies[browser] for browser in args.cookie_browsers] g4f.cookies.browsers = [g4f.cookies[browser] for browser in args.cookie_browsers]
run_api( run_api(

View File

@@ -73,7 +73,7 @@ def iter_response(
finish_reason = "stop" finish_reason = "stop"
if stream: if stream:
yield ChatCompletionChunk(chunk, None, completion_id, int(time.time())) yield ChatCompletionChunk.model_construct(chunk, None, completion_id, int(time.time()))
if finish_reason is not None: if finish_reason is not None:
break break
@@ -83,12 +83,12 @@ def iter_response(
finish_reason = "stop" if finish_reason is None else finish_reason finish_reason = "stop" if finish_reason is None else finish_reason
if stream: if stream:
yield ChatCompletionChunk(None, finish_reason, completion_id, int(time.time())) yield ChatCompletionChunk.model_construct(None, finish_reason, completion_id, int(time.time()))
else: else:
if response_format is not None and "type" in response_format: if response_format is not None and "type" in response_format:
if response_format["type"] == "json_object": if response_format["type"] == "json_object":
content = filter_json(content) content = filter_json(content)
yield ChatCompletion(content, finish_reason, completion_id, int(time.time())) yield ChatCompletion.model_construct(content, finish_reason, completion_id, int(time.time()))
# Synchronous iter_append_model_and_provider function # Synchronous iter_append_model_and_provider function
def iter_append_model_and_provider(response: ChatCompletionResponseType) -> ChatCompletionResponseType: def iter_append_model_and_provider(response: ChatCompletionResponseType) -> ChatCompletionResponseType:
@@ -137,7 +137,7 @@ async def async_iter_response(
finish_reason = "stop" finish_reason = "stop"
if stream: if stream:
yield ChatCompletionChunk(chunk, None, completion_id, int(time.time())) yield ChatCompletionChunk.model_construct(chunk, None, completion_id, int(time.time()))
if finish_reason is not None: if finish_reason is not None:
break break
@@ -145,12 +145,12 @@ async def async_iter_response(
finish_reason = "stop" if finish_reason is None else finish_reason finish_reason = "stop" if finish_reason is None else finish_reason
if stream: if stream:
yield ChatCompletionChunk(None, finish_reason, completion_id, int(time.time())) yield ChatCompletionChunk.model_construct(None, finish_reason, completion_id, int(time.time()))
else: else:
if response_format is not None and "type" in response_format: if response_format is not None and "type" in response_format:
if response_format["type"] == "json_object": if response_format["type"] == "json_object":
content = filter_json(content) content = filter_json(content)
yield ChatCompletion(content, finish_reason, completion_id, int(time.time())) yield ChatCompletion.model_construct(content, finish_reason, completion_id, int(time.time()))
finally: finally:
if hasattr(response, 'aclose'): if hasattr(response, 'aclose'):
await safe_aclose(response) await safe_aclose(response)
@@ -394,13 +394,13 @@ class Images:
if response_format == "b64_json": if response_format == "b64_json":
with open(os.path.join(images_dir, os.path.basename(image_file)), "rb") as file: with open(os.path.join(images_dir, os.path.basename(image_file)), "rb") as file:
image_data = base64.b64encode(file.read()).decode() image_data = base64.b64encode(file.read()).decode()
return Image(url=image_file, b64_json=image_data, revised_prompt=response.alt) return Image.model_construct(url=image_file, b64_json=image_data, revised_prompt=response.alt)
return Image(url=image_file, revised_prompt=response.alt) return Image.model_construct(url=image_file, revised_prompt=response.alt)
images = await asyncio.gather(*[process_image_item(image) for image in images]) images = await asyncio.gather(*[process_image_item(image) for image in images])
else: else:
images = [Image(url=image, revised_prompt=response.alt) for image in response.get_list()] images = [Image.model_construct(url=image, revised_prompt=response.alt) for image in response.get_list()]
last_provider = get_last_provider(True) last_provider = get_last_provider(True)
return ImagesResponse( return ImagesResponse.model_construct(
images, images,
model=last_provider.get("model") if model is None else model, model=last_provider.get("model") if model is None else model,
provider=last_provider.get("name") if provider is None else provider provider=last_provider.get("name") if provider is None else provider

View File

@@ -1,130 +1,150 @@
from __future__ import annotations from __future__ import annotations
from typing import Union from typing import Optional, List, Dict
from time import time from time import time
class Model(): from .helper import filter_none
...
class ChatCompletion(Model): try:
def __init__( from pydantic import BaseModel, Field
self, except ImportError:
class BaseModel():
@classmethod
def model_construct(cls, **data):
new = cls()
for key, value in data.items():
setattr(new, key, value)
return new
class Field():
def __init__(self, **config):
pass
class ChatCompletionChunk(BaseModel):
id: str
object: str
created: int
model: str
provider: Optional[str]
choices: List[ChatCompletionDeltaChoice]
@classmethod
def model_construct(
cls,
content: str, content: str,
finish_reason: str, finish_reason: str,
completion_id: str = None, completion_id: str = None,
created: int = None created: int = None
): ):
self.id: str = f"chatcmpl-{completion_id}" if completion_id else None return super().model_construct(
self.object: str = "chat.completion" id=f"chatcmpl-{completion_id}" if completion_id else None,
self.created: int = created object="chat.completion.cunk",
self.model: str = None created=created,
self.provider: str = None model=None,
self.choices = [ChatCompletionChoice(ChatCompletionMessage(content), finish_reason)] provider=None,
self.usage: dict[str, int] = { choices=[ChatCompletionDeltaChoice.model_construct(
ChatCompletionDelta.model_construct(content),
finish_reason
)]
)
class ChatCompletionMessage(BaseModel):
role: str
content: str
@classmethod
def model_construct(cls, content: str):
return super().model_construct(role="assistant", content=content)
class ChatCompletionChoice(BaseModel):
index: int
message: ChatCompletionMessage
finish_reason: str
@classmethod
def model_construct(cls, message: ChatCompletionMessage, finish_reason: str):
return super().model_construct(index=0, message=message, finish_reason=finish_reason)
class ChatCompletion(BaseModel):
id: str
object: str
created: int
model: str
provider: Optional[str]
choices: List[ChatCompletionChoice]
usage: Dict[str, int] = Field(examples=[{
"prompt_tokens": 0, #prompt_tokens,
"completion_tokens": 0, #completion_tokens,
"total_tokens": 0, #prompt_tokens + completion_tokens,
}])
@classmethod
def model_construct(
cls,
content: str,
finish_reason: str,
completion_id: str = None,
created: int = None
):
return super().model_construct(
id=f"chatcmpl-{completion_id}" if completion_id else None,
object="chat.completion",
created=created,
model=None,
provider=None,
choices=[ChatCompletionChoice.model_construct(
ChatCompletionMessage.model_construct(content),
finish_reason
)],
usage={
"prompt_tokens": 0, #prompt_tokens, "prompt_tokens": 0, #prompt_tokens,
"completion_tokens": 0, #completion_tokens, "completion_tokens": 0, #completion_tokens,
"total_tokens": 0, #prompt_tokens + completion_tokens, "total_tokens": 0, #prompt_tokens + completion_tokens,
} }
)
def to_json(self): class ChatCompletionDelta(BaseModel):
return { role: str
**self.__dict__, content: str
"choices": [choice.to_json() for choice in self.choices]
}
class ChatCompletionChunk(Model): @classmethod
def __init__( def model_construct(cls, content: Optional[str]):
self, return super().model_construct(role="assistant", content=content)
content: str,
finish_reason: str,
completion_id: str = None,
created: int = None
):
self.id: str = f"chatcmpl-{completion_id}" if completion_id else None
self.object: str = "chat.completion.chunk"
self.created: int = created
self.model: str = None
self.provider: str = None
self.choices = [ChatCompletionDeltaChoice(ChatCompletionDelta(content), finish_reason)]
def to_json(self): class ChatCompletionDeltaChoice(BaseModel):
return { index: int
**self.__dict__, delta: ChatCompletionDelta
"choices": [choice.to_json() for choice in self.choices] finish_reason: Optional[str]
}
class ChatCompletionMessage(Model): @classmethod
def __init__(self, content: Union[str, None]): def model_construct(cls, delta: ChatCompletionDelta, finish_reason: Optional[str]):
self.role = "assistant" return super().model_construct(index=0, delta=delta, finish_reason=finish_reason)
self.content = content
def to_json(self): class Image(BaseModel):
return self.__dict__ url: Optional[str]
b64_json: Optional[str]
revised_prompt: Optional[str]
class ChatCompletionChoice(Model): @classmethod
def __init__(self, message: ChatCompletionMessage, finish_reason: str): def model_construct(cls, url: str = None, b64_json: str = None, revised_prompt: str = None):
self.index = 0 return super().model_construct(**filter_none(
self.message = message url=url,
self.finish_reason = finish_reason b64_json=b64_json,
revised_prompt=revised_prompt
))
def to_json(self): class ImagesResponse(BaseModel):
return {
**self.__dict__,
"message": self.message.to_json()
}
class ChatCompletionDelta(Model):
content: Union[str, None] = None
def __init__(self, content: Union[str, None]):
if content is not None:
self.content = content
self.role = "assistant"
def to_json(self):
return self.__dict__
class ChatCompletionDeltaChoice(Model):
def __init__(self, delta: ChatCompletionDelta, finish_reason: Union[str, None]):
self.index = 0
self.delta = delta
self.finish_reason = finish_reason
def to_json(self):
return {
**self.__dict__,
"delta": self.delta.to_json()
}
class Image(Model):
def __init__(self, url: str = None, b64_json: str = None, revised_prompt: str = None) -> None:
if url is not None:
self.url = url
if b64_json is not None:
self.b64_json = b64_json
if revised_prompt is not None:
self.revised_prompt = revised_prompt
def to_json(self):
return self.__dict__
class ImagesResponse(Model):
data: list[Image] data: list[Image]
model: str model: str
provider: str provider: str
created: int created: int
def __init__(self, data: list[Image], created: int = None, model: str = None, provider: str = None) -> None: @classmethod
self.data = data def model_construct(cls, data: list[Image], created: int = None, model: str = None, provider: str = None):
if created is None: if created is None:
created = int(time()) created = int(time())
self.model = model return super().model_construct(
if provider is not None: data=data,
self.provider = provider model=model,
self.created = created provider=provider,
created=created
def to_json(self): )
return {
**self.__dict__,
"data": [image.to_json() for image in self.data]
}

View File

@@ -8,16 +8,7 @@ try:
except ImportError as e: except ImportError as e:
import_error = e import_error = e
def run_gui(host: str = '0.0.0.0', port: int = 8080, debug: bool = False) -> None: def get_gui_app():
if import_error is not None:
raise MissingRequirementsError(f'Install "gui" requirements | pip install -U g4f[gui]\n{import_error}')
config = {
'host' : host,
'port' : port,
'debug': debug
}
site = Website(app) site = Website(app)
for route in site.routes: for route in site.routes:
app.add_url_rule( app.add_url_rule(
@@ -33,6 +24,19 @@ def run_gui(host: str = '0.0.0.0', port: int = 8080, debug: bool = False) -> Non
view_func = backend_api.routes[route]['function'], view_func = backend_api.routes[route]['function'],
methods = backend_api.routes[route]['methods'], methods = backend_api.routes[route]['methods'],
) )
return app
def run_gui(host: str = '0.0.0.0', port: int = 8080, debug: bool = False) -> None:
if import_error is not None:
raise MissingRequirementsError(f'Install "gui" requirements | pip install -U g4f[gui]\n{import_error}')
config = {
'host' : host,
'port' : port,
'debug': debug
}
get_gui_app()
print(f"Running on port {config['port']}") print(f"Running on port {config['port']}")
app.run(**config) app.run(**config)

View File

@@ -22,11 +22,11 @@ conversations: dict[dict[str, BaseConversation]] = {}
class Api: class Api:
@staticmethod @staticmethod
def get_models() -> list[str]: def get_models():
return models._all_models return models._all_models
@staticmethod @staticmethod
def get_provider_models(provider: str, api_key: str = None) -> list[dict]: def get_provider_models(provider: str, api_key: str = None):
if provider in __map__: if provider in __map__:
provider: ProviderType = __map__[provider] provider: ProviderType = __map__[provider]
if issubclass(provider, ProviderModelMixin): if issubclass(provider, ProviderModelMixin):
@@ -46,39 +46,7 @@ class Api:
return [] return []
@staticmethod @staticmethod
def get_image_models() -> list[dict]: def get_providers() -> dict[str, str]:
image_models = []
index = []
for provider in __providers__:
if hasattr(provider, "image_models"):
if hasattr(provider, "get_models"):
provider.get_models()
parent = provider
if hasattr(provider, "parent"):
parent = __map__[provider.parent]
if parent.__name__ not in index:
for model in provider.image_models:
image_models.append({
"provider": parent.__name__,
"url": parent.url,
"label": parent.label if hasattr(parent, "label") else None,
"image_model": model,
"vision_model": getattr(parent, "default_vision_model", None)
})
index.append(parent.__name__)
elif hasattr(provider, "default_vision_model") and provider.__name__ not in index:
image_models.append({
"provider": provider.__name__,
"url": provider.url,
"label": provider.label if hasattr(provider, "label") else None,
"image_model": None,
"vision_model": provider.default_vision_model
})
index.append(provider.__name__)
return image_models
@staticmethod
def get_providers() -> list[str]:
return { return {
provider.__name__: (provider.label if hasattr(provider, "label") else provider.__name__) provider.__name__: (provider.label if hasattr(provider, "label") else provider.__name__)
+ (" (Image Generation)" if getattr(provider, "image_models", None) else "") + (" (Image Generation)" if getattr(provider, "image_models", None) else "")
@@ -90,7 +58,7 @@ class Api:
} }
@staticmethod @staticmethod
def get_version(): def get_version() -> dict:
try: try:
current_version = version.utils.current_version current_version = version.utils.current_version
except VersionNotFoundError: except VersionNotFoundError:

View File

@@ -3,7 +3,7 @@ import flask
import os import os
import logging import logging
import asyncio import asyncio
from flask import request, Flask from flask import Flask, request, jsonify
from typing import Generator from typing import Generator
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
@@ -42,17 +42,26 @@ class Backend_Api(Api):
app (Flask): Flask application instance to attach routes to. app (Flask): Flask application instance to attach routes to.
""" """
self.app: Flask = app self.app: Flask = app
def jsonify_models(**kwargs):
response = self.get_models(**kwargs)
if isinstance(response, list):
return jsonify(response)
return response
def jsonify_provider_models(**kwargs):
response = self.get_provider_models(**kwargs)
if isinstance(response, list):
return jsonify(response)
return response
self.routes = { self.routes = {
'/backend-api/v2/models': { '/backend-api/v2/models': {
'function': self.get_models, 'function': jsonify_models,
'methods': ['GET'] 'methods': ['GET']
}, },
'/backend-api/v2/models/<provider>': { '/backend-api/v2/models/<provider>': {
'function': self.get_provider_models, 'function': jsonify_provider_models,
'methods': ['GET']
},
'/backend-api/v2/image_models': {
'function': self.get_image_models,
'methods': ['GET'] 'methods': ['GET']
}, },
'/backend-api/v2/providers': { '/backend-api/v2/providers': {

View File

@@ -1,11 +1,12 @@
import uuid import uuid
from flask import render_template, redirect from flask import render_template, redirect
def redirect_home():
return redirect('/chat')
class Website: class Website:
def __init__(self, app) -> None: def __init__(self, app) -> None:
self.app = app self.app = app
def redirect_home():
return redirect('/chat')
self.routes = { self.routes = {
'/': { '/': {
'function': redirect_home, 'function': redirect_home,
@@ -35,7 +36,7 @@ class Website:
def _chat(self, conversation_id): def _chat(self, conversation_id):
if '-' not in conversation_id: if '-' not in conversation_id:
return redirect('/chat') return redirect_home()
return render_template('index.html', chat_id=conversation_id) return render_template('index.html', chat_id=conversation_id)
def _index(self): def _index(self):

View File

@@ -11,7 +11,7 @@ class CloudflareError(ResponseStatusError):
... ...
def is_cloudflare(text: str) -> bool: def is_cloudflare(text: str) -> bool:
if "Generated by cloudfront" in text: if "Generated by cloudfront" in text or '<p id="cf-spinner-please-wait">' in text:
return True return True
elif "<title>Attention Required! | Cloudflare</title>" in text or 'id="cf-cloudflare-status"' in text: elif "<title>Attention Required! | Cloudflare</title>" in text or 'id="cf-cloudflare-status"' in text:
return True return True