From ea8d6b847a0e656cc5583948c5745592adda7103 Mon Sep 17 00:00:00 2001 From: Heiner Lohaus Date: Sat, 13 Jan 2024 15:37:36 +0100 Subject: Support upload image in gui Add image upload to OpenaiChat Add image response to OpenaiChat Improve ChatGPT Plus Support Remove unused requirements --- g4f/gui/client/css/style.css | 34 +++++++++++---- g4f/gui/client/html/index.html | 93 ++++++++-------------------------------- g4f/gui/client/js/chat.v1.js | 96 ++++++++++++++++++++++-------------------- g4f/gui/server/backend.py | 58 ++++++++++++++++++------- 4 files changed, 137 insertions(+), 144 deletions(-) (limited to 'g4f/gui') diff --git a/g4f/gui/client/css/style.css b/g4f/gui/client/css/style.css index 3e2d6d6f..59464272 100644 --- a/g4f/gui/client/css/style.css +++ b/g4f/gui/client/css/style.css @@ -217,7 +217,6 @@ body { } .message { - width: 100%; overflow-wrap: break-word; display: flex; @@ -302,10 +301,14 @@ body { line-height: 1.3; color: var(--colour-3); } -.message .content pre { +.message .content pre{ white-space: pre-wrap; } +.message .content img{ + max-width: 400px; +} + .message .user i { position: absolute; bottom: -6px; @@ -401,13 +404,28 @@ body { display: none; } -input[type="checkbox"] { +#image { + display: none; +} + +label[for="image"]:has(> input:valid){ + color: var(--accent); +} + +label[for="image"] { + cursor: pointer; + position: absolute; + top: 10px; + left: 10px; +} + +.buttons input[type="checkbox"] { height: 0; width: 0; display: none; } -label { +.buttons label { cursor: pointer; text-indent: -9999px; width: 50px; @@ -424,7 +442,7 @@ label { transition: 0.33s; } -label:after { +.buttons label:after { content: ""; position: absolute; top: 50%; @@ -437,11 +455,11 @@ label:after { transition: 0.33s; } -input:checked+label { - background: var(--blur-border); +.buttons input:checked+label { + background: var(--accent); } -input:checked+label:after { +.buttons input:checked+label:after { left: calc(100% - 5px - 20px); } diff --git a/g4f/gui/client/html/index.html b/g4f/gui/client/html/index.html index bc41bd45..3f2bb0c0 100644 --- a/g4f/gui/client/html/index.html +++ b/g4f/gui/client/html/index.html @@ -36,7 +36,8 @@ #message-input { margin-right: 30px; - height: 80px; + height: 82px; + margin-left: 20px; } #message-input::-webkit-scrollbar { @@ -113,6 +114,10 @@
+
@@ -120,52 +125,7 @@
- - - Web Access -
-
- - - Image Generator -
-
- +
- +
+
+ + + Web Access +
+
+ + + Image Generator +
diff --git a/g4f/gui/client/js/chat.v1.js b/g4f/gui/client/js/chat.v1.js index fffe9fe9..e763f52d 100644 --- a/g4f/gui/client/js/chat.v1.js +++ b/g4f/gui/client/js/chat.v1.js @@ -7,6 +7,7 @@ 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`); +const imageInput = document.querySelector('#image') ; let prompt_lock = false; hljs.addPlugin(new CopyButtonPlugin()); @@ -34,7 +35,7 @@ const delete_conversations = async () => { }; const handle_ask = async () => { - message_input.style.height = `80px`; + message_input.style.height = `82px`; message_input.focus(); window.scrollTo(0, 0); message = message_input.value @@ -103,8 +104,7 @@ const ask_gpt = async () => {
-
-
+
`; @@ -114,29 +114,32 @@ const ask_gpt = async () => { message_box.scrollTop = message_box.scrollHeight; window.scrollTo(0, 0); try { + let body = JSON.stringify({ + id: window.token, + conversation_id: window.conversation_id, + model: model.options[model.selectedIndex].value, + jailbreak: jailbreak.options[jailbreak.selectedIndex].value, + web_search: document.getElementById(`switch`).checked, + provider: provider.options[provider.selectedIndex].value, + patch_provider: document.getElementById('patch').checked, + messages: messages + }); + const headers = { + accept: 'text/event-stream' + } + if (imageInput && imageInput.files.length > 0) { + const formData = new FormData(); + formData.append('image', imageInput.files[0]); + formData.append('json', body); + body = formData; + } else { + headers['content-type'] = 'application/json'; + } const response = await fetch(`/backend-api/v2/conversation`, { - method: `POST`, + method: 'POST', signal: window.controller.signal, - headers: { - 'content-type': `application/json`, - accept: `text/event-stream`, - }, - body: JSON.stringify({ - conversation_id: window.conversation_id, - 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, - patch_provider: document.getElementById('patch').checked, - meta: { - id: window.token, - content: { - content_type: `text`, - parts: messages, - }, - }, - }), + headers: headers, + body: body }); await new Promise((r) => setTimeout(r, 1000)); @@ -159,13 +162,17 @@ const ask_gpt = async () => { '' + provider.name + "" } else if (message["type"] == "error") { error = message["error"]; + } else if (message["type"] == "message") { + console.error(message["message"]) } } if (error) { console.error(error); content_inner.innerHTML = "An error occured, please try again, if the problem persists, please use a other model or provider"; } else { - content_inner.innerHTML = markdown_render(text); + html = markdown_render(text); + html = html.substring(0, html.lastIndexOf('

')) + '

'; + content_inner.innerHTML = html; document.querySelectorAll('code').forEach((el) => { hljs.highlightElement(el); }); @@ -174,9 +181,9 @@ const ask_gpt = async () => { window.scrollTo(0, 0); message_box.scrollTo({ top: message_box.scrollHeight, behavior: "auto" }); } + if (!error && imageInput) imageInput.value = ""; } catch (e) { - console.log(e); - + console.error(e); if (e.name != `AbortError`) { text = `oops ! something went wrong, please try again / reload. [stacktrace in console]`; @@ -444,34 +451,34 @@ document.querySelector(".mobile-sidebar").addEventListener("click", (event) => { }); const register_settings_localstorage = async () => { - settings_ids = ["switch", "model", "jailbreak", "patch", "provider"]; - settings_elements = settings_ids.map((id) => document.getElementById(id)); - settings_elements.map((element) => - element.addEventListener(`change`, async (event) => { + for (id of ["switch", "model", "jailbreak", "patch", "provider"]) { + element = document.getElementById(id); + element.addEventListener('change', async (event) => { switch (event.target.type) { case "checkbox": - localStorage.setItem(event.target.id, event.target.checked); + localStorage.setItem(id, event.target.checked); break; case "select-one": - localStorage.setItem(event.target.id, event.target.selectedIndex); + localStorage.setItem(id, event.target.selectedIndex); break; default: console.warn("Unresolved element type"); } - }) - ); -}; + }); + } +} const load_settings_localstorage = async () => { for (id of ["switch", "model", "jailbreak", "patch", "provider"]) { element = document.getElementById(id); - if (localStorage.getItem(element.id)) { + value = localStorage.getItem(element.id); + if (value) { switch (element.type) { case "checkbox": - element.checked = localStorage.getItem(element.id) === "true"; + element.checked = value === "true"; break; case "select-one": - element.selectedIndex = parseInt(localStorage.getItem(element.id)); + element.selectedIndex = parseInt(value); break; default: console.warn("Unresolved element type"); @@ -529,7 +536,6 @@ colorThemes.forEach((themeOption) => { window.onload = async () => { - load_settings_localstorage(); setTheme(); let conversations = 0; @@ -610,16 +616,14 @@ observer.observe(message_input, { attributes: true }); option.value = option.text = model; select.appendChild(option); } -})(); -(async () => { response = await fetch('/backend-api/v2/providers') providers = await response.json() - let select = document.getElementById('provider'); + select = document.getElementById('provider'); select.textContent = ''; - let auto = document.createElement('option'); + auto = document.createElement('option'); auto.value = ''; auto.text = 'Provider: Auto'; select.appendChild(auto); @@ -629,6 +633,8 @@ observer.observe(message_input, { attributes: true }); option.value = option.text = provider; select.appendChild(option); } + + await load_settings_localstorage() })(); (async () => { @@ -644,4 +650,4 @@ observer.observe(message_input, { attributes: true }); text += versions["version"]; } document.getElementById("version_text").innerHTML = text -})(); \ No newline at end of file +})() \ No newline at end of file diff --git a/g4f/gui/server/backend.py b/g4f/gui/server/backend.py index 67f13de4..3ccd1a59 100644 --- a/g4f/gui/server/backend.py +++ b/g4f/gui/server/backend.py @@ -3,6 +3,7 @@ import json from flask import request, Flask from g4f import debug, version, models from g4f import _all_models, get_last_provider, ChatCompletion +from g4f.image import is_allowed_extension, to_image from g4f.Provider import __providers__ from g4f.Provider.bing.create_images import patch_provider from .internet import get_search_message @@ -55,7 +56,7 @@ class Backend_Api: def version(self): return { "version": version.utils.current_version, - "lastet_version": version.utils.latest_version, + "lastet_version": version.get_latest_version(), } def _gen_title(self): @@ -64,15 +65,31 @@ class Backend_Api: } def _conversation(self): - #jailbreak = request.json['jailbreak'] - messages = request.json['meta']['content']['parts'] - if request.json.get('internet_access'): - messages[-1]["content"] = get_search_message(messages[-1]["content"]) - model = request.json.get('model') + kwargs = {} + if 'image' in request.files: + file = request.files['image'] + if file.filename != '' and is_allowed_extension(file.filename): + kwargs['image'] = to_image(file.stream) + if 'json' in request.form: + json_data = json.loads(request.form['json']) + else: + json_data = request.json + + provider = json_data.get('provider', '').replace('g4f.Provider.', '') + provider = provider if provider and provider != "Auto" else None + if provider == 'OpenaiChat': + kwargs['auto_continue'] = True + messages = json_data['messages'] + if json_data.get('web_search'): + if provider == "Bing": + kwargs['web_search'] = True + else: + messages[-1]["content"] = get_search_message(messages[-1]["content"]) + model = json_data.get('model') model = model if model else models.default - provider = request.json.get('provider', '').replace('g4f.Provider.', '') + provider = json_data.get('provider', '').replace('g4f.Provider.', '') provider = provider if provider and provider != "Auto" else None - patch = patch_provider if request.json.get('patch_provider') else None + patch = patch_provider if json_data.get('patch_provider') else None def try_response(): try: @@ -83,7 +100,8 @@ class Backend_Api: messages=messages, stream=True, ignore_stream_and_auth=True, - patch_provider=patch + patch_provider=patch, + **kwargs ): if first: first = False @@ -91,16 +109,24 @@ class Backend_Api: 'type' : 'provider', 'provider': get_last_provider(True) }) + "\n" - yield json.dumps({ - 'type' : 'content', - 'content': chunk, - }) + "\n" - + if isinstance(chunk, Exception): + yield json.dumps({ + 'type' : 'message', + 'message': get_error_message(chunk), + }) + "\n" + else: + yield json.dumps({ + 'type' : 'content', + 'content': str(chunk), + }) + "\n" except Exception as e: logging.exception(e) yield json.dumps({ 'type' : 'error', - 'error': f'{e.__class__.__name__}: {e}' + 'error': get_error_message(e) }) - return self.app.response_class(try_response(), mimetype='text/event-stream') \ No newline at end of file + return self.app.response_class(try_response(), mimetype='text/event-stream') + +def get_error_message(exception: Exception) -> str: + return f"{get_last_provider().__name__}: {type(exception).__name__}: {exception}" \ No newline at end of file -- cgit v1.2.3