XFEstudio/gpt4free
refactor: replace constants module with config module
- Replaced imports of `STATIC_URL` from `..constants` to `..config` in: - `g4f/Provider/PollinationsAI.py` - `g4f/Provider/PollinationsImage.py` - Updated `client.py` to import `CONFIG_DIR` and `COOKIES_DIR` from `g4f.config` instead of defining platform-specific directories. - Changed the handling of conversation history in `ConversationManager`: - Updated `self.history` to retrieve data from `data.get("items", [])` instead of `data.get("history", [])`. - Modified the `stream_response` function to use `media` instead of `image` for handling media content. - Updated the `save_content` function to accept `media_content` of type `Optional[MediaResponse]` instead of `content`. - Adjusted the `run_client_args` function to handle media URLs and files more effectively, appending valid media to a list. - Removed the `constants.py` file and added a new `config.py` file to centralize configuration settings. - Updated the `CookiesConfig` class to set `cookies_dir` based on the existence of `CUSTOM_COOKIES_DIR`. - Adjusted the `render` function in `website.py` to correctly handle file paths and requests for HTML files. - Updated various references to use the new `config` module instead of the removed `constants` module.
bdc356c4
代码差异
@@ -21,7 +21,7 @@ from ..image.copy_images import save_response_media
from ..image import use_aspect_ratio
from ..providers.response import FinishReason, Usage, ToolCalls, ImageResponse, Reasoning, TitleGeneration, SuggestedFollowups, ProviderInfo, AudioResponse
from ..tools.media import render_messages
from ..constants import STATIC_URL
from ..config import STATIC_URL
from .. import debug
DEFAULT_HEADERS = {
@@ -4,7 +4,7 @@ from typing import Optional
from .helper import format_media_prompt
from ..typing import AsyncResult, Messages, MediaListType
from ..constants import STATIC_URL
from ..config import STATIC_URL
from .PollinationsAI import PollinationsAI
class PollinationsImage(PollinationsAI):
@@ -6,29 +6,20 @@ import asyncio
import json
import argparse
import traceback
import requests
from pathlib import Path
from typing import Optional, List, Dict
from g4f.client import AsyncClient
from g4f.providers.response import JsonConversation, is_content
from g4f.providers.response import JsonConversation, MediaResponse, is_content
from g4f.cookies import set_cookies_dir, read_cookie_files
from g4f.Provider import ProviderUtils
from g4f.image import extract_data_uri, is_accepted_format
from g4f.image.copy_images import get_media_dir
from g4f.client.helper import filter_markdown
from g4f.integration.markitdown import MarkItDown
from g4f.config import CONFIG_DIR, COOKIES_DIR
from g4f import debug
# Platform-appropriate directories
def get_config_dir() -> Path:
"""Get platform-appropriate config directory."""
if sys.platform == "win32":
return Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
elif sys.platform == "darwin":
return Path.home() / "Library" / "Application Support"
else: # Linux and other UNIX-like
return Path.home() / ".config"
CONFIG_DIR = get_config_dir() / "g4f-cli"
COOKIES_DIR = CONFIG_DIR / "cookies"
CONVERSATION_FILE = CONFIG_DIR / "conversation.json"
class ConversationManager:
@@ -59,7 +50,7 @@ class ConversationManager:
self.conversation = JsonConversation(**self.data.get(self.provider))
elif not self.provider and self.data:
self.conversation = JsonConversation(**self.data)
self.history = data.get("history", [])
self.history = data.get("items", [])
except (json.JSONDecodeError, KeyError) as e:
print(f"Error loading conversation: {e}", file=sys.stderr)
except Exception as e:
@@ -80,7 +71,7 @@ class ConversationManager:
"model": self.model,
"provider": self.provider,
"data": self.data,
"history": self.history
"items": self.history
}, f, indent=2, ensure_ascii=False)
except Exception as e:
print(f"Error saving conversation: {e}", file=sys.stderr)
@@ -101,9 +92,9 @@ async def stream_response(
instructions: Optional[str] = None
) -> None:
"""Stream the response from the API and update conversation."""
image = None
media = None
if isinstance(input_text, tuple):
image, input_text = input_text
media, input_text = input_text
if instructions:
# Add system instructions to conversation if provided
@@ -115,7 +106,7 @@ async def stream_response(
create_args = {
"messages": conversation.get_messages(),
"stream": True,
"image": image
"media": media
}
if conversation.model:
@@ -141,9 +132,10 @@ async def stream_response(
print("\n", end="")
conversation.conversation = getattr(last_chunk, 'conversation', None)
media_content = next(iter([chunk for chunk in response_content if isinstance(chunk, MediaResponse)]), None)
response_content = response_content[0] if len(response_content) == 1 else "".join([str(chunk) for chunk in response_content])
if output_file:
if save_content(response_content, output_file):
if save_content(response_content, media_content, output_file):
print(f"\nResponse saved to {output_file}")
if response_content:
@@ -152,13 +144,12 @@ async def stream_response(
else:
raise RuntimeError("No response received from the API")
def save_content(content, filepath: str, allowed_types = None):
if hasattr(content, "urls"):
import requests
for url in content.urls:
def save_content(content, media_content: Optional[MediaResponse], filepath: str, allowed_types = None):
if media_content is not None:
for url in media_content.urls:
if url.startswith("http://") or url.startswith("https://"):
try:
response = requests.get(url, cookies=content.get("cookies"), headers=content.get("headers"))
response = requests.get(url, cookies=media_content.get("cookies"), headers=media_content.get("headers"))
if response.status_code == 200:
with open(filepath, "wb") as f:
f.write(response.content)
@@ -279,25 +270,44 @@ async def run_args(input_text: str, args):
def run_client_args(args):
input_text = ""
if args.input and os.path.isfile(args.input[0]):
try:
with open(args.input[0], 'rb') as f:
if is_accepted_format(f.read(12)):
input_text = (Path(args.input[0]), " ".join(args.input[1:]))
except ValueError:
# If not a valid image, read as text
try:
with open(args.input[0], 'r', encoding='utf-8') as f:
file_content = f.read().strip()
except UnicodeDecodeError:
print(f"Error reading file {args.input[0]} as text. Ensure it is a valid text file.", file=sys.stderr)
sys.exit(1)
if len(args.input) > 1:
input_text = f"{' '.join(args.input[1:])}\n```{os.path.basename(args.input[0])}\n{file_content}\n```"
media = []
rest = 0
for idx, input_value in enumerate(args.input):
if input_value.startswith("http://") or input_value.startswith("https://"):
response = requests.head(input_value)
if not response.ok:
print(f"Error accessing URL {input_value}: {response.status_code}", file=sys.stderr)
break
if response.headers.get('Content-Type', '').startswith('image/'):
media.append(input_value)
else:
input_text = file_content
elif args.input:
input_text = (" ".join(args.input)).strip()
try:
md = MarkItDown()
text_content = md.convert_url(input_value).text_content
input_text += f"\n```\n{text_content}\n\nSource: {input_value}\n```\n"
except Exception as e:
print(f"Error processing URL {input_value}: {type(e).__name__}: {e}", file=sys.stderr)
break
elif os.path.isfile(input_value):
try:
with open(input_value, 'rb') as f:
if is_accepted_format(f.read(12)):
media.append(Path(input_value))
except ValueError:
# If not a valid image, read as text
try:
with open(input_value, 'r', encoding='utf-8') as f:
file_content = f.read().strip()
except UnicodeDecodeError:
print(f"Error reading file {input_value} as text. Ensure it is a valid text file.", file=sys.stderr)
break
input_text += f"\n```{input_value}\n{file_content}\n```\n"
else:
break
rest = idx + 1
input_text = (" ".join(args.input[rest:])).strip() + input_text
if media:
input_text = (media, input_text)
if not input_text:
input_text = sys.stdin.read().strip()
if not input_text:
@@ -50,11 +50,13 @@ def resolve_media(kwargs: dict, image = None, image_name: str = None) -> None:
kwargs["media"] = [(image, getattr(image, "name", image_name))]
elif "images" in kwargs:
kwargs["media"] = kwargs.pop("images")
if "media" in kwargs and not isinstance(kwargs["media"], list):
if kwargs.get("media") is None:
kwargs.pop("media", None)
elif not isinstance(kwargs["media"], list):
kwargs["media"] = [kwargs["media"]]
for idx, media in enumerate(kwargs.get("media", [])):
if not isinstance(media, (list, tuple)):
kwargs["media"][idx] = (media, os.path.basename(getattr(media, "name", "")))
kwargs["media"][idx] = (media, getattr(media, "name", None))
# Synchronous iter_response function
def iter_response(
@@ -433,12 +435,10 @@ class Images:
provider_handler = self.provider
if provider_handler is None:
provider_handler = self.client.models.get(model, default)
elif isinstance(provider, str):
provider_handler = convert_to_provider(provider)
else:
provider_handler = provider
if provider_handler is None:
return default
if isinstance(provider_handler, str):
provider_handler = convert_to_provider(provider_handler)
return provider_handler
async def async_generate(
@@ -538,13 +538,21 @@ class Images:
def create_variation(
self,
image: ImageType,
image_name: str = None,
prompt: str = "Create a variation of this image",
model: str = None,
provider: Optional[ProviderType] = None,
response_format: Optional[str] = None,
**kwargs
) -> ImagesResponse:
return asyncio.run(self.async_create_variation(
image, model, provider, response_format, **kwargs
image=image,
image_name=image_name,
prompt=prompt,
model=model,
provider=provider,
response_format=response_format,
**kwargs
))
async def async_create_variation(
@@ -619,6 +627,7 @@ class Images:
images = await asyncio.gather(*[get_b64_from_url(image) for image in response.get_list()])
else:
# Save locally for None (default) case
images = response.get_list()
if download_media or response.get("cookies") or response.get("headers"):
images = await copy_media(response.get_list(), response.get("cookies"), response.get("headers"), proxy, response.alt)
images = [Image.model_construct(url=image, revised_prompt=response.alt) for image in images]
@@ -0,0 +1,24 @@
import os
import sys
from pathlib import Path
# Platform-appropriate directories
def get_config_dir() -> Path:
"""Get platform-appropriate config directory."""
if sys.platform == "win32":
return Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
elif sys.platform == "darwin":
return Path.home() / "Library" / "Application Support"
else: # Linux and other UNIX-like
return Path.home() / ".config"
CONFIG_DIR = get_config_dir() / "g4f"
COOKIES_DIR = CONFIG_DIR / "cookies"
CUSTOM_COOKIES_DIR = "./har_and_cookies"
PACKAGE_NAME = "g4f"
ORGANIZATION = "gpt4free"
GITHUB_REPOSITORY = f"xtekky/{ORGANIZATION}"
STATIC_DOMAIN = f"g4f.dev"
STATIC_URL = f"https://{STATIC_DOMAIN}/"
DIST_DIR = f"./{STATIC_DOMAIN}/dist"
DOWNLOAD_URL = f"https://raw.githubusercontent.com/{ORGANIZATION}/{STATIC_DOMAIN}/refs/heads/main/"
@@ -1,7 +0,0 @@
PACKAGE_NAME = "g4f"
ORGANIZATION = "gpt4free"
GITHUB_REPOSITORY = f"xtekky/{ORGANIZATION}"
STATIC_DOMAIN = f"g4f.dev"
STATIC_URL = f"https://{STATIC_DOMAIN}/"
DIST_DIR = f"./{STATIC_DOMAIN}/dist"
DOWNLOAD_URL = f"https://raw.githubusercontent.com/{ORGANIZATION}/{STATIC_DOMAIN}/refs/heads/main/"
@@ -44,11 +44,12 @@ except ImportError:
from .typing import Dict, Cookies
from .errors import MissingRequirementsError
from .config import COOKIES_DIR, CUSTOM_COOKIES_DIR
from . import debug
class CookiesConfig():
cookies: Dict[str, Cookies] = {}
cookies_dir: str = "./har_and_cookies"
cookies_dir: str = CUSTOM_COOKIES_DIR if os.path.exists(CUSTOM_COOKIES_DIR) else COOKIES_DIR
DOMAINS = [
".bing.com",
@@ -8,15 +8,16 @@ from flask import send_from_directory, redirect, request
from ...image.copy_images import secure_filename
from ...cookies import get_cookies_dir
from ...errors import VersionNotFoundError
from ...constants import STATIC_URL, DOWNLOAD_URL, DIST_DIR
from ...config import STATIC_URL, DOWNLOAD_URL, DIST_DIR
from ... import version
def redirect_home():
return redirect('/chat/')
def render(filename = "home"):
filename += ("" if "." in filename else ".html")
if os.path.exists(DIST_DIR) and not request.args.get("debug"):
path = os.path.abspath(os.path.join(os.path.dirname(DIST_DIR), (filename + ("" if "." in filename else ".html"))))
path = os.path.abspath(os.path.join(os.path.dirname(DIST_DIR), filename))
return send_from_directory(os.path.dirname(path), os.path.basename(path))
try:
latest_version = version.utils.latest_version
@@ -31,7 +32,7 @@ def render(filename = "home"):
is_temp = True
else:
os.makedirs(cache_dir, exist_ok=True)
response = requests.get(f"{DOWNLOAD_URL}{filename}.html")
response = requests.get(f"{DOWNLOAD_URL}{filename}")
if not response.ok:
found = None
for root, _, files in os.walk(cache_dir):
@@ -251,7 +251,7 @@ def to_bytes(image: ImageType) -> bytes:
elif image.startswith("http://") or image.startswith("https://"):
path: str = urlparse(image).path
if path.startswith("/files/"):
path = get_bucket_dir(path.split(path, "/")[1:])
path = get_bucket_dir(*path.split("/")[2:])
if os.path.exists(path):
return Path(path).read_bytes()
else:
@@ -438,6 +438,7 @@ async def download_urls(
if text_content:
filename = get_filename_from_url(url)
target = bucket_dir / filename
text_content = f"{text_content.strip()}\n\nSource: {url}\n"
target.write_text(text_content, errors="replace")
return filename
except Exception as e:
@@ -6,7 +6,7 @@ from functools import cached_property
from importlib.metadata import version as get_package_version, PackageNotFoundError
from subprocess import check_output, CalledProcessError, PIPE
from .errors import VersionNotFoundError
from .constants import PACKAGE_NAME, GITHUB_REPOSITORY
from .config import PACKAGE_NAME, GITHUB_REPOSITORY
from . import debug
def get_pypi_version(package_name: str) -> str: