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 @@
};
+