mirror of
https://github.com/xtekky/gpt4free.git
synced 2025-12-06 02:30:41 -08:00
commit
55d6709efc
20 changed files with 270 additions and 106 deletions
|
|
@ -382,7 +382,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
|
|||
# if auth_result.arkose_token is None:
|
||||
# raise MissingAuthError("No arkose token found in .har file")
|
||||
if "proofofwork" in chat_requirements:
|
||||
if getattr(auth_result, "proof_token") is None:
|
||||
if getattr(auth_result, "proof_token", None) is None:
|
||||
auth_result.proof_token = get_config(auth_result.headers.get("user-agent"))
|
||||
proofofwork = generate_proof_token(
|
||||
**chat_requirements["proofofwork"],
|
||||
|
|
@ -444,12 +444,9 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
|
|||
headers=headers
|
||||
) as response:
|
||||
cls._update_request_args(auth_result, session)
|
||||
if response.status in (403, 404) and max_retries > 0:
|
||||
max_retries -= 1
|
||||
debug.log(f"Retry: Error {response.status}: {await response.text()}")
|
||||
conversation.conversation_id = None
|
||||
await asyncio.sleep(5)
|
||||
continue
|
||||
if response.status == 403:
|
||||
auth_result.proof_token = None
|
||||
RequestConfig.proof_token = None
|
||||
await raise_for_status(response)
|
||||
buffer = u""
|
||||
async for line in response.iter_lines():
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ from types import SimpleNamespace
|
|||
from typing import Union, Optional, List
|
||||
|
||||
import g4f
|
||||
import g4f.Provider
|
||||
import g4f.debug
|
||||
from g4f.client import AsyncClient, ChatCompletion, ImagesResponse, convert_to_provider
|
||||
from g4f.providers.response import BaseConversation, JsonConversation
|
||||
|
|
@ -223,7 +224,18 @@ class Api:
|
|||
"created": 0,
|
||||
"owned_by": model.base_provider,
|
||||
"image": isinstance(model, g4f.models.ImageModel),
|
||||
} for model_id, model in g4f.models.ModelUtils.convert.items()]
|
||||
"provider": False,
|
||||
} for model_id, model in g4f.models.ModelUtils.convert.items()] +
|
||||
[{
|
||||
"id": provider_name,
|
||||
"object": "model",
|
||||
"created": 0,
|
||||
"owned_by": getattr(provider, "label", None),
|
||||
"image": bool(getattr(provider, "image_models", False)),
|
||||
"provider": True,
|
||||
} for provider_name, provider in g4f.Provider.ProviderUtils.convert.items()
|
||||
if provider.working and provider_name != "Custom"
|
||||
]
|
||||
}
|
||||
|
||||
@self.app.get("/v1/models/{model_name}", responses={
|
||||
|
|
|
|||
|
|
@ -136,8 +136,7 @@
|
|||
font-size: 1.2rem;
|
||||
margin-bottom: 30px;
|
||||
color: var(--colour-2);
|
||||
} return app
|
||||
|
||||
}
|
||||
|
||||
.input-field {
|
||||
width: 80%;
|
||||
|
|
@ -211,9 +210,7 @@
|
|||
|
||||
<!-- Input and Button -->
|
||||
<form action="/chat/">
|
||||
<!--
|
||||
<input type="text" name="prompt" class="input-field" placeholder="Enter your query...">
|
||||
-->
|
||||
<input type="text" name="prompt" class="input-field" placeholder="Enter your query...">
|
||||
<button class="button">Open Chat</button>
|
||||
</form>
|
||||
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@
|
|||
<meta property="og:description" content="A conversational AI system that listens, learns, and challenges">
|
||||
<meta property="og:url" content="https://g4f.ai">
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
<link rel="stylesheet" href="/static/css/all.min.css">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/static/img/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/static/img/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/img/favicon-16x16.png">
|
||||
<link rel="manifest" href="/static/img/site.webmanifest">
|
||||
<script src="/static/js/icons.js"></script>
|
||||
<script src="/static/js/highlightjs-copy.min.js"></script>
|
||||
<script src="/static/js/chat.v1.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/markdown-it@13.0.1/dist/markdown-it.min.js"></script>
|
||||
|
|
@ -172,7 +172,7 @@
|
|||
</div>
|
||||
<div class="bottom_buttons">
|
||||
<button onclick="delete_conversations()">
|
||||
<i class="fa-regular fa-trash"></i>
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
<span>Clear Conversations</span>
|
||||
</button>
|
||||
<button onclick="save_storage()">
|
||||
|
|
@ -243,7 +243,7 @@
|
|||
<i class="fa-solid fa-microphone-slash"></i>
|
||||
</label>
|
||||
<div id="send-button">
|
||||
<i class="fa-solid fa-paper-plane-top"></i>
|
||||
<i class="fa-regular fa-paper-plane"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
9
g4f/gui/client/static/css/all.min.css
vendored
Normal file
9
g4f/gui/client/static/css/all.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1290,7 +1290,7 @@ ul {
|
|||
border: 1px dashed #e4d4ffa6;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
padding-left: 8px;
|
||||
padding-left: 4px;
|
||||
padding-right: 5px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
|
|
|
|||
|
|
@ -15,5 +15,13 @@
|
|||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
"display": "standalone",
|
||||
"share_target": {
|
||||
"action": "/chat/",
|
||||
"method": "GET",
|
||||
"enctype": "application/x-www-form-urlencoded",
|
||||
"params": {
|
||||
"title": "prompt"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -421,18 +421,18 @@ regenerate_button.addEventListener("click", async () => {
|
|||
});
|
||||
|
||||
stop_generating.addEventListener("click", async () => {
|
||||
stop_generating.classList.add("stop_generating-hidden");
|
||||
regenerate_button.classList.remove("regenerate-hidden");
|
||||
stop_generating.classList.add("stop_generating-hidden");
|
||||
let key;
|
||||
for (key in controller_storage) {
|
||||
if (!controller_storage[key].signal.aborted) {
|
||||
console.log(`aborted ${window.conversation_id} #${key}`);
|
||||
controller_storage[key].abort();
|
||||
let message = message_storage[key];
|
||||
if (message) {
|
||||
content_storage[key].inner.innerHTML += " [aborted]";
|
||||
message_storage[key] += " [aborted]";
|
||||
console.log(`aborted ${window.conversation_id} #${key}`);
|
||||
}
|
||||
controller_storage[key].abort();
|
||||
}
|
||||
}
|
||||
await load_conversation(window.conversation_id, false);
|
||||
|
|
@ -727,6 +727,13 @@ async function add_message_chunk(message, message_id, provider, scroll) {
|
|||
}
|
||||
}
|
||||
|
||||
function is_stopped() {
|
||||
if (stop_generating.classList.contains('stop_generating-hidden')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const ask_gpt = async (message_id, message_index = -1, regenerate = false, provider = null, model = null, action = null) => {
|
||||
if (!model && !provider) {
|
||||
model = get_selected_model()?.value || null;
|
||||
|
|
@ -826,13 +833,12 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
|
|||
content_map.inner.innerHTML += markdown_render(`**An error occured:** ${e}`);
|
||||
}
|
||||
}
|
||||
delete controller_storage[message_id];
|
||||
if (message_storage[message_id]) {
|
||||
const message_provider = message_id in provider_storage ? provider_storage[message_id] : null;
|
||||
await add_message(
|
||||
window.conversation_id,
|
||||
"assistant",
|
||||
message_storage[message_id] + (error_storage[message_id] ? " [error]" : ""),
|
||||
message_storage[message_id] + (error_storage[message_id] ? " [error]" : "") + (stop_generating.classList.contains('stop_generating-hidden') ? " [aborted]" : ""),
|
||||
message_provider,
|
||||
message_index,
|
||||
synthesize_storage[message_id],
|
||||
|
|
@ -842,6 +848,7 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
|
|||
usage_storage[message_id],
|
||||
action=="continue"
|
||||
);
|
||||
delete controller_storage[message_id];
|
||||
delete message_storage[message_id];
|
||||
if (!error_storage[message_id]) {
|
||||
await safe_load_conversation(window.conversation_id, scroll);
|
||||
|
|
@ -961,7 +968,7 @@ const delete_conversation = async (conversation_id) => {
|
|||
|
||||
const set_conversation = async (conversation_id) => {
|
||||
try {
|
||||
history.pushState({}, null, `/chat/${conversation_id}`);
|
||||
add_url_to_history(`/chat/${conversation_id}`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
|
@ -1242,7 +1249,7 @@ async function add_conversation(conversation_id) {
|
|||
});
|
||||
}
|
||||
try {
|
||||
history.pushState({}, null, `/chat/${conversation_id}`);
|
||||
add_url_to_history(`/chat/${conversation_id}`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
|
@ -1371,7 +1378,7 @@ const load_conversations = async () => {
|
|||
</div>
|
||||
<i onclick="show_option('${conversation.id}')" class="fa-solid fa-ellipsis-vertical" id="conv-${conversation.id}"></i>
|
||||
<div id="cho-${conversation.id}" class="choise" style="display:none;">
|
||||
<i onclick="delete_conversation('${conversation.id}')" class="fa-regular fa-trash"></i>
|
||||
<i onclick="delete_conversation('${conversation.id}')" class="fa-solid fa-trash"></i>
|
||||
<i onclick="hide_option('${conversation.id}')" class="fa-regular fa-x"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1434,17 +1441,23 @@ sidebar_button.addEventListener("click", async () => {
|
|||
} else {
|
||||
sidebar.classList.add("shown");
|
||||
sidebar_button.classList.add("rotated");
|
||||
history.pushState({}, null, "/menu/");
|
||||
add_url_to_history("/menu/");
|
||||
}
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
|
||||
function add_url_to_history(url) {
|
||||
if (!window?.pywebview) {
|
||||
history.pushState({}, null, url);
|
||||
}
|
||||
}
|
||||
|
||||
function open_settings() {
|
||||
if (settings.classList.contains("hidden")) {
|
||||
chat.classList.add("hidden");
|
||||
sidebar.classList.remove("shown");
|
||||
settings.classList.remove("hidden");
|
||||
history.pushState({}, null, "/settings/");
|
||||
add_url_to_history("/settings/");
|
||||
} else {
|
||||
settings.classList.add("hidden");
|
||||
chat.classList.remove("hidden");
|
||||
|
|
@ -1523,8 +1536,9 @@ const load_settings_storage = async () => {
|
|||
} else {
|
||||
element.value = value;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.warn("Unresolved element type");
|
||||
console.warn("`Unresolved element type:", element.type);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -1644,17 +1658,29 @@ window.addEventListener('DOMContentLoaded', async function() {
|
|||
await on_load();
|
||||
if (window.conversation_id == "{{chat_id}}") {
|
||||
window.conversation_id = uuid();
|
||||
} else {
|
||||
await on_api();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('pywebviewready', async function() {
|
||||
await on_api();
|
||||
});
|
||||
|
||||
async function on_load() {
|
||||
count_input();
|
||||
if (/\/chat\/.+/.test(window.location.href)) {
|
||||
if (/\/chat\/[^?]+/.test(window.location.href)) {
|
||||
load_conversation(window.conversation_id);
|
||||
} else {
|
||||
say_hello()
|
||||
chatPrompt.value = document.getElementById("systemPrompt")?.value || "";
|
||||
let chat_url = new URL(window.location.href)
|
||||
let chat_params = new URLSearchParams(chat_url.search);
|
||||
if (chat_params.get("prompt")) {
|
||||
messageInput.value = chat_params.get("prompt");
|
||||
await handle_ask();
|
||||
} else {
|
||||
say_hello()
|
||||
}
|
||||
}
|
||||
load_conversations();
|
||||
}
|
||||
|
|
@ -1694,6 +1720,7 @@ const load_provider_option = (input, provider_name) => {
|
|||
};
|
||||
|
||||
async function on_api() {
|
||||
load_version();
|
||||
let prompt_lock = false;
|
||||
messageInput.addEventListener("keydown", async (evt) => {
|
||||
if (prompt_lock) return;
|
||||
|
|
@ -1718,82 +1745,75 @@ async function on_api() {
|
|||
});
|
||||
messageInput.focus();
|
||||
let provider_options = [];
|
||||
try {
|
||||
models = await api("models");
|
||||
models.forEach((model) => {
|
||||
let option = document.createElement("option");
|
||||
option.value = model.name;
|
||||
option.text = model.name + (model.image ? " (Image Generation)" : "");
|
||||
option.dataset.providers = model.providers.join(" ");
|
||||
modelSelect.appendChild(option);
|
||||
});
|
||||
providers = await api("providers")
|
||||
providers.sort((a, b) => a.label.localeCompare(b.label));
|
||||
let login_urls = {};
|
||||
providers.forEach((provider) => {
|
||||
let option = document.createElement("option");
|
||||
option.value = provider.name;
|
||||
option.dataset.label = provider.label;
|
||||
option.text = provider.label
|
||||
+ (provider.vision ? " (Image Upload)" : "")
|
||||
+ (provider.image ? " (Image Generation)" : "")
|
||||
+ (provider.webdriver ? " (Webdriver)" : "")
|
||||
+ (provider.auth ? " (Auth)" : "");
|
||||
if (provider.parent)
|
||||
option.dataset.parent = provider.parent;
|
||||
providerSelect.appendChild(option);
|
||||
models = await api("models");
|
||||
models.forEach((model) => {
|
||||
let option = document.createElement("option");
|
||||
option.value = model.name;
|
||||
option.text = model.name + (model.image ? " (Image Generation)" : "");
|
||||
option.dataset.providers = model.providers.join(" ");
|
||||
modelSelect.appendChild(option);
|
||||
});
|
||||
providers = await api("providers")
|
||||
providers.sort((a, b) => a.label.localeCompare(b.label));
|
||||
let login_urls = {};
|
||||
providers.forEach((provider) => {
|
||||
let option = document.createElement("option");
|
||||
option.value = provider.name;
|
||||
option.dataset.label = provider.label;
|
||||
option.text = provider.label
|
||||
+ (provider.vision ? " (Image Upload)" : "")
|
||||
+ (provider.image ? " (Image Generation)" : "")
|
||||
+ (provider.webdriver ? " (Webdriver)" : "")
|
||||
+ (provider.auth ? " (Auth)" : "");
|
||||
if (provider.parent)
|
||||
option.dataset.parent = provider.parent;
|
||||
providerSelect.appendChild(option);
|
||||
|
||||
if (provider.parent) {
|
||||
if (!login_urls[provider.parent]) {
|
||||
login_urls[provider.parent] = [provider.label, provider.login_url, [provider.name]];
|
||||
} else {
|
||||
login_urls[provider.parent][2].push(provider.name);
|
||||
}
|
||||
} else if (provider.login_url) {
|
||||
if (!login_urls[provider.name]) {
|
||||
login_urls[provider.name] = [provider.label, provider.login_url, []];
|
||||
} else {
|
||||
login_urls[provider.name][0] = provider.label;
|
||||
login_urls[provider.name][1] = provider.login_url;
|
||||
}
|
||||
if (provider.parent) {
|
||||
if (!login_urls[provider.parent]) {
|
||||
login_urls[provider.parent] = [provider.label, provider.login_url, [provider.name]];
|
||||
} else {
|
||||
login_urls[provider.parent][2].push(provider.name);
|
||||
}
|
||||
});
|
||||
for (let [name, [label, login_url, childs]] of Object.entries(login_urls)) {
|
||||
if (!login_url) {
|
||||
continue;
|
||||
} else if (provider.login_url) {
|
||||
if (!login_urls[provider.name]) {
|
||||
login_urls[provider.name] = [provider.label, provider.login_url, []];
|
||||
} else {
|
||||
login_urls[provider.name][0] = provider.label;
|
||||
login_urls[provider.name][1] = provider.login_url;
|
||||
}
|
||||
option = document.createElement("div");
|
||||
option.classList.add("field", "box", "hidden");
|
||||
childs = childs.map((child)=>`${child}-api_key`).join(" ");
|
||||
option.innerHTML = `
|
||||
<label for="${name}-api_key" class="label" title="">${label}:</label>
|
||||
<input type="text" id="${name}-api_key" name="${name}[api_key]" class="${childs}" placeholder="api_key"/>
|
||||
<a href="${login_url}" target="_blank" title="Login to ${label}">Get API key</a>
|
||||
`;
|
||||
settings.querySelector(".paper").appendChild(option);
|
||||
}
|
||||
providers.forEach((provider) => {
|
||||
if (!provider.parent) {
|
||||
option = document.createElement("div");
|
||||
option.classList.add("field");
|
||||
option.innerHTML = `
|
||||
<span class="label">Enable ${provider.label}</span>
|
||||
<input id="Provider${provider.name}" type="checkbox" name="Provider${provider.name}" value="${provider.name}" class="provider" checked="">
|
||||
<label for="Provider${provider.name}" class="toogle" title="Remove provider from dropdown"></label>
|
||||
`;
|
||||
option.querySelector("input").addEventListener("change", (event) => load_provider_option(event.target, provider.name));
|
||||
settings.querySelector(".paper").appendChild(option);
|
||||
provider_options[provider.name] = option;
|
||||
}
|
||||
});
|
||||
await load_provider_models(appStorage.getItem("provider"));
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
// Redirect to show basic authenfication
|
||||
if (document.location.pathname == "/chat/") {
|
||||
//document.location.href = `/chat/error`;
|
||||
});
|
||||
for (let [name, [label, login_url, childs]] of Object.entries(login_urls)) {
|
||||
if (!login_url) {
|
||||
continue;
|
||||
}
|
||||
option = document.createElement("div");
|
||||
option.classList.add("field", "box", "hidden");
|
||||
childs = childs.map((child)=>`${child}-api_key`).join(" ");
|
||||
option.innerHTML = `
|
||||
<label for="${name}-api_key" class="label" title="">${label}:</label>
|
||||
<input type="text" id="${name}-api_key" name="${name}[api_key]" class="${childs}" placeholder="api_key"/>
|
||||
<a href="${login_url}" target="_blank" title="Login to ${label}">Get API key</a>
|
||||
`;
|
||||
settings.querySelector(".paper").appendChild(option);
|
||||
}
|
||||
providers.forEach((provider) => {
|
||||
if (!provider.parent) {
|
||||
option = document.createElement("div");
|
||||
option.classList.add("field");
|
||||
option.innerHTML = `
|
||||
<span class="label">Enable ${provider.label}</span>
|
||||
<input id="Provider${provider.name}" type="checkbox" name="Provider${provider.name}" value="${provider.name}" class="provider" checked="">
|
||||
<label for="Provider${provider.name}" class="toogle" title="Remove provider from dropdown"></label>
|
||||
`;
|
||||
option.querySelector("input").addEventListener("change", (event) => load_provider_option(event.target, provider.name));
|
||||
settings.querySelector(".paper").appendChild(option);
|
||||
provider_options[provider.name] = option;
|
||||
}
|
||||
});
|
||||
await load_provider_models(appStorage.getItem("provider"))
|
||||
|
||||
register_settings_storage();
|
||||
await load_settings_storage()
|
||||
Object.entries(provider_options).forEach(
|
||||
|
|
@ -1870,7 +1890,6 @@ async function load_version() {
|
|||
document.getElementById("version_text").innerHTML = text
|
||||
setTimeout(load_version, 1000 * 60 * 60); // 1 hour
|
||||
}
|
||||
setTimeout(load_version, 100);
|
||||
|
||||
[imageInput, cameraInput].forEach((el) => {
|
||||
el.addEventListener('click', async () => {
|
||||
|
|
@ -1886,6 +1905,20 @@ fileInput.addEventListener('click', async (event) => {
|
|||
fileInput.value = '';
|
||||
});
|
||||
|
||||
cameraInput?.addEventListener("click", (e) => {
|
||||
if (window?.pywebview) {
|
||||
e.preventDefault();
|
||||
pywebview.api.take_picture();
|
||||
}
|
||||
});
|
||||
|
||||
imageInput?.addEventListener("click", (e) => {
|
||||
if (window?.pywebview) {
|
||||
e.preventDefault();
|
||||
pywebview.api.choose_image();
|
||||
}
|
||||
});
|
||||
|
||||
async function upload_cookies() {
|
||||
const file = fileInput.files[0];
|
||||
const formData = new FormData();
|
||||
|
|
@ -2021,6 +2054,18 @@ function get_selected_model() {
|
|||
}
|
||||
|
||||
async function api(ressource, args=null, files=null, message_id=null, scroll=true) {
|
||||
if (window?.pywebview) {
|
||||
if (args !== null) {
|
||||
if (ressource == "conversation") {
|
||||
return pywebview.api[`get_${ressource}`](args, message_id, scroll);
|
||||
}
|
||||
if (ressource == "models") {
|
||||
ressource = "provider_models";
|
||||
}
|
||||
return pywebview.api[`get_${ressource}`](args);
|
||||
}
|
||||
return pywebview.api[`get_${ressource}`]();
|
||||
}
|
||||
const headers = {};
|
||||
if (ressource == "models" && args) {
|
||||
api_key = get_api_key_by_provider(args);
|
||||
|
|
@ -2033,7 +2078,7 @@ async function api(ressource, args=null, files=null, message_id=null, scroll=tru
|
|||
}
|
||||
ressource = `${ressource}/${args}`;
|
||||
}
|
||||
const url = `/backend-api/v2/${ressource}`;
|
||||
const url = new URL(`/backend-api/v2/${ressource}`, window?.location || "http://localhost:8080");
|
||||
if (ressource == "conversation") {
|
||||
let body = JSON.stringify(args);
|
||||
headers.accept = 'text/event-stream';
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
BIN
g4f/gui/client/static/webfonts/fa-brands-400.ttf
Normal file
BIN
g4f/gui/client/static/webfonts/fa-brands-400.ttf
Normal file
Binary file not shown.
BIN
g4f/gui/client/static/webfonts/fa-brands-400.woff2
Normal file
BIN
g4f/gui/client/static/webfonts/fa-brands-400.woff2
Normal file
Binary file not shown.
BIN
g4f/gui/client/static/webfonts/fa-regular-400.ttf
Normal file
BIN
g4f/gui/client/static/webfonts/fa-regular-400.ttf
Normal file
Binary file not shown.
BIN
g4f/gui/client/static/webfonts/fa-regular-400.woff2
Normal file
BIN
g4f/gui/client/static/webfonts/fa-regular-400.woff2
Normal file
Binary file not shown.
BIN
g4f/gui/client/static/webfonts/fa-solid-900.ttf
Normal file
BIN
g4f/gui/client/static/webfonts/fa-solid-900.ttf
Normal file
Binary file not shown.
BIN
g4f/gui/client/static/webfonts/fa-solid-900.woff2
Normal file
BIN
g4f/gui/client/static/webfonts/fa-solid-900.woff2
Normal file
Binary file not shown.
BIN
g4f/gui/client/static/webfonts/fa-v4compatibility.ttf
Normal file
BIN
g4f/gui/client/static/webfonts/fa-v4compatibility.ttf
Normal file
Binary file not shown.
BIN
g4f/gui/client/static/webfonts/fa-v4compatibility.woff2
Normal file
BIN
g4f/gui/client/static/webfonts/fa-v4compatibility.woff2
Normal file
Binary file not shown.
|
|
@ -88,10 +88,10 @@ class Api:
|
|||
provider = json_data.get('provider')
|
||||
messages = json_data.get('messages')
|
||||
api_key = json_data.get("api_key")
|
||||
if api_key is not None:
|
||||
if api_key:
|
||||
kwargs["api_key"] = api_key
|
||||
api_base = json_data.get("api_base")
|
||||
if api_base is not None:
|
||||
if api_base:
|
||||
kwargs["api_base"] = api_base
|
||||
kwargs["tool_calls"] = [{
|
||||
"function": {
|
||||
|
|
|
|||
95
g4f/gui/server/js_api.py
Normal file
95
g4f/gui/server/js_api.py
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os.path
|
||||
from typing import Iterator
|
||||
from uuid import uuid4
|
||||
from functools import partial
|
||||
import webview
|
||||
import platformdirs
|
||||
from plyer import camera
|
||||
from plyer import filechooser
|
||||
|
||||
app_storage_path = platformdirs.user_pictures_dir
|
||||
user_select_image = partial(
|
||||
filechooser.open_file,
|
||||
path=platformdirs.user_pictures_dir(),
|
||||
filters=[["Image", "*.jpg", "*.jpeg", "*.png", "*.webp", "*.svg"]],
|
||||
)
|
||||
|
||||
from .api import Api
|
||||
|
||||
class JsApi(Api):
|
||||
|
||||
def get_conversation(self, options: dict, message_id: str = None, scroll: bool = None, **kwargs) -> Iterator:
|
||||
window = webview.windows[0]
|
||||
if hasattr(self, "image") and self.image is not None:
|
||||
kwargs["image"] = open(self.image, "rb")
|
||||
for message in self._create_response_stream(
|
||||
self._prepare_conversation_kwargs(options, kwargs),
|
||||
options.get("conversation_id"),
|
||||
options.get('provider')
|
||||
):
|
||||
if window.evaluate_js(
|
||||
f"""
|
||||
is_stopped() ? true :
|
||||
this.add_message_chunk({
|
||||
json.dumps(message)
|
||||
}, {
|
||||
json.dumps(message_id)
|
||||
}, {
|
||||
json.dumps(options.get('provider'))
|
||||
}, {
|
||||
'true' if scroll else 'false'
|
||||
}); is_stopped();
|
||||
"""):
|
||||
break
|
||||
self.image = None
|
||||
self.set_selected(None)
|
||||
|
||||
def choose_image(self):
|
||||
user_select_image(
|
||||
on_selection=self.on_image_selection
|
||||
)
|
||||
|
||||
def take_picture(self):
|
||||
filename = os.path.join(app_storage_path(), f"chat-{uuid4()}.png")
|
||||
camera.take_picture(filename=filename, on_complete=self.on_camera)
|
||||
|
||||
def on_image_selection(self, filename):
|
||||
filename = filename[0] if isinstance(filename, list) and filename else filename
|
||||
if filename and os.path.exists(filename):
|
||||
self.image = filename
|
||||
else:
|
||||
self.image = None
|
||||
self.set_selected(None if self.image is None else "image")
|
||||
|
||||
def on_camera(self, filename):
|
||||
if filename and os.path.exists(filename):
|
||||
self.image = filename
|
||||
else:
|
||||
self.image = None
|
||||
self.set_selected(None if self.image is None else "camera")
|
||||
|
||||
def set_selected(self, input_id: str = None):
|
||||
window = webview.windows[0]
|
||||
if window is not None:
|
||||
window.evaluate_js(
|
||||
f"document.querySelector(`.image-label.selected`)?.classList.remove(`selected`);"
|
||||
)
|
||||
if input_id is not None and input_id in ("image", "camera"):
|
||||
window.evaluate_js(
|
||||
f'document.querySelector(`label[for="{input_id}"]`)?.classList.add(`selected`);'
|
||||
)
|
||||
|
||||
def get_version(self):
|
||||
return super().get_version()
|
||||
|
||||
def get_models(self):
|
||||
return super().get_models()
|
||||
|
||||
def get_providers(self):
|
||||
return super().get_providers()
|
||||
|
||||
def get_provider_models(self, provider: str, **kwargs):
|
||||
return super().get_provider_models(provider, **kwargs)
|
||||
|
|
@ -10,6 +10,7 @@ except ImportError:
|
|||
has_platformdirs = False
|
||||
|
||||
from g4f.gui.gui_parser import gui_parser
|
||||
from g4f.gui.server.js_api import JsApi
|
||||
import g4f.version
|
||||
import g4f.debug
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ def run_webview(
|
|||
f"g4f - {g4f.version.utils.current_version}",
|
||||
os.path.join(dirname, "client/index.html"),
|
||||
text_select=True,
|
||||
js_api=JsApi(),
|
||||
)
|
||||
if has_platformdirs and storage_path is None:
|
||||
storage_path = user_config_dir("g4f-webview")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue