From 1d3a139a53b09be5ab8111bab38d5ffd2dd31f1e Mon Sep 17 00:00:00 2001 From: hlohaus <983577+hlohaus@users.noreply.github.com> Date: Wed, 26 Feb 2025 11:41:00 +0100 Subject: Add new media selection in UI Add HuggingFace provider provider Auto refresh Google Gemini cookies Add sources to search results --- README.md | 1 + g4f/Provider/hf/HuggingFaceInference.py | 61 +++++++++++------- g4f/Provider/needs_auth/Gemini.py | 106 ++++++++++++++++++++++++++++---- g4f/cookies.py | 4 +- g4f/gui/client/demo.html | 14 ++++- g4f/gui/client/index.html | 26 ++++++-- g4f/gui/client/static/css/style.css | 46 +++++++++++--- g4f/gui/client/static/js/chat.v1.js | 83 ++++++++++++++++--------- g4f/models.py | 4 +- g4f/providers/asyncio.py | 3 +- g4f/providers/response.py | 17 +++-- g4f/tools/run_tools.py | 19 +++++- g4f/tools/web_search.py | 47 ++++++++++---- 13 files changed, 326 insertions(+), 105 deletions(-) diff --git a/README.md b/README.md index 978d8e54..9e52a2d5 100644 --- a/README.md +++ b/README.md @@ -835,6 +835,7 @@ A list of all contributors is available [here](https://github.com/xtekky/gpt4fre - The [`Gemini.py`](https://github.com/xtekky/gpt4free/blob/main/g4f/Provider/needs_auth/Gemini.py) has input from [dsdanielpark/Gemini-API](https://github.com/dsdanielpark/Gemini-API) - The [`MetaAI.py`](https://github.com/xtekky/gpt4free/blob/main/g4f/Provider/MetaAI.py) file contains code from [meta-ai-api](https://github.com/Strvm/meta-ai-api) by [@Strvm](https://github.com/Strvm) - The [`proofofwork.py`](https://github.com/xtekky/gpt4free/blob/main/g4f/Provider/openai/proofofwork.py) has input from [missuo/FreeGPT35](https://github.com/missuo/FreeGPT35) +- The [`Gemini.py`](https://github.com/xtekky/gpt4free/blob/main/g4f/Provider/needs_auth/Gemini.py) has input from [HanaokaYuzu/Gemini-API](https://github.com/HanaokaYuzu/Gemini-API) _Having input implies that the AI's code generation utilized it as one of many sources._ diff --git a/g4f/Provider/hf/HuggingFaceInference.py b/g4f/Provider/hf/HuggingFaceInference.py index 32c1f42f..2699e49b 100644 --- a/g4f/Provider/hf/HuggingFaceInference.py +++ b/g4f/Provider/hf/HuggingFaceInference.py @@ -14,6 +14,11 @@ from ..helper import format_image_prompt, get_last_user_message from .models import default_model, default_image_model, model_aliases, text_models, image_models, vision_models from ... import debug +provider_together_urls = { + "black-forest-labs/FLUX.1-dev": "https://router.huggingface.co/together/v1/images/generations", + "black-forest-labs/FLUX.1-schnell": "https://router.huggingface.co/together/v1/images/generations", +} + class HuggingFaceInference(AsyncGeneratorProvider, ProviderModelMixin): url = "https://huggingface.co" parent = "HuggingFace" @@ -63,6 +68,7 @@ class HuggingFaceInference(AsyncGeneratorProvider, ProviderModelMixin): messages: Messages, stream: bool = True, proxy: str = None, + timeout: int = 600, api_base: str = "https://api-inference.huggingface.co", api_key: str = None, max_tokens: int = 1024, @@ -71,6 +77,8 @@ class HuggingFaceInference(AsyncGeneratorProvider, ProviderModelMixin): action: str = None, extra_data: dict = {}, seed: int = None, + width: int = 1024, + height: int = 1024, **kwargs ) -> AsyncResult: try: @@ -78,36 +86,43 @@ class HuggingFaceInference(AsyncGeneratorProvider, ProviderModelMixin): except ModelNotSupportedError: pass headers = { - 'accept': '*/*', - 'accept-language': 'en', - 'cache-control': 'no-cache', - 'origin': 'https://huggingface.co', - 'pragma': 'no-cache', - 'priority': 'u=1, i', - 'referer': 'https://huggingface.co/chat/', - 'sec-ch-ua': '"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"', - 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"macOS"', - 'sec-fetch-dest': 'empty', - 'sec-fetch-mode': 'cors', - 'sec-fetch-site': 'same-origin', - 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36', + 'Accept-Encoding': 'gzip, deflate', + 'Content-Type': 'application/json', } if api_key is not None: headers["Authorization"] = f"Bearer {api_key}" - payload = None - params = { - "return_full_text": False, - "max_new_tokens": max_tokens, - "temperature": temperature, - **extra_data - } - do_continue = action == "continue" async with StreamSession( headers=headers, proxy=proxy, - timeout=600 + timeout=timeout ) as session: + try: + if model in provider_together_urls: + data = { + "response_format": "url", + "prompt": format_image_prompt(messages, prompt), + "model": model, + "width": width, + "height": height, + **extra_data + } + async with session.post(provider_together_urls[model], json=data) as response: + if response.status == 404: + raise ModelNotSupportedError(f"Model is not supported: {model}") + await raise_for_status(response) + result = await response.json() + yield ImageResponse([item["url"] for item in result["data"]], data["prompt"]) + return + except ModelNotSupportedError: + pass + payload = None + params = { + "return_full_text": False, + "max_new_tokens": max_tokens, + "temperature": temperature, + **extra_data + } + do_continue = action == "continue" if payload is None: model_data = await cls.get_model_data(session, model) pipeline_tag = model_data.get("pipeline_tag") diff --git a/g4f/Provider/needs_auth/Gemini.py b/g4f/Provider/needs_auth/Gemini.py index af953129..461c149f 100644 --- a/g4f/Provider/needs_auth/Gemini.py +++ b/g4f/Provider/needs_auth/Gemini.py @@ -6,7 +6,10 @@ import random import re import base64 import asyncio +import time +from urllib.parse import quote_plus, unquote_plus +from pathlib import Path from aiohttp import ClientSession, BaseConnector try: @@ -17,15 +20,15 @@ except ImportError: from ... import debug from ...typing import Messages, Cookies, ImagesType, AsyncResult, AsyncIterator -from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin -from ..helper import format_prompt, get_cookies -from ...providers.response import JsonConversation, Reasoning, RequestLogin, ImageResponse +from ...providers.response import JsonConversation, Reasoning, RequestLogin, ImageResponse, YouTube from ...requests.raise_for_status import raise_for_status from ...requests.aiohttp import get_connector from ...requests import get_nodriver from ...errors import MissingAuthError from ...image import to_bytes -from ..helper import get_last_user_message +from ...cookies import get_cookies_dir +from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin +from ..helper import format_prompt, get_cookies, get_last_user_message from ... import debug REQUEST_HEADERS = { @@ -52,6 +55,9 @@ UPLOAD_IMAGE_HEADERS = { "x-goog-upload-protocol": "resumable", "x-tenant-id": "bard-storage", } +GOOGLE_COOKIE_DOMAIN = ".google.com" +ROTATE_COOKIES_URL = "https://accounts.google.com/RotateCookies" +GGOGLE_SID_COOKIE = "__Secure-1PSID" models = { "gemini-2.0-flash": {"x-goog-ext-525001261-jspb": '[null,null,null,null,"f299729663a2343f"]'}, @@ -87,6 +93,10 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin): _snlm0e: str = None _sid: str = None + auto_refresh = True + refresh_interval = 540 + rotate_tasks = {} + @classmethod async def nodriver_login(cls, proxy: str = None) -> AsyncIterator[str]: if not has_nodriver: @@ -108,6 +118,29 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin): finally: stop_browser() + @classmethod + async def start_auto_refresh(cls, proxy: str = None) -> None: + """ + Start the background task to automatically refresh cookies. + """ + + while True: + try: + new_1psidts = await rotate_1psidts(cls.url, cls._cookies, proxy) + except Exception as e: + debug.error(f"Failed to refresh cookies: {e}") + task = cls.rotate_tasks.get(cls._cookies[GGOGLE_SID_COOKIE]) + if task: + task.cancel() + debug.error( + "Failed to refresh cookies. Background auto refresh task canceled." + ) + + debug.log(f"Gemini: Cookies refreshed. New __Secure-1PSIDTS: {new_1psidts}") + if new_1psidts: + cls._cookies["__Secure-1PSIDTS"] = new_1psidts + await asyncio.sleep(cls.refresh_interval) + @classmethod async def create_async_generator( cls, @@ -122,8 +155,10 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin): language: str = "en", **kwargs ) -> AsyncResult: + cls._cookies = cookies or cls._cookies or get_cookies(GOOGLE_COOKIE_DOMAIN, False, True) + if conversation is not None and getattr(conversation, "model", None) != model: + conversation = None prompt = format_prompt(messages) if conversation is None else get_last_user_message(messages) - cls._cookies = cookies or cls._cookies or get_cookies(".google.com", False, True) base_connector = get_connector(connector, proxy) async with ClientSession( @@ -144,6 +179,12 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin): await cls.fetch_snlm0e(session, cls._cookies) if not cls._snlm0e: raise RuntimeError("Invalid cookies. SNlM0e not found") + if GGOGLE_SID_COOKIE in cls._cookies: + task = cls.rotate_tasks.get(cls._cookies[GGOGLE_SID_COOKIE]) + if not task: + cls.rotate_tasks[cls._cookies[GGOGLE_SID_COOKIE]] = asyncio.create_task( + cls.start_auto_refresh() + ) images = await cls.upload_images(base_connector, images) if images else None async with ClientSession( @@ -190,7 +231,7 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin): if not response_part[4]: continue if return_conversation: - yield Conversation(response_part[1][0], response_part[1][1], response_part[4][0][0]) + yield Conversation(response_part[1][0], response_part[1][1], response_part[4][0][0], model) def read_recusive(data): for item in data: if isinstance(item, list): @@ -222,12 +263,13 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin): if match: image_prompt = match.group(1) content = content.replace(match.group(0), '') - pattern = r"http://googleusercontent.com/(?:image_generation|youtube)_content/\d+" + pattern = r"http://googleusercontent.com/(?:image_generation|youtube|map)_content/\d+" content = re.sub(pattern, "", content) content = content.replace("", "") - content = content.replace("https://www.google.com/search?q=http://", "https://") - content = content.replace("https://www.google.com/search?q=https://", "https://") - content = content.replace("https://www.google.com/url?sa=E&source=gmail&q=http://", "http://") + def replace_link(match): + return f"(https://{quote_plus(unquote_plus(match.group(1)), '/?&=#')})" + content = re.sub(r"\(https://www.google.com/(?:search\?q=|url\?sa=E&source=gmail&q=)https?://(.+?)\)", replace_link, content) + if last_content and content.startswith(last_content): yield content[len(last_content):] else: @@ -240,6 +282,13 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin): yield ImageResponse(images, image_prompt, {"cookies": cls._cookies}) except (TypeError, IndexError, KeyError): pass + youtube_ids = [] + pattern = re.compile(r"http://www.youtube.com/watch\?v=(\w+)") + for match in pattern.finditer(content): + if match.group(1) not in youtube_ids: + youtube_ids.append(match.group(1)) + if youtube_ids: + yield YouTube(youtube_ids) @classmethod async def synthesize(cls, params: dict, proxy: str = None) -> AsyncIterator[bytes]: @@ -354,11 +403,13 @@ class Conversation(JsonConversation): def __init__(self, conversation_id: str, response_id: str, - choice_id: str + choice_id: str, + model: str ) -> None: self.conversation_id = conversation_id self.response_id = response_id self.choice_id = choice_id + self.model = model async def iter_filter_base64(chunks: AsyncIterator[bytes]) -> AsyncIterator[bytes]: search_for = b'[["wrb.fr","XqA3Ic","[\\"' @@ -386,4 +437,35 @@ async def iter_base64_decode(chunks: AsyncIterator[bytes]) -> AsyncIterator[byte buffer = chunk[-rest:] yield base64.b64decode(chunk[:-rest]) if rest > 0: - yield base64.b64decode(buffer+rest*b"=") \ No newline at end of file + yield base64.b64decode(buffer+rest*b"=") + +async def rotate_1psidts(url, cookies: dict, proxy: str | None = None) -> str: + path = Path(get_cookies_dir()) + path.mkdir(parents=True, exist_ok=True) + filename = f"auth_Gemini.json" + path = path / filename + + # Check if the cache file was modified in the last minute to avoid 429 Too Many Requests + if not (path.is_file() and time.time() - os.path.getmtime(path) <= 60): + async with ClientSession(proxy=proxy) as client: + response = await client.post( + url=ROTATE_COOKIES_URL, + headers={ + "Content-Type": "application/json", + }, + cookies=cookies, + data='[000,"-0000000000000000000"]', + ) + if response.status == 401: + raise MissingAuthError("Invalid cookies") + response.raise_for_status() + for key, c in response.cookies.items(): + cookies[key] = c.value + new_1psidts = response.cookies.get("__Secure-1PSIDTS") + path.write_text(json.dumps([{ + "name": k, + "value": v, + "domain": GOOGLE_COOKIE_DOMAIN, + } for k, v in cookies.items()])) + if new_1psidts: + return new_1psidts \ No newline at end of file diff --git a/g4f/cookies.py b/g4f/cookies.py index 88d1f019..b872be73 100644 --- a/g4f/cookies.py +++ b/g4f/cookies.py @@ -192,5 +192,5 @@ def read_cookie_files(dirPath: str = None): new_cookies[c["domain"]] = {} new_cookies[c["domain"]][c["name"]] = c["value"] for domain, new_values in new_cookies.items(): - debug.log(f"Cookies added: {len(new_values)} from {domain}") - CookiesConfig.cookies[domain] = new_values \ No newline at end of file + CookiesConfig.cookies[domain] = new_values + debug.log(f"Cookies added: {len(new_values)} from {domain}") \ No newline at end of file diff --git a/g4f/gui/client/demo.html b/g4f/gui/client/demo.html index 36c2afed..3fb34040 100644 --- a/g4f/gui/client/demo.html +++ b/g4f/gui/client/demo.html @@ -254,6 +254,13 @@ } catch(e) { console.log(e); input.setCustomValidity("Invalid Access Token."); + localStorage.removeItem("HuggingFace-api_key"); + if (localStorage.getItem("oauth")) { + window.location.href = (await oauthLoginUrl({ + clientId: 'ed074164-4f8d-4fb2-8bec-44952707965e', + scopes: ['inference-api'] + })); + } return; } localStorage.setItem("HuggingFace-api_key", accessToken); @@ -289,10 +296,13 @@ window.location.reload(); } } else { + localStorage.removeItem("oauth"); document.getElementById("signin").style.removeProperty("display"); document.getElementById("signin").onclick = async function() { - // prompt=consent to re-trigger the consent screen instead of silently redirecting - window.location.href = (await oauthLoginUrl({clientId: 'ed074164-4f8d-4fb2-8bec-44952707965e', scopes: ['inference-api']})) + "&prompt=consent"; + window.location.href = (await oauthLoginUrl({ + clientId: 'ed074164-4f8d-4fb2-8bec-44952707965e', + scopes: ['inference-api'] + })); } } diff --git a/g4f/gui/client/index.html b/g4f/gui/client/index.html index f007a084..48904917 100644 --- a/g4f/gui/client/index.html +++ b/g4f/gui/client/index.html @@ -33,6 +33,12 @@ }; +