Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some HuggingSpace providers #2553

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions g4f/Provider/hf_space/Qwen_QVQ_72B.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from __future__ import annotations

import json
from aiohttp import ClientSession, FormData

from ...typing import AsyncResult, Messages, ImagesType
from ...requests import raise_for_status
from ...errors import ResponseError
from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
from ..helper import format_prompt, get_random_string
from ...image import to_bytes, is_accepted_format

class Qwen_QVQ_72B(AsyncGeneratorProvider, ProviderModelMixin):
url = "https://qwen-qvq-72b-preview.hf.space"
api_endpoint = "/gradio_api/call/generate"

working = True

default_model = "QVQ-72B-Preview"
models = [default_model]

@classmethod
async def create_async_generator(
cls, model: str, messages: Messages,
images: ImagesType = None,
api_key: str = None,
proxy: str = None,
**kwargs
) -> AsyncResult:
headers = {
"Accept": "application/json",
}
if api_key is not None:
headers["Authorization"] = f"Bearer {api_key}"
async with ClientSession(headers=headers) as session:
if images:
data = FormData()
data_bytes = to_bytes(images[0][0])
data.add_field("files", data_bytes, content_type=is_accepted_format(data_bytes), filename=images[0][1])
url = f"https://qwen-qvq-72b-preview.hf.space/gradio_api/upload?upload_id={get_random_string()}"
async with session.post(url, data=data, proxy=proxy) as response:
await raise_for_status(response)
image = await response.json()
data = {"data": [{"path": image[0]}, format_prompt(messages)]}
else:
data = {"data": [None, format_prompt(messages)]}
async with session.post(f"{cls.url}{cls.api_endpoint}", json=data, proxy=proxy) as response:
await raise_for_status(response)
event_id = (await response.json()).get("event_id")
async with session.get(f"{cls.url}{cls.api_endpoint}/{event_id}") as event_response:
await raise_for_status(event_response)
event = None
text_position = 0
async for chunk in event_response.content:
if chunk.startswith(b"event: "):
event = chunk[7:].decode(errors="replace").strip()
if chunk.startswith(b"data: "):
if event == "error":
raise ResponseError(f"GPU token limit exceeded: {chunk.decode(errors='replace')}")
if event in ("complete", "generating"):
try:
data = json.loads(chunk[6:])
except (json.JSONDecodeError, KeyError, TypeError) as e:
raise RuntimeError(f"Failed to read response: {chunk.decode(errors='replace')}", e)
if event == "generating":
if isinstance(data[0], str):
yield data[0][text_position:]
text_position = len(data[0])
else:
break
71 changes: 71 additions & 0 deletions g4f/Provider/hf_space/StableDiffusion35Large.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from __future__ import annotations

import json
from aiohttp import ClientSession

from ...typing import AsyncResult, Messages
from ...image import ImageResponse, ImagePreview
from ...errors import ResponseError
from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin

class StableDiffusion35Large(AsyncGeneratorProvider, ProviderModelMixin):
url = "https://stabilityai-stable-diffusion-3-5-large.hf.space"
api_endpoint = "/gradio_api/call/infer"

working = True

default_model = 'stable-diffusion-3.5-large'
models = [default_model]
image_models = [default_model]

@classmethod
async def create_async_generator(
cls, model: str, messages: Messages,
prompt: str = None,
negative_prompt: str = None,
api_key: str = None,
proxy: str = None,
width: int = 1024,
height: int = 1024,
guidance_scale: float = 4.5,
num_inference_steps: int = 50,
seed: int = 0,
randomize_seed: bool = True,
**kwargs
) -> AsyncResult:
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
}
if api_key is not None:
headers["Authorization"] = f"Bearer {api_key}"
async with ClientSession(headers=headers) as session:
prompt = messages[-1]["content"] if prompt is None else prompt
data = {
"data": [prompt, negative_prompt, seed, randomize_seed, width, height, guidance_scale, num_inference_steps]
}
async with session.post(f"{cls.url}{cls.api_endpoint}", json=data, proxy=proxy) as response:
response.raise_for_status()
event_id = (await response.json()).get("event_id")
async with session.get(f"{cls.url}{cls.api_endpoint}/{event_id}") as event_response:
event_response.raise_for_status()
event = None
async for chunk in event_response.content:
if chunk.startswith(b"event: "):
event = chunk[7:].decode(errors="replace").strip()
if chunk.startswith(b"data: "):
if event == "error":
raise ResponseError(f"GPU token limit exceeded: {chunk.decode(errors='replace')}")
if event in ("complete", "generating"):
try:
data = json.loads(chunk[6:])
if data is None:
continue
url = data[0]["url"]
except (json.JSONDecodeError, KeyError, TypeError) as e:
raise RuntimeError(f"Failed to parse image URL: {chunk.decode(errors='replace')}", e)
if event == "generating":
yield ImagePreview(url, prompt)
else:
yield ImageResponse(url, prompt)
break
4 changes: 3 additions & 1 deletion g4f/Provider/hf_space/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
from .BlackForestLabsFlux1Dev import BlackForestLabsFlux1Dev
from .BlackForestLabsFlux1Schnell import BlackForestLabsFlux1Schnell
from .VoodoohopFlux1Schnell import VoodoohopFlux1Schnell
from .StableDiffusion35Large import StableDiffusion35Large
from .Qwen_QVQ_72B import Qwen_QVQ_72B

class HuggingSpace(AsyncGeneratorProvider, ProviderModelMixin):
url = "https://huggingface.co/spaces"
working = True
default_model = BlackForestLabsFlux1Dev.default_model
providers = [BlackForestLabsFlux1Dev, BlackForestLabsFlux1Schnell, VoodoohopFlux1Schnell]
providers = [BlackForestLabsFlux1Dev, BlackForestLabsFlux1Schnell, VoodoohopFlux1Schnell, StableDiffusion35Large, Qwen_QVQ_72B]

@classmethod
def get_parameters(cls, **kwargs) -> dict:
Expand Down
10 changes: 9 additions & 1 deletion g4f/gui/client/static/js/chat.v1.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ messageInput.addEventListener("focus", () => {
document.documentElement.scrollTop = document.documentElement.scrollHeight;
});

// Check if site's storage has been marked as persistent
(async () => {
if (window.localStorage && navigator.storage && navigator.storage.persist) {
const isPersisted = await navigator.storage.persisted();
console.log(`Persisted storage granted: ${isPersisted}`);
}
})();

appStorage = window.localStorage || {
setItem: (key, value) => self[key] = value,
getItem: (key) => self[key],
Expand Down Expand Up @@ -1110,7 +1118,7 @@ const load_conversation = async (conversation_id, scroll=true) => {
if (lastLine.endsWith("[aborted]") || lastLine.endsWith("[error]")) {
reason = "error";
// Has an even number of start or end code tags
} else if (reason = "stop" && buffer.split("```").length - 1 % 2 === 1) {
} else if (reason == "stop" && buffer.split("```").length - 1 % 2 === 1) {
reason = "length";
}
if (reason == "length" || reason == "max_tokens" || reason == "error") {
Expand Down
13 changes: 3 additions & 10 deletions g4f/providers/asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,8 @@ def to_sync_generator(generator: AsyncIterator, stream: bool = True) -> Iterator

# Helper function to convert a synchronous iterator to an async iterator
async def to_async_iterator(iterator: Iterator) -> AsyncIterator:
if isinstance(iterator, str):
yield iterator
elif hasattr(iterator, "__await__"):
yield await iterator
elif hasattr(iterator, "__aiter__"):
try:
async for item in iterator:
yield item
elif hasattr(iterator, "__iter__"):
for item in iterator:
yield item
else:
yield iterator
except TypeError:
yield await iterator
25 changes: 18 additions & 7 deletions g4f/tools/web_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,10 @@ def scrape_text(html: str, max_words: int = None, add_source=True, count_images:

image_select = "img[alt][src^=http]:not([alt=''])"
image_link_select = f"a:has({image_select})"
yield_words = []
for paragraph in soup.select(f"h1, h2, h3, h4, h5, h6, p, table:not(:has(p)), ul:not(:has(p)), {image_link_select}"):
image = paragraph.select_one(image_select)
if count_images > 0:
image = paragraph.select_one(image_select)
if image:
title = paragraph.get("title") or paragraph.text
if title:
Expand All @@ -104,15 +105,19 @@ def scrape_text(html: str, max_words: int = None, add_source=True, count_images:
continue

for line in paragraph.text.splitlines():
words = [word for word in line.replace("\t", " ").split(" ") if word]
words = [word for word in line.split() if word]
count = len(words)
if not count:
continue
words = " ".join(words)
if words in yield_words:
continue
if max_words:
max_words -= count
if max_words <= 0:
break
yield " ".join(words) + "\n"
yield words + "\n"
yield_words.append(words)

if add_source:
canonical_link = source.find("link", rel="canonical")
Expand All @@ -126,7 +131,7 @@ async def fetch_and_scrape(session: ClientSession, url: str, max_words: int = No
bucket_dir: Path = Path(get_cookies_dir()) / ".scrape_cache" / "fetch_and_scrape"
bucket_dir.mkdir(parents=True, exist_ok=True)
md5_hash = hashlib.md5(url.encode()).hexdigest()
cache_file = bucket_dir / f"{url.split('?')[0].split('//')[1].replace('/', '+')[:16]}.{datetime.date.today()}.{md5_hash}.cache"
cache_file = bucket_dir / f"{quote_plus(url.split('?')[0].split('//')[1].replace('/', ' ')[:48])}.{datetime.date.today()}.{md5_hash[:16]}.cache"
if cache_file.exists():
return cache_file.read_text()
async with session.get(url) as response:
Expand Down Expand Up @@ -173,7 +178,7 @@ async def search(query: str, max_results: int = 5, max_words: int = 2500, backen
for i, entry in enumerate(results):
if add_text:
entry.text = texts[i]
if left_words:
if max_words:
left_words -= entry.title.count(" ") + 5
if entry.text:
left_words -= entry.text.count(" ")
Expand Down Expand Up @@ -202,13 +207,19 @@ async def do_search(prompt: str, query: str = None, instructions: str = DEFAULT_
if search_results.results:
with cache_file.open("w") as f:
f.write(str(search_results))

new_prompt = f"""
if instructions:
new_prompt = f"""
{search_results}

Instruction: {instructions}

User request:
{prompt}
"""
else:
new_prompt = f"""
{search_results}

{prompt}
"""
debug.log(f"Web search: '{query.strip()[:50]}...'")
Expand Down
Loading