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 @@
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