Skip to content

Commit bdc356c

Browse files
committed
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.
1 parent dc9f115 commit bdc356c

File tree

12 files changed

+103
-64
lines changed

12 files changed

+103
-64
lines changed

g4f/Provider/PollinationsAI.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from ..image import use_aspect_ratio
2222
from ..providers.response import FinishReason, Usage, ToolCalls, ImageResponse, Reasoning, TitleGeneration, SuggestedFollowups, ProviderInfo, AudioResponse
2323
from ..tools.media import render_messages
24-
from ..constants import STATIC_URL
24+
from ..config import STATIC_URL
2525
from .. import debug
2626

2727
DEFAULT_HEADERS = {

g4f/Provider/PollinationsImage.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from .helper import format_media_prompt
66
from ..typing import AsyncResult, Messages, MediaListType
7-
from ..constants import STATIC_URL
7+
from ..config import STATIC_URL
88
from .PollinationsAI import PollinationsAI
99

1010
class PollinationsImage(PollinationsAI):

g4f/cli/client.py

Lines changed: 52 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,20 @@
66
import json
77
import argparse
88
import traceback
9+
import requests
910
from pathlib import Path
1011
from typing import Optional, List, Dict
1112
from g4f.client import AsyncClient
12-
from g4f.providers.response import JsonConversation, is_content
13+
from g4f.providers.response import JsonConversation, MediaResponse, is_content
1314
from g4f.cookies import set_cookies_dir, read_cookie_files
1415
from g4f.Provider import ProviderUtils
1516
from g4f.image import extract_data_uri, is_accepted_format
1617
from g4f.image.copy_images import get_media_dir
1718
from g4f.client.helper import filter_markdown
19+
from g4f.integration.markitdown import MarkItDown
20+
from g4f.config import CONFIG_DIR, COOKIES_DIR
1821
from g4f import debug
1922

20-
# Platform-appropriate directories
21-
def get_config_dir() -> Path:
22-
"""Get platform-appropriate config directory."""
23-
if sys.platform == "win32":
24-
return Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
25-
elif sys.platform == "darwin":
26-
return Path.home() / "Library" / "Application Support"
27-
else: # Linux and other UNIX-like
28-
return Path.home() / ".config"
29-
30-
CONFIG_DIR = get_config_dir() / "g4f-cli"
31-
COOKIES_DIR = CONFIG_DIR / "cookies"
3223
CONVERSATION_FILE = CONFIG_DIR / "conversation.json"
3324

3425
class ConversationManager:
@@ -59,7 +50,7 @@ def _load(self) -> None:
5950
self.conversation = JsonConversation(**self.data.get(self.provider))
6051
elif not self.provider and self.data:
6152
self.conversation = JsonConversation(**self.data)
62-
self.history = data.get("history", [])
53+
self.history = data.get("items", [])
6354
except (json.JSONDecodeError, KeyError) as e:
6455
print(f"Error loading conversation: {e}", file=sys.stderr)
6556
except Exception as e:
@@ -80,7 +71,7 @@ def save(self) -> None:
8071
"model": self.model,
8172
"provider": self.provider,
8273
"data": self.data,
83-
"history": self.history
74+
"items": self.history
8475
}, f, indent=2, ensure_ascii=False)
8576
except Exception as e:
8677
print(f"Error saving conversation: {e}", file=sys.stderr)
@@ -101,9 +92,9 @@ async def stream_response(
10192
instructions: Optional[str] = None
10293
) -> None:
10394
"""Stream the response from the API and update conversation."""
104-
image = None
95+
media = None
10596
if isinstance(input_text, tuple):
106-
image, input_text = input_text
97+
media, input_text = input_text
10798

10899
if instructions:
109100
# Add system instructions to conversation if provided
@@ -115,7 +106,7 @@ async def stream_response(
115106
create_args = {
116107
"messages": conversation.get_messages(),
117108
"stream": True,
118-
"image": image
109+
"media": media
119110
}
120111

121112
if conversation.model:
@@ -141,9 +132,10 @@ async def stream_response(
141132
print("\n", end="")
142133

143134
conversation.conversation = getattr(last_chunk, 'conversation', None)
135+
media_content = next(iter([chunk for chunk in response_content if isinstance(chunk, MediaResponse)]), None)
144136
response_content = response_content[0] if len(response_content) == 1 else "".join([str(chunk) for chunk in response_content])
145137
if output_file:
146-
if save_content(response_content, output_file):
138+
if save_content(response_content, media_content, output_file):
147139
print(f"\nResponse saved to {output_file}")
148140

149141
if response_content:
@@ -152,13 +144,12 @@ async def stream_response(
152144
else:
153145
raise RuntimeError("No response received from the API")
154146

155-
def save_content(content, filepath: str, allowed_types = None):
156-
if hasattr(content, "urls"):
157-
import requests
158-
for url in content.urls:
147+
def save_content(content, media_content: Optional[MediaResponse], filepath: str, allowed_types = None):
148+
if media_content is not None:
149+
for url in media_content.urls:
159150
if url.startswith("http://") or url.startswith("https://"):
160151
try:
161-
response = requests.get(url, cookies=content.get("cookies"), headers=content.get("headers"))
152+
response = requests.get(url, cookies=media_content.get("cookies"), headers=media_content.get("headers"))
162153
if response.status_code == 200:
163154
with open(filepath, "wb") as f:
164155
f.write(response.content)
@@ -279,25 +270,44 @@ async def run_args(input_text: str, args):
279270

280271
def run_client_args(args):
281272
input_text = ""
282-
if args.input and os.path.isfile(args.input[0]):
283-
try:
284-
with open(args.input[0], 'rb') as f:
285-
if is_accepted_format(f.read(12)):
286-
input_text = (Path(args.input[0]), " ".join(args.input[1:]))
287-
except ValueError:
288-
# If not a valid image, read as text
289-
try:
290-
with open(args.input[0], 'r', encoding='utf-8') as f:
291-
file_content = f.read().strip()
292-
except UnicodeDecodeError:
293-
print(f"Error reading file {args.input[0]} as text. Ensure it is a valid text file.", file=sys.stderr)
294-
sys.exit(1)
295-
if len(args.input) > 1:
296-
input_text = f"{' '.join(args.input[1:])}\n```{os.path.basename(args.input[0])}\n{file_content}\n```"
273+
media = []
274+
rest = 0
275+
for idx, input_value in enumerate(args.input):
276+
if input_value.startswith("http://") or input_value.startswith("https://"):
277+
response = requests.head(input_value)
278+
if not response.ok:
279+
print(f"Error accessing URL {input_value}: {response.status_code}", file=sys.stderr)
280+
break
281+
if response.headers.get('Content-Type', '').startswith('image/'):
282+
media.append(input_value)
297283
else:
298-
input_text = file_content
299-
elif args.input:
300-
input_text = (" ".join(args.input)).strip()
284+
try:
285+
md = MarkItDown()
286+
text_content = md.convert_url(input_value).text_content
287+
input_text += f"\n```\n{text_content}\n\nSource: {input_value}\n```\n"
288+
except Exception as e:
289+
print(f"Error processing URL {input_value}: {type(e).__name__}: {e}", file=sys.stderr)
290+
break
291+
elif os.path.isfile(input_value):
292+
try:
293+
with open(input_value, 'rb') as f:
294+
if is_accepted_format(f.read(12)):
295+
media.append(Path(input_value))
296+
except ValueError:
297+
# If not a valid image, read as text
298+
try:
299+
with open(input_value, 'r', encoding='utf-8') as f:
300+
file_content = f.read().strip()
301+
except UnicodeDecodeError:
302+
print(f"Error reading file {input_value} as text. Ensure it is a valid text file.", file=sys.stderr)
303+
break
304+
input_text += f"\n```{input_value}\n{file_content}\n```\n"
305+
else:
306+
break
307+
rest = idx + 1
308+
input_text = (" ".join(args.input[rest:])).strip() + input_text
309+
if media:
310+
input_text = (media, input_text)
301311
if not input_text:
302312
input_text = sys.stdin.read().strip()
303313
if not input_text:

g4f/client/__init__.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ def resolve_media(kwargs: dict, image = None, image_name: str = None) -> None:
5050
kwargs["media"] = [(image, getattr(image, "name", image_name))]
5151
elif "images" in kwargs:
5252
kwargs["media"] = kwargs.pop("images")
53-
if "media" in kwargs and not isinstance(kwargs["media"], list):
53+
if kwargs.get("media") is None:
54+
kwargs.pop("media", None)
55+
elif not isinstance(kwargs["media"], list):
5456
kwargs["media"] = [kwargs["media"]]
5557
for idx, media in enumerate(kwargs.get("media", [])):
5658
if not isinstance(media, (list, tuple)):
57-
kwargs["media"][idx] = (media, os.path.basename(getattr(media, "name", "")))
59+
kwargs["media"][idx] = (media, getattr(media, "name", None))
5860

5961
# Synchronous iter_response function
6062
def iter_response(
@@ -433,12 +435,10 @@ async def get_provider_handler(self, model: Optional[str], provider: Optional[Pr
433435
provider_handler = self.provider
434436
if provider_handler is None:
435437
provider_handler = self.client.models.get(model, default)
436-
elif isinstance(provider, str):
437-
provider_handler = convert_to_provider(provider)
438438
else:
439439
provider_handler = provider
440-
if provider_handler is None:
441-
return default
440+
if isinstance(provider_handler, str):
441+
provider_handler = convert_to_provider(provider_handler)
442442
return provider_handler
443443

444444
async def async_generate(
@@ -538,13 +538,21 @@ async def _generate_image_response(
538538
def create_variation(
539539
self,
540540
image: ImageType,
541+
image_name: str = None,
542+
prompt: str = "Create a variation of this image",
541543
model: str = None,
542544
provider: Optional[ProviderType] = None,
543545
response_format: Optional[str] = None,
544546
**kwargs
545547
) -> ImagesResponse:
546548
return asyncio.run(self.async_create_variation(
547-
image, model, provider, response_format, **kwargs
549+
image=image,
550+
image_name=image_name,
551+
prompt=prompt,
552+
model=model,
553+
provider=provider,
554+
response_format=response_format,
555+
**kwargs
548556
))
549557

550558
async def async_create_variation(
@@ -619,6 +627,7 @@ async def get_b64_from_url(url: str) -> Image:
619627
images = await asyncio.gather(*[get_b64_from_url(image) for image in response.get_list()])
620628
else:
621629
# Save locally for None (default) case
630+
images = response.get_list()
622631
if download_media or response.get("cookies") or response.get("headers"):
623632
images = await copy_media(response.get_list(), response.get("cookies"), response.get("headers"), proxy, response.alt)
624633
images = [Image.model_construct(url=image, revised_prompt=response.alt) for image in images]

g4f/config.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import os
2+
import sys
3+
from pathlib import Path
4+
5+
# Platform-appropriate directories
6+
def get_config_dir() -> Path:
7+
"""Get platform-appropriate config directory."""
8+
if sys.platform == "win32":
9+
return Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
10+
elif sys.platform == "darwin":
11+
return Path.home() / "Library" / "Application Support"
12+
else: # Linux and other UNIX-like
13+
return Path.home() / ".config"
14+
15+
CONFIG_DIR = get_config_dir() / "g4f"
16+
COOKIES_DIR = CONFIG_DIR / "cookies"
17+
CUSTOM_COOKIES_DIR = "./har_and_cookies"
18+
PACKAGE_NAME = "g4f"
19+
ORGANIZATION = "gpt4free"
20+
GITHUB_REPOSITORY = f"xtekky/{ORGANIZATION}"
21+
STATIC_DOMAIN = f"g4f.dev"
22+
STATIC_URL = f"https://{STATIC_DOMAIN}/"
23+
DIST_DIR = f"./{STATIC_DOMAIN}/dist"
24+
DOWNLOAD_URL = f"https://raw.githubusercontent.com/{ORGANIZATION}/{STATIC_DOMAIN}/refs/heads/main/"

g4f/constants.py

Lines changed: 0 additions & 7 deletions
This file was deleted.

g4f/cookies.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,12 @@ def g4f(domain_name: str) -> list:
4444

4545
from .typing import Dict, Cookies
4646
from .errors import MissingRequirementsError
47+
from .config import COOKIES_DIR, CUSTOM_COOKIES_DIR
4748
from . import debug
4849

4950
class CookiesConfig():
5051
cookies: Dict[str, Cookies] = {}
51-
cookies_dir: str = "./har_and_cookies"
52+
cookies_dir: str = CUSTOM_COOKIES_DIR if os.path.exists(CUSTOM_COOKIES_DIR) else COOKIES_DIR
5253

5354
DOMAINS = [
5455
".bing.com",

g4f/gui/server/website.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@
88
from ...image.copy_images import secure_filename
99
from ...cookies import get_cookies_dir
1010
from ...errors import VersionNotFoundError
11-
from ...constants import STATIC_URL, DOWNLOAD_URL, DIST_DIR
11+
from ...config import STATIC_URL, DOWNLOAD_URL, DIST_DIR
1212
from ... import version
1313

1414
def redirect_home():
1515
return redirect('/chat/')
1616

1717
def render(filename = "home"):
18+
filename += ("" if "." in filename else ".html")
1819
if os.path.exists(DIST_DIR) and not request.args.get("debug"):
19-
path = os.path.abspath(os.path.join(os.path.dirname(DIST_DIR), (filename + ("" if "." in filename else ".html"))))
20+
path = os.path.abspath(os.path.join(os.path.dirname(DIST_DIR), filename))
2021
return send_from_directory(os.path.dirname(path), os.path.basename(path))
2122
try:
2223
latest_version = version.utils.latest_version
@@ -31,7 +32,7 @@ def render(filename = "home"):
3132
is_temp = True
3233
else:
3334
os.makedirs(cache_dir, exist_ok=True)
34-
response = requests.get(f"{DOWNLOAD_URL}{filename}.html")
35+
response = requests.get(f"{DOWNLOAD_URL}{filename}")
3536
if not response.ok:
3637
found = None
3738
for root, _, files in os.walk(cache_dir):

g4f/image/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def to_bytes(image: ImageType) -> bytes:
251251
elif image.startswith("http://") or image.startswith("https://"):
252252
path: str = urlparse(image).path
253253
if path.startswith("/files/"):
254-
path = get_bucket_dir(path.split(path, "/")[1:])
254+
path = get_bucket_dir(*path.split("/")[2:])
255255
if os.path.exists(path):
256256
return Path(path).read_bytes()
257257
else:

g4f/tools/files.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ async def download_url(url: str, max_depth: int) -> str:
438438
if text_content:
439439
filename = get_filename_from_url(url)
440440
target = bucket_dir / filename
441+
text_content = f"{text_content.strip()}\n\nSource: {url}\n"
441442
target.write_text(text_content, errors="replace")
442443
return filename
443444
except Exception as e:

0 commit comments

Comments
 (0)