66import json
77import argparse
88import traceback
9+ import requests
910from pathlib import Path
1011from typing import Optional , List , Dict
1112from g4f .client import AsyncClient
12- from g4f .providers .response import JsonConversation , is_content
13+ from g4f .providers .response import JsonConversation , MediaResponse , is_content
1314from g4f .cookies import set_cookies_dir , read_cookie_files
1415from g4f .Provider import ProviderUtils
1516from g4f .image import extract_data_uri , is_accepted_format
1617from g4f .image .copy_images import get_media_dir
1718from g4f .client .helper import filter_markdown
19+ from g4f .integration .markitdown import MarkItDown
20+ from g4f .config import CONFIG_DIR , COOKIES_DIR
1821from 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"
3223CONVERSATION_FILE = CONFIG_DIR / "conversation.json"
3324
3425class 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"\n Response 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
280271def 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 \n Source: { 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 :
0 commit comments