diff options
-rw-r--r-- | g4f/Provider/You.py | 66 | ||||
-rw-r--r-- | g4f/cookies.py | 4 | ||||
-rw-r--r-- | g4f/gui/__init__.py | 3 | ||||
-rw-r--r-- | g4f/gui/client/css/style.css | 25 | ||||
-rw-r--r-- | g4f/gui/client/html/index.html | 8 | ||||
-rw-r--r-- | g4f/gui/client/js/chat.v1.js | 51 | ||||
-rw-r--r-- | g4f/gui/server/backend.py | 9 |
7 files changed, 136 insertions, 30 deletions
diff --git a/g4f/Provider/You.py b/g4f/Provider/You.py index 91a195cf..17e6269a 100644 --- a/g4f/Provider/You.py +++ b/g4f/Provider/You.py @@ -1,6 +1,8 @@ from __future__ import annotations import json +import base64 +import uuid from ..requests import StreamSession from ..typing import AsyncGenerator, Messages @@ -11,7 +13,9 @@ class You(AsyncGeneratorProvider): url = "https://you.com" working = True supports_gpt_35_turbo = True - + supports_gpt_4 = True + _session_used = 0 + _session_token = None @classmethod async def create_async_generator( @@ -27,14 +31,70 @@ class You(AsyncGeneratorProvider): "Accept": "text/event-stream", "Referer": f"{cls.url}/search?fromSearchBar=true&tbm=youchat", } - data = {"q": format_prompt(messages), "domain": "youchat", "chat": ""} + data = { + "q": format_prompt(messages), + "domain": "youchat", + "chat": "", "selectedChatMode": "gpt-4" if model == "gpt-4" else "default" + } async with session.get( f"{cls.url}/api/streamingSearch", params=data, - headers=headers + headers=headers, + cookies=cls.get_cookies(await cls.get_session_token(proxy, timeout)) if model == "gpt-4" else None ) 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]) + + @classmethod + async def get_session_token(cls, proxy: str, timeout: int): + if not cls._session_token or cls._session_used >= 5: + cls._session_token = await cls.create_session_token(proxy, timeout) + cls._session_used += 1 + return cls._session_token + + def get_cookies(access_token: str, session_jwt: str = "0"): + return { + 'stytch_session_jwt': session_jwt, + 'ydc_stytch_session': access_token, + 'ydc_stytch_session_jwt': session_jwt + } + + @classmethod + def get_jwt(cls): + 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() + + @classmethod + async def create_session_token(cls, proxy: str, timeout: int): + async with StreamSession(proxies={"https": proxy}, impersonate="chrome110", timeout=timeout) as session: + user_uuid = str(uuid.uuid4()) + 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() + async with session.post( + "https://web.stytch.com/sdk/v1/passwords", + headers={ + "Authorization": f"Basic {auth}", + "X-SDK-Client": cls.get_jwt(), + "X-SDK-Parent-Host": "https://you.com" + }, + 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()}") + return (await response.json())["data"]["session_token"]
\ No newline at end of file diff --git a/g4f/cookies.py b/g4f/cookies.py index af0e6192..a38488c2 100644 --- a/g4f/cookies.py +++ b/g4f/cookies.py @@ -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: diff --git a/g4f/gui/__init__.py b/g4f/gui/__init__.py index 46b4f56a..dff720ac 100644 --- a/g4f/gui/__init__.py +++ b/g4f/gui/__init__.py @@ -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, diff --git a/g4f/gui/client/css/style.css b/g4f/gui/client/css/style.css index e03f36d2..8752dee5 100644 --- a/g4f/gui/client/css/style.css +++ b/g4f/gui/client/css/style.css @@ -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"] { diff --git a/g4f/gui/client/html/index.html b/g4f/gui/client/html/index.html index 55b54b48..175b7dc8 100644 --- a/g4f/gui/client/html/index.html +++ b/g4f/gui/client/html/index.html @@ -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 only with Bing, Gemini and OpenaiChat"> + <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> diff --git a/g4f/gui/client/js/chat.v1.js b/g4f/gui/client/js/chat.v1.js index 86eef8c9..4b2f1c1a 100644 --- a/g4f/gui/client/js/chat.v1.js +++ b/g4f/gui/client/js/chat.v1.js @@ -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> `; @@ -141,9 +146,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 +217,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); @@ -668,21 +677,27 @@ 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 (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; + console.log(el.dataset.src); + }); + reader.readAsDataURL(el.files[0]); + } } -}); + do_load() + el.addEventListener('change', do_load); +} fileInput.addEventListener('click', async (event) => { fileInput.value = ''; delete fileInput.dataset.text; diff --git a/g4f/gui/server/backend.py b/g4f/gui/server/backend.py index 2218452c..14f6cece 100644 --- a/g4f/gui/server/backend.py +++ b/g4f/gui/server/backend.py @@ -134,25 +134,30 @@ 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: + 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 |