mirror of
https://github.com/xtekky/gpt4free.git
synced 2024-11-23 00:22:09 +03:00
Add aiohttp_socks to requirements
Fix preview for uploaded and generated images in gui Improve typing, readme
This commit is contained in:
parent
3bb6560fce
commit
a28bab9387
55
README.md
55
README.md
@ -99,8 +99,29 @@ or set the api base in your client to: [http://localhost:1337/v1](http://localho
|
||||
|
||||
##### Install using pypi:
|
||||
|
||||
Install all supported tools / all used packages:
|
||||
```
|
||||
pip install -U "g4f[all]"
|
||||
pip install -U g4f[all]
|
||||
```
|
||||
Install packages for uploading / generating images:
|
||||
```
|
||||
pip install -U g4f[image]
|
||||
```
|
||||
Install the packages required for providers with webdriver:
|
||||
```
|
||||
pip install -U g4f[webdriver]
|
||||
```
|
||||
Install the packages required for the OpenaiChat provider:
|
||||
```
|
||||
pip install -U g4f[openai]
|
||||
```
|
||||
Install the packages required for the interference api:
|
||||
```
|
||||
pip install -U g4f[api]
|
||||
```
|
||||
Install the packages required for the web gui:
|
||||
```
|
||||
pip install -U g4f[gui]
|
||||
```
|
||||
|
||||
##### or:
|
||||
@ -202,8 +223,9 @@ docker-compose down
|
||||
|
||||
### The Web UI
|
||||
|
||||
To use it in the web interface, type the following codes in the command line.
|
||||
```python3
|
||||
To start the web interface, type the following codes in the command line.
|
||||
|
||||
```python
|
||||
from g4f.gui import run_gui
|
||||
run_gui()
|
||||
```
|
||||
@ -283,6 +305,33 @@ for message in response:
|
||||
print(message)
|
||||
```
|
||||
|
||||
##### Cookies / Access Token
|
||||
|
||||
For generating images with Bing and for the OpenAi Chat you need cookies or a token from your browser session. From Bing you need the "_U" cookie and from OpenAI you need the "access_token". You can pass the cookies / the access token in the create function or you use the `set_cookies` setter:
|
||||
|
||||
```python
|
||||
from g4f import set_cookies
|
||||
|
||||
set_cookies(".bing", {
|
||||
"_U": "cookie value"
|
||||
})
|
||||
set_cookies("chat.openai.com", {
|
||||
"access_token": "token value"
|
||||
})
|
||||
|
||||
from g4f.gui import run_gui
|
||||
run_gui()
|
||||
```
|
||||
|
||||
Alternatively, g4f reads the cookies with “browser_cookie3” from your browser
|
||||
or it starts a browser instance with selenium "webdriver" for logging in.
|
||||
If you use the pip package, you have to install “browser_cookie3” or "webdriver" by yourself.
|
||||
|
||||
```bash
|
||||
pip install browser_cookie3
|
||||
pip install g4f[webdriver]
|
||||
```
|
||||
|
||||
##### Using Browser
|
||||
|
||||
Some providers using a browser to bypass the bot protection. They using the selenium webdriver to control the browser. The browser settings and the login data are saved in a custom directory. If the headless mode is enabled, the browser windows are loaded invisibly. For performance reasons, it is recommended to reuse the browser instances and close them yourself at the end:
|
||||
|
@ -86,6 +86,5 @@ RUN pip install --upgrade pip && pip install -r requirements.txt
|
||||
# Copy the entire package into the container.
|
||||
ADD --chown=$G4F_USER:$G4F_USER g4f $G4F_DIR/g4f
|
||||
|
||||
|
||||
# Expose ports
|
||||
EXPOSE 8080 1337
|
||||
EXPOSE 8080 1337
|
@ -288,8 +288,6 @@ async def stream_generate(
|
||||
) as session:
|
||||
conversation = await create_conversation(session)
|
||||
image_request = await upload_image(session, image, tone) if image else None
|
||||
if image_request:
|
||||
yield image_request
|
||||
|
||||
try:
|
||||
async with session.ws_connect(
|
||||
@ -327,7 +325,7 @@ async def stream_generate(
|
||||
elif message.get('contentType') == "IMAGE":
|
||||
prompt = message.get('text')
|
||||
try:
|
||||
image_response = ImageResponse(await create_images(session, prompt), prompt)
|
||||
image_response = ImageResponse(await create_images(session, prompt), prompt, {"preview": "{image}?w=200&h=200"})
|
||||
except:
|
||||
response_txt += f"\nhttps://www.bing.com/images/create?q={parse.quote(prompt)}"
|
||||
final = True
|
||||
|
@ -187,11 +187,11 @@ def get_cookies_from_browser(proxy: str = None) -> dict[str, str]:
|
||||
|
||||
class CreateImagesBing:
|
||||
"""A class for creating images using Bing."""
|
||||
|
||||
|
||||
def __init__(self, cookies: dict[str, str] = {}, proxy: str = None) -> None:
|
||||
self.cookies = cookies
|
||||
self.proxy = proxy
|
||||
|
||||
|
||||
def create_completion(self, prompt: str) -> Generator[ImageResponse, None, None]:
|
||||
"""
|
||||
Generator for creating imagecompletion based on a prompt.
|
||||
@ -229,9 +229,7 @@ class CreateImagesBing:
|
||||
proxy = os.environ.get("G4F_PROXY")
|
||||
async with create_session(cookies, proxy) as session:
|
||||
images = await create_images(session, prompt, self.proxy)
|
||||
return ImageResponse(images, prompt)
|
||||
|
||||
service = CreateImagesBing()
|
||||
return ImageResponse(images, prompt, {"preview": "{image}?w=200&h=200"})
|
||||
|
||||
def patch_provider(provider: ProviderType) -> CreateImagesProvider:
|
||||
"""
|
||||
@ -243,6 +241,7 @@ def patch_provider(provider: ProviderType) -> CreateImagesProvider:
|
||||
Returns:
|
||||
CreateImagesProvider: The patched provider with image creation capabilities.
|
||||
"""
|
||||
service = CreateImagesBing()
|
||||
return CreateImagesProvider(
|
||||
provider,
|
||||
service.create_completion,
|
||||
|
@ -149,4 +149,4 @@ def parse_image_response(response: dict) -> ImageRequest:
|
||||
if IMAGE_CONFIG["enableFaceBlurDebug"] else
|
||||
f"https://www.bing.com/images/blob?bcid={result['bcid']}"
|
||||
)
|
||||
return ImageRequest(result["imageUrl"], "", result)
|
||||
return ImageRequest(result)
|
@ -150,8 +150,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
|
||||
headers=headers
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
download_url = (await response.json())["download_url"]
|
||||
return ImageRequest(download_url, image_data["file_name"], image_data)
|
||||
image_data["download_url"] = (await response.json())["download_url"]
|
||||
return ImageRequest(image_data)
|
||||
|
||||
@classmethod
|
||||
async def get_default_model(cls, session: StreamSession, headers: dict):
|
||||
@ -175,7 +175,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
|
||||
return cls.default_model
|
||||
|
||||
@classmethod
|
||||
def create_messages(cls, prompt: str, image_response: ImageRequest = None):
|
||||
def create_messages(cls, prompt: str, image_request: ImageRequest = None):
|
||||
"""
|
||||
Create a list of messages for the user input
|
||||
|
||||
@ -187,7 +187,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
|
||||
A list of messages with the user input and the image, if any
|
||||
"""
|
||||
# Check if there is an image response
|
||||
if not image_response:
|
||||
if not image_request:
|
||||
# Create a content object with the text type and the prompt
|
||||
content = {"content_type": "text", "parts": [prompt]}
|
||||
else:
|
||||
@ -195,10 +195,10 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
|
||||
content = {
|
||||
"content_type": "multimodal_text",
|
||||
"parts": [{
|
||||
"asset_pointer": f"file-service://{image_response.get('file_id')}",
|
||||
"height": image_response.get("height"),
|
||||
"size_bytes": image_response.get("file_size"),
|
||||
"width": image_response.get("width"),
|
||||
"asset_pointer": f"file-service://{image_request.get('file_id')}",
|
||||
"height": image_request.get("height"),
|
||||
"size_bytes": image_request.get("file_size"),
|
||||
"width": image_request.get("width"),
|
||||
}, prompt]
|
||||
}
|
||||
# Create a message object with the user role and the content
|
||||
@ -208,16 +208,16 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
|
||||
"content": content,
|
||||
}]
|
||||
# Check if there is an image response
|
||||
if image_response:
|
||||
if image_request:
|
||||
# Add the metadata object with the attachments
|
||||
messages[0]["metadata"] = {
|
||||
"attachments": [{
|
||||
"height": image_response.get("height"),
|
||||
"id": image_response.get("file_id"),
|
||||
"mimeType": image_response.get("mime_type"),
|
||||
"name": image_response.get("file_name"),
|
||||
"size": image_response.get("file_size"),
|
||||
"width": image_response.get("width"),
|
||||
"height": image_request.get("height"),
|
||||
"id": image_request.get("file_id"),
|
||||
"mimeType": image_request.get("mime_type"),
|
||||
"name": image_request.get("file_name"),
|
||||
"size": image_request.get("file_size"),
|
||||
"width": image_request.get("width"),
|
||||
}]
|
||||
}
|
||||
return messages
|
||||
@ -352,7 +352,6 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
|
||||
image_response = None
|
||||
if image:
|
||||
image_response = await cls.upload_image(session, headers, image)
|
||||
yield image_response
|
||||
except Exception as e:
|
||||
yield e
|
||||
end_turn = EndTurn()
|
||||
|
13
g4f/defaults.py
Normal file
13
g4f/defaults.py
Normal file
@ -0,0 +1,13 @@
|
||||
DEFAULT_HEADERS = {
|
||||
'Accept': '*/*',
|
||||
'Accept-Encoding': 'gzip, deflate, br',
|
||||
'Accept-Language': 'en-US',
|
||||
'Connection': 'keep-alive',
|
||||
'Sec-Ch-Ua': '"Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"',
|
||||
'Sec-Ch-Ua-Mobile': '?0',
|
||||
'Sec-Ch-Ua-Platform': '"Windows"',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'same-site',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'
|
||||
}
|
@ -59,6 +59,10 @@ const handle_ask = async () => {
|
||||
</div>
|
||||
<div class="content" id="user_${token}">
|
||||
${markdown_render(message)}
|
||||
${imageInput.dataset.src
|
||||
? '<img src="' + imageInput.dataset.src + '" alt="Image upload">'
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -666,6 +670,18 @@ observer.observe(message_input, { attributes: true });
|
||||
})()
|
||||
imageInput.addEventListener('click', async (event) => {
|
||||
imageInput.value = '';
|
||||
delete imageInput.dataset.src;
|
||||
});
|
||||
imageInput.addEventListener('change', async (event) => {
|
||||
if (imageInput.files.length) {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', (event) => {
|
||||
imageInput.dataset.src = event.target.result;
|
||||
});
|
||||
reader.readAsDataURL(imageInput.files[0]);
|
||||
} else {
|
||||
delete imageInput.dataset.src;
|
||||
}
|
||||
});
|
||||
fileInput.addEventListener('click', async (event) => {
|
||||
fileInput.value = '';
|
||||
|
39
g4f/image.py
39
g4f/image.py
@ -3,14 +3,13 @@ from __future__ import annotations
|
||||
import re
|
||||
from io import BytesIO
|
||||
import base64
|
||||
from .typing import ImageType, Union
|
||||
from .typing import ImageType, Union, Image
|
||||
|
||||
try:
|
||||
from PIL.Image import open as open_image, new as new_image, Image
|
||||
from PIL.Image import open as open_image, new as new_image
|
||||
from PIL.Image import FLIP_LEFT_RIGHT, ROTATE_180, ROTATE_270, ROTATE_90
|
||||
has_requirements = True
|
||||
except ImportError:
|
||||
Image = type
|
||||
has_requirements = False
|
||||
|
||||
from .errors import MissingRequirementsError
|
||||
@ -29,6 +28,9 @@ def to_image(image: ImageType, is_svg: bool = False) -> Image:
|
||||
"""
|
||||
if not has_requirements:
|
||||
raise MissingRequirementsError('Install "pillow" package for images')
|
||||
if isinstance(image, str):
|
||||
is_data_uri_an_image(image)
|
||||
image = extract_data_uri(image)
|
||||
if is_svg:
|
||||
try:
|
||||
import cairosvg
|
||||
@ -39,9 +41,6 @@ def to_image(image: ImageType, is_svg: bool = False) -> Image:
|
||||
buffer = BytesIO()
|
||||
cairosvg.svg2png(image, write_to=buffer)
|
||||
return open_image(buffer)
|
||||
if isinstance(image, str):
|
||||
is_data_uri_an_image(image)
|
||||
image = extract_data_uri(image)
|
||||
if isinstance(image, bytes):
|
||||
is_accepted_format(image)
|
||||
return open_image(BytesIO(image))
|
||||
@ -79,9 +78,9 @@ def is_data_uri_an_image(data_uri: str) -> bool:
|
||||
if not re.match(r'data:image/(\w+);base64,', data_uri):
|
||||
raise ValueError("Invalid data URI image.")
|
||||
# Extract the image format from the data URI
|
||||
image_format = re.match(r'data:image/(\w+);base64,', data_uri).group(1)
|
||||
image_format = re.match(r'data:image/(\w+);base64,', data_uri).group(1).lower()
|
||||
# Check if the image format is one of the allowed formats (jpg, jpeg, png, gif)
|
||||
if image_format.lower() not in ALLOWED_EXTENSIONS:
|
||||
if image_format not in ALLOWED_EXTENSIONS and image_format != "svg+xml":
|
||||
raise ValueError("Invalid image format (from mime file type).")
|
||||
|
||||
def is_accepted_format(binary_data: bytes) -> bool:
|
||||
@ -187,7 +186,7 @@ def to_base64_jpg(image: Image, compression_rate: float) -> str:
|
||||
image.save(output_buffer, format="JPEG", quality=int(compression_rate * 100))
|
||||
return base64.b64encode(output_buffer.getvalue()).decode()
|
||||
|
||||
def format_images_markdown(images, alt: str, preview: str="{image}?w=200&h=200") -> str:
|
||||
def format_images_markdown(images, alt: str, preview: str = None) -> str:
|
||||
"""
|
||||
Formats the given images as a markdown string.
|
||||
|
||||
@ -200,9 +199,12 @@ def format_images_markdown(images, alt: str, preview: str="{image}?w=200&h=200")
|
||||
str: The formatted markdown string.
|
||||
"""
|
||||
if isinstance(images, str):
|
||||
images = f"[![{alt}]({preview.replace('{image}', images)})]({images})"
|
||||
images = f"[![{alt}]({preview.replace('{image}', images) if preview else images})]({images})"
|
||||
else:
|
||||
images = [f"[![#{idx+1} {alt}]({preview.replace('{image}', image)})]({image})" for idx, image in enumerate(images)]
|
||||
images = [
|
||||
f"[![#{idx+1} {alt}]({preview.replace('{image}', image) if preview else image})]({image})"
|
||||
for idx, image in enumerate(images)
|
||||
]
|
||||
images = "\n".join(images)
|
||||
start_flag = "<!-- generated images start -->\n"
|
||||
end_flag = "<!-- generated images end -->\n"
|
||||
@ -223,7 +225,7 @@ def to_bytes(image: Image) -> bytes:
|
||||
image.seek(0)
|
||||
return bytes_io.getvalue()
|
||||
|
||||
class ImageResponse():
|
||||
class ImageResponse:
|
||||
def __init__(
|
||||
self,
|
||||
images: Union[str, list],
|
||||
@ -235,10 +237,17 @@ class ImageResponse():
|
||||
self.options = options
|
||||
|
||||
def __str__(self) -> str:
|
||||
return format_images_markdown(self.images, self.alt)
|
||||
return format_images_markdown(self.images, self.alt, self.get("preview"))
|
||||
|
||||
def get(self, key: str):
|
||||
return self.options.get(key)
|
||||
|
||||
class ImageRequest(ImageResponse):
|
||||
pass
|
||||
class ImageRequest:
|
||||
def __init__(
|
||||
self,
|
||||
options: dict = {}
|
||||
):
|
||||
self.options = options
|
||||
|
||||
def get(self, key: str):
|
||||
return self.options.get(key)
|
@ -7,13 +7,13 @@ try:
|
||||
from .requests_curl_cffi import StreamResponse, StreamSession
|
||||
has_curl_cffi = True
|
||||
except ImportError:
|
||||
Session = type
|
||||
from typing import Type as Session
|
||||
from .requests_aiohttp import StreamResponse, StreamSession
|
||||
has_curl_cffi = False
|
||||
|
||||
from .webdriver import WebDriver, WebDriverSession, bypass_cloudflare, get_driver_cookies
|
||||
from .errors import MissingRequirementsError
|
||||
|
||||
from .defaults import DEFAULT_HEADERS
|
||||
|
||||
def get_args_from_browser(url: str, webdriver: WebDriver = None, proxy: str = None, timeout: int = 120) -> dict:
|
||||
"""
|
||||
@ -36,22 +36,14 @@ def get_args_from_browser(url: str, webdriver: WebDriver = None, proxy: str = No
|
||||
return {
|
||||
'cookies': cookies,
|
||||
'headers': {
|
||||
'accept': '*/*',
|
||||
"accept-language": "en-US",
|
||||
"accept-encoding": "gzip, deflate, br",
|
||||
'authority': parse.netloc,
|
||||
'origin': f'{parse.scheme}://{parse.netloc}',
|
||||
'referer': url,
|
||||
"sec-ch-ua": "\"Google Chrome\";v=\"121\", \"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"121\"",
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": "Windows",
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-origin',
|
||||
'user-agent': user_agent,
|
||||
**DEFAULT_HEADERS,
|
||||
'Authority': parse.netloc,
|
||||
'Origin': f'{parse.scheme}://{parse.netloc}',
|
||||
'Referer': url,
|
||||
'User-Agent': user_agent,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_session_from_browser(url: str, webdriver: WebDriver = None, proxy: str = None, timeout: int = 120) -> Session:
|
||||
if not has_curl_cffi:
|
||||
raise MissingRequirementsError('Install "curl_cffi" package')
|
||||
|
@ -4,6 +4,7 @@ from aiohttp import ClientSession, ClientResponse, ClientTimeout
|
||||
from typing import AsyncGenerator, Any
|
||||
|
||||
from .Provider.helper import get_connector
|
||||
from .defaults import DEFAULT_HEADERS
|
||||
|
||||
class StreamResponse(ClientResponse):
|
||||
async def iter_lines(self) -> AsyncGenerator[bytes, None]:
|
||||
@ -17,17 +18,7 @@ class StreamSession(ClientSession):
|
||||
def __init__(self, headers: dict = {}, timeout: int = None, proxies: dict = {}, impersonate = None, **kwargs):
|
||||
if impersonate:
|
||||
headers = {
|
||||
'Accept-Encoding': 'gzip, deflate, br',
|
||||
'Accept-Language': 'en-US',
|
||||
'Connection': 'keep-alive',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'same-site',
|
||||
"User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36',
|
||||
'Accept': '*/*',
|
||||
'sec-ch-ua': '"Google Chrome";v="107", "Chromium";v="107", "Not?A_Brand";v="24"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
**DEFAULT_HEADERS,
|
||||
**headers
|
||||
}
|
||||
super().__init__(
|
||||
|
@ -1,9 +1,10 @@
|
||||
import sys
|
||||
from typing import Any, AsyncGenerator, Generator, NewType, Tuple, Union, List, Dict, Type, IO, Optional
|
||||
|
||||
try:
|
||||
from PIL.Image import Image
|
||||
except ImportError:
|
||||
Image = type
|
||||
from typing import Type as Image
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from typing import TypedDict
|
||||
@ -14,7 +15,7 @@ SHA256 = NewType('sha_256_hash', str)
|
||||
CreateResult = Generator[str, None, None]
|
||||
AsyncResult = AsyncGenerator[str, None]
|
||||
Messages = List[Dict[str, str]]
|
||||
Cookies = List[Dict[str, str]]
|
||||
Cookies = Dict[str, str]
|
||||
ImageType = Union[str, bytes, IO, Image, None]
|
||||
|
||||
__all__ = [
|
||||
@ -33,5 +34,7 @@ __all__ = [
|
||||
'CreateResult',
|
||||
'AsyncResult',
|
||||
'Messages',
|
||||
'Cookies',
|
||||
'Image',
|
||||
'ImageType'
|
||||
]
|
||||
|
@ -18,6 +18,7 @@ import time
|
||||
from shutil import which
|
||||
from os import path
|
||||
from os import access, R_OK
|
||||
from .typing import Cookies
|
||||
from .errors import MissingRequirementsError
|
||||
from . import debug
|
||||
|
||||
@ -56,9 +57,7 @@ def get_browser(
|
||||
if proxy:
|
||||
options.add_argument(f'--proxy-server={proxy}')
|
||||
# Check for system driver in docker
|
||||
driver = which('chromedriver')
|
||||
if not driver:
|
||||
driver = '/usr/bin/chromedriver'
|
||||
driver = which('chromedriver') or '/usr/bin/chromedriver'
|
||||
if not path.isfile(driver) or not access(driver, R_OK):
|
||||
driver = None
|
||||
return Chrome(
|
||||
@ -68,7 +67,7 @@ def get_browser(
|
||||
headless=headless
|
||||
)
|
||||
|
||||
def get_driver_cookies(driver: WebDriver) -> dict:
|
||||
def get_driver_cookies(driver: WebDriver) -> Cookies:
|
||||
"""
|
||||
Retrieves cookies from the specified WebDriver.
|
||||
|
||||
@ -115,8 +114,8 @@ def bypass_cloudflare(driver: WebDriver, url: str, timeout: int) -> None:
|
||||
driver.switch_to.window(window_handle)
|
||||
break
|
||||
|
||||
# Click on the challenge button in the iframe
|
||||
try:
|
||||
# Click on the challenge button in the iframe
|
||||
driver.switch_to.frame(driver.find_element(By.CSS_SELECTOR, "#turnstile-wrapper iframe"))
|
||||
WebDriverWait(driver, 5).until(
|
||||
EC.presence_of_element_located((By.CSS_SELECTOR, "#challenge-stage input"))
|
||||
|
@ -19,4 +19,5 @@ async-property
|
||||
undetected-chromedriver
|
||||
brotli
|
||||
beautifulsoup4
|
||||
setuptools
|
||||
setuptools
|
||||
aiohttp_socks
|
Loading…
Reference in New Issue
Block a user