From f1ef23285ae6e63d5fb28f5e271fec0e40ad65f2 Mon Sep 17 00:00:00 2001 From: Heiner Lohaus Date: Mon, 18 Nov 2024 15:41:45 +0100 Subject: Fix load model list i AirforceChat provider Add Microsoft Copilot provider Show image support in the model list of the gui --- g4f/Provider/Blackbox.py | 65 ++++++++++++-------------- g4f/Provider/Copilot.py | 87 +++++++++++++++++++++++++++++++++++ g4f/Provider/DeepInfraChat.py | 2 - g4f/Provider/__init__.py | 1 + g4f/Provider/airforce/AirforceChat.py | 10 ++-- g4f/Provider/needs_auth/OpenaiChat.py | 16 ++++++- 6 files changed, 139 insertions(+), 42 deletions(-) create mode 100644 g4f/Provider/Copilot.py (limited to 'g4f/Provider') diff --git a/g4f/Provider/Blackbox.py b/g4f/Provider/Blackbox.py index 75abb183..97466c04 100644 --- a/g4f/Provider/Blackbox.py +++ b/g4f/Provider/Blackbox.py @@ -10,6 +10,7 @@ import aiohttp from ..typing import AsyncResult, Messages, ImageType from .base_provider import AsyncGeneratorProvider, ProviderModelMixin from ..image import ImageResponse, to_data_uri +from .helper import get_random_string class Blackbox(AsyncGeneratorProvider, ProviderModelMixin): label = "Blackbox AI" @@ -22,11 +23,13 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin): _last_validated_value = None default_model = 'blackboxai' + default_vision_model = default_model default_image_model = 'generate_image' image_models = [default_image_model, 'repomap'] text_models = [default_model, 'gpt-4o', 'gemini-pro', 'claude-sonnet-3.5', 'blackboxai-pro'] + vision_models = [default_model, 'gpt-4o', 'gemini-pro', 'blackboxai-pro'] agentMode = { - 'Image Generation': {'mode': True, 'id': "ImageGenerationLV45LJp", 'name': "Image Generation"}, + default_image_model: {'mode': True, 'id': "ImageGenerationLV45LJp", 'name': "Image Generation"}, } trendingAgentMode = { "gemini-1.5-flash": {'mode': True, 'id': 'Gemini'}, @@ -111,11 +114,6 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin): return cls._last_validated_value - @staticmethod - def generate_id(length=7): - characters = string.ascii_letters + string.digits - return ''.join(random.choice(characters) for _ in range(length)) - @classmethod def add_prefix_to_messages(cls, messages: Messages, model: str) -> Messages: prefix = cls.model_prefixes.get(model, "") @@ -143,12 +141,12 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin): **kwargs ) -> AsyncResult: model = cls.get_model(model) - message_id = cls.generate_id() - messages_with_prefix = cls.add_prefix_to_messages(messages, model) + message_id = get_random_string(7) + messages = cls.add_prefix_to_messages(messages, model) validated_value = await cls.fetch_validated() if image is not None: - messages_with_prefix[-1]['data'] = { + messages[-1]['data'] = { 'fileText': '', 'imageBase64': to_data_uri(image), 'title': image_name @@ -171,9 +169,9 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin): 'sec-fetch-site': 'same-origin', 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36' } - + data = { - "messages": messages_with_prefix, + "messages": messages, "id": message_id, "previewToken": None, "userId": None, @@ -200,27 +198,24 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin): async with ClientSession(headers=headers) as session: async with session.post(cls.api_endpoint, json=data, proxy=proxy) as response: response.raise_for_status() - response_text = await response.text() - - if model in cls.image_models: - image_matches = re.findall(r'!\[.*?\]\((https?://[^\)]+)\)', response_text) - if image_matches: - image_url = image_matches[0] - image_response = ImageResponse(images=[image_url], alt="Generated Image") - yield image_response - return - - response_text = re.sub(r'Generated by BLACKBOX.AI, try unlimited chat https://www.blackbox.ai', '', response_text, flags=re.DOTALL) - - json_match = re.search(r'\$~~~\$(.*?)\$~~~\$', response_text, re.DOTALL) - if json_match: - search_results = json.loads(json_match.group(1)) - answer = response_text.split('$~~~$')[-1].strip() - - formatted_response = f"{answer}\n\n**Source:**" - for i, result in enumerate(search_results, 1): - formatted_response += f"\n{i}. {result['title']}: {result['link']}" - - yield formatted_response - else: - yield response_text.strip() + async for chunk in response.content.iter_any(): + text_chunk = chunk.decode(errors="ignore") + if model in cls.image_models: + image_matches = re.findall(r'!\[.*?\]\((https?://[^\)]+)\)', text_chunk) + if image_matches: + image_url = image_matches[0] + image_response = ImageResponse(images=[image_url]) + yield image_response + continue + + text_chunk = re.sub(r'Generated by BLACKBOX.AI, try unlimited chat https://www.blackbox.ai', '', text_chunk, flags=re.DOTALL) + json_match = re.search(r'\$~~~\$(.*?)\$~~~\$', text_chunk, re.DOTALL) + if json_match: + search_results = json.loads(json_match.group(1)) + answer = text_chunk.split('$~~~$')[-1].strip() + formatted_response = f"{answer}\n\n**Source:**" + for i, result in enumerate(search_results, 1): + formatted_response += f"\n{i}. {result['title']}: {result['link']}" + yield formatted_response + else: + yield text_chunk.strip() diff --git a/g4f/Provider/Copilot.py b/g4f/Provider/Copilot.py new file mode 100644 index 00000000..ddfed4a8 --- /dev/null +++ b/g4f/Provider/Copilot.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import json +from http.cookiejar import CookieJar +try: + from curl_cffi.requests import Session, CurlWsFlag + has_curl_cffi = True +except ImportError: + has_curl_cffi = False + +from .base_provider import AbstractProvider, BaseConversation +from .helper import format_prompt +from ..typing import CreateResult, Messages +from ..errors import MissingRequirementsError +from ..requests.raise_for_status import raise_for_status +from .. import debug + +class Conversation(BaseConversation): + conversation_id: str + cookie_jar: CookieJar + + def __init__(self, conversation_id: str, cookie_jar: CookieJar): + self.conversation_id = conversation_id + self.cookie_jar = cookie_jar + +class Copilot(AbstractProvider): + label = "Microsoft Copilot" + url = "https://copilot.microsoft.com" + working = True + supports_stream = True + + websocket_url = "wss://copilot.microsoft.com/c/api/chat?api-version=2" + conversation_url = f"{url}/c/api/conversations" + + @classmethod + def create_completion( + cls, + model: str, + messages: Messages, + stream: bool = False, + proxy: str = None, + timeout: int = 900, + conversation: Conversation = None, + return_conversation: bool = False, + **kwargs + ) -> CreateResult: + if not has_curl_cffi: + raise MissingRequirementsError('Install or update "curl_cffi" package | pip install -U nodriver') + + cookies = conversation.cookie_jar if conversation is not None else None + with Session(timeout=timeout, proxy=proxy, impersonate="chrome", cookies=cookies) as session: + response = session.get(f"{cls.url}/") + raise_for_status(response) + if conversation is None: + response = session.post(cls.conversation_url) + raise_for_status(response) + conversation_id = response.json().get("id") + if return_conversation: + yield Conversation(conversation_id, session.cookies.jar) + prompt = format_prompt(messages) + if debug.logging: + print(f"Copilot: Created conversation: {conversation_id}") + else: + conversation_id = conversation.conversation_id + prompt = messages[-1]["content"] + if debug.logging: + print(f"Copilot: Use conversation: {conversation_id}") + + wss = session.ws_connect(cls.websocket_url) + wss.send(json.dumps({ + "event": "send", + "conversationId": conversation_id, + "content": [{ + "type": "text", + "text": prompt, + }], + "mode": "chat" + }).encode(), CurlWsFlag.TEXT) + while True: + try: + msg = json.loads(wss.recv()[0]) + except: + break + if msg.get("event") == "appendText": + yield msg.get("text") + elif msg.get("event") in ["done", "partCompleted"]: + break \ No newline at end of file diff --git a/g4f/Provider/DeepInfraChat.py b/g4f/Provider/DeepInfraChat.py index 5c668599..d8cb072a 100644 --- a/g4f/Provider/DeepInfraChat.py +++ b/g4f/Provider/DeepInfraChat.py @@ -4,10 +4,8 @@ from aiohttp import ClientSession import json from ..typing import AsyncResult, Messages, ImageType -from ..image import to_data_uri from .base_provider import AsyncGeneratorProvider, ProviderModelMixin - class DeepInfraChat(AsyncGeneratorProvider, ProviderModelMixin): url = "https://deepinfra.com/chat" api_endpoint = "https://api.deepinfra.com/v1/openai/chat/completions" diff --git a/g4f/Provider/__init__.py b/g4f/Provider/__init__.py index 8a162baf..faf9979e 100644 --- a/g4f/Provider/__init__.py +++ b/g4f/Provider/__init__.py @@ -19,6 +19,7 @@ from .Blackbox import Blackbox from .ChatGpt import ChatGpt from .ChatGptEs import ChatGptEs from .Cloudflare import Cloudflare +from .Copilot import Copilot from .DarkAI import DarkAI from .DDG import DDG from .DeepInfraChat import DeepInfraChat diff --git a/g4f/Provider/airforce/AirforceChat.py b/g4f/Provider/airforce/AirforceChat.py index cec911a3..e94dd0a8 100644 --- a/g4f/Provider/airforce/AirforceChat.py +++ b/g4f/Provider/airforce/AirforceChat.py @@ -50,11 +50,13 @@ class AirforceChat(AsyncGeneratorProvider, ProviderModelMixin): supports_message_history = True default_model = 'llama-3.1-70b-chat' - response = requests.get('https://api.airforce/models') - data = response.json() - text_models = [model['id'] for model in data['data']] - models = [*text_models] + @classmethod + def get_models(cls) -> list: + if not cls.models: + response = requests.get('https://api.airforce/models') + data = response.json() + cls.models = [model['id'] for model in data['data']] model_aliases = { # openchat diff --git a/g4f/Provider/needs_auth/OpenaiChat.py b/g4f/Provider/needs_auth/OpenaiChat.py index 43444699..13e15f1d 100644 --- a/g4f/Provider/needs_auth/OpenaiChat.py +++ b/g4f/Provider/needs_auth/OpenaiChat.py @@ -6,6 +6,7 @@ import uuid import json import base64 import time +import requests from aiohttp import ClientWebSocketResponse from copy import copy @@ -62,13 +63,26 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): supports_system_message = True default_model = "auto" default_vision_model = "gpt-4o" - models = ["auto", "gpt-4o-mini", "gpt-4o", "gpt-4", "gpt-4-gizmo"] + fallback_models = ["auto", "gpt-4", "gpt-4o", "gpt-4o-mini", "gpt-4o-canmore", "o1-preview", "o1-mini"] + vision_models = fallback_models _api_key: str = None _headers: dict = None _cookies: Cookies = None _expires: int = None + @classmethod + def get_models(cls): + if not cls.models: + try: + response = requests.get(f"{cls.url}/backend-anon/models") + response.raise_for_status() + data = response.json() + cls.models = [model.get("slug") for model in data.get("models")] + except Exception: + cls.models = cls.fallback_models + return cls.models + @classmethod async def create( cls, -- cgit v1.2.3 From 8bc456f3b60743fb9a728bf3831fbb9a0b354a2f Mon Sep 17 00:00:00 2001 From: Heiner Lohaus Date: Mon, 18 Nov 2024 17:53:15 +0100 Subject: Add account support in Copilot provider --- g4f/Provider/Copilot.py | 78 +++++++++++++++++++++++++++++-- g4f/Provider/needs_auth/CopilotAccount.py | 8 ++++ g4f/Provider/needs_auth/__init__.py | 4 +- 3 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 g4f/Provider/needs_auth/CopilotAccount.py (limited to 'g4f/Provider') diff --git a/g4f/Provider/Copilot.py b/g4f/Provider/Copilot.py index ddfed4a8..0d8b612b 100644 --- a/g4f/Provider/Copilot.py +++ b/g4f/Provider/Copilot.py @@ -1,12 +1,25 @@ from __future__ import annotations import json +import asyncio from http.cookiejar import CookieJar +from urllib.parse import quote + try: from curl_cffi.requests import Session, CurlWsFlag has_curl_cffi = True except ImportError: has_curl_cffi = False +try: + import nodriver + has_nodriver = True +except ImportError: + has_nodriver = False +try: + from platformdirs import user_config_dir + has_platformdirs = True +except ImportError: + has_platformdirs = False from .base_provider import AbstractProvider, BaseConversation from .helper import format_prompt @@ -18,10 +31,12 @@ from .. import debug class Conversation(BaseConversation): conversation_id: str cookie_jar: CookieJar + access_token: str - def __init__(self, conversation_id: str, cookie_jar: CookieJar): + def __init__(self, conversation_id: str, cookie_jar: CookieJar, access_token: str = None): self.conversation_id = conversation_id self.cookie_jar = cookie_jar + self.access_token = access_token class Copilot(AbstractProvider): label = "Microsoft Copilot" @@ -45,10 +60,26 @@ class Copilot(AbstractProvider): **kwargs ) -> CreateResult: if not has_curl_cffi: - raise MissingRequirementsError('Install or update "curl_cffi" package | pip install -U nodriver') + raise MissingRequirementsError('Install or update "curl_cffi" package | pip install -U curl_cffi') + websocket_url = cls.websocket_url + access_token = None cookies = conversation.cookie_jar if conversation is not None else None - with Session(timeout=timeout, proxy=proxy, impersonate="chrome", cookies=cookies) as session: + if cls.needs_auth: + if conversation is None or conversation.access_token is None: + access_token, cookies = asyncio.run(cls.get_access_token_and_cookies(proxy)) + else: + access_token = conversation.access_token + websocket_url = f"{websocket_url}&acessToken={quote(access_token)}" + headers = {"Authorization": f"Bearer {access_token}"} + + with Session( + timeout=timeout, + proxy=proxy, + impersonate="chrome", + headers=headers, + cookies=cookies + ) as session: response = session.get(f"{cls.url}/") raise_for_status(response) if conversation is None: @@ -56,7 +87,7 @@ class Copilot(AbstractProvider): raise_for_status(response) conversation_id = response.json().get("id") if return_conversation: - yield Conversation(conversation_id, session.cookies.jar) + yield Conversation(conversation_id, session.cookies.jar, access_token) prompt = format_prompt(messages) if debug.logging: print(f"Copilot: Created conversation: {conversation_id}") @@ -84,4 +115,41 @@ class Copilot(AbstractProvider): if msg.get("event") == "appendText": yield msg.get("text") elif msg.get("event") in ["done", "partCompleted"]: - break \ No newline at end of file + break + + @classmethod + async def get_access_token_and_cookies(cls, proxy: str = None): + if not has_nodriver: + raise MissingRequirementsError('Install "nodriver" package | pip install -U nodriver') + if has_platformdirs: + user_data_dir = user_config_dir("g4f-nodriver") + else: + user_data_dir = None + if debug.logging: + print(f"Copilot: Open nodriver with user_dir: {user_data_dir}") + browser = await nodriver.start( + user_data_dir=user_data_dir, + browser_args=None if proxy is None else [f"--proxy-server={proxy}"], + ) + page = await browser.get(cls.url) + while True: + access_token = await page.evaluate(""" + (() => { + for (var i = 0; i < localStorage.length; i++) { + try { + item = JSON.parse(localStorage.getItem(localStorage.key(i))); + if (item.credentialType == "AccessToken") { + return item.secret; + } + } catch(e) {} + } + })() + """) + if access_token: + break + asyncio.sleep(1) + cookies = {} + for c in await page.send(nodriver.cdp.network.get_cookies([cls.url])): + cookies[c.name] = c.value + await page.close() + return access_token, cookies \ No newline at end of file diff --git a/g4f/Provider/needs_auth/CopilotAccount.py b/g4f/Provider/needs_auth/CopilotAccount.py new file mode 100644 index 00000000..fa43867e --- /dev/null +++ b/g4f/Provider/needs_auth/CopilotAccount.py @@ -0,0 +1,8 @@ +from __future__ import annotations + +from ..Copilot import Copilot + +class CopilotAccount(Copilot): + needs_auth = True + parent = "Copilot" + default_model = "" \ No newline at end of file diff --git a/g4f/Provider/needs_auth/__init__.py b/g4f/Provider/needs_auth/__init__.py index ace53876..0f430ab5 100644 --- a/g4f/Provider/needs_auth/__init__.py +++ b/g4f/Provider/needs_auth/__init__.py @@ -1,9 +1,7 @@ from .gigachat import * -#from .MetaAIAccount import MetaAIAccount -#from .OpenaiAccount import OpenaiAccount - from .BingCreateImages import BingCreateImages +from .CopilotAccount import CopilotAccount from .DeepInfra import DeepInfra from .DeepInfraImage import DeepInfraImage from .Gemini import Gemini -- cgit v1.2.3 From 2fe43166cc4ab4d05261c3fe9690a49284c90570 Mon Sep 17 00:00:00 2001 From: Heiner Lohaus Date: Mon, 18 Nov 2024 18:23:16 +0100 Subject: Update unittests --- g4f/Provider/Copilot.py | 1 + 1 file changed, 1 insertion(+) (limited to 'g4f/Provider') diff --git a/g4f/Provider/Copilot.py b/g4f/Provider/Copilot.py index 0d8b612b..f10202bf 100644 --- a/g4f/Provider/Copilot.py +++ b/g4f/Provider/Copilot.py @@ -64,6 +64,7 @@ class Copilot(AbstractProvider): websocket_url = cls.websocket_url access_token = None + headers = None cookies = conversation.cookie_jar if conversation is not None else None if cls.needs_auth: if conversation is None or conversation.access_token is None: -- cgit v1.2.3