XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/gpt4free

Add get/set cookies dir, hide prompt option in gui

9ddac171
Heiner Lohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

12 个文件 +139 -174
Modified README.md +25 -5
@@ -236,19 +236,39 @@ set_cookies(".google.com", {
236 236 })
237 237 ```
238 238
239 Alternatively, you can place your .har and cookie files in the `/har_and_cookies` directory. To export a cookie file, use the EditThisCookie extension available on the Chrome Web Store: [EditThisCookie Extension](https://chromewebstore.google.com/detail/editthiscookie/fngmhnnpilhplaeedifhccceomclgfbg).
239 #### Using .har and Cookie Files
240 240
241 You can also create .har files to capture cookies. If you need further assistance, refer to the next section.
241 You can place `.har` and cookie files in the default `./har_and_cookies` directory. To export a cookie file, use the [EditThisCookie Extension](https://chromewebstore.google.com/detail/editthiscookie/fngmhnnpilhplaeedifhccceomclgfbg) available on the Chrome Web Store.
242 242
243 ```bash
244 python -m g4f.cli api --debug
243 #### Creating .har Files to Capture Cookies
244
245 To capture cookies, you can also create `.har` files. For more details, refer to the next section.
246
247 #### Changing the Cookies Directory and Loading Cookie Files in Python
248
249 You can change the cookies directory and load cookie files in your Python environment. To set the cookies directory relative to your Python file, use the following code:
250
251 ```python
252 import os.path
253 from g4f.cookies import set_cookies_dir, read_cookie_files
254
255 import g4f.debug
256 g4f.debug.logging = True
257
258 cookies_dir = os.path.join(os.path.dirname(__file__), "har_and_cookies")
259 set_cookies_dir(cookies_dir)
260 read_cookie_files(cookies_dir)
245 261 ```
262
263 ### Debug Mode
264
265 If you enable debug mode, you will see logs similar to the following:
266
246 267 ```
247 268 Read .har file: ./har_and_cookies/you.com.har
248 269 Cookies added: 10 from .you.com
249 270 Read cookie file: ./har_and_cookies/google.json
250 271 Cookies added: 16 from .google.com
251 Starting server... [g4f v-0.0.0] (debug)
252 272 ```
253 273
254 274 #### .HAR File for OpenaiChat Provider
Modified g4f/Provider/BingCreateImages.py +7 -33
@@ -1,18 +1,14 @@
1 1 from __future__ import annotations
2 2
3 import asyncio
4 import os
5 from typing import Iterator, Union
6
7 3 from ..cookies import get_cookies
8 4 from ..image import ImageResponse
9 from ..errors import MissingRequirementsError, MissingAuthError
5 from ..errors import MissingAuthError
10 6 from ..typing import AsyncResult, Messages, Cookies
11 7 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
12 from .bing.create_images import create_images, create_session, get_cookies_from_browser
8 from .bing.create_images import create_images, create_session
13 9
14 10 class BingCreateImages(AsyncGeneratorProvider, ProviderModelMixin):
15 label = "Microsoft Designer"
11 label = "Microsoft Designer in Bing"
16 12 parent = "Bing"
17 13 url = "https://www.bing.com/images/create"
18 14 working = True
@@ -38,30 +34,9 @@ class BingCreateImages(AsyncGeneratorProvider, ProviderModelMixin):
38 34 **kwargs
39 35 ) -> AsyncResult:
40 36 session = BingCreateImages(cookies, proxy, api_key)
41 yield await session.create_async(messages[-1]["content"])
42
43 def create(self, prompt: str) -> Iterator[Union[ImageResponse, str]]:
44 """
45 Generator for creating imagecompletion based on a prompt.
46
47 Args:
48 prompt (str): Prompt to generate images.
49
50 Yields:
51 Generator[str, None, None]: The final output as markdown formatted string with images.
52 """
53 cookies = self.cookies or get_cookies(".bing.com", False)
54 if cookies is None or "_U" not in cookies:
55 login_url = os.environ.get("G4F_LOGIN_URL")
56 if login_url:
57 yield f"Please login: [Bing]({login_url})\n\n"
58 try:
59 self.cookies = get_cookies_from_browser(self.proxy)
60 except MissingRequirementsError as e:
61 raise MissingAuthError(f'Missing "_U" cookie. {e}')
62 yield asyncio.run(self.create_async(prompt))
37 yield await session.generate(messages[-1]["content"])
63 38
64 async def create_async(self, prompt: str) -> ImageResponse:
39 async def generate(self, prompt: str) -> ImageResponse:
65 40 """
66 41 Asynchronously creates a markdown formatted string with images based on the prompt.
67 42
@@ -74,7 +49,6 @@ class BingCreateImages(AsyncGeneratorProvider, ProviderModelMixin):
74 49 cookies = self.cookies or get_cookies(".bing.com", False)
75 50 if cookies is None or "_U" not in cookies:
76 51 raise MissingAuthError('Missing "_U" cookie')
77 proxy = self.proxy or os.environ.get("G4F_PROXY")
78 async with create_session(cookies, proxy) as session:
79 images = await create_images(session, prompt, proxy)
52 async with create_session(cookies, self.proxy) as session:
53 images = await create_images(session, prompt)
80 54 return ImageResponse(images, prompt, {"preview": "{image}?w=200&h=200"} if len(images) > 1 else {})
Modified g4f/Provider/Blackbox.py +9 -1
@@ -4,7 +4,8 @@ import uuid
4 4 import secrets
5 5 from aiohttp import ClientSession
6 6
7 from ..typing import AsyncResult, Messages
7 from ..typing import AsyncResult, Messages, ImageType
8 from ..image import to_data_uri
8 9 from .base_provider import AsyncGeneratorProvider
9 10
10 11 class Blackbox(AsyncGeneratorProvider):
@@ -17,8 +18,15 @@ class Blackbox(AsyncGeneratorProvider):
17 18 model: str,
18 19 messages: Messages,
19 20 proxy: str = None,
21 image: ImageType = None,
22 image_name: str = None,
20 23 **kwargs
21 24 ) -> AsyncResult:
25 if image is not None:
26 messages[-1]["data"] = {
27 "fileText": image_name,
28 "imageBase64": to_data_uri(image)
29 }
22 30 headers = {
23 31 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
24 32 "Accept": "*/*",
Modified g4f/Provider/bing/create_images.py +3 -27
@@ -1,7 +1,3 @@
1 """
2 This module provides functionalities for creating and managing images using Bing's service.
3 It includes functions for user login, session creation, image creation, and processing.
4 """
5 1 from __future__ import annotations
6 2
7 3 import asyncio
@@ -17,9 +13,7 @@ try:
17 13 except ImportError:
18 14 has_requirements = False
19 15
20 from ...providers.create_images import CreateImagesProvider
21 16 from ..helper import get_connector
22 from ...providers.types import ProviderType
23 17 from ...errors import MissingRequirementsError, RateLimitError
24 18 from ...webdriver import WebDriver, get_driver_cookies, get_browser
25 19
@@ -101,7 +95,7 @@ def create_session(cookies: Dict[str, str], proxy: str = None, connector: BaseCo
101 95 headers["Cookie"] = "; ".join(f"{k}={v}" for k, v in cookies.items())
102 96 return ClientSession(headers=headers, connector=get_connector(connector, proxy))
103 97
104 async def create_images(session: ClientSession, prompt: str, proxy: str = None, timeout: int = TIMEOUT_IMAGE_CREATION) -> List[str]:
98 async def create_images(session: ClientSession, prompt: str, timeout: int = TIMEOUT_IMAGE_CREATION) -> List[str]:
105 99 """
106 100 Creates images based on a given prompt using Bing's service.
107 101
@@ -132,7 +126,7 @@ async def create_images(session: ClientSession, prompt: str, proxy: str = None,
132 126 raise RuntimeError(f"Create images failed: {error}")
133 127 if response.status != 302:
134 128 url = f"{BING_URL}/images/create?q={url_encoded_prompt}&rt=3&FORM=GENCRE"
135 async with session.post(url, allow_redirects=False, proxy=proxy, timeout=timeout) as response:
129 async with session.post(url, allow_redirects=False, timeout=timeout) as response:
136 130 if response.status != 302:
137 131 raise RuntimeError(f"Create images failed. Code: {response.status}")
138 132
@@ -185,22 +179,4 @@ def read_images(html_content: str) -> List[str]:
185 179 raise RuntimeError("Bad images found")
186 180 if not images:
187 181 raise RuntimeError("No images found")
188 return images
189
190 def patch_provider(provider: ProviderType) -> CreateImagesProvider:
191 """
192 Patches a provider to include image creation capabilities.
193
194 Args:
195 provider (ProviderType): The provider to be patched.
196
197 Returns:
198 CreateImagesProvider: The patched provider with image creation capabilities.
199 """
200 from ..BingCreateImages import BingCreateImages
201 service = BingCreateImages()
202 return CreateImagesProvider(
203 provider,
204 service.create,
205 service.create_async
206 )
182 return images
Modified g4f/Provider/needs_auth/OpenaiChat.py +21 -56
@@ -26,7 +26,7 @@ from ...webdriver import get_browser
26 26 from ...typing import AsyncResult, Messages, Cookies, ImageType, AsyncIterator
27 27 from ...requests import get_args_from_browser, raise_for_status
28 28 from ...requests.aiohttp import StreamSession
29 from ...image import to_image, to_bytes, ImageResponse, ImageRequest
29 from ...image import ImageResponse, ImageRequest, to_image, to_bytes, is_accepted_format
30 30 from ...errors import MissingAuthError, ResponseError
31 31 from ...providers.conversation import BaseConversation
32 32 from ..helper import format_cookies
@@ -138,23 +138,22 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
138 138 An ImageRequest object that contains the download URL, file name, and other data
139 139 """
140 140 # Convert the image to a PIL Image object and get the extension
141 image = to_image(image)
142 extension = image.format.lower()
143 # Convert the image to a bytes object and get the size
144 141 data_bytes = to_bytes(image)
142 image = to_image(data_bytes)
143 extension = image.format.lower()
145 144 data = {
146 "file_name": image_name if image_name else f"{image.width}x{image.height}.{extension}",
145 "file_name": "" if image_name is None else image_name,
147 146 "file_size": len(data_bytes),
148 147 "use_case": "multimodal"
149 148 }
150 149 # Post the image data to the service and get the image data
151 150 async with session.post(f"{cls.url}/backend-api/files", json=data, headers=headers) as response:
152 cls._update_request_args()
151 cls._update_request_args(session)
153 152 await raise_for_status(response)
154 153 image_data = {
155 154 **data,
156 155 **await response.json(),
157 "mime_type": f"image/{extension}",
156 "mime_type": is_accepted_format(data_bytes),
158 157 "extension": extension,
159 158 "height": image.height,
160 159 "width": image.width
@@ -275,7 +274,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
275 274 first_part = line["message"]["content"]["parts"][0]
276 275 if "asset_pointer" not in first_part or "metadata" not in first_part:
277 276 return
278 if first_part["metadata"] is None:
277 if first_part["metadata"] is None or first_part["metadata"]["dalle"] is None:
279 278 return
280 279 prompt = first_part["metadata"]["dalle"]["prompt"]
281 280 file_id = first_part["asset_pointer"].split("file-service://", 1)[1]
@@ -365,49 +364,17 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
365 364 ) as session:
366 365 if cls._expires is not None and cls._expires < time.time():
367 366 cls._headers = cls._api_key = None
368 if cls._headers is None or cookies is not None:
369 cls._create_request_args(cookies)
370 api_key = kwargs["access_token"] if "access_token" in kwargs else api_key
371 if api_key is not None:
372 cls._set_api_key(api_key)
373
374 if cls.default_model is None and (not cls.needs_auth or cls._api_key is not None):
375 if cls._api_key is None:
376 cls._create_request_args(cookies)
377 async with session.get(
378 f"{cls.url}/",
379 headers=DEFAULT_HEADERS
380 ) as response:
381 cls._update_request_args(session)
382 await raise_for_status(response)
383 try:
384 if not model:
385 cls.default_model = cls.get_model(await cls.get_default_model(session, cls._headers))
386 else:
387 cls.default_model = cls.get_model(model)
388 except MissingAuthError:
389 pass
390 except Exception as e:
391 api_key = cls._api_key = None
392 cls._create_request_args()
393 if debug.logging:
394 print("OpenaiChat: Load default model failed")
395 print(f"{e.__class__.__name__}: {e}")
396
397 367 arkose_token = None
398 368 proofTokens = None
399 if cls.default_model is None:
400 error = None
401 try:
402 arkose_token, api_key, cookies, headers, proofTokens = await getArkoseAndAccessToken(proxy)
403 cls._create_request_args(cookies, headers)
404 cls._set_api_key(api_key)
405 except NoValidHarFileError as e:
406 error = e
407 if cls._api_key is None:
408 await cls.nodriver_access_token(proxy)
369 try:
370 arkose_token, api_key, cookies, headers, proofTokens = await getArkoseAndAccessToken(proxy)
371 cls._create_request_args(cookies, headers)
372 cls._set_api_key(api_key)
373 except NoValidHarFileError as e:
409 374 if cls._api_key is None and cls.needs_auth:
410 raise error
375 raise e
376
377 if cls.default_model is None:
411 378 cls.default_model = cls.get_model(await cls.get_default_model(session, cls._headers))
412 379
413 380 try:
@@ -461,7 +428,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
461 428 )
462 429 ws = None
463 430 if need_arkose:
464 async with session.post("https://chatgpt.com/backend-api/register-websocket", headers=cls._headers) as response:
431 async with session.post(f"{cls.url}/backend-api/register-websocket", headers=cls._headers) as response:
465 432 wss_url = (await response.json()).get("wss_url")
466 433 if wss_url:
467 434 ws = await session.ws_connect(wss_url)
@@ -490,7 +457,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
490 457 if proofofwork is not None:
491 458 headers["Openai-Sentinel-Proof-Token"] = proofofwork
492 459 async with session.post(
493 f"{cls.url}/backend-anon/conversation" if cls._api_key is None else
460 f"{cls.url}/backend-anon/conversation"
461 if cls._api_key is None else
494 462 f"{cls.url}/backend-api/conversation",
495 463 json=data,
496 464 headers=headers
@@ -580,12 +548,9 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
580 548 raise RuntimeError(line["error"])
581 549 if "message_type" not in line["message"]["metadata"]:
582 550 return
583 try:
584 image_response = await cls.get_generated_image(session, cls._headers, line)
585 if image_response is not None:
586 yield image_response
587 except Exception as e:
588 yield e
551 image_response = await cls.get_generated_image(session, cls._headers, line)
552 if image_response is not None:
553 yield image_response
589 554 if line["message"]["author"]["role"] != "assistant":
590 555 return
591 556 if line["message"]["content"]["content_type"] != "text":
Modified g4f/Provider/openai/har_file.py +2 -4
@@ -12,6 +12,7 @@ from copy import deepcopy
12 12
13 13 from .crypt import decrypt, encrypt
14 14 from ...requests import StreamSession
15 from ...cookies import get_cookies_dir
15 16 from ... import debug
16 17
17 18 class NoValidHarFileError(Exception):
@@ -36,17 +37,14 @@ proofTokens: list = []
36 37
37 38 def readHAR():
38 39 global proofTokens
39 dirPath = "./"
40 40 harPath = []
41 41 chatArks = []
42 42 accessToken = None
43 43 cookies = {}
44 for root, dirs, files in os.walk(dirPath):
44 for root, dirs, files in os.walk(get_cookies_dir()):
45 45 for file in files:
46 46 if file.endswith(".har"):
47 47 harPath.append(os.path.join(root, file))
48 if harPath:
49 break
50 48 if not harPath:
51 49 raise NoValidHarFileError("No .har file found")
52 50 for path in harPath:
Modified g4f/Provider/you/har_file.py +6 -12
@@ -7,6 +7,7 @@ import random
7 7 import logging
8 8
9 9 from ...requests import StreamSession, raise_for_status
10 from ...cookies import get_cookies_dir
10 11 from ...errors import MissingRequirementsError
11 12 from ... import debug
12 13
@@ -28,10 +29,9 @@ public_token = "public-token-live-507a52ad-7e69-496b-aee0-1c9863c7c819"
28 29 chatArks: list = None
29 30
30 31 def readHAR():
31 dirPath = "./"
32 32 harPath = []
33 33 chatArks = []
34 for root, dirs, files in os.walk(dirPath):
34 for root, dirs, files in os.walk(get_cookies_dir()):
35 35 for file in files:
36 36 if file.endswith(".har"):
37 37 harPath.append(os.path.join(root, file))
@@ -65,16 +65,10 @@ def parseHAREntry(entry) -> arkReq:
65 65 return tmpArk
66 66
67 67 async def sendRequest(tmpArk: arkReq, proxy: str = None):
68 try:
69 async with StreamSession(headers=tmpArk.arkHeaders, cookies=tmpArk.arkCookies, proxy=proxy) as session:
70 async with session.post(tmpArk.arkURL, data=tmpArk.arkBody) as response:
71 await raise_for_status(response)
72 return await response.text()
73 except RuntimeError as e:
74 if str(e) == "Event loop is closed":
75 print("Event loop is closed error occurred in sendRequest.")
76 else:
77 raise
68 async with StreamSession(headers=tmpArk.arkHeaders, cookies=tmpArk.arkCookies, proxy=proxy) as session:
69 async with session.post(tmpArk.arkURL, data=tmpArk.arkBody) as response:
70 await raise_for_status(response)
71 return await response.text()
78 72
79 73 async def create_telemetry_id(proxy: str = None):
80 74 global chatArks
Modified g4f/cookies.py +21 -16
@@ -23,8 +23,9 @@ from .typing import Dict, Cookies
23 23 from .errors import MissingRequirementsError
24 24 from . import debug
25 25
26 # Global variable to store cookies
27 _cookies: Dict[str, Cookies] = {}
26 class CookiesConfig():
27 cookies: Dict[str, Cookies] = {}
28 cookies_dir: str = "./har_and_cookies"
28 29
29 30 DOMAINS = [
30 31 ".bing.com",
@@ -48,20 +49,18 @@ def get_cookies(domain_name: str = '', raise_requirements_error: bool = True, si
48 49 Returns:
49 50 Dict[str, str]: A dictionary of cookie names and values.
50 51 """
51 global _cookies
52 if domain_name in _cookies:
53 return _cookies[domain_name]
52 if domain_name in CookiesConfig.cookies:
53 return CookiesConfig.cookies[domain_name]
54 54
55 55 cookies = load_cookies_from_browsers(domain_name, raise_requirements_error, single_browser)
56 _cookies[domain_name] = cookies
56 CookiesConfig.cookies[domain_name] = cookies
57 57 return cookies
58 58
59 59 def set_cookies(domain_name: str, cookies: Cookies = None) -> None:
60 global _cookies
61 60 if cookies:
62 _cookies[domain_name] = cookies
63 elif domain_name in _cookies:
64 _cookies.pop(domain_name)
61 CookiesConfig.cookies[domain_name] = cookies
62 elif domain_name in CookiesConfig.cookies:
63 CookiesConfig.cookies.pop(domain_name)
65 64
66 65 def load_cookies_from_browsers(domain_name: str, raise_requirements_error: bool = True, single_browser: bool = False) -> Cookies:
67 66 """
@@ -96,7 +95,13 @@ def load_cookies_from_browsers(domain_name: str, raise_requirements_error: bool
96 95 print(f"Error reading cookies from {cookie_fn.__name__} for {domain_name}: {e}")
97 96 return cookies
98 97
99 def read_cookie_files(dirPath: str = "./har_and_cookies"):
98 def set_cookies_dir(dir: str) -> None:
99 CookiesConfig.cookies_dir = dir
100
101 def get_cookies_dir() -> str:
102 return CookiesConfig.cookies_dir
103
104 def read_cookie_files(dirPath: str = None):
100 105 def get_domain(v: dict) -> str:
101 106 host = [h["value"] for h in v['request']['headers'] if h["name"].lower() in ("host", ":authority")]
102 107 if not host:
@@ -106,16 +111,16 @@ def read_cookie_files(dirPath: str = "./har_and_cookies"):
106 111 if d in host:
107 112 return d
108 113
109 global _cookies
110 114 harFiles = []
111 115 cookieFiles = []
112 for root, dirs, files in os.walk(dirPath):
116 for root, dirs, files in os.walk(CookiesConfig.cookies_dir if dirPath is None else dirPath):
113 117 for file in files:
114 118 if file.endswith(".har"):
115 119 harFiles.append(os.path.join(root, file))
116 120 elif file.endswith(".json"):
117 121 cookieFiles.append(os.path.join(root, file))
118 _cookies = {}
122
123 CookiesConfig.cookies = {}
119 124 for path in harFiles:
120 125 with open(path, 'rb') as file:
121 126 try:
@@ -134,7 +139,7 @@ def read_cookie_files(dirPath: str = "./har_and_cookies"):
134 139 for c in v['request']['cookies']:
135 140 v_cookies[c['name']] = c['value']
136 141 if len(v_cookies) > 0:
137 _cookies[domain] = v_cookies
142 CookiesConfig.cookies[domain] = v_cookies
138 143 new_cookies[domain] = len(v_cookies)
139 144 if debug.logging:
140 145 for domain, new_values in new_cookies.items():
@@ -159,7 +164,7 @@ def read_cookie_files(dirPath: str = "./har_and_cookies"):
159 164 for domain, new_values in new_cookies.items():
160 165 if debug.logging:
161 166 print(f"Cookies added: {len(new_values)} from {domain}")
162 _cookies[domain] = new_values
167 CookiesConfig.cookies[domain] = new_values
163 168
164 169 def _g4f(domain_name: str) -> list:
165 170 """
Modified g4f/gui/client/index.html +14 -11
@@ -93,22 +93,22 @@
93 93 <div class="paper">
94 94 <h3>Settings</h3>
95 95 <div class="field">
96 <span class="label">Web Access</span>
96 <span class="label">Web Access with DuckDuckGo</span>
97 97 <input type="checkbox" id="switch" />
98 98 <label for="switch" class="toogle" title="Add the pages of the first 5 search results to the query."></label>
99 99 </div>
100 100 <div class="field">
101 <span class="label">Disable History</span>
101 <span class="label">Disable Conversation History</span>
102 102 <input type="checkbox" id="history" />
103 103 <label for="history" class="toogle" title="To improve the reaction time or if you have trouble with large conversations."></label>
104 104 </div>
105 105 <div class="field">
106 <span class="label">Hide System prompt</span>
106 <span class="label">Hide System-prompt</span>
107 107 <input type="checkbox" id="hide-systemPrompt" />
108 108 <label for="hide-systemPrompt" class="toogle" title="For more space on phones"></label>
109 109 </div>
110 110 <div class="field">
111 <span class="label">Auto continue</span>
111 <span class="label">Auto continue in ChatGPT</span>
112 112 <input id="auto_continue" type="checkbox" name="auto_continue" checked/>
113 113 <label for="auto_continue" class="toogle" title="Continue large responses in OpenaiChat"></label>
114 114 </div>
@@ -121,8 +121,8 @@
121 121 <input type="text" id="recognition-language" value="" placeholder="navigator.language"/>
122 122 </div>
123 123 <div class="field box">
124 <label for="Bing-api_key" class="label" title="">Bing:</label>
125 <textarea id="Bing-api_key" name="Bing[api_key]" class="BingCreateImages-api_key" placeholder="&quot;_U&quot; cookie"></textarea>
124 <label for="BingCreateImages-api_key" class="label" title="">Microsoft Designer in Bing:</label>
125 <textarea id="BingCreateImages-api_key" name="BingCreateImages[api_key]" placeholder="&quot;_U&quot; cookie"></textarea>
126 126 </div>
127 127 <div class="field box">
128 128 <label for="DeepInfra-api_key" class="label" title="">DeepInfra:</label>
@@ -144,14 +144,14 @@
144 144 <label for="Openai-api_key" class="label" title="">OpenAI API:</label>
145 145 <textarea id="Openai-api_key" name="Openai[api_key]" placeholder="api_key"></textarea>
146 146 </div>
147 <div class="field box">
148 <label for="OpenaiAccount-api_key" class="label" title="">OpenAI ChatGPT:</label>
149 <textarea id="OpenaiAccount-api_key" name="OpenaiAccount[api_key]" class="OpenaiChat-api_key" placeholder="access_key"></textarea>
150 </div>
151 147 <div class="field box">
152 148 <label for="OpenRouter-api_key" class="label" title="">OpenRouter:</label>
153 149 <textarea id="OpenRouter-api_key" name="OpenRouter[api_key]" placeholder="api_key"></textarea>
154 150 </div>
151 <div class="field box">
152 <label for="PerplexityApi-api_key" class="label" title="">Perplexity API:</label>
153 <textarea id="PerplexityApi-api_key" name="PerplexityApi[api_key]" placeholder="api_key"></textarea>
154 </div>
155 155 <div class="field box">
156 156 <label for="Replicate-api_key" class="label" title="">Replicate:</label>
157 157 <textarea id="Replicate-api_key" name="Replicate[api_key]" class="ReplicateImage-api_key" placeholder="api_key"></textarea>
@@ -173,7 +173,10 @@
173 173 <div id="messages" class="box"></div>
174 174 <div class="toolbar">
175 175 <div id="input-count" class="">
176 &nbsp;
176 <button class="hide-input">
177 <i class="fa-solid fa-angles-down"></i>
178 </button>
179 <span class="text"></span>
177 180 </div>
178 181 <div class="stop_generating stop_generating-hidden">
179 182 <button id="cancelButton">
Modified g4f/gui/client/static/css/style.css +12 -1
@@ -457,7 +457,11 @@ body {
457 457 #input-count {
458 458 width: fit-content;
459 459 font-size: 12px;
460 padding: 6px var(--inner-gap);
460 padding: 6px 6px;
461 }
462
463 #input-count .text {
464 padding: 0 4px;
461 465 }
462 466
463 467 .stop_generating, .toolbar .regenerate {
@@ -491,6 +495,13 @@ body {
491 495 animation: show_popup 0.4s;
492 496 }
493 497
498 .toolbar .hide-input {
499 background: transparent;
500 border: none;
501 color: var(--colour-3);
502 cursor: pointer;
503 }
504
494 505 @keyframes show_popup {
495 506 from {
496 507 opacity: 0;
Modified g4f/gui/client/static/js/chat.v1.js +17 -6
@@ -11,7 +11,7 @@ const imageInput = document.getElementById("image");
11 11 const cameraInput = document.getElementById("camera");
12 12 const fileInput = document.getElementById("file");
13 13 const microLabel = document.querySelector(".micro-label");
14 const inputCount = document.getElementById("input-count");
14 const inputCount = document.getElementById("input-count").querySelector(".text");
15 15 const providerSelect = document.getElementById("provider");
16 16 const modelSelect = document.getElementById("model");
17 17 const modelProvider = document.getElementById("model2");
@@ -41,9 +41,7 @@ appStorage = window.localStorage || {
41 41 length: 0
42 42 }
43 43
44 const markdown = window.markdownit({
45 html: true,
46 });
44 const markdown = window.markdownit();
47 45 const markdown_render = (content) => {
48 46 return markdown.render(content
49 47 .replaceAll(/<!-- generated images start -->|<!-- generated images end -->/gm, "")
@@ -813,6 +811,17 @@ document.getElementById("regenerateButton").addEventListener("click", async () =
813 811 await ask_gpt();
814 812 });
815 813
814 const hide_input = document.querySelector(".toolbar .hide-input");
815 hide_input.addEventListener("click", async (e) => {
816 const icon = hide_input.querySelector("i");
817 const func = icon.classList.contains("fa-angles-down") ? "add" : "remove";
818 const remv = icon.classList.contains("fa-angles-down") ? "remove" : "add";
819 icon.classList[func]("fa-angles-up");
820 icon.classList[remv]("fa-angles-down");
821 document.querySelector(".conversation .user-input").classList[func]("hidden");
822 document.querySelector(".conversation .buttons").classList[func]("hidden");
823 });
824
816 825 const uuid = () => {
817 826 return `xxxxxxxx-xxxx-4xxx-yxxx-${Date.now().toString(16)}`.replace(
818 827 /[xy]/g,
@@ -1016,7 +1025,7 @@ const count_input = async () => {
1016 1025 if (countFocus.value) {
1017 1026 inputCount.innerText = count_words_and_tokens(countFocus.value, get_selected_model());
1018 1027 } else {
1019 inputCount.innerHTML = "&nbsp;"
1028 inputCount.innerText = "";
1020 1029 }
1021 1030 }, 100);
1022 1031 };
@@ -1060,6 +1069,8 @@ async function on_api() {
1060 1069 messageInput.addEventListener("keydown", async (evt) => {
1061 1070 if (prompt_lock) return;
1062 1071
1072 // If not mobile
1073 if (!window.matchMedia("(pointer:coarse)").matches)
1063 1074 if (evt.keyCode === 13 && !evt.shiftKey) {
1064 1075 evt.preventDefault();
1065 1076 console.log("pressed enter");
@@ -1262,6 +1273,7 @@ async function load_provider_models(providerIndex=null) {
1262 1273 if (!providerIndex) {
1263 1274 providerIndex = providerSelect.selectedIndex;
1264 1275 }
1276 modelProvider.innerHTML = '';
1265 1277 const provider = providerSelect.options[providerIndex].value;
1266 1278 if (!provider) {
1267 1279 modelProvider.classList.add("hidden");
@@ -1269,7 +1281,6 @@ async function load_provider_models(providerIndex=null) {
1269 1281 return;
1270 1282 }
1271 1283 const models = await api('models', provider);
1272 modelProvider.innerHTML = '';
1273 1284 if (models.length > 0) {
1274 1285 modelSelect.classList.add("hidden");
1275 1286 modelProvider.classList.remove("hidden");
Modified g4f/image.py +2 -2
@@ -210,8 +210,8 @@ def format_images_markdown(images: Union[str, list], alt: str, preview: Union[st
210 210 if not isinstance(preview, list):
211 211 preview = [preview.replace('{image}', image) if preview else image for image in images]
212 212 result = "\n".join(
213 #f"[![#{idx+1} {alt}]({preview[idx]})]({image})"
214 f'[<img src="{preview[idx]}" width="200" alt="#{idx+1} {alt}">]({image})'
213 f"[![#{idx+1} {alt}]({preview[idx]})]({image})"
214 #f'[<img src="{preview[idx]}" width="200" alt="#{idx+1} {alt}">]({image})'
215 215 for idx, image in enumerate(images)
216 216 )
217 217 start_flag = "<!-- generated images start -->\n"