This commit is contained in:
abc 2024-03-05 22:06:31 +00:00
commit 9d17588fcb
5 changed files with 137 additions and 70 deletions

View File

@ -26,6 +26,7 @@ docker pull hlohaus789/g4f
- [/docs/guides/help_me](/docs/guides/help_me.md) - [/docs/guides/help_me](/docs/guides/help_me.md)
- Join our Telegram Channel: [t.me/g4f_channel](https://telegram.me/g4f_channel) - Join our Telegram Channel: [t.me/g4f_channel](https://telegram.me/g4f_channel)
- Join our Discord Group: [discord.gg/XfybzPXPH5](https://discord.gg/XfybzPXPH5) - Join our Discord Group: [discord.gg/XfybzPXPH5](https://discord.gg/XfybzPXPH5)
- Check out [G4F, but 100% local](https://github.com/gpt4free/g4f-local)
## 🔻 Site Takedown ## 🔻 Site Takedown
Is your site on this repository and you want to take it down? Send an email to takedown@g4f.ai with proof it is yours and it will be removed as fast as possible. To prevent reproduction please secure your API ;) Is your site on this repository and you want to take it down? Send an email to takedown@g4f.ai with proof it is yours and it will be removed as fast as possible. To prevent reproduction please secure your API ;)

View File

@ -85,7 +85,9 @@ class Api:
if config.api_key is None and request is not None: if config.api_key is None and request is not None:
auth_header = request.headers.get("Authorization") auth_header = request.headers.get("Authorization")
if auth_header is not None: if auth_header is not None:
config.api_key = auth_header.split(None, 1)[-1] auth_header = auth_header.split(None, 1)[-1]
if auth_header and auth_header != "Bearer":
config.api_key = auth_header
response = self.client.chat.completions.create( response = self.client.chat.completions.create(
**config.dict(exclude_none=True), **config.dict(exclude_none=True),
ignored=self.list_ignored_providers ignored=self.list_ignored_providers

View File

@ -119,7 +119,7 @@ body {
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 15px; gap: 5px;
} }
.conversation #messages { .conversation #messages {
@ -129,11 +129,12 @@ body {
flex-direction: column; flex-direction: column;
overflow: auto; overflow: auto;
overflow-wrap: break-word; overflow-wrap: break-word;
padding-bottom: 20px; padding-bottom: 10px;
} }
.conversation .user-input { .conversation .user-input {
max-height: 200px; max-height: 200px;
margin-bottom: 10px;
} }
.conversation .user-input input { .conversation .user-input input {
@ -385,12 +386,29 @@ body {
font-size: 14px; font-size: 14px;
} }
.toolbar {
position: relative;
}
#input-count {
width: fit-content;
font-size: 12px;
padding: 6px 15px;
}
.stop_generating, .regenerate { .stop_generating, .regenerate {
position: absolute; position: absolute;
bottom: 122px;
left: 50%;
transform: translateX(-50%);
z-index: 1000000; z-index: 1000000;
top: 0;
right: 0;
}
@media only screen and (min-width: 40em) {
.stop_generating, .regenerate {
left: 50%;
transform: translateX(-50%);
right: auto;
}
} }
.stop_generating button, .regenerate button{ .stop_generating button, .regenerate button{
@ -399,7 +417,7 @@ body {
background-color: var(--blur-bg); background-color: var(--blur-bg);
border-radius: var(--border-radius-1); border-radius: var(--border-radius-1);
border: 1px solid var(--blur-border); border: 1px solid var(--blur-border);
padding: 10px 15px; padding: 5px 15px;
color: var(--colour-3); color: var(--colour-3);
display: flex; display: flex;
justify-content: center; justify-content: center;

View File

@ -112,19 +112,23 @@
</div> </div>
</div> </div>
<div class="conversation"> <div class="conversation">
<div class="stop_generating stop_generating-hidden"> <div id="messages" class="box"></div>
<button id="cancelButton"> <div class="toolbar">
<span>Stop Generating</span> <div id="input-count" class="">
<i class="fa-regular fa-stop"></i> &nbsp;
</button> </div>
</div> <div class="stop_generating stop_generating-hidden">
<div class="regenerate regenerate-hidden"> <button id="cancelButton">
<button id="regenerateButton"> <span>Stop Generating</span>
<span>Regenerate</span> <i class="fa-regular fa-stop"></i>
<i class="fa-solid fa-rotate"></i> </button>
</button> </div>
</div> <div class="regenerate regenerate-hidden">
<div class="box" id="messages"> <button id="regenerateButton">
<span>Regenerate</span>
<i class="fa-solid fa-rotate"></i>
</button>
</div>
</div> </div>
<div class="user-input"> <div class="user-input">
<div class="box input-box"> <div class="box input-box">

View File

@ -5,10 +5,14 @@ const message_input = document.getElementById(`message-input`);
const box_conversations = document.querySelector(`.top`); const box_conversations = document.querySelector(`.top`);
const stop_generating = document.querySelector(`.stop_generating`); const stop_generating = document.querySelector(`.stop_generating`);
const regenerate = document.querySelector(`.regenerate`); const regenerate = document.querySelector(`.regenerate`);
const send_button = document.querySelector(`#send-button`); const sidebar = document.querySelector(".conversations");
const imageInput = document.querySelector('#image'); const sidebar_button = document.querySelector(".mobile-sidebar");
const cameraInput = document.querySelector('#camera'); const send_button = document.getElementById("send-button");
const fileInput = document.querySelector('#file'); const imageInput = document.getElementById("image");
const cameraInput = document.getElementById("camera");
const fileInput = document.getElementById("file");
const inputCount = document.getElementById("input-count")
const modelSelect = document.getElementById("model");
let prompt_lock = false; let prompt_lock = false;
@ -63,6 +67,13 @@ const register_remove_message = async () => {
const delete_conversations = async () => { const delete_conversations = async () => {
localStorage.clear(); localStorage.clear();
for (let i = 0; i < localStorage.length; i++){
let key = localStorage.key(i);
if (key.startsWith("conversation:")) {
localStorage.removeItem(key);
}
}
hide_sidebar();
await new_conversation(); await new_conversation();
}; };
@ -75,6 +86,7 @@ const handle_ask = async () => {
if (message.length > 0) { if (message.length > 0) {
message_input.value = ''; message_input.value = '';
prompt_lock = true; prompt_lock = true;
count_input()
await add_conversation(window.conversation_id, message); await add_conversation(window.conversation_id, message);
if ("text" in fileInput.dataset) { if ("text" in fileInput.dataset) {
message += '\n```' + fileInput.dataset.type + '\n'; message += '\n```' + fileInput.dataset.type + '\n';
@ -89,6 +101,7 @@ const handle_ask = async () => {
if (input.files.length > 0) imageInput.dataset.src = URL.createObjectURL(input.files[0]); if (input.files.length > 0) imageInput.dataset.src = URL.createObjectURL(input.files[0]);
else delete imageInput.dataset.src else delete imageInput.dataset.src
model = modelSelect.options[modelSelect.selectedIndex].value
message_box.innerHTML += ` message_box.innerHTML += `
<div class="message" data-index="${message_index}"> <div class="message" data-index="${message_index}">
<div class="user"> <div class="user">
@ -97,11 +110,14 @@ const handle_ask = async () => {
<i class="fa-regular fa-phone-arrow-up-right"></i> <i class="fa-regular fa-phone-arrow-up-right"></i>
</div> </div>
<div class="content" id="user_${token}"> <div class="content" id="user_${token}">
<div class="content_inner">
${markdown_render(message)} ${markdown_render(message)}
${imageInput.dataset.src ${imageInput.dataset.src
? '<img src="' + imageInput.dataset.src + '" alt="Image upload">' ? '<img src="' + imageInput.dataset.src + '" alt="Image upload">'
: '' : ''
} }
</div>
<div class="count">${count_words_and_tokens(message, model)}</div>
</div> </div>
</div> </div>
`; `;
@ -120,19 +136,25 @@ const remove_cancel_button = async () => {
}, 300); }, 300);
}; };
const filter_messages = (messages) => { const filter_messages = (messages, filter_last_message = true) => {
// Removes none user messages at end // Removes none user messages at end
let last_message; if (filter_last_message) {
while (last_message = messages.pop()) { let last_message;
if (last_message["role"] == "user") { while (last_message = messages.pop()) {
messages.push(last_message); if (last_message["role"] == "user") {
break; messages.push(last_message);
break;
}
} }
} }
// Remove history, if it is selected // Remove history, if it is selected
if (document.getElementById('history')?.checked) { if (document.getElementById('history')?.checked) {
messages = [messages[messages.length-1]]; if (filter_last_message) {
messages = [messages.pop()];
} else {
messages = [messages.pop(), messages.pop()];
}
} }
let new_messages = []; let new_messages = [];
@ -165,7 +187,6 @@ const ask_gpt = async () => {
jailbreak = document.getElementById("jailbreak"); jailbreak = document.getElementById("jailbreak");
provider = document.getElementById("provider"); provider = document.getElementById("provider");
model = document.getElementById("model");
window.text = ''; window.text = '';
stop_generating.classList.remove(`stop_generating-hidden`); stop_generating.classList.remove(`stop_generating-hidden`);
@ -188,11 +209,13 @@ const ask_gpt = async () => {
<div class="content" id="gpt_${window.token}"> <div class="content" id="gpt_${window.token}">
<div class="provider"></div> <div class="provider"></div>
<div class="content_inner"><span id="cursor"></span></div> <div class="content_inner"><span id="cursor"></span></div>
<div class="count"></div>
</div> </div>
</div> </div>
`; `;
content = document.getElementById(`gpt_${window.token}`); content = document.getElementById(`gpt_${window.token}`);
content_inner = content.querySelector('.content_inner'); content_inner = content.querySelector('.content_inner');
content_count = content.querySelector('.count');
message_box.scrollTop = message_box.scrollHeight; message_box.scrollTop = message_box.scrollHeight;
window.scrollTo(0, 0); window.scrollTo(0, 0);
@ -200,7 +223,7 @@ const ask_gpt = async () => {
let body = JSON.stringify({ let body = JSON.stringify({
id: window.token, id: window.token,
conversation_id: window.conversation_id, conversation_id: window.conversation_id,
model: model.options[model.selectedIndex].value, model: modelSelect.options[modelSelect.selectedIndex].value,
jailbreak: jailbreak.options[jailbreak.selectedIndex].value, jailbreak: jailbreak.options[jailbreak.selectedIndex].value,
web_search: document.getElementById(`switch`).checked, web_search: document.getElementById(`switch`).checked,
provider: provider.options[provider.selectedIndex].value, provider: provider.options[provider.selectedIndex].value,
@ -270,6 +293,7 @@ const ask_gpt = async () => {
html = html.substring(0, lastIndex) + '<span id="cursor"></span>' + lastElement; html = html.substring(0, lastIndex) + '<span id="cursor"></span>' + lastElement;
} }
content_inner.innerHTML = html; content_inner.innerHTML = html;
content_count.innerText = count_words_and_tokens(text, provider?.model);
highlight(content_inner); highlight(content_inner);
} }
@ -380,7 +404,8 @@ const set_conversation = async (conversation_id) => {
await clear_conversation(); await clear_conversation();
await load_conversation(conversation_id); await load_conversation(conversation_id);
await load_conversations(); load_conversations();
hide_sidebar();
}; };
const new_conversation = async () => { const new_conversation = async () => {
@ -388,23 +413,23 @@ const new_conversation = async () => {
window.conversation_id = uuid(); window.conversation_id = uuid();
await clear_conversation(); await clear_conversation();
await load_conversations(); load_conversations();
hide_sidebar();
await say_hello() say_hello();
}; };
const load_conversation = async (conversation_id) => { const load_conversation = async (conversation_id) => {
let messages = await get_messages(conversation_id); let messages = await get_messages(conversation_id);
let elements = ""; let elements = "";
let last_model = null;
for (i in messages) { for (i in messages) {
let item = messages[i]; let item = messages[i];
last_model = item?.provider?.model;
let next_i = parseInt(i) + 1; let next_i = parseInt(i) + 1;
let next_provider = item.provider ? item.provider : (messages.length > next_i ? messages[next_i].provider : null); let next_provider = item.provider ? item.provider : (messages.length > next_i ? messages[next_i].provider : null);
let tokens_count = next_provider?.model ? count_tokens(next_provider.model, item.content) : "";
let append_count = tokens_count ? `, ${tokens_count} tokens` : ""; let provider_link = item.provider?.name ? `<a href="${item.provider?.url}" target="_blank">${item.provider.name}</a>` : "";
let words_count = `(${count_words(item.content)} words${append_count})`
let provider_link = item?.provider?.name ? `<a href="${item?.provider?.url}" target="_blank">${item.provider.name}</a>` : "";
let provider = provider_link ? ` let provider = provider_link ? `
<div class="provider"> <div class="provider">
${provider_link} ${provider_link}
@ -413,7 +438,7 @@ const load_conversation = async (conversation_id) => {
` : ""; ` : "";
elements += ` elements += `
<div class="message" data-index="${i}"> <div class="message" data-index="${i}">
<div class=${item.role == "assistant" ? "assistant" : "user"}> <div class="${item.role}">
${item.role == "assistant" ? gpt_image : user_image} ${item.role == "assistant" ? gpt_image : user_image}
<i class="fa-solid fa-xmark"></i> <i class="fa-solid fa-xmark"></i>
${item.role == "assistant" ${item.role == "assistant"
@ -424,15 +449,16 @@ const load_conversation = async (conversation_id) => {
<div class="content"> <div class="content">
${provider} ${provider}
<div class="content_inner">${markdown_render(item.content)}</div> <div class="content_inner">${markdown_render(item.content)}</div>
<div class="count">${words_count}</div> <div class="count">${count_words_and_tokens(item.content, next_provider?.model)}</div>
</div> </div>
</div> </div>
`; `;
} }
const filtered = filter_messages(messages); const filtered = filter_messages(messages, false);
if (filtered.length > 0) { if (filtered.length > 0) {
let count_total = GPTTokenizer_cl100k_base?.encodeChat(filtered, "gpt-3.5-turbo").length last_model = last_model?.startsWith("gpt-4") ? "gpt-4" : "gpt-3.5-turbo"
let count_total = GPTTokenizer_cl100k_base?.encodeChat(filtered, last_model).length
if (count_total > 0) { if (count_total > 0) {
elements += `<div class="count_total">(${count_total} tokens used)</div>`; elements += `<div class="count_total">(${count_total} tokens used)</div>`;
} }
@ -440,7 +466,7 @@ const load_conversation = async (conversation_id) => {
message_box.innerHTML = elements; message_box.innerHTML = elements;
await register_remove_message(); register_remove_message();
highlight(message_box); highlight(message_box);
message_box.scrollTo({ top: message_box.scrollHeight, behavior: "smooth" }); message_box.scrollTo({ top: message_box.scrollHeight, behavior: "smooth" });
@ -450,13 +476,14 @@ const load_conversation = async (conversation_id) => {
}, 500); }, 500);
}; };
function count_words(text) { // https://stackoverflow.com/questions/20396456/how-to-do-word-counts-for-a-mixture-of-english-and-chinese-in-javascript
var matches = text.match(/[\w\d\\'-]+/gi); function count_words(str) {
var matches = str.match(/[\u00ff-\uffff]|\S+/g);
return matches ? matches.length : 0; return matches ? matches.length : 0;
} }
function count_tokens(model, text) { function count_tokens(model, text) {
if (model.startsWith("gpt-3") || model.startsWith("gpt-4") || model.startsWith("text-davinci")) { if (model.startsWith("gpt-3") || model.startsWith("gpt-4")) {
return GPTTokenizer_cl100k_base?.encode(text).length; return GPTTokenizer_cl100k_base?.encode(text).length;
} }
if (model.startsWith("llama2") || model.startsWith("codellama")) { if (model.startsWith("llama2") || model.startsWith("codellama")) {
@ -467,6 +494,12 @@ function count_tokens(model, text) {
} }
} }
function count_words_and_tokens(text, model) {
const tokens_count = model ? count_tokens(model, text) : null;
const tokens_append = tokens_count ? `, ${tokens_count} tokens` : "";
return `(${count_words(text)} words${tokens_append})`
}
const get_conversation = async (conversation_id) => { const get_conversation = async (conversation_id) => {
let conversation = await JSON.parse( let conversation = await JSON.parse(
localStorage.getItem(`conversation:${conversation_id}`) localStorage.getItem(`conversation:${conversation_id}`)
@ -503,7 +536,9 @@ const add_conversation = async (conversation_id, content) => {
const hide_last_message = async (conversation_id) => { const hide_last_message = async (conversation_id) => {
const conversation = await get_conversation(conversation_id) const conversation = await get_conversation(conversation_id)
const last_message = conversation.items.pop(); const last_message = conversation.items.pop();
last_message["regenerate"] = true; if (last_message["role"] == "assistant") {
last_message["regenerate"] = true;
}
conversation.items.push(last_message); conversation.items.push(last_message);
localStorage.setItem( localStorage.setItem(
@ -605,15 +640,17 @@ const message_id = () => {
return BigInt(`0b${unix}${random_bytes}`).toString(); return BigInt(`0b${unix}${random_bytes}`).toString();
}; };
document.querySelector(".mobile-sidebar").addEventListener("click", (event) => { async function hide_sidebar() {
const sidebar = document.querySelector(".conversations"); sidebar.classList.remove("shown");
sidebar_button.classList.remove("rotated");
}
sidebar_button.addEventListener("click", (event) => {
if (sidebar.classList.contains("shown")) { if (sidebar.classList.contains("shown")) {
sidebar.classList.remove("shown"); hide_sidebar();
event.target.classList.remove("rotated");
} else { } else {
sidebar.classList.add("shown"); sidebar.classList.add("shown");
event.target.classList.add("rotated"); sidebar_button.classList.add("rotated");
} }
window.scrollTo(0, 0); window.scrollTo(0, 0);
@ -703,28 +740,34 @@ colorThemes.forEach((themeOption) => {
}); });
}); });
const count_input = async () => {
if (message_input.value) {
model = modelSelect.options[modelSelect.selectedIndex].value;
inputCount.innerText = count_words_and_tokens(message_input.value, model);
} else {
inputCount.innerHTML = "&nbsp;"
}
};
message_input.addEventListener("keyup", count_input);
window.onload = async () => { window.onload = async () => {
setTheme(); setTheme();
let conversations = 0; count_input();
for (let i = 0; i < localStorage.length; i++) {
if (localStorage.key(i).startsWith("conversation:")) { if (/\/chat\/.+/.test(window.location.href)) {
conversations += 1; load_conversation(window.conversation_id);
} } else {
say_hello()
} }
await setTimeout(() => { setTimeout(() => {
load_conversations(); load_conversations();
}, 1); }, 1);
if (/\/chat\/.+/.test(window.location.href)) { message_input.addEventListener("keydown", async (evt) => {
await load_conversation(window.conversation_id);
} else {
await say_hello()
}
message_input.addEventListener(`keydown`, async (evt) => {
if (prompt_lock) return; if (prompt_lock) return;
if (evt.keyCode === 13 && !evt.shiftKey) { if (evt.keyCode === 13 && !evt.shiftKey) {
evt.preventDefault(); evt.preventDefault();
console.log("pressed enter"); console.log("pressed enter");
@ -768,12 +811,11 @@ observer.observe(message_input, { attributes: true });
(async () => { (async () => {
response = await fetch('/backend-api/v2/models') response = await fetch('/backend-api/v2/models')
models = await response.json() models = await response.json()
let select = document.getElementById('model');
for (model of models) { for (model of models) {
let option = document.createElement('option'); let option = document.createElement('option');
option.value = option.text = model; option.value = option.text = model;
select.appendChild(option); modelSelect.appendChild(option);
} }
response = await fetch('/backend-api/v2/providers') response = await fetch('/backend-api/v2/providers')