diff options
-rw-r--r-- | README.md | 10 | ||||
-rw-r--r-- | docker-compose.yml | 2 | ||||
-rw-r--r-- | docker/Dockerfile | 34 | ||||
-rwxr-xr-x | docker/supervisor-gui.conf | 12 | ||||
-rwxr-xr-x | docker/supervisor.conf | 13 | ||||
-rw-r--r-- | g4f/Provider/retry_provider.py | 7 | ||||
-rw-r--r-- | g4f/__init__.py | 81 | ||||
-rw-r--r-- | g4f/cli.py | 2 | ||||
-rw-r--r-- | g4f/debug.py | 39 | ||||
-rw-r--r-- | g4f/errors.py | 26 | ||||
-rw-r--r-- | g4f/gui/client/css/style.css | 18 | ||||
-rw-r--r-- | g4f/gui/client/html/index.html | 6 | ||||
-rw-r--r-- | g4f/gui/client/js/chat.v1.js | 239 | ||||
-rw-r--r-- | g4f/gui/run.py | 2 | ||||
-rw-r--r-- | g4f/gui/server/backend.py | 70 |
15 files changed, 301 insertions, 260 deletions
@@ -23,7 +23,7 @@ docker pull hlohaus789/g4f ## 📚 Table of Contents -- [🆕 What's New](#-what-s-new) +- [🆕 What's New](#-whats-new) - [📚 Table of Contents](#-table-of-contents) - [🛠️ Getting Started](#-getting-started) + [Docker container](#docker-container) @@ -254,7 +254,7 @@ for message in response: ##### Using Browser -Some providers using a a browser to bypass the bot protection. They using the selenium webdriver to control the browser. The browser settings and the login data are saved in a custom directory. If the headless mode is enabled, the browser windows are loaded invisibly. For performance reasons, it is recommended to reuse the browser instances and close them yourself at the end: +Some providers using a browser to bypass the bot protection. They using the selenium webdriver to control the browser. The browser settings and the login data are saved in a custom directory. If the headless mode is enabled, the browser windows are loaded invisibly. For performance reasons, it is recommended to reuse the browser instances and close them yourself at the end: ```python import g4f @@ -336,6 +336,12 @@ response = g4f.ChatCompletion.create( print(f"Result:", response) ``` +You can also set a proxy globally via an environment variable: + +```sh +export G4F_PROXY="http://host:port" +``` + ### Interference openai-proxy API (Use with openai python package) #### Run interference API from PyPi package diff --git a/docker-compose.yml b/docker-compose.yml index 66ba8f34..1b99ba97 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,6 @@ services: volumes: - .:/app ports: - - '8080:80' + - '8080:8080' - '1337:1337' - '7900:7900'
\ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index 66f7fd3c..d160fcac 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,13 +1,27 @@ FROM selenium/node-chrome ENV SE_SCREEN_WIDTH 1850 -ENV G4F_LOGIN_URL http://localhost:7900/?autoconnect=1&resize=scale&password=secret ENV PYTHONUNBUFFERED 1 +ENV G4F_DIR /app +ENV G4F_LOGIN_URL http://localhost:7900/?autoconnect=1&resize=scale&password=secret ARG G4F_VERSION ENV G4F_VERSION ${G4F_VERSION} +ARG G4F_USER +ENV G4F_USER ${G4F_USER:-g4f} +ARG G4F_USER_ID +ENV G4F_USER_ID ${G4F_USER_ID:-1000} +ARG G4F_NO_GUI +ENV G4F_NO_GUI ${G4F_NO_GUI} +ENV HOME /home/$G4F_USER USER root +# If docker compose, install git +RUN if [ "$G4F_VERSION" = "" ] ; then \ + apt-get -qqy update && \ + apt-get -qqy install git \ + ; fi + # Python packages RUN apt-get -qqy update \ && apt-get -qqy install \ @@ -22,24 +36,32 @@ RUN rm -rf /var/lib/apt/lists/* /var/cache/apt/* \ # Update entrypoint COPY docker/supervisor.conf /etc/supervisor/conf.d/selenium.conf +COPY docker/supervisor-gui.conf /etc/supervisor/conf.d/gui.conf + +# If no gui +RUN if [ "$G4F_NO_GUI" ] ; then \ + rm /etc/supervisor/conf.d/gui.conf \ + ; fi # Change background image COPY docker/background.png /usr/share/images/fluxbox/ubuntu-light.png # Switch user -USER 1200 +RUN groupadd -g $G4F_USER_ID $G4F_USER +RUN useradd -rm -G sudo -u $G4F_USER_ID -g $G4F_USER_ID $G4F_USER +USER $G4F_USER_ID # Set the working directory in the container. -WORKDIR /app +WORKDIR $G4F_DIR # Copy the project's requirements file into the container. -COPY requirements.txt /app/ +COPY requirements.txt $G4F_DIR # Upgrade pip for the latest features and install the project's Python dependencies. RUN pip install --upgrade pip && pip install -r requirements.txt # Copy the entire package into the container. -COPY g4f /app/g4f +ADD --chown=$G4F_USER g4f $G4F_DIR/ # Expose ports -EXPOSE 80 1337
\ No newline at end of file +EXPOSE 8080 1337
\ No newline at end of file diff --git a/docker/supervisor-gui.conf b/docker/supervisor-gui.conf new file mode 100755 index 00000000..44273c67 --- /dev/null +++ b/docker/supervisor-gui.conf @@ -0,0 +1,12 @@ +[program:g4f-gui] +priority=15 +command=python -m g4f.cli gui +directory=/app +stopasgroup=true +autostart=true +autorestart=true + +;Logs (all Hub activity redirected to stdout so it can be seen through "docker logs" +redirect_stderr=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0
\ No newline at end of file diff --git a/docker/supervisor.conf b/docker/supervisor.conf index e76f9a6c..f0f01fd1 100755 --- a/docker/supervisor.conf +++ b/docker/supervisor.conf @@ -60,17 +60,4 @@ autorestart=true ;Logs (all Hub activity redirected to stdout so it can be seen through "docker logs" redirect_stderr=true stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 - -[program:g4f-gui] -priority=15 -command=python -m g4f.cli gui -directory=/app -stopasgroup=true -autostart=true -autorestart=true - -;Logs (all Hub activity redirected to stdout so it can be seen through "docker logs" -redirect_stderr=true -stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0
\ No newline at end of file diff --git a/g4f/Provider/retry_provider.py b/g4f/Provider/retry_provider.py index 6fdefe0f..e49b6da6 100644 --- a/g4f/Provider/retry_provider.py +++ b/g4f/Provider/retry_provider.py @@ -6,6 +6,7 @@ from typing import List, Type, Dict from ..typing import CreateResult, Messages from .base_provider import BaseProvider, AsyncProvider from .. import debug +from ..errors import RetryProviderError, RetryNoProviderError class RetryProvider(AsyncProvider): @@ -84,8 +85,8 @@ class RetryProvider(AsyncProvider): def raise_exceptions(self) -> None: if self.exceptions: - raise RuntimeError("\n".join(["RetryProvider failed:"] + [ - f"{p}: {self.exceptions[p].__class__.__name__}: {self.exceptions[p]}" for p in self.exceptions + raise RetryProviderError("RetryProvider failed:\n" + "\n".join([ + f"{p}: {exception.__class__.__name__}: {exception}" for p, exception in self.exceptions.items() ])) - raise RuntimeError("RetryProvider: No provider found")
\ No newline at end of file + raise RetryNoProviderError("No provider found")
\ No newline at end of file diff --git a/g4f/__init__.py b/g4f/__init__.py index 92bce194..3b0fcad0 100644 --- a/g4f/__init__.py +++ b/g4f/__init__.py @@ -1,61 +1,35 @@ from __future__ import annotations import os -from requests import get -from importlib.metadata import version as get_package_version, PackageNotFoundError -from subprocess import check_output, CalledProcessError, PIPE +from .errors import * from .models import Model, ModelUtils, _all_models -from .Provider import BaseProvider, AsyncGeneratorProvider, RetryProvider +from .Provider import BaseProvider, AsyncGeneratorProvider, RetryProvider, ProviderUtils from .typing import Messages, CreateResult, AsyncResult, Union, List from . import debug -def get_version() -> str: - # Read from package manager - try: - return get_package_version("g4f") - except PackageNotFoundError: - pass - # Read from docker environment - current_version = os.environ.get("G4F_VERSION") - if current_version: - return current_version - # Read from git repository - try: - command = ["git", "describe", "--tags", "--abbrev=0"] - return check_output(command, text=True, stderr=PIPE).strip() - except CalledProcessError: - pass - -def get_lastet_version() -> str: - response = get("https://pypi.org/pypi/g4f/json").json() - return response["info"]["version"] - -def check_pypi_version() -> None: - try: - version = get_version() - latest_version = get_lastet_version() - except Exception as e: - print(f'Failed to check g4f pypi version: {e}') - if version != latest_version: - print(f'New pypi version: {latest_version} (current: {version}) | pip install -U g4f') - def get_model_and_provider(model : Union[Model, str], - provider : Union[type[BaseProvider], None], + provider : Union[type[BaseProvider], str, None], stream : bool, ignored : List[str] = None, ignore_working: bool = False, ignore_stream: bool = False) -> tuple[Model, type[BaseProvider]]: if debug.version_check: - check_pypi_version() debug.version_check = False - + debug.check_pypi_version() + + if isinstance(provider, str): + if provider in ProviderUtils.convert: + provider = ProviderUtils.convert[provider] + else: + raise ProviderNotFoundError(f'Provider not found: {provider}') + if isinstance(model, str): if model in ModelUtils.convert: model = ModelUtils.convert[model] else: - raise ValueError(f'The model: {model} does not exist') - + raise ModelNotFoundError(f'The model: {model} does not exist') + if not provider: provider = model.best_provider @@ -63,14 +37,14 @@ def get_model_and_provider(model : Union[Model, str], provider.providers = [p for p in provider.providers if p.__name__ not in ignored] if not provider: - raise RuntimeError(f'No provider found for model: {model}') - + raise ProviderNotFoundError(f'No provider found for model: {model}') + if not provider.working and not ignore_working: - raise RuntimeError(f'{provider.__name__} is not working') - + raise ProviderNotWorkingError(f'{provider.__name__} is not working') + if not ignore_stream and not provider.supports_stream and stream: - raise ValueError(f'{provider.__name__} does not support "stream" argument') - + raise StreamNotSupportedError(f'{provider.__name__} does not support "stream" argument') + if debug.logging: print(f'Using {provider.__name__} provider') @@ -80,7 +54,7 @@ class ChatCompletion: @staticmethod def create(model : Union[Model, str], messages : Messages, - provider : Union[type[BaseProvider], None] = None, + provider : Union[type[BaseProvider], str, None] = None, stream : bool = False, auth : Union[str, None] = None, ignored : List[str] = None, @@ -91,11 +65,15 @@ class ChatCompletion: model, provider = get_model_and_provider(model, provider, stream, ignored, ignore_working, ignore_stream_and_auth) if not ignore_stream_and_auth and provider.needs_auth and not auth: - raise ValueError( - f'{provider.__name__} requires authentication (use auth=\'cookie or token or jwt ...\' param)') + raise AuthenticationRequiredError(f'{provider.__name__} requires authentication (use auth=\'cookie or token or jwt ...\' param)') if auth: kwargs['auth'] = auth + + if "proxy" not in kwargs: + proxy = os.environ.get("G4F_PROXY") + if proxy: + kwargs['proxy'] = proxy result = provider.create_completion(model.name, messages, stream, **kwargs) return result if stream else ''.join(result) @@ -103,7 +81,7 @@ class ChatCompletion: @staticmethod async def create_async(model : Union[Model, str], messages : Messages, - provider : Union[type[BaseProvider], None] = None, + provider : Union[type[BaseProvider], str, None] = None, stream : bool = False, ignored : List[str] = None, **kwargs) -> Union[AsyncResult, str]: @@ -112,7 +90,7 @@ class ChatCompletion: if stream: if isinstance(provider, type) and issubclass(provider, AsyncGeneratorProvider): return await provider.create_async_generator(model.name, messages, **kwargs) - raise ValueError(f'{provider.__name__} does not support "stream" argument') + raise StreamNotSupportedError(f'{provider.__name__} does not support "stream" argument in "create_async"') return await provider.create_async(model.name, messages, **kwargs) @@ -132,9 +110,8 @@ class Completion: 'text-davinci-002', 'text-davinci-003' ] - if model not in allowed_models: - raise Exception(f'ValueError: Can\'t use {model} with Completion.create()') + raise ModelNotAllowed(f'Can\'t use {model} with Completion.create()') model, provider = get_model_and_provider(model, provider, stream, ignored) @@ -15,7 +15,7 @@ def main(): parser = argparse.ArgumentParser(description="Run gpt4free") subparsers = parser.add_subparsers(dest="mode", help="Mode to run the g4f in.") api_parser=subparsers.add_parser("api") - api_parser.add_argument("--bind", default="127.0.0.1:1337", help="The bind string.") + api_parser.add_argument("--bind", default="0.0.0.0:1337", help="The bind string.") api_parser.add_argument("--debug", type=bool, default=False, help="Enable verbose logging") api_parser.add_argument("--ignored-providers", nargs="+", choices=[provider.name for provider in IgnoredProviders], default=[], help="List of providers to ignore when processing request.") diff --git a/g4f/debug.py b/g4f/debug.py index 984d973a..1ee1506f 100644 --- a/g4f/debug.py +++ b/g4f/debug.py @@ -1,2 +1,39 @@ +from os import environ +from requests import get +from importlib.metadata import version as get_package_version, PackageNotFoundError +from subprocess import check_output, CalledProcessError, PIPE +from .errors import VersionNotFoundError + logging = False -version_check = True
\ No newline at end of file +version_check = True + +def get_version() -> str: + # Read from package manager + try: + return get_package_version("g4f") + except PackageNotFoundError: + pass + # Read from docker environment + current_version = environ.get("G4F_VERSION") + if current_version: + return current_version + # Read from git repository + try: + command = ["git", "describe", "--tags", "--abbrev=0"] + return check_output(command, text=True, stderr=PIPE).strip() + except CalledProcessError: + pass + raise VersionNotFoundError("Version not found") + +def get_lastet_version() -> str: + response = get("https://pypi.org/pypi/g4f/json").json() + return response["info"]["version"] + +def check_pypi_version() -> None: + try: + version = get_version() + latest_version = get_lastet_version() + if version != latest_version: + print(f'New pypi version: {latest_version} (current: {version}) | pip install -U g4f') + except Exception as e: + print(f'Failed to check g4f pypi version: {e}')
\ No newline at end of file diff --git a/g4f/errors.py b/g4f/errors.py new file mode 100644 index 00000000..b554aead --- /dev/null +++ b/g4f/errors.py @@ -0,0 +1,26 @@ +class ProviderNotFoundError(Exception): + pass + +class ProviderNotWorkingError(Exception): + pass + +class StreamNotSupportedError(Exception): + pass + +class AuthenticationRequiredError(Exception): + pass + +class ModelNotFoundError(Exception): + pass + +class ModelNotAllowed(Exception): + pass + +class RetryProviderError(Exception): + pass + +class RetryNoProviderError(Exception): + pass + +class VersionNotFoundError(Exception): + pass
\ No newline at end of file diff --git a/g4f/gui/client/css/style.css b/g4f/gui/client/css/style.css index 254a4b15..b6d73650 100644 --- a/g4f/gui/client/css/style.css +++ b/g4f/gui/client/css/style.css @@ -301,6 +301,9 @@ body { font-size: 15px; line-height: 1.3; } +.message .content pre { + white-space: pre-wrap; +} .message .user i { position: absolute; @@ -338,19 +341,15 @@ body { font-size: 14px; } - -.stop_generating { +.stop_generating, .regenerate { position: absolute; - bottom: 118px; - /* left: 10px; - bottom: 125px; - right: 8px; */ + bottom: 158px; left: 50%; transform: translateX(-50%); z-index: 1000000; } -.stop_generating button { +.stop_generating button, .regenerate button{ backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); background-color: var(--blur-bg); @@ -380,11 +379,8 @@ body { } } -.stop_generating-hiding button { +.stop_generating-hidden #cancelButton, .regenerate-hidden #regenerateButton { animation: hide_popup 0.4s; -} - -.stop_generating-hidden button { display: none; } diff --git a/g4f/gui/client/html/index.html b/g4f/gui/client/html/index.html index 53c028d7..da7aeefb 100644 --- a/g4f/gui/client/html/index.html +++ b/g4f/gui/client/html/index.html @@ -101,6 +101,12 @@ <i class="fa-regular fa-stop"></i> </button> </div> + <div class="regenerate regenerate-hidden"> + <button id="regenerateButton"> + <span>Regenerate</span> + <i class="fa-solid fa-rotate"></i> + </button> + </div> <div class="box" id="messages"> </div> <div class="user-input"> diff --git a/g4f/gui/client/js/chat.v1.js b/g4f/gui/client/js/chat.v1.js index 2a1bdd73..644ff77a 100644 --- a/g4f/gui/client/js/chat.v1.js +++ b/g4f/gui/client/js/chat.v1.js @@ -5,15 +5,12 @@ const message_input = document.getElementById(`message-input`); const box_conversations = document.querySelector(`.top`); const spinner = box_conversations.querySelector(".spinner"); const stop_generating = document.querySelector(`.stop_generating`); +const regenerate = document.querySelector(`.regenerate`); const send_button = document.querySelector(`#send-button`); let prompt_lock = false; hljs.addPlugin(new CopyButtonPlugin()); -const format = (text) => { - return text.replace(/(?:\r\n|\r|\n)/g, "<br>"); -}; - message_input.addEventListener("blur", () => { window.scrollTo(0, 0); }); @@ -22,6 +19,10 @@ message_input.addEventListener("focus", () => { document.documentElement.scrollTop = document.documentElement.scrollHeight; }); +const markdown_render = (content) => { + return markdown.render(content).replace("<a href=", '<a target="_blank" href=').replace('<code>', '<code class="language-plaintext">') +} + const delete_conversations = async () => { localStorage.clear(); await new_conversation(); @@ -30,38 +31,25 @@ const delete_conversations = async () => { const handle_ask = async () => { message_input.style.height = `80px`; message_input.focus(); - - let txtMsgs = []; - const divTags = document.getElementsByClassName("message"); - for(let i=0;i<divTags.length;i++){ - if(!divTags[i].children[1].classList.contains("welcome-message")){ - if(divTags[i].children[0].className == "assistant"){ - const msg = { - role: "assistant", - content: divTags[i].children[1].textContent+" " - }; - txtMsgs.push(msg); - }else{ - const msg = { - role: "user", - content: divTags[i].children[1].textContent+" " - }; - txtMsgs.push(msg); - } - } - } - window.scrollTo(0, 0); - let message = message_input.value; - const msg = { - role: "user", - content: message - }; - txtMsgs.push(msg); - + message = message_input.value if (message.length > 0) { - message_input.value = ``; - await ask_gpt(txtMsgs); + message_input.value = ''; + await add_conversation(window.conversation_id, message); + await add_message(window.conversation_id, "user", message); + window.token = message_id(); + message_box.innerHTML += ` + <div class="message"> + <div class="user"> + ${user_image} + <i class="fa-regular fa-phone-arrow-up-right"></i> + </div> + <div class="content" id="user_${token}"> + ${markdown_render(message)} + </div> + </div> + `; + await ask_gpt(); } }; @@ -74,58 +62,40 @@ const remove_cancel_button = async () => { }, 300); }; -const ask_gpt = async (txtMsgs) => { - try { - message_input.value = ``; - message_input.innerHTML = ``; - message_input.innerText = ``; +const ask_gpt = async () => { + regenerate.classList.add(`regenerate-hidden`); + messages = await get_messages(window.conversation_id); - add_conversation(window.conversation_id, txtMsgs[0].content); - window.scrollTo(0, 0); - window.controller = new AbortController(); + window.scrollTo(0, 0); + window.controller = new AbortController(); - jailbreak = document.getElementById("jailbreak"); - provider = document.getElementById("provider"); - model = document.getElementById("model"); - prompt_lock = true; - window.text = ``; - window.token = message_id(); + jailbreak = document.getElementById("jailbreak"); + provider = document.getElementById("provider"); + model = document.getElementById("model"); + prompt_lock = true; + window.text = ``; - stop_generating.classList.remove(`stop_generating-hidden`); + stop_generating.classList.remove(`stop_generating-hidden`); - message_box.innerHTML += ` - <div class="message"> - <div class="user"> - ${user_image} - <i class="fa-regular fa-phone-arrow-up-right"></i> - </div> - <div class="content" id="user_${token}"> - ${format(txtMsgs[txtMsgs.length-1].content)} - </div> - </div> - `; - - message_box.scrollTop = message_box.scrollHeight; - window.scrollTo(0, 0); - await new Promise((r) => setTimeout(r, 500)); - window.scrollTo(0, 0); + message_box.scrollTop = message_box.scrollHeight; + window.scrollTo(0, 0); + await new Promise((r) => setTimeout(r, 500)); + window.scrollTo(0, 0); - message_box.innerHTML += ` - <div class="message"> - <div class="assistant"> - ${gpt_image} <i class="fa-regular fa-phone-arrow-down-left"></i> - </div> - <div class="content" id="gpt_${window.token}"> - <div id="cursor"></div> - </div> + message_box.innerHTML += ` + <div class="message"> + <div class="assistant"> + ${gpt_image} <i class="fa-regular fa-phone-arrow-down-left"></i> </div> - `; - - message_box.scrollTop = message_box.scrollHeight; - window.scrollTo(0, 0); - await new Promise((r) => setTimeout(r, 1000)); - window.scrollTo(0, 0); + <div class="content" id="gpt_${window.token}"> + <div id="cursor"></div> + </div> + </div> + `; + message_box.scrollTop = message_box.scrollHeight; + window.scrollTo(0, 0); + try { const response = await fetch(`/backend-api/v2/conversation`, { method: `POST`, signal: window.controller.signal, @@ -138,21 +108,22 @@ const ask_gpt = async (txtMsgs) => { action: `_ask`, model: model.options[model.selectedIndex].value, jailbreak: jailbreak.options[jailbreak.selectedIndex].value, + internet_access: document.getElementById(`switch`).checked, provider: provider.options[provider.selectedIndex].value, meta: { id: window.token, content: { - conversation: await get_conversation(window.conversation_id), - internet_access: document.getElementById(`switch`).checked, content_type: `text`, - parts: txtMsgs, + parts: messages, }, }, }), }); - const reader = response.body.getReader(); + await new Promise((r) => setTimeout(r, 1000)); + window.scrollTo(0, 0); + const reader = response.body.getReader(); while (true) { const { value, done } = await reader.read(); if (done) break; @@ -161,7 +132,7 @@ const ask_gpt = async (txtMsgs) => { text += chunk; - document.getElementById(`gpt_${window.token}`).innerHTML = markdown.render(text).replace("<a href=", '<a target="_blank" href='); + document.getElementById(`gpt_${window.token}`).innerHTML = markdown_render(text); document.querySelectorAll(`code`).forEach((el) => { hljs.highlightElement(el); }); @@ -171,45 +142,30 @@ const ask_gpt = async (txtMsgs) => { } if (text.includes(`G4F_ERROR`)) { - document.getElementById(`gpt_${window.token}`).innerHTML = "An error occured, please try again, if the problem persists, please reload / refresh cache or use a differnet browser"; + console.log("response", text); + document.getElementById(`gpt_${window.token}`).innerHTML = "An error occured, please try again, if the problem persists, please use a other model or provider"; } - - add_message(window.conversation_id, "user", txtMsgs[txtMsgs.length-1].content); - add_message(window.conversation_id, "assistant", text); - - message_box.scrollTop = message_box.scrollHeight; - await remove_cancel_button(); - prompt_lock = false; - - await load_conversations(20, 0); - window.scrollTo(0, 0); - } catch (e) { - add_message(window.conversation_id, "user", txtMsgs[txtMsgs.length-1].content); - - message_box.scrollTop = message_box.scrollHeight; - await remove_cancel_button(); - prompt_lock = false; - - await load_conversations(20, 0); - console.log(e); let cursorDiv = document.getElementById(`cursor`); if (cursorDiv) cursorDiv.parentNode.removeChild(cursorDiv); if (e.name != `AbortError`) { - let error_message = `oops ! something went wrong, please try again / reload. [stacktrace in console]`; - - document.getElementById(`gpt_${window.token}`).innerHTML = error_message; - add_message(window.conversation_id, "assistant", error_message); + text = `oops ! something went wrong, please try again / reload. [stacktrace in console]`; + document.getElementById(`gpt_${window.token}`).innerHTML = text; } else { document.getElementById(`gpt_${window.token}`).innerHTML += ` [aborted]`; - add_message(window.conversation_id, "assistant", text + ` [aborted]`); + text += ` [aborted]` } - - window.scrollTo(0, 0); } + add_message(window.conversation_id, "assistant", text); + message_box.scrollTop = message_box.scrollHeight; + await remove_cancel_button(); + prompt_lock = false; + window.scrollTo(0, 0); + await load_conversations(20, 0); + regenerate.classList.remove(`regenerate-hidden`); }; const clear_conversations = async () => { @@ -280,7 +236,6 @@ const set_conversation = async (conversation_id) => { }; const new_conversation = async () => { - history.pushState({}, null, `/chat/`); window.conversation_id = uuid(); @@ -291,12 +246,9 @@ const new_conversation = async () => { }; const load_conversation = async (conversation_id) => { - let conversation = await JSON.parse( - localStorage.getItem(`conversation:${conversation_id}`) - ); - console.log(conversation, conversation_id); + let messages = await get_messages(conversation_id); - for (item of conversation.items) { + for (item of messages) { message_box.innerHTML += ` <div class="message"> <div class=${item.role == "assistant" ? "assistant" : "user"}> @@ -308,7 +260,7 @@ const load_conversation = async (conversation_id) => { </div> <div class="content"> ${item.role == "assistant" - ? markdown.render(item.content).replace("<a href=", '<a target="_blank" href=') + ? markdown_render(item.content) : item.content } </div> @@ -331,6 +283,11 @@ const get_conversation = async (conversation_id) => { let conversation = await JSON.parse( localStorage.getItem(`conversation:${conversation_id}`) ); + return conversation; +}; + +const get_messages = async (conversation_id) => { + let conversation = await get_conversation(conversation_id); return conversation.items; }; @@ -351,21 +308,32 @@ const add_conversation = async (conversation_id, content) => { }) ); } + + history.pushState({}, null, `/chat/${conversation_id}`); }; -const add_message = async (conversation_id, role, content) => { - before_adding = JSON.parse( - localStorage.getItem(`conversation:${conversation_id}`) +const remove_last_message = async (conversation_id) => { + const conversation = await get_conversation(conversation_id) + + conversation.items.pop(); + + localStorage.setItem( + `conversation:${conversation_id}`, + JSON.stringify(conversation) ); +}; - before_adding.items.push({ +const add_message = async (conversation_id, role, content) => { + const conversation = await get_conversation(conversation_id); + + conversation.items.push({ role: role, content: content, }); localStorage.setItem( `conversation:${conversation_id}`, - JSON.stringify(before_adding) + JSON.stringify(conversation) ); }; @@ -404,6 +372,12 @@ document.getElementById(`cancelButton`).addEventListener(`click`, async () => { console.log(`aborted ${window.conversation_id}`); }); +document.getElementById(`regenerateButton`).addEventListener(`click`, async () => { + await remove_last_message(window.conversation_id); + window.token = message_id(); + await ask_gpt(); +}); + const uuid = () => { return `xxxxxxxx-xxxx-4xxx-yxxx-${Date.now().toString(16)}`.replace( /[xy]/g, @@ -485,17 +459,16 @@ const say_hello = async () => { ${gpt_image} <i class="fa-regular fa-phone-arrow-down-left"></i> </div> - <div class="content welcome-message"> + <div class="content"> + <p class=" welcome-message"></p> </div> </div> `; - content = `` to_modify = document.querySelector(`.welcome-message`); for (token of tokens) { await new Promise(resolve => setTimeout(resolve, (Math.random() * (100 - 200) + 100))) - content += token; - to_modify.innerHTML = markdown.render(content); + to_modify.textContent += token; } } @@ -542,14 +515,12 @@ window.onload = async () => { load_conversations(20, 0); }, 1); - if (!window.location.href.endsWith(`#`)) { - if (/\/chat\/.+/.test(window.location.href)) { - await load_conversation(window.conversation_id); - } + if (/\/chat\/.+/.test(window.location.href)) { + await load_conversation(window.conversation_id); + } else { + await say_hello() } - - await say_hello() - + message_input.addEventListener(`keydown`, async (evt) => { if (prompt_lock) return; if (evt.keyCode === 13 && !evt.shiftKey) { diff --git a/g4f/gui/run.py b/g4f/gui/run.py index 0f94814c..7ff769fd 100644 --- a/g4f/gui/run.py +++ b/g4f/gui/run.py @@ -6,7 +6,7 @@ from g4f.gui import run_gui def gui_parser(): parser = ArgumentParser(description="Run the GUI") parser.add_argument("-host", type=str, default="0.0.0.0", help="hostname") - parser.add_argument("-port", type=int, default=80, help="port") + parser.add_argument("-port", type=int, default=8080, help="port") parser.add_argument("-debug", action="store_true", help="debug mode") return parser diff --git a/g4f/gui/server/backend.py b/g4f/gui/server/backend.py index b0c55c8a..e4669699 100644 --- a/g4f/gui/server/backend.py +++ b/g4f/gui/server/backend.py @@ -1,14 +1,16 @@ import g4f from g4f.Provider import __providers__ -from flask import request +import json +from flask import request, Flask from .internet import get_search_message +from g4f import debug -g4f.debug.logging = True +debug.logging = True class Backend_Api: - def __init__(self, app) -> None: - self.app = app + def __init__(self, app: Flask) -> None: + self.app: Flask = app self.routes = { '/backend-api/v2/models': { 'function': self.models, @@ -51,8 +53,8 @@ class Backend_Api: def version(self): return { - "version": g4f.get_version(), - "lastet_version": g4f.get_lastet_version(), + "version": debug.get_version(), + "lastet_version": debug.get_lastet_version(), } def _gen_title(self): @@ -61,33 +63,31 @@ class Backend_Api: } def _conversation(self): - try: - #jailbreak = request.json['jailbreak'] - web_search = request.json['meta']['content']['internet_access'] - messages = request.json['meta']['content']['parts'] - if web_search: - messages[-1]["content"] = get_search_message(messages[-1]["content"]) - model = request.json.get('model') - model = model if model else g4f.models.default - provider = request.json.get('provider').replace('g4f.Provider.', '') - provider = provider if provider and provider != "Auto" else None - if provider != None: - provider = g4f.Provider.ProviderUtils.convert.get(provider) + #jailbreak = request.json['jailbreak'] + messages = request.json['meta']['content']['parts'] + if request.json['internet_access']: + messages[-1]["content"] = get_search_message(messages[-1]["content"]) + model = request.json.get('model') + model = model if model else g4f.models.default + provider = request.json.get('provider', '').replace('g4f.Provider.', '') + provider = provider if provider and provider != "Auto" else None + + def try_response(): + try: + yield from g4f.ChatCompletion.create( + model=model, + provider=provider, + messages=messages, + stream=True, + ignore_stream_and_auth=True + ) + except Exception as e: + print(e) + yield json.dumps({ + 'code' : 'G4F_ERROR', + '_action': '_ask', + 'success': False, + 'error' : f'{e.__class__.__name__}: {e}' + }) - response = g4f.ChatCompletion.create( - model=model, - provider=provider, - messages=messages, - stream=True, - ignore_stream_and_auth=True - ) - - return self.app.response_class(response, mimetype='text/event-stream') - - except Exception as e: - print(e) - return { - 'code' : 'G4F_ERROR', - '_action': '_ask', - 'success': False, - 'error' : f'an error occurred {str(e)}'}, 400 + return self.app.response_class(try_response(), mimetype='text/event-stream')
\ No newline at end of file |