mirror of
https://github.com/xtekky/gpt4free.git
synced 2024-12-25 20:22:47 +03:00
Merge pull request #1575 from hlohaus/openai
Add GPT 4 support in You Fix convert image to jpg with exceptional modes in Bing Add camera input in GUI Enable logging on debug in GUI Don't load expired cookies Fix display upload image in GUI Add upload image in You provider Add disable history button in GUI Change python version to 3.12 in unittests
This commit is contained in:
commit
3c498496f8
2
.github/workflows/unittest.yml
vendored
2
.github/workflows/unittest.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
cache: 'pip'
|
||||
- name: Install requirements
|
||||
run: pip install -r requirements.txt
|
||||
|
@ -1,40 +1,155 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import base64
|
||||
import uuid
|
||||
from aiohttp import ClientSession, FormData
|
||||
|
||||
from ..requests import StreamSession
|
||||
from ..typing import AsyncGenerator, Messages
|
||||
from .base_provider import AsyncGeneratorProvider, format_prompt
|
||||
|
||||
from ..typing import AsyncGenerator, Messages, ImageType, Cookies
|
||||
from .base_provider import AsyncGeneratorProvider
|
||||
from .helper import get_connector, format_prompt
|
||||
from ..image import to_bytes
|
||||
from ..defaults import DEFAULT_HEADERS
|
||||
|
||||
class You(AsyncGeneratorProvider):
|
||||
url = "https://you.com"
|
||||
working = True
|
||||
supports_gpt_35_turbo = True
|
||||
|
||||
supports_gpt_4 = True
|
||||
_cookies = None
|
||||
_cookies_used = 0
|
||||
|
||||
@classmethod
|
||||
async def create_async_generator(
|
||||
cls,
|
||||
model: str,
|
||||
messages: Messages,
|
||||
image: ImageType = None,
|
||||
image_name: str = None,
|
||||
proxy: str = None,
|
||||
timeout: int = 120,
|
||||
chat_mode: str = "default",
|
||||
**kwargs,
|
||||
) -> AsyncGenerator:
|
||||
async with StreamSession(proxies={"https": proxy}, impersonate="chrome107", timeout=timeout) as session:
|
||||
async with ClientSession(
|
||||
connector=get_connector(kwargs.get("connector"), proxy),
|
||||
headers=DEFAULT_HEADERS
|
||||
) as client:
|
||||
if image:
|
||||
chat_mode = "agent"
|
||||
elif model == "gpt-4":
|
||||
chat_mode = model
|
||||
cookies = await cls.get_cookies(client) if chat_mode != "default" else None
|
||||
upload = json.dumps([await cls.upload_file(client, cookies, to_bytes(image), image_name)]) if image else ""
|
||||
#questions = [message["content"] for message in messages if message["role"] == "user"]
|
||||
# chat = [
|
||||
# {"question": questions[idx-1], "answer": message["content"]}
|
||||
# for idx, message in enumerate(messages)
|
||||
# if message["role"] == "assistant"
|
||||
# and idx < len(questions)
|
||||
# ]
|
||||
headers = {
|
||||
"Accept": "text/event-stream",
|
||||
"Referer": f"{cls.url}/search?fromSearchBar=true&tbm=youchat",
|
||||
}
|
||||
data = {"q": format_prompt(messages), "domain": "youchat", "chat": ""}
|
||||
async with session.get(
|
||||
data = {
|
||||
"userFiles": upload,
|
||||
"q": format_prompt(messages),
|
||||
"domain": "youchat",
|
||||
"selectedChatMode": chat_mode,
|
||||
#"chat": json.dumps(chat),
|
||||
}
|
||||
async with (client.post if chat_mode == "default" else client.get)(
|
||||
f"{cls.url}/api/streamingSearch",
|
||||
params=data,
|
||||
headers=headers
|
||||
data=data,
|
||||
headers=headers,
|
||||
cookies=cookies
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
start = b'data: {"youChatToken": '
|
||||
async for line in response.iter_lines():
|
||||
if line.startswith(start):
|
||||
yield json.loads(line[len(start):-1])
|
||||
async for line in response.content:
|
||||
if line.startswith(b'event: '):
|
||||
event = line[7:-1]
|
||||
elif line.startswith(b'data: '):
|
||||
if event == b"youChatUpdate" or event == b"youChatToken":
|
||||
data = json.loads(line[6:-1])
|
||||
if event == b"youChatToken" and "youChatToken" in data:
|
||||
yield data["youChatToken"]
|
||||
elif event == b"youChatUpdate" and "t" in data:
|
||||
yield data["t"]
|
||||
|
||||
@classmethod
|
||||
async def upload_file(cls, client: ClientSession, cookies: Cookies, file: bytes, filename: str = None) -> dict:
|
||||
async with client.get(
|
||||
f"{cls.url}/api/get_nonce",
|
||||
cookies=cookies,
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
upload_nonce = await response.text()
|
||||
data = FormData()
|
||||
data.add_field('file', file, filename=filename)
|
||||
async with client.post(
|
||||
f"{cls.url}/api/upload",
|
||||
data=data,
|
||||
headers={
|
||||
"X-Upload-Nonce": upload_nonce,
|
||||
},
|
||||
cookies=cookies
|
||||
) as response:
|
||||
if not response.ok:
|
||||
raise RuntimeError(f"Response: {await response.text()}")
|
||||
result = await response.json()
|
||||
result["user_filename"] = filename
|
||||
result["size"] = len(file)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
async def get_cookies(cls, client: ClientSession) -> Cookies:
|
||||
if not cls._cookies or cls._cookies_used >= 5:
|
||||
cls._cookies = await cls.create_cookies(client)
|
||||
cls._cookies_used = 0
|
||||
cls._cookies_used += 1
|
||||
return cls._cookies
|
||||
|
||||
@classmethod
|
||||
def get_sdk(cls) -> str:
|
||||
return base64.standard_b64encode(json.dumps({
|
||||
"event_id":f"event-id-{str(uuid.uuid4())}",
|
||||
"app_session_id":f"app-session-id-{str(uuid.uuid4())}",
|
||||
"persistent_id":f"persistent-id-{uuid.uuid4()}",
|
||||
"client_sent_at":"","timezone":"",
|
||||
"stytch_user_id":f"user-live-{uuid.uuid4()}",
|
||||
"stytch_session_id":f"session-live-{uuid.uuid4()}",
|
||||
"app":{"identifier":"you.com"},
|
||||
"sdk":{"identifier":"Stytch.js Javascript SDK","version":"3.3.0"
|
||||
}}).encode()).decode()
|
||||
|
||||
def get_auth() -> str:
|
||||
auth_uuid = "507a52ad-7e69-496b-aee0-1c9863c7c8"
|
||||
auth_token = f"public-token-live-{auth_uuid}bb:public-token-live-{auth_uuid}19"
|
||||
auth = base64.standard_b64encode(auth_token.encode()).decode()
|
||||
return f"Basic {auth}"
|
||||
|
||||
@classmethod
|
||||
async def create_cookies(cls, client: ClientSession) -> Cookies:
|
||||
user_uuid = str(uuid.uuid4())
|
||||
async with client.post(
|
||||
"https://web.stytch.com/sdk/v1/passwords",
|
||||
headers={
|
||||
"Authorization": cls.get_auth(),
|
||||
"X-SDK-Client": cls.get_sdk(),
|
||||
"X-SDK-Parent-Host": cls.url
|
||||
},
|
||||
json={
|
||||
"email": f"{user_uuid}@gmail.com",
|
||||
"password": f"{user_uuid}#{user_uuid}",
|
||||
"session_duration_minutes": 129600
|
||||
}
|
||||
) as response:
|
||||
if not response.ok:
|
||||
raise RuntimeError(f"Response: {await response.text()}")
|
||||
session = (await response.json())["data"]
|
||||
return {
|
||||
"stytch_session": session["session_token"],
|
||||
'stytch_session_jwt': session["session_jwt"],
|
||||
'ydc_stytch_session': session["session_token"],
|
||||
'ydc_stytch_session_jwt': session["session_jwt"],
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
try:
|
||||
from platformdirs import user_config_dir
|
||||
@ -72,7 +73,8 @@ def load_cookies_from_browsers(domain_name: str, raise_requirements_error: bool
|
||||
print(f"Read cookies from {cookie_fn.__name__} for {domain_name}")
|
||||
for cookie in cookie_jar:
|
||||
if cookie.name not in cookies:
|
||||
cookies[cookie.name] = cookie.value
|
||||
if not cookie.expires or cookie.expires > time.time():
|
||||
cookies[cookie.name] = cookie.value
|
||||
if single_browser and len(cookie_jar):
|
||||
break
|
||||
except BrowserCookieError:
|
||||
|
@ -7,6 +7,9 @@ except ImportError:
|
||||
raise MissingRequirementsError('Install "flask" package for the gui')
|
||||
|
||||
def run_gui(host: str = '0.0.0.0', port: int = 8080, debug: bool = False) -> None:
|
||||
if debug:
|
||||
import g4f
|
||||
g4f.debug.logging = True
|
||||
config = {
|
||||
'host' : host,
|
||||
'port' : port,
|
||||
|
@ -404,7 +404,7 @@ body {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#image, #file {
|
||||
#image, #file, #camera {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -412,20 +412,37 @@ label[for="image"]:has(> input:valid){
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
label[for="camera"]:has(> input:valid){
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
label[for="file"]:has(> input:valid){
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
label[for="image"], label[for="file"] {
|
||||
label[for="image"], label[for="file"], label[for="camera"] {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
label[for="file"] {
|
||||
label[for="image"] {
|
||||
top: 32px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
label[for="camera"] {
|
||||
top: 54px;
|
||||
}
|
||||
|
||||
label[for="camera"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (pointer:none), (pointer:coarse) {
|
||||
label[for="camera"] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons input[type="checkbox"] {
|
||||
|
@ -114,10 +114,14 @@
|
||||
<div class="box input-box">
|
||||
<textarea id="message-input" placeholder="Ask a question" cols="30" rows="10"
|
||||
style="white-space: pre-wrap;resize: none;"></textarea>
|
||||
<label for="image" title="Works only with Bing and OpenaiChat">
|
||||
<input type="file" id="image" name="image" accept="image/png, image/gif, image/jpeg, image/svg+xml" required/>
|
||||
<label for="image" title="Works with Bing, Gemini, OpenaiChat and You">
|
||||
<input type="file" id="image" name="image" accept="image/*" required/>
|
||||
<i class="fa-regular fa-image"></i>
|
||||
</label>
|
||||
<label for="camera">
|
||||
<input type="file" id="camera" name="camera" accept="image/*" capture="camera" required/>
|
||||
<i class="fa-solid fa-camera"></i>
|
||||
</label>
|
||||
<label for="file">
|
||||
<input type="file" id="file" name="file" accept="text/plain, text/html, text/xml, application/json, text/javascript, .sh, .py, .php, .css, .yaml, .sql, .log, .csv, .twig, .md" required/>
|
||||
<i class="fa-solid fa-paperclip"></i>
|
||||
@ -157,20 +161,26 @@
|
||||
<option value="Gemini">Gemini</option>
|
||||
<option value="Liaobots">Liaobots</option>
|
||||
<option value="Phind">Phind</option>
|
||||
<option value="You">You</option>
|
||||
<option value="">----</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<input type="checkbox" id="switch" />
|
||||
<label for="switch"></label>
|
||||
<label for="switch" title="Add the pages of the first 5 search results to the query."></label>
|
||||
<span class="about">Web Access</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<input type="checkbox" id="patch" />
|
||||
<label for="patch" title="Works only with Bing and some other providers"></label>
|
||||
<label for="patch" title="Enable create images with Bing."></label>
|
||||
<span class="about">Image Generator</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<input type="checkbox" id="history" />
|
||||
<label for="history" title="To improve the reaction time or if you have trouble with large conversations."></label>
|
||||
<span class="about">Disable History</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -8,6 +8,7 @@ const stop_generating = document.querySelector(`.stop_generating`);
|
||||
const regenerate = document.querySelector(`.regenerate`);
|
||||
const send_button = document.querySelector(`#send-button`);
|
||||
const imageInput = document.querySelector('#image');
|
||||
const cameraInput = document.querySelector('#camera');
|
||||
const fileInput = document.querySelector('#file');
|
||||
|
||||
let prompt_lock = false;
|
||||
@ -63,6 +64,10 @@ const handle_ask = async () => {
|
||||
? '<img src="' + imageInput.dataset.src + '" alt="Image upload">'
|
||||
: ''
|
||||
}
|
||||
${cameraInput.dataset.src
|
||||
? '<img src="' + cameraInput.dataset.src + '" alt="Image capture">'
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -95,6 +100,11 @@ const ask_gpt = async () => {
|
||||
delete messages[i]["provider"];
|
||||
}
|
||||
|
||||
// Remove history, if it is selected
|
||||
if (document.getElementById('history')?.checked) {
|
||||
messages = [messages[messages.length-1]]
|
||||
}
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
window.controller = new AbortController();
|
||||
|
||||
@ -141,9 +151,10 @@ const ask_gpt = async () => {
|
||||
const headers = {
|
||||
accept: 'text/event-stream'
|
||||
}
|
||||
if (imageInput && imageInput.files.length > 0) {
|
||||
const input = imageInput && imageInput.files.length > 0 ? imageInput : cameraInput
|
||||
if (input && input.files.length > 0) {
|
||||
const formData = new FormData();
|
||||
formData.append('image', imageInput.files[0]);
|
||||
formData.append('image', input.files[0]);
|
||||
formData.append('json', body);
|
||||
body = formData;
|
||||
} else {
|
||||
@ -211,8 +222,11 @@ const ask_gpt = async () => {
|
||||
message_box.scrollTo({ top: message_box.scrollHeight, behavior: "auto" });
|
||||
}
|
||||
}
|
||||
if (!error && imageInput) imageInput.value = "";
|
||||
if (!error && fileInput) fileInput.value = "";
|
||||
if (!error) {
|
||||
if (imageInput) imageInput.value = "";
|
||||
if (cameraInput) cameraInput.value = "";
|
||||
if (fileInput) fileInput.value = "";
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
@ -482,7 +496,7 @@ document.querySelector(".mobile-sidebar").addEventListener("click", (event) => {
|
||||
});
|
||||
|
||||
const register_settings_localstorage = async () => {
|
||||
for (id of ["switch", "model", "jailbreak", "patch", "provider"]) {
|
||||
for (id of ["switch", "model", "jailbreak", "patch", "provider", "history"]) {
|
||||
element = document.getElementById(id);
|
||||
element.addEventListener('change', async (event) => {
|
||||
switch (event.target.type) {
|
||||
@ -500,7 +514,7 @@ const register_settings_localstorage = async () => {
|
||||
}
|
||||
|
||||
const load_settings_localstorage = async () => {
|
||||
for (id of ["switch", "model", "jailbreak", "patch", "provider"]) {
|
||||
for (id of ["switch", "model", "jailbreak", "patch", "provider", "history"]) {
|
||||
element = document.getElementById(id);
|
||||
value = localStorage.getItem(element.id);
|
||||
if (value) {
|
||||
@ -668,21 +682,26 @@ observer.observe(message_input, { attributes: true });
|
||||
}
|
||||
document.getElementById("version_text").innerHTML = text
|
||||
})()
|
||||
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;
|
||||
for (const el of [imageInput, cameraInput]) {
|
||||
console.log(el.files);
|
||||
el.addEventListener('click', async () => {
|
||||
el.value = '';
|
||||
delete el.dataset.src;
|
||||
});
|
||||
do_load = async () => {
|
||||
if (el.files.length) {
|
||||
delete imageInput.dataset.src;
|
||||
delete cameraInput.dataset.src;
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', (event) => {
|
||||
el.dataset.src = event.target.result;
|
||||
});
|
||||
reader.readAsDataURL(el.files[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
do_load()
|
||||
el.addEventListener('change', do_load);
|
||||
}
|
||||
fileInput.addEventListener('click', async (event) => {
|
||||
fileInput.value = '';
|
||||
delete fileInput.dataset.text;
|
||||
|
@ -134,25 +134,31 @@ class Backend_Api:
|
||||
dict: Arguments prepared for chat completion.
|
||||
"""
|
||||
kwargs = {}
|
||||
if 'image' in request.files:
|
||||
if "image" in request.files:
|
||||
file = request.files['image']
|
||||
if file.filename != '' and is_allowed_extension(file.filename):
|
||||
kwargs['image'] = to_image(file.stream, file.filename.endswith('.svg'))
|
||||
if 'json' in request.form:
|
||||
kwargs['image_name'] = file.filename
|
||||
if "json" in request.form:
|
||||
json_data = json.loads(request.form['json'])
|
||||
else:
|
||||
json_data = request.json
|
||||
|
||||
provider = json_data.get('provider', '').replace('g4f.Provider.', '')
|
||||
provider = provider if provider and provider != "Auto" else None
|
||||
|
||||
if "image" in kwargs and not provider:
|
||||
provider = "Bing"
|
||||
if provider == 'OpenaiChat':
|
||||
kwargs['auto_continue'] = True
|
||||
|
||||
messages = json_data['messages']
|
||||
if json_data.get('web_search'):
|
||||
if provider == "Bing":
|
||||
kwargs['web_search'] = True
|
||||
else:
|
||||
messages[-1]["content"] = get_search_message(messages[-1]["content"])
|
||||
|
||||
model = json_data.get('model')
|
||||
model = model if model else models.default
|
||||
patch = patch_provider if json_data.get('patch_provider') else None
|
||||
|
28
g4f/image.py
28
g4f/image.py
@ -137,12 +137,12 @@ def get_orientation(image: Image) -> int:
|
||||
if orientation is not None:
|
||||
return orientation
|
||||
|
||||
def process_image(img: Image, new_width: int, new_height: int) -> Image:
|
||||
def process_image(image: Image, new_width: int, new_height: int) -> Image:
|
||||
"""
|
||||
Processes the given image by adjusting its orientation and resizing it.
|
||||
|
||||
Args:
|
||||
img (Image): The image to process.
|
||||
image (Image): The image to process.
|
||||
new_width (int): The new width of the image.
|
||||
new_height (int): The new height of the image.
|
||||
|
||||
@ -150,25 +150,27 @@ def process_image(img: Image, new_width: int, new_height: int) -> Image:
|
||||
Image: The processed image.
|
||||
"""
|
||||
# Fix orientation
|
||||
orientation = get_orientation(img)
|
||||
orientation = get_orientation(image)
|
||||
if orientation:
|
||||
if orientation > 4:
|
||||
img = img.transpose(FLIP_LEFT_RIGHT)
|
||||
image = image.transpose(FLIP_LEFT_RIGHT)
|
||||
if orientation in [3, 4]:
|
||||
img = img.transpose(ROTATE_180)
|
||||
image = image.transpose(ROTATE_180)
|
||||
if orientation in [5, 6]:
|
||||
img = img.transpose(ROTATE_270)
|
||||
image = image.transpose(ROTATE_270)
|
||||
if orientation in [7, 8]:
|
||||
img = img.transpose(ROTATE_90)
|
||||
image = image.transpose(ROTATE_90)
|
||||
# Resize image
|
||||
img.thumbnail((new_width, new_height))
|
||||
image.thumbnail((new_width, new_height))
|
||||
# Remove transparency
|
||||
if img.mode == "RGBA":
|
||||
img.load()
|
||||
white = new_image('RGB', img.size, (255, 255, 255))
|
||||
white.paste(img, mask=img.split()[-1])
|
||||
if image.mode == "RGBA":
|
||||
image.load()
|
||||
white = new_image('RGB', image.size, (255, 255, 255))
|
||||
white.paste(image, mask=image.split()[-1])
|
||||
return white
|
||||
return img
|
||||
elif image.mode != "RGB":
|
||||
image = image.convert("RGB")
|
||||
return image
|
||||
|
||||
def to_base64_jpg(image: Image, compression_rate: float) -> str:
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user