summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorH Lohaus <hlohaus@users.noreply.github.com>2024-03-13 15:17:25 +0100
committerGitHub <noreply@github.com>2024-03-13 15:17:25 +0100
commit9c381f29060d697fe2d2c4768cd51b37d98ef3e0 (patch)
tree08a729476060f61f16ac8e909ed7dfe1cf4b2843
parentMerge pull request #1680 from Zero6992/main (diff)
parentUpdate Bing.py (diff)
downloadgpt4free-0.2.4.1.tar
gpt4free-0.2.4.1.tar.gz
gpt4free-0.2.4.1.tar.bz2
gpt4free-0.2.4.1.tar.lz
gpt4free-0.2.4.1.tar.xz
gpt4free-0.2.4.1.tar.zst
gpt4free-0.2.4.1.zip
-rw-r--r--g4f/Provider/Bing.py187
-rw-r--r--g4f/Provider/BingCreateImages.py7
-rw-r--r--g4f/Provider/bing/conversation.py34
-rw-r--r--g4f/Provider/bing/upload_image.py12
-rw-r--r--g4f/cookies.py2
-rw-r--r--g4f/gui/client/html/index.html2
-rw-r--r--g4f/gui/client/js/chat.v1.js60
-rw-r--r--g4f/gui/webview.py24
-rw-r--r--g4f/providers/create_images.py5
-rw-r--r--g4f/providers/helper.py12
-rw-r--r--g4f/requests/__init__.py26
-rw-r--r--g4f/webdriver.py5
12 files changed, 223 insertions, 153 deletions
diff --git a/g4f/Provider/Bing.py b/g4f/Provider/Bing.py
index 77178686..786fec49 100644
--- a/g4f/Provider/Bing.py
+++ b/g4f/Provider/Bing.py
@@ -4,16 +4,20 @@ import random
import json
import uuid
import time
+import asyncio
from urllib import parse
-from aiohttp import ClientSession, ClientTimeout, BaseConnector
+from datetime import datetime
+from aiohttp import ClientSession, ClientTimeout, BaseConnector, WSMsgType
from ..typing import AsyncResult, Messages, ImageType, Cookies
-from ..image import ImageResponse, ImageRequest
+from ..image import ImageRequest
+from ..errors import ResponseStatusError
from .base_provider import AsyncGeneratorProvider
-from .helper import get_connector
+from .helper import get_connector, get_random_hex
from .bing.upload_image import upload_image
-from .bing.create_images import create_images
from .bing.conversation import Conversation, create_conversation, delete_conversation
+from .BingCreateImages import BingCreateImages
+from .. import debug
class Tones:
"""
@@ -65,11 +69,14 @@ class Bing(AsyncGeneratorProvider):
prompt = messages[-1]["content"]
context = create_context(messages[:-1])
- cookies = {**get_default_cookies(), **cookies} if cookies else get_default_cookies()
-
gpt4_turbo = True if model.startswith("gpt-4-turbo") else False
- return stream_generate(prompt, tone, image, context, cookies, get_connector(connector, proxy, True), web_search, gpt4_turbo, timeout)
+ return stream_generate(
+ prompt, tone, image, context, cookies,
+ get_connector(connector, proxy, True),
+ proxy, web_search, gpt4_turbo, timeout,
+ **kwargs
+ )
def create_context(messages: Messages) -> str:
"""
@@ -78,14 +85,32 @@ def create_context(messages: Messages) -> str:
:param messages: A list of message dictionaries.
:return: A string representing the context created from the messages.
"""
- return "\n\n".join(
+ return "".join(
f"[{message['role']}]" + ("(#message)" if message['role'] != "system" else "(#additional_instructions)") + f"\n{message['content']}"
for message in messages
- )
+ ) + "\n\n"
def get_ip_address() -> str:
return f"13.{random.randint(104, 107)}.{random.randint(0, 255)}.{random.randint(0, 255)}"
+def get_default_cookies():
+ return {
+ 'SRCHD' : 'AF=NOFORM',
+ 'PPLState' : '1',
+ 'KievRPSSecAuth': '',
+ 'SUID' : '',
+ 'SRCHUSR' : '',
+ 'SRCHHPGUSR' : f'HV={int(time.time())}',
+ }
+
+def create_headers(cookies: Cookies = None) -> dict:
+ if cookies is None:
+ cookies = get_default_cookies()
+ headers = Defaults.headers.copy()
+ headers["cookie"] = "; ".join(f"{k}={v}" for k, v in cookies.items())
+ headers["x-forwarded-for"] = get_ip_address()
+ return headers
+
class Defaults:
"""
Default settings and configurations for the Bing provider.
@@ -169,37 +194,26 @@ class Defaults:
}
# Default headers for requests
+ home = 'https://www.bing.com/chat?q=Bing+AI&FORM=hpcodx'
headers = {
- 'accept': '*/*',
- 'accept-language': 'en-US,en;q=0.9',
- 'cache-control': 'max-age=0',
- 'sec-ch-ua': '"Chromium";v="110", "Not A(Brand";v="24", "Microsoft Edge";v="110"',
- 'sec-ch-ua-arch': '"x86"',
- 'sec-ch-ua-bitness': '"64"',
- 'sec-ch-ua-full-version': '"110.0.1587.69"',
- 'sec-ch-ua-full-version-list': '"Chromium";v="110.0.5481.192", "Not A(Brand";v="24.0.0.0", "Microsoft Edge";v="110.0.1587.69"',
+ 'sec-ch-ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
'sec-ch-ua-mobile': '?0',
+ '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',
+ 'sec-ch-ua-arch': '"x86"',
+ 'sec-ch-ua-full-version': '"122.0.6261.69"',
+ 'accept': 'application/json',
+ 'sec-ch-ua-platform-version': '"15.0.0"',
+ "x-ms-client-request-id": str(uuid.uuid4()),
+ 'sec-ch-ua-full-version-list': '"Chromium";v="122.0.6261.69", "Not(A:Brand";v="24.0.0.0", "Google Chrome";v="122.0.6261.69"',
+ 'x-ms-useragent': 'azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.12.3 OS/Windows',
'sec-ch-ua-model': '""',
'sec-ch-ua-platform': '"Windows"',
- 'sec-ch-ua-platform-version': '"15.0.0"',
- 'sec-fetch-dest': 'document',
- 'sec-fetch-mode': 'navigate',
- 'sec-fetch-site': 'none',
- 'sec-fetch-user': '?1',
- 'upgrade-insecure-requests': '1',
- 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.69',
- 'x-edge-shopping-flag': '1',
- 'x-forwarded-for': get_ip_address(),
- }
-
-def get_default_cookies():
- return {
- 'SRCHD' : 'AF=NOFORM',
- 'PPLState' : '1',
- 'KievRPSSecAuth': '',
- 'SUID' : '',
- 'SRCHUSR' : '',
- 'SRCHHPGUSR' : f'HV={int(time.time())}',
+ 'sec-fetch-site': 'same-origin',
+ 'sec-fetch-mode': 'cors',
+ 'sec-fetch-dest': 'empty',
+ 'referer': home,
+ 'accept-encoding': 'gzip, deflate, br',
+ 'accept-language': 'en-US,en;q=0.9',
}
def format_message(msg: dict) -> str:
@@ -234,8 +248,6 @@ def create_message(
"""
options_sets = []
- if not web_search:
- options_sets.append("nosearchall")
if gpt4_turbo:
options_sets.append("dlgpt4t")
@@ -249,7 +261,7 @@ def create_message(
"verbosity": "verbose",
"scenario": "SERP",
"plugins": [{"id": "c310c353-b9f0-4d76-ab0d-1dd5e979cf68", "category": 1}] if web_search else [],
- "traceId": str(uuid.uuid4()),
+ "traceId": get_random_hex(40),
"conversationHistoryOptionsSets": ["autosave","savemem","uprofupd","uprofgen"],
"gptId": "copilot",
"isStartOfSession": True,
@@ -257,7 +269,7 @@ def create_message(
"message":{
**Defaults.location,
"userIpAddress": get_ip_address(),
- "timestamp": "2024-03-11T22:40:36+01:00",
+ "timestamp": datetime.now().isoformat(),
"author": "user",
"inputMethod": "Keyboard",
"text": prompt,
@@ -266,6 +278,7 @@ def create_message(
"messageId": request_id
},
"tone": tone,
+ "extraExtensionParameters": {"gpt-creator-persona": {"personaId": "copilot"}},
"spokenTextMode": "None",
"conversationId": conversation.conversationId,
"participant": {"id": conversation.clientId}
@@ -299,9 +312,15 @@ async def stream_generate(
context: str = None,
cookies: dict = None,
connector: BaseConnector = None,
+ proxy: str = None,
web_search: bool = False,
gpt4_turbo: bool = False,
- timeout: int = 900
+ timeout: int = 900,
+ conversation: Conversation = None,
+ raise_apology: bool = False,
+ max_retries: int = 5,
+ sleep_retry: int = 15,
+ **kwargs
):
"""
Asynchronously streams generated responses from the Bing API.
@@ -316,20 +335,30 @@ async def stream_generate(
:param timeout: Timeout for the request.
:return: An asynchronous generator yielding responses.
"""
- headers = Defaults.headers
- if cookies:
- headers["cookie"] = "; ".join(f"{k}={v}" for k, v in cookies.items())
+ headers = create_headers(cookies)
async with ClientSession(
- headers=headers, cookies=cookies,
timeout=ClientTimeout(total=timeout), connector=connector
) as session:
- conversation = await create_conversation(session)
- image_request = await upload_image(session, image, tone) if image else None
- try:
+ while conversation is None:
+ do_read = True
+ try:
+ conversation = await create_conversation(session, headers)
+ except ResponseStatusError as e:
+ max_retries -= 1
+ if max_retries < 1:
+ raise e
+ if debug.logging:
+ print(f"Bing: Retry: {e}")
+ headers = create_headers()
+ await asyncio.sleep(sleep_retry)
+ continue
+
+ image_request = await upload_image(session, image, tone, headers) if image else None
async with session.ws_connect(
'wss://sydney.bing.com/sydney/ChatHub',
autoping=False,
- params={'sec_access_token': conversation.conversationSignature}
+ params={'sec_access_token': conversation.conversationSignature},
+ headers=headers
) as wss:
await wss.send_str(format_message({'protocol': 'json', 'version': 1}))
await wss.send_str(format_message({"type": 6}))
@@ -337,11 +366,12 @@ async def stream_generate(
await wss.send_str(create_message(conversation, prompt, tone, context, image_request, web_search, gpt4_turbo))
response_txt = ''
returned_text = ''
- final = False
message_id = None
- while not final:
+ while do_read:
msg = await wss.receive(timeout=timeout)
- if not msg.data:
+ if msg.type == WSMsgType.CLOSED:
+ break
+ if msg.type != WSMsgType.TEXT or not msg.data:
continue
objects = msg.data.split(Defaults.delimiter)
for obj in objects:
@@ -350,26 +380,27 @@ async def stream_generate(
response = json.loads(obj)
if response and response.get('type') == 1 and response['arguments'][0].get('messages'):
message = response['arguments'][0]['messages'][0]
- # Reset memory, if we have a new message
if message_id is not None and message_id != message["messageId"]:
returned_text = ''
message_id = message["messageId"]
image_response = None
- if (message['contentOrigin'] != 'Apology'):
- if 'adaptiveCards' in message:
- card = message['adaptiveCards'][0]['body'][0]
- if "text" in card:
- response_txt = card.get('text')
- if message.get('messageType') and "inlines" in card:
- inline_txt = card['inlines'][0].get('text')
- response_txt += inline_txt + '\n'
- elif message.get('contentType') == "IMAGE":
- prompt = message.get('text')
- try:
- image_response = ImageResponse(await create_images(session, prompt), prompt, {"preview": "{image}?w=200&h=200"})
- except:
- response_txt += f"\nhttps://www.bing.com/images/create?q={parse.quote(prompt)}"
- final = True
+ if (raise_apology and message['contentOrigin'] == 'Apology'):
+ raise RuntimeError("Apology Response Error")
+ if 'adaptiveCards' in message:
+ card = message['adaptiveCards'][0]['body'][0]
+ if "text" in card:
+ response_txt = card.get('text')
+ if message.get('messageType') and "inlines" in card:
+ inline_txt = card['inlines'][0].get('text')
+ response_txt += inline_txt + '\n'
+ elif message.get('contentType') == "IMAGE":
+ prompt = message.get('text')
+ try:
+ image_client = BingCreateImages(cookies, proxy)
+ image_response = await image_client.create_async(prompt)
+ except Exception as e:
+ response_txt += f"\nhttps://www.bing.com/images/create?q={parse.quote(prompt)}"
+ do_read = False
if response_txt.startswith(returned_text):
new = response_txt[len(returned_text):]
if new != "\n":
@@ -380,10 +411,18 @@ async def stream_generate(
elif response.get('type') == 2:
result = response['item']['result']
if result.get('error'):
- if result["value"] == "CaptchaChallenge":
- raise Exception(f"{result['value']}: Use other cookies or/and ip address")
- else:
- raise Exception(f"{result['value']}: {result['message']}")
+ max_retries -= 1
+ if max_retries < 1:
+ if result["value"] == "CaptchaChallenge":
+ raise RuntimeError(f"{result['value']}: Use other cookies or/and ip address")
+ else:
+ raise RuntimeError(f"{result['value']}: {result['message']}")
+ if debug.logging:
+ print(f"Bing: Retry: {result['value']}: {result['message']}")
+ headers = create_headers()
+ do_read = False
+ conversation = None
+ await asyncio.sleep(sleep_retry)
+ break
return
- finally:
- await delete_conversation(session, conversation)
+ await delete_conversation(session, conversation, headers)
diff --git a/g4f/Provider/BingCreateImages.py b/g4f/Provider/BingCreateImages.py
index c465c1d8..f9c4f3b3 100644
--- a/g4f/Provider/BingCreateImages.py
+++ b/g4f/Provider/BingCreateImages.py
@@ -7,14 +7,15 @@ from typing import Iterator, Union
from ..cookies import get_cookies
from ..image import ImageResponse
from ..errors import MissingRequirementsError, MissingAuthError
+from ..typing import Cookies
from .bing.create_images import create_images, create_session, get_cookies_from_browser
class BingCreateImages:
"""A class for creating images using Bing."""
- def __init__(self, cookies: dict[str, str] = {}, proxy: str = None) -> None:
- self.cookies = cookies
- self.proxy = proxy
+ def __init__(self, cookies: Cookies = None, proxy: str = None) -> None:
+ self.cookies: Cookies = cookies
+ self.proxy: str = proxy
def create(self, prompt: str) -> Iterator[Union[ImageResponse, str]]:
"""
diff --git a/g4f/Provider/bing/conversation.py b/g4f/Provider/bing/conversation.py
index 03f17ee7..da842808 100644
--- a/g4f/Provider/bing/conversation.py
+++ b/g4f/Provider/bing/conversation.py
@@ -1,8 +1,6 @@
from __future__ import annotations
-import uuid
from aiohttp import ClientSession
-from ...errors import ResponseStatusError
from ...requests import raise_for_status
class Conversation:
@@ -22,7 +20,7 @@ class Conversation:
self.clientId = clientId
self.conversationSignature = conversationSignature
-async def create_conversation(session: ClientSession, proxy: str = None) -> Conversation:
+async def create_conversation(session: ClientSession, headers: dict) -> Conversation:
"""
Create a new conversation asynchronously.
@@ -33,33 +31,15 @@ async def create_conversation(session: ClientSession, proxy: str = None) -> Conv
Returns:
Conversation: An instance representing the created conversation.
"""
- url = 'https://www.bing.com/search?toncp=0&FORM=hpcodx&q=Bing+AI&showconv=1&cc=en'
- headers = {
- "cookie": "; ".join(f"{c.key}={c.value}" for c in session.cookie_jar)
- }
+ url = "https://www.bing.com/turing/conversation/create?bundleVersion=1.1626.1"
async with session.get(url, headers=headers) as response:
- await raise_for_status(response)
- headers = {
- "accept": "application/json",
- "sec-fetch-dest": "empty",
- "sec-fetch-mode": "cors",
- "sec-fetch-site": "same-origin",
- "x-ms-client-request-id": str(uuid.uuid4()),
- "x-ms-useragent": "azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.12.3 OS/Windows",
- "referer": "https://www.bing.com/search?toncp=0&FORM=hpcodx&q=Bing+AI&showconv=1&cc=en",
- "cookie": "; ".join(f"{c.key}={c.value}" for c in session.cookie_jar)
- }
- url = "https://www.bing.com/turing/conversation/create?bundleVersion=1.1634.0-service-contracts"
- async with session.get(url, headers=headers, proxy=proxy) as response:
- if response.status == 404:
- raise ResponseStatusError(f"Response {response.status}: Can't create a new chat")
- await raise_for_status(response)
+ await raise_for_status(response, "Failed to create conversation")
data = await response.json()
conversationId = data.get('conversationId')
clientId = data.get('clientId')
conversationSignature = response.headers.get('X-Sydney-Encryptedconversationsignature')
if not conversationId or not clientId or not conversationSignature:
- raise Exception('Failed to create conversation.')
+ raise RuntimeError('Empty fields: Failed to create conversation')
return Conversation(conversationId, clientId, conversationSignature)
async def list_conversations(session: ClientSession) -> list:
@@ -76,8 +56,8 @@ async def list_conversations(session: ClientSession) -> list:
async with session.get(url) as response:
response = await response.json()
return response["chats"]
-
-async def delete_conversation(session: ClientSession, conversation: Conversation, proxy: str = None) -> bool:
+
+async def delete_conversation(session: ClientSession, conversation: Conversation, headers: dict) -> bool:
"""
Delete a conversation asynchronously.
@@ -98,7 +78,7 @@ async def delete_conversation(session: ClientSession, conversation: Conversation
"optionsSets": ["autosave"]
}
try:
- async with session.post(url, json=json, proxy=proxy) as response:
+ async with session.post(url, json=json, headers=headers) as response:
response = await response.json()
return response["result"]["value"] == "Success"
except:
diff --git a/g4f/Provider/bing/upload_image.py b/g4f/Provider/bing/upload_image.py
index 6d51aba0..c517e493 100644
--- a/g4f/Provider/bing/upload_image.py
+++ b/g4f/Provider/bing/upload_image.py
@@ -9,6 +9,7 @@ from aiohttp import ClientSession, FormData
from ...typing import ImageType, Tuple
from ...image import to_image, process_image, to_base64_jpg, ImageRequest, Image
+from ...requests import raise_for_status
IMAGE_CONFIG = {
"maxImagePixels": 360000,
@@ -20,7 +21,7 @@ async def upload_image(
session: ClientSession,
image_data: ImageType,
tone: str,
- proxy: str = None
+ headers: dict
) -> ImageRequest:
"""
Uploads an image to Bing's AI service and returns the image response.
@@ -43,11 +44,9 @@ async def upload_image(
img_binary_data = to_base64_jpg(image, IMAGE_CONFIG['imageCompressionRate'])
data = build_image_upload_payload(img_binary_data, tone)
- headers = prepare_headers(session)
- async with session.post("https://www.bing.com/images/kblob", data=data, headers=headers, proxy=proxy) as response:
- if response.status != 200:
- raise RuntimeError("Failed to upload image.")
+ async with session.post("https://www.bing.com/images/kblob", data=data, headers=prepare_headers(headers)) as response:
+ await raise_for_status(response, "Failed to upload image")
return parse_image_response(await response.json())
def calculate_new_dimensions(image: Image) -> Tuple[int, int]:
@@ -109,7 +108,7 @@ def build_knowledge_request(tone: str) -> dict:
}
}
-def prepare_headers(session: ClientSession) -> dict:
+def prepare_headers(headers: dict) -> dict:
"""
Prepares the headers for the image upload request.
@@ -120,7 +119,6 @@ def prepare_headers(session: ClientSession) -> dict:
Returns:
dict: The headers for the request.
"""
- headers = session.headers.copy()
headers["Referer"] = 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx'
headers["Origin"] = 'https://www.bing.com'
return headers
diff --git a/g4f/cookies.py b/g4f/cookies.py
index a38488c2..578be8db 100644
--- a/g4f/cookies.py
+++ b/g4f/cookies.py
@@ -40,7 +40,7 @@ def get_cookies(domain_name: str = '', raise_requirements_error: bool = True, si
"""
if domain_name in _cookies:
return _cookies[domain_name]
-
+
cookies = load_cookies_from_browsers(domain_name, raise_requirements_error, single_browser)
_cookies[domain_name] = cookies
return cookies
diff --git a/g4f/gui/client/html/index.html b/g4f/gui/client/html/index.html
index 85192d23..46a9c541 100644
--- a/g4f/gui/client/html/index.html
+++ b/g4f/gui/client/html/index.html
@@ -189,11 +189,13 @@
<label for="switch" title="Add the pages of the first 5 search results to the query."></label>
<span class="about">Web Access</span>
</div>
+ <!--
<div class="field">
<input type="checkbox" id="patch" />
<label for="patch" title="Enable create images with Bing."></label>
<span class="about">Image Generator</span>
</div>
+ -->
<div class="field">
<input type="checkbox" id="history" />
<label for="history" title="To improve the reaction time or if you have trouble with large conversations."></label>
diff --git a/g4f/gui/client/js/chat.v1.js b/g4f/gui/client/js/chat.v1.js
index 8dd17275..8774fbf1 100644
--- a/g4f/gui/client/js/chat.v1.js
+++ b/g4f/gui/client/js/chat.v1.js
@@ -27,6 +27,13 @@ messageInput.addEventListener("focus", () => {
document.documentElement.scrollTop = document.documentElement.scrollHeight;
});
+appStorage = window.localStorage || {
+ setItem: (key, value) => self[key] = value,
+ getItem: (key) => self[key],
+ removeItem: (key) => delete self[key],
+ length: 0
+}
+
const markdown_render = (content) => {
return markdown.render(content
.replaceAll(/<!--.+-->/gm, "")
@@ -67,10 +74,10 @@ const register_remove_message = async () => {
}
const delete_conversations = async () => {
- for (let i = 0; i < localStorage.length; i++){
- let key = localStorage.key(i);
+ for (let i = 0; i < appStorage.length; i++){
+ let key = appStorage.key(i);
if (key.startsWith("conversation:")) {
- localStorage.removeItem(key);
+ appStorage.removeItem(key);
}
}
hide_sidebar();
@@ -238,7 +245,7 @@ const ask_gpt = async () => {
jailbreak: jailbreak.options[jailbreak.selectedIndex].value,
web_search: document.getElementById(`switch`).checked,
provider: provider.options[provider.selectedIndex].value,
- patch_provider: document.getElementById('patch').checked,
+ patch_provider: document.getElementById('patch')?.checked,
messages: messages
});
const headers = {
@@ -261,6 +268,7 @@ const ask_gpt = async () => {
body: body
});
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
+ let buffer = ""
while (true) {
const { value, done } = await reader.read();
if (done) break;
@@ -268,7 +276,14 @@ const ask_gpt = async () => {
if (!line) {
continue;
}
- const message = JSON.parse(line);
+ let message;
+ try {
+ message = JSON.parse(buffer + line);
+ buffer = "";
+ } catch {
+ buffer += line
+ continue;
+ }
if (message.type == "content") {
text += message.content;
} else if (message.type == "provider") {
@@ -389,7 +404,7 @@ const hide_option = async (conversation_id) => {
};
const delete_conversation = async (conversation_id) => {
- localStorage.removeItem(`conversation:${conversation_id}`);
+ appStorage.removeItem(`conversation:${conversation_id}`);
const conversation = document.getElementById(`convo-${conversation_id}`);
conversation.remove();
@@ -491,13 +506,13 @@ const load_conversation = async (conversation_id, scroll = true) => {
async function get_conversation(conversation_id) {
let conversation = await JSON.parse(
- localStorage.getItem(`conversation:${conversation_id}`)
+ appStorage.getItem(`conversation:${conversation_id}`)
);
return conversation;
}
async function save_conversation(conversation_id, conversation) {
- localStorage.setItem(
+ appStorage.setItem(
`conversation:${conversation_id}`,
JSON.stringify(conversation)
);
@@ -515,7 +530,7 @@ async function add_conversation(conversation_id, content) {
title = content + '&nbsp;'.repeat(19 - content.length)
}
- if (localStorage.getItem(`conversation:${conversation_id}`) == null) {
+ if (appStorage.getItem(`conversation:${conversation_id}`) == null) {
await save_conversation(conversation_id, {
id: conversation_id,
title: title,
@@ -577,9 +592,9 @@ const add_message = async (conversation_id, role, content, provider) => {
const load_conversations = async () => {
let conversations = [];
- for (let i = 0; i < localStorage.length; i++) {
- if (localStorage.key(i).startsWith("conversation:")) {
- let conversation = localStorage.getItem(localStorage.key(i));
+ for (let i = 0; i < appStorage.length; i++) {
+ if (appStorage.key(i).startsWith("conversation:")) {
+ let conversation = appStorage.getItem(appStorage.key(i));
conversations.push(JSON.parse(conversation));
}
}
@@ -654,13 +669,16 @@ sidebar_button.addEventListener("click", (event) => {
const register_settings_localstorage = async () => {
for (id of ["switch", "model", "jailbreak", "patch", "provider", "history"]) {
element = document.getElementById(id);
+ if (!element) {
+ continue;
+ }
element.addEventListener('change', async (event) => {
switch (event.target.type) {
case "checkbox":
- localStorage.setItem(id, event.target.checked);
+ appStorage.setItem(id, event.target.checked);
break;
case "select-one":
- localStorage.setItem(id, event.target.selectedIndex);
+ appStorage.setItem(id, event.target.selectedIndex);
break;
default:
console.warn("Unresolved element type");
@@ -672,7 +690,9 @@ const register_settings_localstorage = async () => {
const load_settings_localstorage = async () => {
for (id of ["switch", "model", "jailbreak", "patch", "provider", "history"]) {
element = document.getElementById(id);
- value = localStorage.getItem(element.id);
+ if (!element || !(value = appStorage.getItem(element.id))) {
+ continue;
+ }
if (value) {
switch (element.type) {
case "checkbox":
@@ -712,12 +732,12 @@ const say_hello = async () => {
// Theme storage for recurring viewers
const storeTheme = function (theme) {
- localStorage.setItem("theme", theme);
+ appStorage.setItem("theme", theme);
};
// set theme when visitor returns
const setTheme = function () {
- const activeTheme = localStorage.getItem("theme");
+ const activeTheme = appStorage.getItem("theme");
colorThemes.forEach((themeOption) => {
if (themeOption.id === activeTheme) {
themeOption.checked = true;
@@ -751,8 +771,12 @@ function count_words(text) {
return text.trim().match(/[\w\u4E00-\u9FA5]+/gu)?.length || 0;
}
+function count_chars(text) {
+ return text.match(/[^\s\p{P}]/gu)?.length || 0;
+}
+
function count_words_and_tokens(text, model) {
- return `(${count_words(text)} words, ${count_tokens(model, text)} tokens)`;
+ return `(${count_words(text)} words, ${count_chars(text)} chars, ${count_tokens(model, text)} tokens)`;
}
let countFocus = messageInput;
diff --git a/g4f/gui/webview.py b/g4f/gui/webview.py
new file mode 100644
index 00000000..5a4263dc
--- /dev/null
+++ b/g4f/gui/webview.py
@@ -0,0 +1,24 @@
+import webview
+from functools import partial
+from platformdirs import user_config_dir
+
+from g4f.gui import run_gui
+from g4f.gui.run import gui_parser
+import g4f.version
+import g4f.debug
+
+def run_webview(host: str = "0.0.0.0", port: int = 8080, debug: bool = True):
+ webview.create_window(f"g4f - {g4f.version.utils.current_version}", f"http://{host}:{port}/")
+ if debug:
+ g4f.debug.logging = True
+ webview.start(
+ partial(run_gui, host, port),
+ private_mode=False,
+ storage_path=user_config_dir("g4f-webview"),
+ debug=debug
+ )
+
+if __name__ == "__main__":
+ parser = gui_parser()
+ args = parser.parse_args()
+ run_webview(args.host, args.port, args.debug) \ No newline at end of file
diff --git a/g4f/providers/create_images.py b/g4f/providers/create_images.py
index 29a2a041..29db9435 100644
--- a/g4f/providers/create_images.py
+++ b/g4f/providers/create_images.py
@@ -6,6 +6,7 @@ import asyncio
from .. import debug
from ..typing import CreateResult, Messages
from .types import BaseProvider, ProviderType
+from ..image import ImageResponse
system_message = """
You can generate images, pictures, photos or img with the DALL-E 3 image generator.
@@ -92,7 +93,9 @@ class CreateImagesProvider(BaseProvider):
messages.insert(0, {"role": "system", "content": self.system_message})
buffer = ""
for chunk in self.provider.create_completion(model, messages, stream, **kwargs):
- if isinstance(chunk, str) and buffer or "<" in chunk:
+ if isinstance(chunk, ImageResponse):
+ yield chunk
+ elif isinstance(chunk, str) and buffer or "<" in chunk:
buffer += chunk
if ">" in buffer:
match = re.search(r'<img data-prompt="(.*?)">', buffer)
diff --git a/g4f/providers/helper.py b/g4f/providers/helper.py
index df6767a4..5f3b4fb6 100644
--- a/g4f/providers/helper.py
+++ b/g4f/providers/helper.py
@@ -1,7 +1,6 @@
from __future__ import annotations
import random
-import secrets
import string
from ..typing import Messages
@@ -40,11 +39,14 @@ def get_random_string(length: int = 10) -> str:
for _ in range(length)
)
-def get_random_hex() -> str:
+def get_random_hex(length: int = 32) -> str:
"""
- Generate a random hexadecimal string of a fixed length.
+ Generate a random hexadecimal string with n length.
Returns:
- str: A random hexadecimal string of 32 characters (16 bytes).
+ str: A random hexadecimal string of n characters.
"""
- return secrets.token_hex(16).zfill(32) \ No newline at end of file
+ return ''.join(
+ random.choice("abcdef" + string.digits)
+ for _ in range(length)
+ ) \ No newline at end of file
diff --git a/g4f/requests/__init__.py b/g4f/requests/__init__.py
index d4ef9cec..f2946fc1 100644
--- a/g4f/requests/__init__.py
+++ b/g4f/requests/__init__.py
@@ -1,6 +1,5 @@
from __future__ import annotations
-from urllib.parse import urlparse
from typing import Union
from aiohttp import ClientResponse
from requests import Response as RequestsResponse
@@ -15,7 +14,7 @@ except ImportError:
has_curl_cffi = False
from ..webdriver import WebDriver, WebDriverSession
-from ..webdriver import user_config_dir, bypass_cloudflare, get_driver_cookies
+from ..webdriver import bypass_cloudflare, get_driver_cookies
from ..errors import MissingRequirementsError, RateLimitError, ResponseStatusError
from .defaults import DEFAULT_HEADERS
@@ -39,17 +38,16 @@ def get_args_from_browser(
Returns:
Session: A Session object configured with cookies and headers from the WebDriver.
"""
- user_data_dir = "" #user_config_dir(f"g4f-{urlparse(url).hostname}")
- with WebDriverSession(webdriver, user_data_dir, proxy=proxy, virtual_display=virtual_display) as driver:
+ with WebDriverSession(webdriver, "", proxy=proxy, virtual_display=virtual_display) as driver:
if do_bypass_cloudflare:
bypass_cloudflare(driver, url, timeout)
- user_agent = driver.execute_script("return navigator.userAgent")
headers = {
**DEFAULT_HEADERS,
'referer': url,
- 'user-agent': user_agent,
}
- if hasattr(driver, "requests"):
+ if not hasattr(driver, "requests"):
+ headers["user-agent"] = driver.execute_script("return navigator.userAgent")
+ else:
for request in driver.requests:
if request.url.startswith(url):
for key, value in request.headers.items():
@@ -83,22 +81,22 @@ def get_session_from_browser(url: str, webdriver: WebDriver = None, proxy: str =
impersonate="chrome"
)
-async def raise_for_status_async(response: Union[StreamResponse, ClientResponse]):
+async def raise_for_status_async(response: Union[StreamResponse, ClientResponse], message: str = None):
if response.status in (429, 402):
raise RateLimitError(f"Response {response.status}: Rate limit reached")
- text = await response.text() if not response.ok else None
- if response.status == 403 and "<title>Just a moment...</title>" in text:
+ message = await response.text() if not response.ok and message is None else message
+ if response.status == 403 and "<title>Just a moment...</title>" in message:
raise ResponseStatusError(f"Response {response.status}: Cloudflare detected")
elif not response.ok:
- raise ResponseStatusError(f"Response {response.status}: {text}")
+ raise ResponseStatusError(f"Response {response.status}: {message}")
-def raise_for_status(response: Union[StreamResponse, ClientResponse, Response, RequestsResponse]):
+def raise_for_status(response: Union[StreamResponse, ClientResponse, Response, RequestsResponse], message: str = None):
if isinstance(response, StreamSession) or isinstance(response, ClientResponse):
- return raise_for_status_async(response)
+ return raise_for_status_async(response, message)
if response.status_code in (429, 402):
raise RateLimitError(f"Response {response.status_code}: Rate limit reached")
elif response.status_code == 403 and "<title>Just a moment...</title>" in response.text:
raise ResponseStatusError(f"Response {response.status_code}: Cloudflare detected")
elif not response.ok:
- raise ResponseStatusError(f"Response {response.status_code}: {response.text}") \ No newline at end of file
+ raise ResponseStatusError(f"Response {response.status_code}: {response.text if message is None else message}") \ No newline at end of file
diff --git a/g4f/webdriver.py b/g4f/webdriver.py
index 21dbc469..2b7a7241 100644
--- a/g4f/webdriver.py
+++ b/g4f/webdriver.py
@@ -10,10 +10,9 @@ try:
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import NoSuchElementException
- from undetected_chromedriver import Chrome, ChromeOptions
has_requirements = True
except ImportError:
- from typing import Type as WebDriver, Callable as user_config_dir
+ from typing import Type as WebDriver
has_requirements = False
import time
@@ -38,9 +37,9 @@ try:
def __init__(self, *args, options=None, seleniumwire_options={}, **kwargs):
if options is None:
options = ChromeOptions()
- options.add_argument('--proxy-bypass-list=<-loopback>')
config = self._setup_backend(seleniumwire_options)
options.add_argument(f"--proxy-server={config['proxy']['httpProxy']}")
+ options.add_argument('--proxy-bypass-list=<-loopback>')
options.add_argument("--ignore-certificate-errors")
super().__init__(*args, options=options, **kwargs)
has_seleniumwire = True