KodaChat is generating..

This commit is contained in:
2026-02-22 14:55:37 +10:00
parent ee85c48273
commit b2e84d6f37
19 changed files with 1266 additions and 1 deletions

2
.gitignore vendored
View File

@@ -6,7 +6,7 @@ __pycache__/
*.log
# Виртуальное окружение
# venv/
.venv/
# env/
# IDE

13
GPT_chat.code-workspace Normal file
View File

@@ -0,0 +1,13 @@
{
"folders": [
{
"name": "GPT_chat",
"path": "."
},
{
"name": "data",
"path": "../Free-GPT4-WEB-API/src/data"
}
],
"settings": {}
}

1
data/cookies.json Normal file
View File

@@ -0,0 +1 @@
{"field": "value"}

1
data/proxies.json Normal file
View File

@@ -0,0 +1 @@
[{"protocol": "socks5", "username": "usr", "password": "pass", "ip": "0.0.0.0", "port": "8080"}, {"protocol": "https", "username": "usr", "password": "pass", "ip": "0.0.0.1", "port": "443"}]

BIN
data/settings.db Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
1756455950.493793

View File

@@ -0,0 +1 @@
{"api_key": null, "cookies": {}}

File diff suppressed because one or more lines are too long

564
main.py Normal file
View File

@@ -0,0 +1,564 @@
import re
import argparse
import json
import os
import random
import string
import sqlite3
from uuid import uuid4
import threading
# GPT Library
import g4f
from g4f.api import run_api
# Server
from flask import Flask, redirect, render_template
from flask import request
import getpass
from werkzeug.security import generate_password_hash, check_password_hash
from werkzeug.utils import secure_filename
app = Flask(__name__)
UPLOAD_FOLDER = 'data/'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1000 * 1000 # 16 MB
# Settings file path
SETTINGS_FILE = "./data/settings.db"
PROXIES_FILE = "./data/proxies.json"
# Available providers
PROVIDERS = {
"Auto": "",
# "Acytoo": g4f.Provider.Acytoo,
# "Aichat": g4f.Provider.Aichat,
# "Ails": g4f.Provider.Ails,
# "BlackBox": g4f.Provider.Blackbox,
# "Chatgpt4o": g4f.Provider.Chatgpt4o,
# "ChatGpt": g4f.Provider.ChatGpt,
# "ChatGptt" : g4f.Provider.ChatGptt,
# "DeepInfraChat": g4f.Provider.DeepInfraChat,
# "Glider": g4f.Provider.Glider,
# "H2o": g4f.Provider.H2o,
# "HuggingChat": g4f.Provider.HuggingChat,
# "Opchatgpts": g4f.Provider.Opchatgpts,
# "OpenAssistant": g4f.Provider.OpenAssistant,
# "OpenaiChat": g4f.Provider.OpenaiChat,
# "Raycast": g4f.Provider.Raycast,
# "Theb": g4f.Provider.Theb,
# "Wewordle": g4f.Provider.Wewordle,
# "You": g4f.Provider.You,
# "Yqcloud": g4f.Provider.Yqcloud,
# "Pizzagpt": g4f.Provider.Pizzagpt,
# "HuggingChat": g4f.Provider.HuggingChat,
# "HuggingFace": g4f.Provider.HuggingFace
}
GENERIC_MODELS = ["gpt-3.5-turbo", "gpt-4", "gpt-4o","gpt-4o-mini"]
print(
"""
FreeGPT4 Web API - A Web API for GPT-4
Repo: github.com/aledipa/FreeGPT4-WEB-API
By: Alessandro Di Pasquale
GPT4Free Credits: github.com/xtekky/gpt4free
""",
)
parser = argparse.ArgumentParser()
parser.add_argument(
"--remove-sources",
action='store_true',
required=False,
help="Remove the sources from the response",
)
parser.add_argument(
"--enable-gui",
action='store_true',
required=False,
help="Use a graphical interface for settings",
)
parser.add_argument(
"--private-mode",
action='store_true',
required=False,
help="Use a private token to access the API",
)
parser.add_argument(
"--enable-proxies",
action='store_true',
required=False,
help="Use one or more proxies to avoid being blocked or banned",
)
parser.add_argument(
"--enable-history",
action='store_true',
required=False,
help="Enable the history of the messages",
)
parser.add_argument(
"--password",
action='store',
required=False,
help="Set or change the password for the settings page [mandatory in docker envirtonment]",
)
parser.add_argument(
"--cookie-file",
action='store',
required=False,
type=str,
help="Use a cookie file",
)
parser.add_argument(
"--file-input",
action='store_true',
required=False,
help="Add the file as input support",
)
parser.add_argument(
"--port",
action='store',
required=False,
type=str,
help="Change the port (default: 5500)",
)
parser.add_argument(
"--model",
action='store',
required=False,
type=str,
help="Change the model (default: gpt_4)",
)
parser.add_argument(
"--provider",
action='store',
required=False,
type=str,
help="Change the provider (default: Bing)",
)
parser.add_argument(
"--keyword",
action='store',
required=False,
type=str,
help="Add the keyword support",
)
parser.add_argument(
"--system-prompt",
action='store',
required=False,
type=str,
help="Use a system prompt to 'customize' the answers",
)
parser.add_argument(
"--enable-fast-api",
action='store_true',
required=False,
help="Use the fast API standard (PORT 1337 - compatible with OpenAI integrations)",
)
args, unknown = parser.parse_known_args()
# Get the absolute path of the script
script_path = os.path.abspath(__file__)
# Get the directory of the script
script_dir = os.path.dirname(script_path)
# Change the current working directory
os.chdir(script_dir)
# Loads the proxies from the file
if (args.enable_proxies and os.path.exists(PROXIES_FILE)):
proxies = json.load(open(PROXIES_FILE))
else:
proxies = None
# Crates the Fast API Thread
t = threading.Thread(target=run_api, name="fastapi")
# Loads the settings from the file
def load_settings(file=SETTINGS_FILE):
conn = sqlite3.connect(file)
c = conn.cursor()
c.execute("SELECT * FROM settings")
data = c.fetchone()
conn.close()
# Converts the data to a dictionary
data = {
"keyword": data[1],
"file_input": data[2],
"port": data[3],
"provider": data[4],
"model": data[5],
"cookie_file": data[6],
"token": data[7],
"remove_sources": data[8],
"system_prompt": data[9],
"message_history": data[10],
"proxies": data[11],
"password": data[12],
"fast_api": data[13]
}
print(data)
return data
def start_fast_api():
print("[!] FAST API PORT: 1336")
t.daemon = True
t.start()
# Updates the settings
data = load_settings()
if (args.keyword == None):
args.keyword = data["keyword"]
if (args.file_input == False):
args.file_input = data["file_input"]
if (args.port == None):
args.port = data["port"]
if (args.provider == None):
args.provider = data["provider"]
if (args.model == None):
args.model = data["model"]
if (args.cookie_file == None):
args.cookie_file = data["cookie_file"]
if (args.private_mode and data["token"] == ""):
token = str(uuid4())
data["token"] = token
elif (data["token"] != ""):
token = data["token"]
args.private_mode = True
if (args.remove_sources == False):
args.remove_sources = data["remove_sources"]
if (args.system_prompt == None):
args.system_prompt = data["system_prompt"]
if (args.enable_history == False):
args.enable_history = data["message_history"]
if (args.enable_proxies == False):
args.enable_proxies = data["proxies"]
if (args.enable_fast_api or data["fast_api"]):
start_fast_api()
# Initializes the message history
message_history = [{"role": "system", "content": args.system_prompt}]
if (args.enable_gui):
# Asks for password to set to lock the settings page
# Checks if settings.db contains a password
try:
set_password = True
if (args.password != None):
password = args.password
confirm_password = password
else:
if (data["password"] == ""):
password = getpass.getpass("Settings page password:\n > ")
confirm_password = getpass.getpass("Confirm password:\n > ")
else:
set_password = False
if (set_password):
if(password == "" or confirm_password == ""):
print("[X] Password cannot be empty")
exit()
if ((password != confirm_password) and (data["password"] == "")):
print("[X] Passwords don't match")
exit()
else:
password = generate_password_hash(password)
confirm_password = generate_password_hash(confirm_password)
print("[V] Password set.")
try:
conn = sqlite3.connect(SETTINGS_FILE)
c = conn.cursor()
c.execute("UPDATE settings SET password = ?", (password,))
conn.commit()
conn.close()
print("[V] Password saved.")
except Exception as error:
print("[X] Error:", error)
exit()
except Exception as error:
print("[X] Error:", error)
exit()
else:
print("[!] GUI disabled")
# Saves the settings to the file
def save_settings(request, file):
conn = sqlite3.connect(file)
c = conn.cursor()
c.execute("UPDATE settings SET file_input = ?", (bool(request.form["file_input"] == "true"),))
c.execute("UPDATE settings SET remove_sources = ?", (bool(request.form["remove_sources"] == "true"),))
c.execute("UPDATE settings SET port = ?", (request.form["port"],))
c.execute("UPDATE settings SET model = ?", (request.form["model"],))
c.execute("UPDATE settings SET keyword = ?", (request.form["keyword"],))
c.execute("UPDATE settings SET provider = ?", (request.form["provider"],))
c.execute("UPDATE settings SET system_prompt = ?", (request.form["system_prompt"],))
c.execute("UPDATE settings SET message_history = ?", (bool(request.form["message_history"] == "true"),))
c.execute("UPDATE settings SET proxies = ?", (bool(request.form["proxies"] == "true"),))
c.execute("UPDATE settings SET fast_api = ?", (bool(request.form["fast_api"] == "true"),))
if (len(request.form["new_password"]) > 0):
c.execute("UPDATE settings SET password = ?", (generate_password_hash(request.form["new_password"]),))
file = request.files["cookie_file"]
if (bool(request.form["private_mode"] == "true")):
c.execute("UPDATE settings SET token = ?", (request.form["token"],))
args.private_mode = True
else:
c.execute("UPDATE settings SET token = ''")
args.private_mode = False
#checks if the file is not empty
if file.filename != '':
#checks if the file is a json file
if file.filename.endswith('.json'):
#saves the file
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
#updates the cookie_file
c.execute("UPDATE settings SET cookie_file = ?", (os.path.join(app.config['UPLOAD_FOLDER'], filename),))
else:
print("The file is not a json file")
if (args.enable_proxies or bool(request.form["proxies"] == "true")):
# Extracts the proxies from the form
proxies = []
i = 1
while True:
if (("proxy_" + str(i)) in request.form):
proxy = request.form["proxy_" + str(i)]
if (proxy != ""):
# Checks if the proxy syntax is correct
if (proxy.count(":") == 3 and proxy.count("@") == 1):
proxy = {
"protocol": proxy.split("://")[0],
"username": proxy.split("://")[1].split(":")[0],
"password": proxy.split("://")[1].split(":")[1].split("@")[0],
"ip": proxy.split("://")[1].split(":")[1].split("@")[1],
"port": proxy.split("://")[1].split(":")[2]
}
proxies.append(proxy)
i += 1
else:
break
# Saves the proxies to the file proxies.json
with open(PROXIES_FILE, "w") as pf:
json.dump(proxies, pf)
pf.close()
if (bool(request.form["fast_api"] == "true") and not args.enable_fast_api):
start_fast_api()
conn.commit()
conn.close()
return
# Loads the settings from the file and updates the args
def apply_settings(file):
data = load_settings(file)
args.keyword = data["keyword"]
args.file_input = data["file_input"]
args.port = data["port"]
args.provider = data["provider"]
args.model = data["model"]
args.cookie_file = data["cookie_file"]
args.token = data["token"]
args.remove_sources = data["remove_sources"]
args.system_prompt = data["system_prompt"]
args.enable_history = data["message_history"]
args.enable_proxies = data["proxies"]
args.password = data["password"]
args.enable_fast_api = data["fast_api"]
return
@app.route("/", methods=["GET", "POST"])
async def index() -> str:
"""
Main function
"""
# Starts the bot and gets the input
print("Initializing...")
question = None
print("start")
if request.method == "GET":
question = request.args.get(args.keyword) #text
print(args.private_mode)
if (args.private_mode and request.args.get("token") != data["token"]):
return "<p id='response'>Invalid token</p>"
print("get")
else:
file = request.files["file"]
text = file.read().decode("utf-8")
question = text
print("Post reading the file", question)
print("ici")
if question is None:
return "<p id='response'>Please enter a question</p>"
# Gets the response from the bot
# print(PROVIDERS[args.provider].params) # supported args
print("\nCookies: " + str(len(args.cookie_file)))
print("\nInput: " + question)
if (len(args.cookie_file) != 0):
try:
cookies = json.load(open(args.cookie_file)) # Loads the cookies from the file
print("COOKIES: "+str(cookies))
if (len(cookies) == 0):
cookies = {"a": "b"} # Dummy cookies
except Exception as error:
print("[X] Error:", error)
exit()
else:
cookies = {"a": "b"} # Dummy cookies
if (args.enable_history):
print("History enabled")
message_history.append({"role": "user", "content": question})
else:
print("History disabled")
message_history.clear()
message_history.append({"role": "system", "content": args.system_prompt})
message_history.append({"role": "user", "content": question})
proxy = None
if (args.enable_proxies):
# Extracts a proxy from the list
proxy = random.choice(proxies)
# Formats the proxy like https://user:password@host:port
proxy = f"{proxy['protocol']}://{proxy['username']}:{proxy['password']}@{proxy['ip']}:{proxy['port']}"
print("Proxy: " + proxy)
if (args.provider == "Auto"):
response = (
await g4f.ChatCompletion.create_async(
model=args.model,
messages=message_history,
cookies=cookies,
proxy=proxy
)
)
else:
response = (
await g4f.ChatCompletion.create_async(
model=args.model,
provider=args.provider,
messages=message_history,
cookies=cookies,
proxy=proxy
)
)
print(response)
#Joins the response into a single string
resp_str = ""
for message in response:
resp_str += message
# Cleans the response from the resources links
if (args.remove_sources):
if re.search(r"\[\^[0-9]+\^\]\[[0-9]+\]", resp_str):
resp_str = resp_str.split("\n\n")
if len(resp_str) > 1:
resp_str.pop(0)
resp_str = re.sub(r"\[\^[0-9]+\^\]\[[0-9]+\]", "", str(resp_str[0]))
# Returns the response
return resp_str
# return "<p id='response'>" + resp + "</p>" # Uncomment if preferred
def auth():
# Reads the password from the data file
data = load_settings()
# Checks if the password is set
if (data["password"] != ""):
if (request.method == "POST"):
password = request.form["password"]
if (check_password_hash(data["password"], password)):
return True
else:
return False
else:
return False
else:
return True
@app.route("/login", methods=["GET", "POST"])
async def login():
if (args.enable_gui):
return render_template("login.html", **locals())
else:
return "The GUI is disabled. In order to enable it, use the --enable-gui argument."
@app.route("/settings", methods=["GET", "POST"])
async def settings():
if (request.method == "GET"):
return redirect("/login", code=302)
if (auth()):
try:
providers=PROVIDERS
generic_models=GENERIC_MODELS
data = load_settings()
proxies = json.loads(open(PROXIES_FILE).read())
return render_template("settings.html", **locals())
except Exception as error:
print("[X] Error:", error)
return "Error: " + str(error)
else:
return render_template("login.html", **locals())
@app.route("/save", methods=["POST"])
async def save():
if (auth()):
try:
if (request.method == "POST"):
save_settings(request, SETTINGS_FILE)
apply_settings(SETTINGS_FILE)
return "New settings saved and applied successfully!"
else:
return render_template("login.html", **locals())
except Exception as error:
print("[X] Error:", error)
return "Error: " + str(error)
else:
return render_template("login.html", **locals())
@app.route("/models", methods=["GET"])
async def get_models():
provider = request.args.get("provider")
if (provider == "Auto"):
return GENERIC_MODELS
try:
return PROVIDERS[provider].models
except:
return ["default"]
@app.route("/generatetoken", methods=["GET", "POST"])
async def get_token():
return str(uuid4())
if __name__ == "__main__":
# # Starts the server, change the port if needed
app.run("0.0.0.0", port=args.port, debug=False)

13
requirements.txt Normal file
View File

@@ -0,0 +1,13 @@
curl_cffi
aiohttp
aiohttp_socks
auth
Flask[async]
g4f
Werkzeug>=3.0.3
aiohttp_socks
nodriver
# pysqlite3
python-multipart
uvicorn
fastapi

73
static/css/style.css Normal file
View File

@@ -0,0 +1,73 @@
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap");
/* font-family: 'Inter', sans-serif; */
body {
margin: 0;
padding: 0;
font-family: "Inter", sans-serif;
background-color: #e4f2f4;
}
input::placeholder {
font-style: italic;
}
textarea::placeholder {
font-style: italic;
}
.inter {
font-family: "Inter", sans-serif;
}
.darkblue {
color: #034953;
}
.blue {
color: #007080;
}
.label_gray {
color: #c8d9de;
}
.label_green {
color: #035306;
}
.label_red {
color: #8b0000;
}
.border_red {
border-color: #8b0000;
}
.label_darkorange {
color: #8b7b00;
}
.darkblue_bg {
background-color: #034953;
}
.darkgreen_bg {
background-color: #035306;
}
.darkred_bg {
background-color: #8b0000;
}
.blue_bg {
background-color: #007080;
}
.lightpink_bg {
background-color: #fff2f2;
}
.fileholder {
background-color: white !important;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

252
static/js/script.js Normal file
View File

@@ -0,0 +1,252 @@
// Turns on the button
function turnOn(idOn, idOff, set) {
document.getElementById(idOff).style.color = "#C8D9DE";
document.getElementById(idOn).style.color = "#035306";
document.getElementById(set).style.color = "#035306";
}
// Turns off the button
function turnOff(idOn, idOff, set) {
document.getElementById(idOff).style.color = "#8B0000";
document.getElementById(idOn).style.color = "#C8D9DE";
document.getElementById(set).style.color = "#8B0000";
}
// Changes the button text and color
function changeButton(buttonID) {
document.getElementById(buttonID).style.backgroundColor = "#035306";
document.getElementById(buttonID).textContent = "Change";
}
// Hides an element and shows another
function replaceElement(idToHide, idToShow) {
document.getElementById(idToHide).hidden = true;
document.getElementById(idToShow).hidden = false;
}
// Shows an element
function showElement(idToShow) {
element = document.getElementById(idToShow);
element.hidden = false;
}
// Hides an element
function hideElement(idToHide) {
element = document.getElementById(idToHide);
element.hidden = true;
}
// Hides a composed warning
function hideWarning(idToHide) {
document.getElementById(idToHide).classList.add("hidden");
}
// Shows a composed warning
function showWarning(idToShow) {
document.getElementById(idToShow).classList.remove("hidden");
}
// Copies the text in the given id to the clipboard
function copyTextToClipboardFromName(name) {
var copyText = document.getElementsByName(name)[0].value;
navigator.clipboard.writeText(copyText);
}
// Replaces the element in the given ID with the given text
function replaceElementWithText(id, text) {
var element = document.getElementById(id);
var originalElement = element;
// Creates a new element
var newElement = document.createElement("p");
// Adds some content to the new element
newElement.textContent = text;
newElement.classList.add("darkblue");
newElement.classList.add("inline-block");
newElement.id = id;
// Replaces the old element with the new one
element.parentNode.replaceChild(newElement, element);
return originalElement;
}
function copy(name, id, text) {
copyTextToClipboardFromName(name);
var oldElement = replaceElementWithText(id, text);
setTimeout(
(param) => {
var textElement = document.getElementById(id);
textElement.parentNode.replaceChild(param, textElement);
},
2000,
oldElement,
);
}
function createToken(targetID) {
$.ajax({
url: "/generatetoken",
data: {},
success: function (response) {
var tokenText = document.getElementById(targetID).innerText;
if (tokenText.length == 0) {
document.getElementById(targetID).innerText = response;
document.getElementById("token").value = response;
}
},
});
}
function addProxy() {
// Gets the number of proxy input fields in 'proxy_list_div'
var proxyListDiv = document.getElementById("proxy_list_div");
var proxyList = proxyListDiv.getElementsByTagName("input");
// Checks if there's already an empty proxy input field
for (var i = 0; i < proxyList.length; i++) {
if (proxyList[i].value.length == 0) {
return;
}
}
// adds a new proxy input field
var newProxyDiv = document.createElement("div");
newProxyDiv.classList.add("flex", "justify-start", "mb-3");
var newProxy = document.createElement("input");
newProxy.type = "text";
newProxy.name = "proxy_" + (proxyList.length + 1);
newProxy.id = newProxy.name;
newProxy.classList.add(
"input",
"outline-none",
"py-1",
"px-2",
"rounded-lg",
"inter",
"w-full",
);
newProxy.placeholder = "protocol://user:password@host:port";
newProxy.setAttribute("onchange", "checkAllProxies()");
newProxyDiv.appendChild(newProxy);
// adds the 'delete' image button to the new proxy input field
var deleteButton = document.createElement("img");
deleteButton.src = "static/img/delete(Anggara).png";
deleteButton.classList.add(
"inline-block",
"mx-2",
"p-1",
"hover:brightness-125",
);
deleteButton.width = 32;
deleteButton.onclick = function () {
newProxyDiv.remove();
};
newProxyDiv.appendChild(deleteButton);
document.getElementById("proxy_list_div").appendChild(newProxyDiv);
}
function deleteElement(id) {
document.getElementById(id).remove();
}
// Checks if the proxy syntax is correct
function checkProxySyntax(proxy) {
var proxyRegex =
/^((http|https|socks4|socks5):\/\/)?([a-zA-Z0-9]+:[a-zA-Z0-9]+@)?([a-zA-Z0-9.-]+):([0-9]+)$/;
return proxyRegex.test(proxy);
}
// Warns the user if the proxy syntax is incorrect
function warnProxySyntax(proxy, proxyID) {
if (checkProxySyntax(proxy)) {
document
.getElementById(proxyID)
.classList.remove("border_red", "label_red", "lightpink_bg");
} else {
document
.getElementById(proxyID)
.classList.add("border_red", "border", "label_red", "lightpink_bg");
}
}
// Checks the syntax at every proxy input field at input change
function checkAllProxies() {
var proxyListDiv = document.getElementById("proxy_list_div");
var proxyList = proxyListDiv.getElementsByTagName("input");
for (var i = 0; i < proxyList.length; i++) {
warnProxySyntax(proxyList[i].value, proxyList[i].id);
}
}
// Checks if new password and confirm password match
function checkPasswordMatch() {
var newPassword = document.getElementById("new_password").value;
var confirmPassword = document.getElementById("confirm_password").value;
if (newPassword == confirmPassword) {
if (newPassword.length > 0) {
replaceElement("error_password", "success_password");
return true;
}
} else {
replaceElement("success_password", "error_password");
}
return false;
}
// Opens the update password form
function openPasswordUpdate() {
replaceElement("open_password_update", "cancel_password_update");
showElement("password_update");
}
// Closes the update password form
function cancelPasswordUpdate() {
hideElement("password_update");
hideElement("success_password");
hideElement("error_password");
replaceElement("cancel_password_update", "open_password_update");
document.getElementById("new_password").value = "";
document.getElementById("confirm_password").value = "";
}
// Enables the save button if the password is correct
function enableSaveButton() {
var pass_length = document.getElementById("password").value.length;
var isPasswordUpdateClosed = document.getElementById("password_update").hidden;
if ((checkPasswordMatch() || isPasswordUpdateClosed) && pass_length > 0) {
replaceElement('save_label_dummy', 'save_label');
} else {
replaceElement('save_label', 'save_label_dummy');
}
}
// Gets available models for choosen A.I. Provider
$(document).ready(function () {
$("#provider").change(function () {
var inputValue = $(this).val();
$.ajax({
url: "/models",
data: {
provider: inputValue,
},
success: function (response) {
var select = document.getElementById("model");
// Remove existing models
while (select.firstChild) {
select.removeChild(select.firstChild);
}
// Add the new models
for (var i = 0; i < response.length; i++) {
var option = document.createElement("option");
option.innerText = response[i];
option.value = response[i];
select.add(option);
}
},
});
});
});

49
templates/login.html Normal file
View File

@@ -0,0 +1,49 @@
<!-- flask login page -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet" type="text/css">
<link rel="shortcut icon" href="{{ url_for('static', filename='img/favicon(Nicoladipa).png') }}" type="image/x-icon">
<title>Login</title>
</head>
<body>
{% block content %}
<!-- Logo on the left and title on the right -->
<table class="w-50 m-7">
<tr>
<td class="m-7">
<img src="{{ url_for('static', filename='img/favicon(Nicoladipa).png') }}" width="96" class="inline-block" alt="Logo">
</td>
<td class="m-7">
<h1 class="title font-bold inter darkblue text-4xl">FreeGPT4</h1>
<h2 class="title blue
text-2xl">Server settings</h2>
</td>
</tr>
</table>
<div class="flex justify-center w-screen h-96">
<div class="flex items-center">
<form method="POST" action="/settings">
<div class="field">
<div class="control">
<input class="input outline-none py-3 px-4 rounded-lg inter w-56" type="password" name="password" placeholder="Your Password">
</div>
</div>
<button class="button outline-none py-3 px-4 rounded-lg darkblue_bg inter text-white text-md mt-5 hover:opacity-95 w-56">Login</button>
</form>
</div>
</div>
{% endblock %}
</body>
<!-- script import -->
<script src="{{ url_for('static', filename='js/script.js') }}" type="text/javascript"></script>
</html>
<!-- Path: src/templates/settings.html -->

292
templates/settings.html Normal file
View File

@@ -0,0 +1,292 @@
<!-- flask settings page -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet" type="text/css">
<link rel="shortcut icon" href="{{ url_for('static', filename='img/favicon(Nicoladipa).png') }}" type="image/x-icon">
<title>Settings</title>
</head>
<body>
{% block content %}
<!-- Logo on the left and title on the right -->
<table class="w-50 m-7">
<tr>
<td class="m-7">
<img src="{{ url_for('static', filename='img/favicon(Nicoladipa).png') }}" width="96" class="inline-block" alt="Logo">
</td>
<td class="m-7">
<h1 class="title font-bold inter darkblue text-4xl">FreeGPT4</h1>
<h2 class="title blue
text-2xl">Server settings</h2>
</td>
</tr>
</table>
<form class="m-7 mt-10 inter" action="/save" method="post" enctype=multipart/form-data>
<table class="table-fixed w-full">
<tr>
<td class="py-1 fond-bold inter darkblue text-lg border-b border-slate-800" id="set_0"><b>File input support:</b></td>
<td class="py-1 fond-bold inter darkblue text-lg">
{% if data['file_input'] %}
<input type="radio" id="off_rad_0" name="file_input" value="false" hidden oninput="turnOff('on_0', 'off_0', 'set_0')">
<label for="off_rad_0" id="off_0" class="label_gray"><b>OFF</b></label>
<p class="inline-block mx-1"> - </p>
<input type="radio" id="on_rad_0" id="file_input" name="file_input" value="true" hidden oninput="turnOn('on_0', 'off_0', 'set_0')" checked>
<label for="on_rad_0" id="on_0" class="label_green"><b>ON</b></label>
{% else %}
<input type="radio" id="off_rad_0" name="file_input" value="false" hidden oninput="turnOff('on_0', 'off_0', 'set_0')" checked>
<label for="off_rad_0" id="off_0" class="label_red"><b>OFF</b></label>
<p class="inline-block mx-1"> - </p>
<input type="radio" id="on_rad_0" id="file_input" name="file_input" value="true" hidden oninput="turnOn('on_0', 'off_0', 'set_0')">
<label for="on_rad_0" id="on_0" class="label_gray"><b>ON</b></label>
{% endif %}
</td>
</tr>
<tr>
<td class="py-1 fond-bold inter darkblue text-lg border-b border-slate-800" id="set_1"><b>Show sources:</b></td>
<td class="py-1 fond-bold inter darkblue text-lg">
{% if data['remove_sources'] %}
<input type="radio" id="off_rad_1" name="remove_sources" value="true" hidden oninput="turnOff('on_1', 'off_1', 'set_1')" checked>
<label for="off_rad_1" id="off_1" class="label_red"><b>OFF</b></label>
<p class="inline-block mx-1"> - </p>
<input type="radio" id="on_rad_1" name="remove_sources" value="false" hidden oninput="turnOn('on_1', 'off_1', 'set_1')">
<label for="on_rad_1" id="on_1" class="label_gray"><b>ON</b></label>
{% else %}
<input type="radio" id="off_rad_1" name="remove_sources" value="true" hidden oninput="turnOff('on_1', 'off_1', 'set_1')">
<label for="off_rad_1" id="off_1" class="label_gray"><b>OFF</b></label>
<p class="inline-block mx-1"> - </p>
<input type="radio" id="on_rad_1" name="remove_sources" value="false" hidden oninput="turnOn('on_1', 'off_1', 'set_1')" checked>
<label for="on_rad_1" id="on_1" class="label_green"><b>ON</b></label>
{% endif %}
</td>
</tr>
<tr>
<td class="py-1 fond-bold inter darkblue text-lg border-b border-slate-800" id="set_2"><b>Message history:</b></td>
<td class="py-1 fond-bold inter darkblue text-lg">
{% if data['message_history'] %}
<input type="radio" id="off_rad_2" name="message_history" value="false" hidden oninput="turnOff('on_2', 'off_2', 'set_2')">
<label for="off_rad_2" id="off_2" class="label_gray"><b>OFF</b></label>
<p class="inline-block mx-1"> - </p>
<input type="radio" id="on_rad_2" name="message_history" value="true" hidden oninput="turnOn('on_2', 'off_2', 'set_2')" checked>
<label for="on_rad_2" id="on_2" class="label_green"><b>ON</b></label>
{% else %}
<input type="radio" id="off_rad_2" name="message_history" value="false" hidden oninput="turnOff('on_2', 'off_2', 'set_2')" checked>
<label for="off_rad_2" id="off_2" class="label_red"><b>OFF</b></label>
<p class="inline-block mx-1"> - </p>
<input type="radio" id="on_rad_2" name="message_history" value="true" hidden oninput="turnOn('on_2', 'off_2', 'set_2')">
<label for="on_rad_2" id="on_2" class="label_gray"><b>ON</b></label>
{% endif %}
</td>
</tr>
<tr>
<td class="py-1 fond-bold inter darkblue text-lg border-b border-slate-800" id="set_3">
<b>Private Mode:</b>
<br>
<p id="warning_0" class="text-sm label_darkorange" hidden> <b>Warning:</b> your current token will be deleted </p>
<input name="token" id="token" value="{{ data['token'] }}" hidden/>
{% if data['token']|length > 0 %}
<span id="warning_1" class="text-sm blue inline-block">
{% else %}
<span id="warning_1" class="text-sm blue inline-block hidden">
{% endif %}
<b>Your Token: </b> <i id="token_text" onclick="copyTextToClipboardFromName('token');">{{ data['token'] }}</i>
<img id="copy_icon" src="{{ url_for('static', filename='img/copy(Gregor_Cresnar).png') }}" width="16" class="inline-block" alt="Copy" onclick="copy('token', this.id, 'Copied!');"/>
</span>
</td>
<td class="py-1 fond-bold inter darkblue text-lg">
{% if data['token'] %}
<input type="radio" id="off_rad_3" name="private_mode" value="false" hidden oninput="turnOff('on_3', 'off_3', 'set_3'); hideWarning('warning_1'); showElement('warning_0');">
<label for="off_rad_3" id="off_3" class="label_gray"><b>OFF</b></label>
<p class="inline-block mx-1"> - </p>
<input type="radio" id="on_rad_3" name="private_mode" value="true" hidden oninput="turnOn('on_3', 'off_3', 'set_3'); showWarning('warning_1'); hideElement('warning_0'); createToken('token_text')" checked>
<label for="on_rad_3" id="on_3" class="label_green"><b>ON</b></label>
{% else %}
<input type="radio" id="off_rad_3" name="private_mode" value="false" hidden oninput="turnOff('on_3', 'off_3', 'set_3'); hideWarning('warning_1'); showElement('warning_0');" checked>
<label for="off_rad_3" id="off_3" class="label_red"><b>OFF</b></label>
<p class="inline-block mx-1"> - </p>
<input type="radio" id="on_rad_3" name="private_mode" value="true" hidden oninput="turnOn('on_3', 'off_3', 'set_3'); showWarning('warning_1'); hideElement('warning_0'); createToken('token_text')">
<label for="on_rad_3" id="on_3" class="label_gray"><b>ON</b></label>
{% endif %}
</td>
</tr>
<tr>
<td class="py-1 fond-bold inter darkblue text-lg border-b border-slate-800" id="set_4">
<b>Proxies:</b>
<br>
{% if data['proxies'] %}
<div id="proxy_list" class="mt-3">
{% else %}
<div id="proxy_list" class="mt-3" hidden>
{% endif %}
<label id="add_proxy_button" class="button outline-none py-1 px-4 rounded-lg darkgreen_bg text-white text-md hover:opacity-95 w-24 mt-5" onclick="addProxy();">Add Proxy</label>
<br>
<p class="text-sm mb-2 mt-4"> <b> Your Proxies: </b></p>
<div id="proxy_list_div">
{% for proxy in proxies %}
<!-- proxy form: protocol://user:password@ip:port -->
<div class="flex justify-start mb-3" id="proxy_div_{{ loop.index }}">
<input type="text" id="proxy_{{ loop.index }}" name="proxy_{{ loop.index }}" class="input outline-none py-1 px-2 rounded-lg inter w-full" onchange="warnProxySyntax(this.value, this.id);" placeholder="protocol://user:password@host:port" value="{{ proxy['protocol'] }}://{{ proxy['username'] }}:{{ proxy['password'] }}@{{ proxy['ip'] }}:{{ proxy['port'] }}">
<img id="delete_icon_{{ loop.index }}" src="{{ url_for('static', filename='img/delete(Anggara).png') }}" width="32" class="inline-block mx-2 p-1 hover:brightness-125" alt="Delete" onclick="deleteElement('proxy_div_{{ loop.index }}');"/>
</div>
{% endfor %}
</div>
</div>
</td>
<td class="py-1 fond-bold inter darkblue text-lg align-top">
{% if data['proxies'] %}
<input type="radio" id="off_rad_4" name="proxies" value="false" hidden oninput="turnOff('on_4', 'off_4', 'set_4'); hideElement('proxy_list');">
<label for="off_rad_4" id="off_4" class="label_gray"><b>OFF</b></label>
<p class="inline-block mx-1"> - </p>
<input type="radio" id="on_rad_4" name="proxies" value="true" hidden oninput="turnOn('on_4', 'off_4', 'set_4'); showElement('proxy_list');" checked>
<label for="on_rad_4" id="on_4" class="label_green"><b>ON</b></label>
{% else %}
<input type="radio" id="off_rad_4" name="proxies" value="false" hidden oninput="turnOff('on_4', 'off_4', 'set_4'); hideElement('proxy_list');" checked>
<label for="off_rad_4" id="off_4" class="label_red"><b>OFF</b></label>
<p class="inline-block mx-1"> - </p>
<input type="radio" id="on_rad_4" name="proxies" value="true" hidden oninput="turnOn('on_4', 'off_4', 'set_4'); showElement('proxy_list');">
<label for="on_rad_4" id="on_4" class="label_gray"><b>ON</b></label>
{% endif %}
</td>
</tr>
<tr>
<td class="py-1 fond-bold inter darkblue text-lg border-b border-slate-800" id="set_5">
<b>Fast API:</b>
<br>
<p id="warning_2" class="text-sm label_darkorange" hidden> <b>Warning:</b> the Fast API shutdown will take effect only after you've manually restarted the server </p>
</td>
<td class="py-1 fond-bold inter darkblue text-lg">
{% if data['fast_api'] %}
<input type="radio" id="off_rad_5" name="fast_api" value="false" hidden oninput="turnOff('on_5', 'off_5', 'set_5'); showElement('warning_2');">
<label for="off_rad_5" id="off_5" class="label_gray"><b>OFF</b></label>
<p class="inline-block mx-1"> - </p>
<input type="radio" id="on_rad_5" name="fast_api" value="true" hidden oninput="turnOn('on_5', 'off_5', 'set_5'); hideElement('warning_2');" checked>
<label for="on_rad_5" id="on_5" class="label_green"><b>ON</b></label>
{% else %}
<input type="radio" id="off_rad_5" name="fast_api" value="false" hidden oninput="turnOff('on_5', 'off_5', 'set_5'); showElement('warning_2');" checked>
<label for="off_rad_5" id="off_5" class="label_red"><b>OFF</b></label>
<p class="inline-block mx-1"> - </p>
<input type="radio" id="on_rad_5" name="fast_api" value="true" hidden oninput="turnOn('on_5', 'off_5', 'set_5'); hideElement('warning_2');">
<label for="on_rad_5" id="on_5" class="label_gray"><b>ON</b></label>
{% endif %}
</td>
</tr>
<tr>
<td class="py-1 fond-bold inter darkblue text-lg border-b border-slate-800">
<b>Server port:</b>
<br>
<p id="warning_3" class="text-sm label_darkorange" hidden> <b>Warning:</b> the port change will take effect only after you've manually restarted the server </p>
</td>
<td class="py-1 fond-bold inter darkblue text-lg">
<b>
<input type="number" id="port" name="port" class="input outline-none py-1 px-2 rounded-lg inter w-24" placeholder="Port" value="{{ data['port'] }}" onchange="showElement('warning_3')">
</b>
</td>
</tr>
<tr>
<td class="py-1 fond-bold inter darkblue text-lg border-b border-slate-800"><b>A.I. Provider:</b></td>
<td class="py-1 fond-bold inter darkblue text-lg">
<b>
<select name="provider" id="provider" class="input outline-none py-1 px-2 rounded-lg inter w-24">
<option value="{{ data['provider'] }}"> {{ data['provider'] }} - Default </option>
{% for key, value in providers.items() %}
<option value="{{ key }}"> {{ key }} </option>
{% endfor %}
</select>
</b>
</td>
</tr>
<tr>
<td class="py-1 fond-bold inter darkblue text-lg border-b border-slate-800"><b>A.I. Model:</b></td>
<td class="py-1 fond-bold inter darkblue text-lg">
<b>
<select name="model" id="model" class="input outline-none py-1 px-2 rounded-lg inter w-24">
<option value="{{ data['model'] }}"> {{ data['model'] }} </option>
{% if data['provider'] == "Auto" %}
{% for model in generic_models %}
<option value="{{ model }}"> {{ model }} </option>
{% endfor %}
{% else %}
{% for model in providers[data['provider']].models %}
<option value="{{ model }}"> {{ model }} </option>
{% endfor %}
{% endif %}
</select>
</b>
</td>
</tr>
<tr>
<td class="py-1 fond-bold inter darkblue text-lg border-b border-slate-800"><b>Cookies:</b></td>
<td class="py-1 fond-bold inter darkblue text-lg">
<b>
<input type="file" id="cookie_file" name="cookie_file" class="input outline-none py-1 px-2 rounded-lg inter w-24 fileholder" placeholder="Cookies" value="{{ data['cookie_file'] }}" accept=".json" oninput="changeButton('cookies_button')" hidden>
{% if data['cookie_file']|length == 0 %}
<label for="cookie_file" id="cookies_button" class="button outline-none py-1 px-4 rounded-lg darkblue_bg inter text-white text-md mt-5 hover:opacity-95 w-24">Upload</label>
{% else %}
<label for="cookie_file" id="change_cookies_button" class="button outline-none py-1 px-4 rounded-lg darkgreen_bg inter text-white text-md mt-5 hover:opacity-95 w-24">Change</label>
{% endif %}
</b>
</td>
</tr>
<tr>
<td class="py-1 fond-bold inter darkblue text-lg border-b border-slate-800"><b>Input keyword:</b></td>
<td class="py-1 fond-bold inter darkblue text-lg">
<b>
<input type="text" id="keyword" name="keyword" class="input outline-none py-1 px-2 rounded-lg inter w-24" placeholder="i.e. input" value="{{ data['keyword'] }}" suggested="text"></input>
</b>
</td>
</tr>
<tr>
<td class="py-1 fond-bold inter darkblue text-lg border-b border-slate-800 pb-3">
<b>Password:</b>
<div id="password_update" class="mt-4" hidden>
<p class="text-sm"> <b> Set Password </b></p>
<input type="password" id="new_password" name="new_password" class="input outline-none py-1 px-2 rounded-lg inter" placeholder="Your new Password" onchange="enableSaveButton();" autocomplete="new-password"></input>
<p class="text-sm mt-4"> <b> Confirm Password </b></p>
<input type="password" id="confirm_password" name="confirm_password" class="input outline-none py-1 px-2 rounded-lg inter" placeholder="New Password Again" onchange="enableSaveButton();" autocomplete="new-password"></input>
<p id="error_password" class="text-sm label_red mt-1" hidden> <b> Error: </b> Passwords do not match </p>
<p id="success_password" class="text-sm label_green mt-1" hidden>
<b> Success: </b> Passwords match. <br>
<i> When entering the old password below to confirm, the password will be updated </i>
</p>
</div>
</td>
<td class="py-1 fond-bold inter darkblue text-lg align-top">
<b>
<label id="open_password_update" class="button outline-none py-1 px-4 rounded-lg darkgreen_bg inter text-white text-md mt-5 hover:opacity-95 w-24" onclick="openPasswordUpdate(); enableSaveButton();">Update</label>
<label id="cancel_password_update" class="button outline-none py-1 px-4 rounded-lg darkred_bg inter text-white text-md mt-5 hover:opacity-95 w-24" onclick="cancelPasswordUpdate(); enableSaveButton();" hidden>Cancel</label>
</b>
</td>
</tr>
<tr>
<td class="py-1 fond-bold inter darkblue text-lg border-b border-slate-800">
<b>System Prompt:</b>
<textarea type="text" id="system_prompt" name="system_prompt" class="input outline-none py-1 px-2 my-2 rounded-lg inter w-full font-normal" placeholder="i.e. You're a virtual flight assistant, from now on follow your role for every answer.">{{ data['system_prompt'] }}</textarea>
</td>
</tr>
</table>
<!-- enter password to confirm -->
<div class="flex justify-start mt-12">
<input type="password" id="password" name="password" class="input outline-none py-3 px-4 rounded-lg inter w-60" placeholder="Enter password to confirm" onchange="enableSaveButton();" autocomplete="current-password">
</div>
<!-- save and apply button -->
<div class="flex justify-start">
<button type="submit" id="save" hidden>Save and apply</button>
<!-- dummy label gray -->
<label for="save" id="save_label" class="button outline-none py-3 px-4 pl-5 rounded-lg darkblue_bg inter text-white text-md mt-3 hover:opacity-95 w-40" hidden>Save and apply</label>
<label id="save_label_dummy" class="button outline-none py-3 px-4 pl-5 rounded-lg bg-slate-300 inter text-white text-md mt-3 w-40">Save and apply</label>
</div>
</form>
{% endblock %}
</body>
<!-- script import -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="{{ url_for('static', filename='js/script.js') }}" type="text/javascript"></script>
</html>
<!-- Path: src/templates/settings.html -->

0
tests/__init__.py Normal file
View File

4
tests/test_launch.py Normal file
View File

@@ -0,0 +1,4 @@
from src.FreeGPT4_Server import index
def test_index():
assert index()