diff --git a/g4f/Provider/PollinationsAI.py b/g4f/Provider/PollinationsAI.py index 33043a5d..aea92c3d 100644 --- a/g4f/Provider/PollinationsAI.py +++ b/g4f/Provider/PollinationsAI.py @@ -3,6 +3,7 @@ from __future__ import annotations import json import random import requests +import asyncio from urllib.parse import quote_plus from typing import Optional from aiohttp import ClientSession @@ -148,6 +149,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin): private: bool = False, enhance: bool = False, safe: bool = False, + n: int = 1, # Text generation parameters media: MediaListType = None, temperature: float = None, @@ -187,7 +189,8 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin): nologo=nologo, private=private, enhance=enhance, - safe=safe + safe=safe, + n=n ): yield chunk else: @@ -223,12 +226,10 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin): nologo: bool, private: bool, enhance: bool, - safe: bool + safe: bool, + n: int ) -> AsyncResult: - if not cache and seed is None: - seed = random.randint(9999, 99999999) params = use_aspect_ratio({ - "seed": seed, "width": width, "height": height, "model": model, @@ -238,12 +239,27 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin): "safe": str(safe).lower() }, aspect_ratio) query = "&".join(f"{k}={quote_plus(str(v))}" for k, v in params.items() if v is not None) - prompt = quote_plus(prompt)[:8112] # Limit URL length + prompt = quote_plus(prompt)[:2048-256-len(query)] url = f"{cls.image_api_endpoint}prompt/{prompt}?{query}" + def get_image_url(i: int = 0, seed: Optional[int] = None): + if i == 0: + if not cache and seed is None: + seed = random.randint(0, 2**32) + else: + seed = random.randint(0, 2**32) + return f"{url}&seed={seed}" if seed else url async with ClientSession(headers=DEFAULT_HEADERS, connector=get_connector(proxy=proxy)) as session: - async with session.get(url, allow_redirects=False) as response: - await raise_for_status(response) - yield ImageResponse(str(response.url), prompt) + async def get_image(i: int = 0, seed: Optional[int] = None): + async with session.get(get_image_url(i, seed), allow_redirects=False) as response: + try: + await raise_for_status(response) + except Exception as e: + debug.error(f"Error fetching image: {e}") + return str(response.url) + return str(response.url) + yield ImageResponse(await asyncio.gather(*[ + get_image(i, seed) for i in range(int(n)) + ]), prompt) @classmethod async def _generate_text( diff --git a/g4f/Provider/PollinationsImage.py b/g4f/Provider/PollinationsImage.py index 3d920ac2..f0955a11 100644 --- a/g4f/Provider/PollinationsImage.py +++ b/g4f/Provider/PollinationsImage.py @@ -45,6 +45,7 @@ class PollinationsImage(PollinationsAI): private: bool = False, enhance: bool = False, safe: bool = False, + n: int = 4, **kwargs ) -> AsyncResult: # Calling model updates before creating a generator @@ -61,6 +62,7 @@ class PollinationsImage(PollinationsAI): nologo=nologo, private=private, enhance=enhance, - safe=safe + safe=safe, + n=n ): yield chunk diff --git a/g4f/gui/client/background.html b/g4f/gui/client/background.html index 6517c7d1..951ca779 100644 --- a/g4f/gui/client/background.html +++ b/g4f/gui/client/background.html @@ -28,7 +28,6 @@ .gradient { position: absolute; - z-index: -1; left: 50vw; border-radius: 50%; background: radial-gradient(circle at center, var(--accent), var(--gradient)); @@ -73,12 +72,6 @@ overflow: hidden; } - iframe { - background: transparent; - width: 100%; - border: none; - } - .hidden { display: none; } @@ -86,7 +79,6 @@ #background, #image-feed, #video-feed { height: 100%; position: absolute; - z-index: -1; object-fit: cover; object-position: center; width: 100%; @@ -100,29 +92,41 @@ -
+
diff --git a/g4f/gui/client/index.html b/g4f/gui/client/index.html index b85af2e5..30e696cd 100644 --- a/g4f/gui/client/index.html +++ b/g4f/gui/client/index.html @@ -63,7 +63,7 @@ diff --git a/g4f/gui/client/qrcode.html b/g4f/gui/client/qrcode.html index 30754771..d4abda85 100644 --- a/g4f/gui/client/qrcode.html +++ b/g4f/gui/client/qrcode.html @@ -74,15 +74,15 @@ }); document.getElementById('generateQRCode').addEventListener('click', async () => { - const chat_id = generate_uuid(); + const share_id = generate_uuid(); - const url = `${share_url}/backend-api/v2/chat/${encodeURI(chat_id)}`; + const url = `${share_url}/backend-api/v2/chat/${encodeURI(share_id)}`; const response = await fetch(url, { method: 'POST', headers: {'content-type': 'application/json'}, body: localStorage.getItem(`conversation:${conversation_id}`) }); - const share = `${share_url}/chat/${encodeURI(chat_id)}/${encodeURI(conversation_id)}`; + const share = `${share_url}/chat/${encodeURI(share_id)}/${encodeURI(conversation_id)}`; const qrcodeStatus = document.getElementById('qrcode-status'); if (response.status !== 200) { qrcodeStatus.innerText = 'Error generating QR code: ' + response.statusText; @@ -115,8 +115,8 @@ const constraints = { video: { - width: { ideal: 1280 }, - height: { ideal: 1280 }, + width: { ideal: 800 }, + height: { ideal: 800 }, facingMode: facingMode }, audio: false diff --git a/g4f/gui/client/static/js/chat.v1.js b/g4f/gui/client/static/js/chat.v1.js index e8a7cc58..6bbe0f65 100644 --- a/g4f/gui/client/static/js/chat.v1.js +++ b/g4f/gui/client/static/js/chat.v1.js @@ -298,8 +298,8 @@ const register_message_buttons = async () => { if (message_el) { if ("index" in message_el.dataset) { await remove_message(window.conversation_id, message_el.dataset.index); + chatBody.removeChild(message_el); } - message_el.remove(); } reloadConversation = true; await safe_load_conversation(window.conversation_id, false); @@ -842,7 +842,7 @@ async function add_message_chunk(message, message_id, provider, scroll, finish_m for (const [key, value] of Object.entries(message.conversation)) { conversation.data[key] = value; } - await save_conversation(conversation_id, conversation); + await save_conversation(conversation_id, get_conversation_data(conversation)); } else if (message.type == "auth") { error_storage[message_id] = message.message content_map.inner.innerHTML += markdown_render(`**An error occured:** ${message.message}`); @@ -1227,7 +1227,6 @@ function sanitize(input, replacement) { } async function set_conversation_title(conversation_id, title) { - window.chat_id = null; conversation = await get_conversation(conversation_id) conversation.new_title = title; const new_id = sanitize(title, " "); @@ -1319,7 +1318,6 @@ const set_conversation = async (conversation_id) => { const new_conversation = async () => { history.pushState({}, null, `/chat/`); - window.chat_id = null; window.conversation_id = uuid(); document.title = window.title || document.title; document.querySelector(".chat-header").innerText = "New Conversation - G4F"; @@ -1391,7 +1389,7 @@ const load_conversation = async (conversation, scroll=true) => { document.title = title; } const chatHeader = document.querySelector(".chat-header"); - if (window.chat_id) { + if (window.share_id && conversation.id == window.start_id) { chatHeader.innerHTML = ' ' + escapeHtml(title); } else { chatHeader.innerText = title; @@ -1581,16 +1579,16 @@ async function get_conversation(conversation_id) { return conversation; } -async function save_conversation(conversation_id, conversation) { +function get_conversation_data(conversation) { conversation.updated = Date.now(); - const data = JSON.stringify(conversation) + return JSON.stringify(conversation); +} + +async function save_conversation(conversation_id, data) { appStorage.setItem( `conversation:${conversation_id}`, data ); - if (conversation_id != window.start_id) { - window.chat_id = null; - } } async function get_messages(conversation_id) { @@ -1600,13 +1598,13 @@ async function get_messages(conversation_id) { async function add_conversation(conversation_id) { if (appStorage.getItem(`conversation:${conversation_id}`) == null) { - await save_conversation(conversation_id, { + await save_conversation(conversation_id, get_conversation_data({ id: conversation_id, title: "", added: Date.now(), system: chatPrompt?.value, items: [], - }); + })); } try { add_url_to_history(`/chat/${conversation_id}`); @@ -1622,7 +1620,7 @@ async function save_system_message() { const conversation = await get_conversation(window.conversation_id); if (conversation) { conversation.system = chatPrompt?.value; - await save_conversation(window.conversation_id, conversation); + await save_conversation(window.conversation_id, get_conversation_data(conversation)); } } @@ -1640,9 +1638,10 @@ const remove_message = async (conversation_id, index) => { } } conversation.items = new_items; - await save_conversation(conversation_id, conversation); - if (window.chat_id && window.conversation_id == window.start_id) { - const url = `${window.share_url}/backend-api/v2/chat/${window.chat_id}`; + const data = get_conversation_data(conversation); + await save_conversation(conversation_id, data); + if (window.share_id && window.conversation_id == window.start_id) { + const url = `${window.share_url}/backend-api/v2/chat/${window.share_id}`; await fetch(url, { method: 'POST', headers: {'content-type': 'application/json'}, @@ -1716,13 +1715,14 @@ const add_message = async ( }); conversation.items = new_messages; } - await save_conversation(conversation_id, conversation); - if (window.chat_id && conversation_id == window.start_id) { - const url = `${window.share_url}/backend-api/v2/chat/${window.chat_id}`; + data = get_conversation_data(conversation); + await save_conversation(conversation_id, data); + if (window.share_id && conversation_id == window.start_id) { + const url = `${window.share_url}/backend-api/v2/chat/${window.share_id}`; fetch(url, { method: 'POST', headers: {'content-type': 'application/json'}, - body: JSON.stringify(conversation), + body: data }); } return conversation.items.length - 1; @@ -1758,7 +1758,7 @@ const load_conversations = async () => { // appStorage.removeItem(`conversation:${conversation.id}`); // return; // } - const shareIcon = (conversation.id == window.start_id && window.chat_id) ? '': ''; + const shareIcon = (conversation.id == window.start_id && window.share_id) ? '': ''; html.push(`
@@ -2071,14 +2071,14 @@ chatPrompt.addEventListener("input", function() { }); window.addEventListener('load', async function() { - if (!window.chat_id) { + if (!window.share_id) { return await load_conversation(JSON.parse(appStorage.getItem(`conversation:${window.conversation_id}`))); } if (!window.conversation_id) { - window.conversation_id = window.chat_id; + window.conversation_id = window.share_id; } - const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.chat_id ? window.chat_id : window.conversation_id}`, { - headers: {'accept': 'application/json'}, + const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.share_id}`, { + headers: {'accept': 'application/json', 'x-conversation-id': window.conversation_id}, }); if (!response.ok) { return await load_conversation(JSON.parse(appStorage.getItem(`conversation:${window.conversation_id}`))); @@ -2087,10 +2087,7 @@ window.addEventListener('load', async function() { if (!window.conversation_id || conversation.id == window.conversation_id) { window.conversation_id = conversation.id; await load_conversation(conversation); - appStorage.setItem( - `conversation:${conversation.id}`, - JSON.stringify(conversation) - ); + await save_conversation(window.conversation_id, JSON.stringify(conversation)); await load_conversations(); let refreshOnHide = true; document.addEventListener("visibilitychange", () => { @@ -2101,7 +2098,7 @@ window.addEventListener('load', async function() { } }); var refreshIntervalId = setInterval(async () => { - if (!window.chat_id) { + if (!window.share_id) { clearInterval(refreshIntervalId); return; } @@ -2111,7 +2108,7 @@ window.addEventListener('load', async function() { if (window.conversation_id != window.start_id) { return; } - const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.chat_id}`, { + const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.share_id}`, { headers: { 'accept': 'application/json', 'if-none-match': conversation.updated, diff --git a/g4f/gui/client/static/js/photoswipe.js b/g4f/gui/client/static/js/photoswipe.js index 003719ca..a079b403 100644 --- a/g4f/gui/client/static/js/photoswipe.js +++ b/g4f/gui/client/static/js/photoswipe.js @@ -4,7 +4,7 @@ import PhotoSwipeAutoHideUI from "https://cdn.jsdelivr.net/gh/arnowelzel/photosw import PhotoSwipeSlideshow from "https://cdn.jsdelivr.net/gh/dpet23/photoswipe-slideshow@v2.0.0/photoswipe-slideshow.esm.min.js"; const lightbox = new PhotoSwipeLightbox({ - gallery: '#messages', + gallery: '#chatBody', children: 'a:has(img)', initialZoomLevel: 'fill', secondaryZoomLevel: 1, diff --git a/g4f/gui/server/backend_api.py b/g4f/gui/server/backend_api.py index d265bb13..4ba080ea 100644 --- a/g4f/gui/server/backend_api.py +++ b/g4f/gui/server/backend_api.py @@ -371,6 +371,8 @@ class Backend_Api(Api): match_files = [file for file, count in match_files.items() if count >= request.args.get("min", len(search))] if int(request.args.get("skip", 0)) >= len(match_files): return jsonify({"error": {"message": "Not found"}}), 404 + if (request.args.get("random", False)): + return redirect(f"/media/{random.choice(match_files)}"), 302 return redirect(f"/media/{match_files[int(request.args.get("skip", 0))]}"), 302 @app.route('/backend-api/v2/upload_cookies', methods=['POST']) @@ -386,12 +388,12 @@ class Backend_Api(Api): return "File saved", 200 return 'Not supported file', 400 - @self.app.route('/backend-api/v2/chat/', methods=['GET']) - def get_chat(chat_id: str) -> str: - chat_id = secure_filename(chat_id) - if self.chat_cache.get(chat_id, 0) == request.headers.get("if-none-match", 0): + @self.app.route('/backend-api/v2/chat/', methods=['GET']) + def get_chat(share_id: str) -> str: + share_id = secure_filename(share_id) + if self.chat_cache.get(share_id, 0) == request.headers.get("if-none-match", 0): return jsonify({"error": {"message": "Not modified"}}), 304 - bucket_dir = get_bucket_dir(chat_id) + bucket_dir = get_bucket_dir(share_id) file = os.path.join(bucket_dir, "chat.json") if not os.path.isfile(file): return jsonify({"error": {"message": "Not found"}}), 404 @@ -399,23 +401,23 @@ class Backend_Api(Api): chat_data = json.load(f) if chat_data.get("updated", 0) == request.headers.get("if-none-match", 0): return jsonify({"error": {"message": "Not modified"}}), 304 - self.chat_cache[chat_id] = chat_data.get("updated", 0) + self.chat_cache[share_id] = chat_data.get("updated", 0) return jsonify(chat_data), 200 - @self.app.route('/backend-api/v2/chat/', methods=['POST']) - def upload_chat(chat_id: str) -> dict: + @self.app.route('/backend-api/v2/chat/', methods=['POST']) + def upload_chat(share_id: str) -> dict: chat_data = {**request.json} updated = chat_data.get("updated", 0) - cache_value = self.chat_cache.get(chat_id, 0) + cache_value = self.chat_cache.get(share_id, 0) if updated == cache_value: return jsonify({"error": {"message": "invalid date"}}), 400 - chat_id = secure_filename(chat_id) - bucket_dir = get_bucket_dir(chat_id) + share_id = secure_filename(share_id) + bucket_dir = get_bucket_dir(share_id) os.makedirs(bucket_dir, exist_ok=True) with open(os.path.join(bucket_dir, "chat.json"), 'w') as f: json.dump(chat_data, f) - self.chat_cache[chat_id] = updated - return {"chat_id": chat_id} + self.chat_cache[share_id] = updated + return {"share_id": share_id} def handle_synthesize(self, provider: str): try: diff --git a/g4f/gui/server/website.py b/g4f/gui/server/website.py index 88155838..d2c30cab 100644 --- a/g4f/gui/server/website.py +++ b/g4f/gui/server/website.py @@ -19,12 +19,12 @@ class Website: 'function': self._chat, 'methods': ['GET', 'POST'] }, - '/chat//': { - 'function': self._chat_id, + '/chat//': { + 'function': self._share_id, 'methods': ['GET', 'POST'] }, - '/chat//': { - 'function': self._chat_id, + '/chat//': { + 'function': self._share_id, 'methods': ['GET', 'POST'] }, '/chat/menu/': { @@ -50,9 +50,9 @@ class Website: return render_template('index.html', conversation_id=str(uuid.uuid4())) return render_template('index.html', conversation_id=conversation_id) - def _chat_id(self, chat_id, conversation_id: str = ""): + def _share_id(self, share_id, conversation_id: str = ""): share_url = os.environ.get("G4F_SHARE_URL", "") - return render_template('index.html', share_url=share_url, chat_id=chat_id, conversation_id=conversation_id) + return render_template('index.html', share_url=share_url, share_id=share_id, conversation_id=conversation_id) def _index(self): return render_template('index.html', conversation_id=str(uuid.uuid4())) diff --git a/g4f/image/copy_images.py b/g4f/image/copy_images.py index c14cd6d4..01a55c75 100644 --- a/g4f/image/copy_images.py +++ b/g4f/image/copy_images.py @@ -25,8 +25,10 @@ def get_media_extension(media: str) -> str: """Extract media file extension from URL or filename""" match = re.search(r"\.(j?[a-z]{3})(?:\?|$)", media, re.IGNORECASE) extension = match.group(1).lower() if match else "" + if not extension: + return "" if extension not in EXTENSIONS_MAP: - raise ValueError(f"Unsupported media extension: {extension}") + raise ValueError(f"Unsupported media extension: {extension} in: {media}") return f".{extension}" def ensure_images_dir(): diff --git a/g4f/requests/__init__.py b/g4f/requests/__init__.py index 3005220f..9db0ff7f 100644 --- a/g4f/requests/__init__.py +++ b/g4f/requests/__init__.py @@ -154,6 +154,7 @@ async def get_nodriver( if not os.path.exists(browser_executable_path): browser_executable_path = None lock_file = Path(get_cookies_dir()) / ".nodriver_is_open" + lock_file.parent.mkdir(exist_ok=True) # Implement a short delay (milliseconds) to prevent race conditions. await asyncio.sleep(0.1 * random.randint(0, 50)) if lock_file.exists():