diff --git a/README.md b/README.md index 5fa5c5ca..442d3f2c 100644 --- a/README.md +++ b/README.md @@ -97,31 +97,31 @@ Is your site on this repository and you want to take it down? Send an email to t 1. **Install Docker:** [Download and install Docker](https://docs.docker.com/get-docker/). 2. **Set Up Directories:** Before running the container, make sure the necessary data directories exist or can be created. For example, you can create and set ownership on these directories by running: ```bash - mkdir -p ${PWD}/har_and_cookies ${PWD}/generated_images - sudo chown -R 1200:1201 ${PWD}/har_and_cookies ${PWD}/generated_images +mkdir -p ${PWD}/har_and_cookies ${PWD}/generated_images +sudo chown -R 1200:1201 ${PWD}/har_and_cookies ${PWD}/generated_images ``` 3. **Run the Docker Container:** Use the following commands to pull the latest image and start the container (Only x64): ```bash - docker pull hlohaus789/g4f - docker run -p 8080:8080 -p 7900:7900 \ - --shm-size="2g" \ - -v ${PWD}/har_and_cookies:/app/har_and_cookies \ - -v ${PWD}/generated_images:/app/generated_images \ - hlohaus789/g4f:latest +docker pull hlohaus789/g4f +docker run -p 8080:8080 -p 7900:7900 \ + --shm-size="2g" \ + -v ${PWD}/har_and_cookies:/app/har_and_cookies \ + -v ${PWD}/generated_images:/app/generated_images \ + hlohaus789/g4f:latest ``` 4. **Running the Slim Docker Image:** And use the following commands to run the Slim Docker image. This command also updates the `g4f` package at startup and installs any additional dependencies: (x64 and arm64) ```bash - mkdir -p ${PWD}/har_and_cookies ${PWD}/generated_images - chown -R 1000:1000 ${PWD}/har_and_cookies ${PWD}/generated_images - docker run \ - -p 1337:1337 \ - -v ${PWD}/har_and_cookies:/app/har_and_cookies \ - -v ${PWD}/generated_images:/app/generated_images \ - hlohaus789/g4f:latest-slim \ - rm -r -f /app/g4f/ \ - && pip install -U g4f[slim] \ - && python -m g4f --debug +mkdir -p ${PWD}/har_and_cookies ${PWD}/generated_images +chown -R 1000:1000 ${PWD}/har_and_cookies ${PWD}/generated_images +docker run \ + -p 1337:1337 \ + -v ${PWD}/har_and_cookies:/app/har_and_cookies \ + -v ${PWD}/generated_images:/app/generated_images \ + hlohaus789/g4f:latest-slim \ + rm -r -f /app/g4f/ \ + && pip install -U g4f[slim] \ + && python -m g4f --debug ``` 5. **Access the Client Interface:** @@ -248,7 +248,8 @@ Run the Web UI on your smartphone for easy access on the go. Check out the dedic - **File API from G4F:** [/docs/file](docs/file.md) - **PydanticAI and LangChain Integration for G4F:** [/docs/pydantic_ai](docs/pydantic_ai.md) - **Legacy API with python modules:** [/docs/legacy](docs/legacy.md) - + - **G4F - Media Documentation** [/docs/media](/docs/meda.md) *(New)* + --- ## 🔗 Powered by gpt4free diff --git a/docs/authentication.md b/docs/authentication.md index 45bad795..2d9bd65d 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -126,7 +126,7 @@ providers = { } for provider_name, api_key in providers.items(): - client = Client(provider=f"g4f.Provider.{provider_name}", api_key=api_key) + client = Client(provider=provider_name, api_key=api_key) response = client.chat.completions.create( model="claude-3.5-sonnet", messages=[{"role": "user", "content": f"Hello from {provider_name}!"}] @@ -144,16 +144,22 @@ for provider_name, api_key in providers.items(): - Firefox: **Storage** → **Cookies** ```python +from g4f.client import Client from g4f.Provider import Gemini -# Initialize with cookies +# Using with cookies client = Client( provider=Gemini, +) +response = client.chat.completions.create( + model="", # Default model + messages="Hello Google", cookies={ "__Secure-1PSID": "your_cookie_value_here", "__Secure-1PSIDTS": "timestamp_value_here" } ) +print(f"Gemini: {response.choices[0].message.content}") ``` --- diff --git a/docs/gui.md b/docs/gui.md index c2d0d9d5..1cab028d 100644 --- a/docs/gui.md +++ b/docs/gui.md @@ -69,9 +69,9 @@ The G4F GUI is a self-contained, user-friendly interface designed for interactin - **Basic Authentication** You can set a password for Basic Authentication using the `--g4f-api-key` argument when starting the web server. -### 9. **Continue Button (ChatGPT & HuggingChat)** +### 9. **Continue Button** - **Automatic Detection of Truncated Responses** - When using **ChatGPT** or **HuggingChat** providers, responses may occasionally be cut off or truncated. + When using providers, responses may occasionally be cut off or truncated. - **Continue Button** If the GUI detects that the response ended abruptly, a **Continue** button appears directly below the truncated message. Clicking this button sends a follow-up request to the same provider and model, retrieving the rest of the message. - **Seamless Conversation Flow** @@ -154,7 +154,7 @@ http://localhost:8080/chat/ - **Text/Code:** The generated response appears in the conversation window. - **Images:** Generated images are displayed as thumbnails. Click on any thumbnail to view it in full size within the lightbox. -5. **Continue Button (ChatGPT & HuggingChat)** +5. **Continue Button** - If a response is truncated, a **Continue** button will appear under the last message. Clicking it asks the same provider to continue the response from where it ended. 6. **Manage Conversations** diff --git a/docs/media.md b/docs/media.md index 42dd0058..dbfa3850 100644 --- a/docs/media.md +++ b/docs/media.md @@ -175,7 +175,7 @@ from g4f.Provider import HuggingFaceMedia async def main(): client = AsyncClient( provider=HuggingFaceMedia, - api_key="hf_***" # Your API key here + api_key=os.getenv("HF_TOKEN") # Your API key here ) video_models = client.models.get_video() @@ -214,7 +214,7 @@ from g4f.Provider import HuggingFaceMedia async def main(): client = AsyncClient( provider=HuggingFaceMedia, - api_key=os.getenv("HUGGINGFACE_API_KEY") # Your API key here + api_key=os.getenv("HF_TOKEN") # Your API key here ) video_models = client.models.get_video() diff --git a/g4f/Provider/Cloudflare.py b/g4f/Provider/Cloudflare.py index 3dd4bef7..97470f5b 100644 --- a/g4f/Provider/Cloudflare.py +++ b/g4f/Provider/Cloudflare.py @@ -83,9 +83,9 @@ class Cloudflare(AsyncGeneratorProvider, ProviderModelMixin, AuthFileMixin): pass data = { "messages": [{ - "role":"user", + **message, "content": message["content"] if isinstance(message["content"], str) else "", - "parts": [{"type":"text", "text":message["content"]}] if isinstance(message["content"], str) else message["content"]} for message in messages], + "parts": [{"type":"text", "text":message["content"]}] if isinstance(message["content"], str) else message} for message in messages], "lora": None, "model": model, "max_tokens": max_tokens, diff --git a/g4f/Provider/Copilot.py b/g4f/Provider/Copilot.py index 5de06840..f7924d19 100644 --- a/g4f/Provider/Copilot.py +++ b/g4f/Provider/Copilot.py @@ -24,7 +24,7 @@ from .openai.har_file import get_headers, get_har_files from ..typing import CreateResult, Messages, MediaListType from ..errors import MissingRequirementsError, NoValidHarFileError, MissingAuthError from ..requests.raise_for_status import raise_for_status -from ..providers.response import BaseConversation, JsonConversation, RequestLogin, ImageResponse +from ..providers.response import BaseConversation, JsonConversation, RequestLogin, ImageResponse, FinishReason, SuggestedFollowups from ..providers.asyncio import get_running_loop from ..tools.media import merge_media from ..requests import get_nodriver @@ -46,10 +46,13 @@ class Copilot(AbstractProvider, ProviderModelMixin): supports_stream = True default_model = "Copilot" - models = [default_model] + models = [default_model, "Think Deeper"] model_aliases = { "gpt-4": default_model, - "o1": default_model, + "gpt-4o": default_model, + "o1": "Think Deeper", + "reasoning": "Think Deeper", + "dall-e-3": default_model } websocket_url = "wss://copilot.microsoft.com/c/api/chat?api-version=2" @@ -75,10 +78,10 @@ class Copilot(AbstractProvider, ProviderModelMixin): ) -> CreateResult: if not has_curl_cffi: raise MissingRequirementsError('Install or update "curl_cffi" package | pip install -U curl_cffi') - + model = cls.get_model(model) websocket_url = cls.websocket_url headers = None - if cls.needs_auth or media is not None: + if cls._access_token: if api_key is not None: cls._access_token = api_key if cls._access_token is None: @@ -163,6 +166,7 @@ class Copilot(AbstractProvider, ProviderModelMixin): # "token": clarity_token, # "method":"clarity" # }).encode(), CurlWsFlag.TEXT) + wss.send(json.dumps({"event":"setOptions","supportedCards":["weather","local","image","sports","video","ads","finance"],"ads":{"supportedTypes":["multimedia","product","tourActivity","propertyPromotion","text"]}})); wss.send(json.dumps({ "event": "send", "conversationId": conversation_id, @@ -170,7 +174,7 @@ class Copilot(AbstractProvider, ProviderModelMixin): "type": "text", "text": prompt, }], - "mode": "chat" + "mode": "reasoning" if "Think" in model else "chat", }).encode(), CurlWsFlag.TEXT) is_started = False @@ -193,6 +197,10 @@ class Copilot(AbstractProvider, ProviderModelMixin): elif msg.get("event") == "imageGenerated": yield ImageResponse(msg.get("url"), image_prompt, {"preview": msg.get("thumbnailUrl")}) elif msg.get("event") == "done": + yield FinishReason("stop") + break + elif msg.get("event") == "suggestedFollowups": + yield SuggestedFollowups(msg.get("suggestions")) break elif msg.get("event") == "replaceText": yield msg.get("text") diff --git a/g4f/Provider/DuckDuckGo.py b/g4f/Provider/DuckDuckGo.py new file mode 100644 index 00000000..5a8c5d3e --- /dev/null +++ b/g4f/Provider/DuckDuckGo.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import asyncio + +try: + from duckduckgo_search import DDGS + from duckduckgo_search.exceptions import DuckDuckGoSearchException, RatelimitException, ConversationLimitException + has_requirements = True +except ImportError: + has_requirements = False +try: + import nodriver + has_nodriver = True +except ImportError: + has_nodriver = False + +from ..typing import AsyncResult, Messages +from ..requests import get_nodriver +from .base_provider import AsyncGeneratorProvider, ProviderModelMixin +from .helper import get_last_user_message + +class DuckDuckGo(AsyncGeneratorProvider, ProviderModelMixin): + label = "Duck.ai (duckduckgo_search)" + url = "https://duckduckgo.com/aichat" + api_base = "https://duckduckgo.com/duckchat/v1/" + + working = False + supports_stream = True + supports_system_message = True + supports_message_history = True + + default_model = "gpt-4o-mini" + models = [default_model, "meta-llama/Llama-3.3-70B-Instruct-Turbo", "claude-3-haiku-20240307", "o3-mini", "mistralai/Mistral-Small-24B-Instruct-2501"] + + ddgs: DDGS = None + + model_aliases = { + "gpt-4": "gpt-4o-mini", + "llama-3.3-70b": "meta-llama/Llama-3.3-70B-Instruct-Turbo", + "claude-3-haiku": "claude-3-haiku-20240307", + "mixtral-small-24b": "mistralai/Mistral-Small-24B-Instruct-2501", + } + + @classmethod + async def create_async_generator( + cls, + model: str, + messages: Messages, + proxy: str = None, + timeout: int = 60, + **kwargs + ) -> AsyncResult: + if not has_requirements: + raise ImportError("duckduckgo_search is not installed. Install it with `pip install duckduckgo-search`.") + if cls.ddgs is None: + cls.ddgs = DDGS(proxy=proxy, timeout=timeout) + if has_nodriver: + await cls.nodriver_auth(proxy=proxy) + model = cls.get_model(model) + for chunk in cls.ddgs.chat_yield(get_last_user_message(messages), model, timeout): + yield chunk + + @classmethod + async def nodriver_auth(cls, proxy: str = None): + browser, stop_browser = await get_nodriver(proxy=proxy) + try: + page = browser.main_tab + def on_request(event: nodriver.cdp.network.RequestWillBeSent, page=None): + if cls.api_base in event.request.url: + if "X-Vqd-4" in event.request.headers: + cls.ddgs._chat_vqd = event.request.headers["X-Vqd-4"] + if "X-Vqd-Hash-1" in event.request.headers: + cls.ddgs._chat_vqd_hash = event.request.headers["X-Vqd-Hash-1"] + if "F-Fe-Version" in event.request.headers: + cls.ddgs._chat_xfe = event.request.headers["F-Fe-Version" ] + await page.send(nodriver.cdp.network.enable()) + page.add_handler(nodriver.cdp.network.RequestWillBeSent, on_request) + page = await browser.get(cls.url) + while True: + if cls.ddgs._chat_vqd: + break + await asyncio.sleep(1) + await page.close() + finally: + stop_browser() \ No newline at end of file diff --git a/g4f/Provider/__init__.py b/g4f/Provider/__init__.py index d253adce..790a76e0 100644 --- a/g4f/Provider/__init__.py +++ b/g4f/Provider/__init__.py @@ -40,6 +40,7 @@ try: from .Copilot import Copilot from .DDG import DDG from .DeepInfraChat import DeepInfraChat + from .DuckDuckGo import DuckDuckGo from .Dynaspark import Dynaspark except ImportError as e: debug.error("Providers not loaded (A-D):", e) diff --git a/g4f/Provider/needs_auth/CopilotAccount.py b/g4f/Provider/needs_auth/CopilotAccount.py index 2ac667cc..bef4fb0b 100644 --- a/g4f/Provider/needs_auth/CopilotAccount.py +++ b/g4f/Provider/needs_auth/CopilotAccount.py @@ -19,11 +19,6 @@ class CopilotAccount(AsyncAuthedProvider, Copilot): parent = "Copilot" default_model = "Copilot" default_vision_model = default_model - models = [default_model] - image_models = models - model_aliases = { - "dall-e-3": default_model - } @classmethod async def on_auth_async(cls, proxy: str = None, **kwargs) -> AsyncIterator: diff --git a/g4f/api/stubs.py b/g4f/api/stubs.py index a81e71e5..c983f38e 100644 --- a/g4f/api/stubs.py +++ b/g4f/api/stubs.py @@ -68,6 +68,7 @@ class ImageGenerationConfig(BaseModel): aspect_ratio: Optional[str] = None n: Optional[int] = None negative_prompt: Optional[str] = None + resolution: Optional[str] = None class ProviderResponseModel(BaseModel): id: str diff --git a/g4f/gui/client/static/js/chat.v1.js b/g4f/gui/client/static/js/chat.v1.js index 0a33d7c4..40768172 100644 --- a/g4f/gui/client/static/js/chat.v1.js +++ b/g4f/gui/client/static/js/chat.v1.js @@ -1325,7 +1325,12 @@ const delete_conversation = async (conversation_id) => { } } } - + if (window.share_id && conversation_id == window.start_id) { + const url = `${window.share_url}/backend-api/v2/files/${window.share_id}`; + await fetch(url, { + method: 'DELETE' + }); + } appStorage.removeItem(`conversation:${conversation_id}`); const item = document.getElementById(`convo-${conversation_id}`); item.remove(); @@ -2622,7 +2627,7 @@ function connectToSSE(url, do_refine, bucket_id) { } else if (data.action == "media") { inputCount.innerText = `File: ${data.filename}`; const url = `/files/${bucket_id}/media/${data.filename}`; - const media = [{bucket_id: bucket_id, url: url}]; + const media = [{bucket_id: bucket_id, url: url, name: data.filename}]; await handle_ask(false, media); } else if (data.action == "load") { inputCount.innerText = `Read data: ${formatFileSize(data.size)}`; diff --git a/g4f/image/__init__.py b/g4f/image/__init__.py index 69f85118..cd0abfd3 100644 --- a/g4f/image/__init__.py +++ b/g4f/image/__init__.py @@ -37,7 +37,7 @@ EXTENSIONS_MAP: dict[str, str] = { "flac": "audio/flac", "opus": "audio/opus", "ogg": "audio/ogg", - "m4a": "audio/mp4", + "m4a": "audio/m4a", # Video "mkv": "video/x-matroska", "webm": "video/webm", diff --git a/g4f/providers/response.py b/g4f/providers/response.py index 1d2d1be5..6ceb3930 100644 --- a/g4f/providers/response.py +++ b/g4f/providers/response.py @@ -284,6 +284,10 @@ class SynthesizeData(HiddenResponse, JsonMixin): self.provider = provider self.data = data +class SuggestedFollowups(HiddenResponse): + def __init__(self, suggestions: list[str]): + self.suggestions = suggestions + class RequestLogin(HiddenResponse): def __init__(self, label: str, login_url: str) -> None: """Initialize with label and login URL.""" diff --git a/g4f/tools/web_search.py b/g4f/tools/web_search.py index a2f0f905..62fc1b96 100644 --- a/g4f/tools/web_search.py +++ b/g4f/tools/web_search.py @@ -13,6 +13,7 @@ try: from duckduckgo_search import DDGS from duckduckgo_search.exceptions import DuckDuckGoSearchException from bs4 import BeautifulSoup + ddgs = DDGS() has_requirements = True except ImportError: has_requirements = False @@ -164,49 +165,49 @@ async def fetch_and_scrape(session: ClientSession, url: str, max_words: int = No async def search(query: str, max_results: int = 5, max_words: int = 2500, backend: str = "auto", add_text: bool = True, timeout: int = 5, region: str = "wt-wt") -> SearchResults: if not has_requirements: raise MissingRequirementsError('Install "duckduckgo-search" and "beautifulsoup4" package | pip install -U g4f[search]') - with DDGS() as ddgs: - results = [] - for result in ddgs.text( - query, - region=region, - safesearch="moderate", - timelimit="y", - max_results=max_results, - backend=backend, - ): - if ".google." in result["href"]: - continue - results.append(SearchResultEntry( - result["title"], - result["href"], - result["body"] - )) + results = [] + for result in ddgs.text( + query, + region=region, + safesearch="moderate", + timelimit="y", + max_results=max_results, + backend=backend, + ): + if ".google." in result["href"]: + continue + results.append(SearchResultEntry( + result["title"], + result["href"], + result["body"] + )) + + if add_text: + requests = [] + async with ClientSession(timeout=ClientTimeout(timeout)) as session: + for entry in results: + requests.append(fetch_and_scrape(session, entry.url, int(max_words / (max_results - 1)), False)) + texts = await asyncio.gather(*requests) + + formatted_results = [] + used_words = 0 + left_words = max_words + for i, entry in enumerate(results): if add_text: - requests = [] - async with ClientSession(timeout=ClientTimeout(timeout)) as session: - for entry in results: - requests.append(fetch_and_scrape(session, entry.url, int(max_words / (max_results - 1)), False)) - texts = await asyncio.gather(*requests) + entry.text = texts[i] + if max_words: + left_words -= entry.title.count(" ") + 5 + if entry.text: + left_words -= entry.text.count(" ") + else: + left_words -= entry.snippet.count(" ") + if 0 > left_words: + break + used_words = max_words - left_words + formatted_results.append(entry) - formatted_results = [] - used_words = 0 - left_words = max_words - for i, entry in enumerate(results): - if add_text: - entry.text = texts[i] - if max_words: - left_words -= entry.title.count(" ") + 5 - if entry.text: - left_words -= entry.text.count(" ") - else: - left_words -= entry.snippet.count(" ") - if 0 > left_words: - break - used_words = max_words - left_words - formatted_results.append(entry) - - return SearchResults(formatted_results, used_words) + return SearchResults(formatted_results, used_words) async def do_search(prompt: str, query: str = None, instructions: str = DEFAULT_INSTRUCTIONS, **kwargs) -> tuple[str, Sources]: if instructions and instructions in prompt: