669 lines
23 KiB
HTML
669 lines
23 KiB
HTML
<!doctype html>
|
||
<html>
|
||
<head>
|
||
<title>Creative Bots</title>
|
||
<meta charset="utf-8">
|
||
<link rel="icon" href="favicon.svg">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
|
||
|
||
|
||
|
||
<!--
|
||
<script src="tabs.js"></script>
|
||
-->
|
||
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js"></script>
|
||
|
||
|
||
<!--
|
||
load the following async:?
|
||
-->
|
||
<script src="viz.js"></script>
|
||
<script src="viz_widget.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js" integrity="sha512-q/dWJ3kcmjBLU4Qc47E4A9kTB4m3wuTY7vkFJDTZKjTs8jhyGQnaUrxa0Ytd0ssMZhbNua9hE+E7Qv1j+DyZwA==" crossorigin="anonymous"></script>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||
|
||
</head>
|
||
<body>
|
||
|
||
<!-- The Login Modal -->
|
||
<div class="modal fade" id="myModal">
|
||
<div class="modal-dialog modal-dialog-centered">
|
||
<div class="modal-content">
|
||
|
||
<!-- Login Modal Header -->
|
||
<div class="modal-header">
|
||
<h4 class="modal-title">Login to account</h4>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
|
||
<form>
|
||
|
||
<!-- Login Modal body -->
|
||
<div class="modal-body">
|
||
|
||
<div class="mb-3 mt-3">
|
||
<label for="email" class="form-label">Email:</label>
|
||
<input type="email" class="form-control" id="email" placeholder="Enter email" name="email">
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="pwd" class="form-label">Password:</label>
|
||
<input type="password" class="form-control" id="pass" placeholder="Enter password" name="pswd">
|
||
</div>
|
||
|
||
<div class="form-check">
|
||
<input type="checkbox" class="form-check-input" onclick="show_password()">
|
||
<label class="form-check-label">Show Password</label>
|
||
</div>
|
||
|
||
<script>
|
||
function show_password() {
|
||
let ele = document.getElementById("pass");
|
||
if(ele.type === "password"){
|
||
ele.type = "text";
|
||
} else {
|
||
ele.type = "password";
|
||
}
|
||
}
|
||
</script>
|
||
</div>
|
||
<!-- Login Modal footer -->
|
||
<div class="modal-footer">
|
||
<button id="submit_login_btn" type="button" class="btn btn-primary">Submit</button>
|
||
</div>
|
||
</form>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="container-fluid p-3 bg-primary text-white text-center">
|
||
<h1>Creative Bots</h1>
|
||
<p>Create and talk to chatbots!</p>
|
||
</div>
|
||
|
||
<div class="container">
|
||
|
||
<!-- Offcanvas Sidebar -->
|
||
<div class="offcanvas offcanvas-start text-bg-dark" id="demo">
|
||
<div class="offcanvas-header">
|
||
<h1 class="offcanvas-title">Settings</h1>
|
||
<button type="button" class="btn-close btn-close-white text-reset" data-bs-dismiss="offcanvas"></button>
|
||
</div>
|
||
<div class="offcanvas-body">
|
||
|
||
<!-- Button to Open the Modal -->
|
||
<button id="login_btn" type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#myModal">Login</button>
|
||
|
||
<!--
|
||
<button id="login_btn" type="button" class="btn btn-primary text-white">Login</button>
|
||
-->
|
||
|
||
|
||
<button id="logout_btn" type="button" class="btn btn-danger text-white">Logout</button>
|
||
|
||
|
||
|
||
<br>
|
||
|
||
<label for="system_prompt">System prompt:</label>
|
||
<textarea id="system_prompt" class="form-control" rows="8" name="text"></textarea>
|
||
|
||
<br>
|
||
|
||
|
||
<!--
|
||
<label for="bots">Choose a bot:</label>
|
||
<select name="bots" id="bot_select" class="form-select"></select>
|
||
|
||
<br>
|
||
-->
|
||
|
||
|
||
<label for="views">Choose a view:</label>
|
||
<select name="views" id="view_select" class="form-select">
|
||
<option value="md">Markdown</option>
|
||
<option value="dot">Dot-Lang</option>
|
||
</select>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div style="height: 5px !important;"></div>
|
||
|
||
<!-- Nav tabs -->
|
||
<ul class="nav nav-tabs">
|
||
<li class="nav-item">
|
||
<a class="nav-link active" data-bs-toggle="tab" href="#home">Chat</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link" data-bs-toggle="tab" href="#create_bot_tab">Create bot</a>
|
||
</li>
|
||
</ul>
|
||
|
||
<!-- Tab panes -->
|
||
<div class="tab-content">
|
||
<div class="tab-pane container active" id="home">
|
||
|
||
<div style="height: 10px !important;"></div>
|
||
|
||
<div id="scroll_div" class="card container" style="overflow:scroll; height: 400px;">
|
||
<table id="log" class="table" style="width: 100%;"></table>
|
||
</div>
|
||
|
||
<br>
|
||
<div class="input-group">
|
||
<span class="input-group-text">@
|
||
<select name="bots" id="bot_select" class="form-select"></select>
|
||
</span>
|
||
<input class="form-control" list="questions" name="question" id="user_input" placeholder="What is...">
|
||
<datalist id="questions">
|
||
<option value="Write all the ministries of Germany and their suborganizations in dot lang and return the source code!">
|
||
<option value="What is a whale?">
|
||
<option value="Is a monad a burito?">
|
||
<option value="Give the JSON of a graph linking Germanys 9 biggest cities">
|
||
</datalist>
|
||
<button id="submit_btn" class="btn btn-success" type="submit">Send</button>
|
||
</div>
|
||
|
||
<!-- Button to open the offcanvas sidebar -->
|
||
<button class="btn btn-light" type="button" data-bs-toggle="offcanvas" data-bs-target="#demo">
|
||
Settings...
|
||
</button>
|
||
|
||
</div>
|
||
<div class="tab-pane container fade" id="create_bot_tab">
|
||
|
||
<div style="height: 10px !important;"></div>
|
||
|
||
<i>Creating a new bot requires an account and login via settings!</i>
|
||
<br>
|
||
<br>
|
||
|
||
<form>
|
||
|
||
|
||
<label for="bot_name" class="form-label">Name:</label>
|
||
<input type="bot_name" class="form-control" id="bot_name" placeholder="MyNewBot">
|
||
|
||
<br>
|
||
|
||
<label for="bot_visibility">Visibility:</label>
|
||
<select name="bot_visibility" id="bot_visibility_select" class="form-select">
|
||
<option value="public">Public to All</option>
|
||
<option value="private">Private to User</option>
|
||
</select>
|
||
|
||
|
||
<br>
|
||
|
||
<label for="bot_description">Description:</label>
|
||
<textarea id="bot_description" class="form-control" rows="8" name="text" placeholder="A bot that cares."></textarea>
|
||
|
||
<br>
|
||
|
||
<label for="bot_llm">Language model:</label>
|
||
<select name="bot_llm" id="bot_llm_select" class="form-select">
|
||
<option value="llama3">Llama3</option>
|
||
</select>
|
||
|
||
<br>
|
||
|
||
<label for="bot_system_prompt">System prompt:</label>
|
||
<textarea id="bot_system_prompt" class="form-control" rows="8" name="text" placeholder="Answer all questions short and sweet!"></textarea>
|
||
|
||
|
||
<br>
|
||
|
||
<label for="avatar">Text documents:</label>
|
||
<input disabled class="form-control" type="file" id="avatar" name="avatar" multiple accept=".pdf,.xml,.txt,.md,.doc,.docx,.odt,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document" />
|
||
|
||
|
||
<br>
|
||
|
||
<label for="url">Links:</label>
|
||
<input disabled class="form-control" type="url" name="url" id="url" placeholder="https://example.com" pattern="https://.*" size="30" required />
|
||
|
||
<hr>
|
||
|
||
<div class="row">
|
||
<div class="col"></div>
|
||
<div class="col"></div>
|
||
<div class="col"></div>
|
||
<div class="col"></div>
|
||
<div class="col">
|
||
<button id="create_bot_btn" disabled "type="button" class="btn btn-primary text-white">Create bot</button>
|
||
</div>
|
||
</div>
|
||
|
||
<br>
|
||
|
||
<!-- alerts -->
|
||
|
||
<div id="alert_spawn"></div>
|
||
|
||
<!--
|
||
<div id="alert_bot_created" style="display: none;" class="alert alert-success alert-dismissible fade show">
|
||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||
<strong>Success!</strong> Bot created!
|
||
</div>
|
||
|
||
|
||
<div id="alert_not_bot_created" style="display: none;" class="alert alert-danger alert-dismissible fade show">
|
||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||
<strong>Couldn't create bot!</strong> Something killed that bot!
|
||
</div>
|
||
-->
|
||
|
||
</form>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
</div>
|
||
|
||
<footer>
|
||
<div class="container-fluid p-3 bg-primary text-white mt-5">
|
||
|
||
<div class="row">
|
||
<div class="col-sm-4">
|
||
<h3>A simple UI</h3>
|
||
<p>This is just a simple frontend with basic functionality hosted on the REST-backend.</p>
|
||
<p>A standalone frontend written in Vue.js is in development</p>
|
||
</div>
|
||
<div class="col-sm-4">
|
||
<h3>Tools used</h3>
|
||
<p>❯ Ollama, Llama3</p>
|
||
<p>❯ Elasticsearch, LangChain</p>
|
||
<p>❯ Flask, OpenAPI</p>
|
||
<p>❯ Bootstrap 5</p>
|
||
</div>
|
||
<div class="col-sm-4">
|
||
<h3>Who? Why?</h3>
|
||
<p>The guy on this site: <a class="text-white" href="https://tobiasweise.dev">tobiasweise.dev</a></p>
|
||
<p>For fun and learning...</p>
|
||
<p>...and maybe getting a job that employs the used skills.</p>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</footer>
|
||
|
||
<script>
|
||
//idea: generate proxy opject via openapi.json api(url).login_now()
|
||
|
||
async function login(email, pwd){
|
||
const formData = new FormData();
|
||
formData.append("email", email);
|
||
formData.append("password", pwd);
|
||
const response = await fetch("/login", {
|
||
method: "POST",
|
||
headers: {
|
||
'accept': '*/*'
|
||
},
|
||
body: formData
|
||
});
|
||
return response.json();
|
||
}
|
||
|
||
async function get_bots(jwt){
|
||
if(jwt){
|
||
const response = await fetch("/bot", {
|
||
method: "GET",
|
||
headers: {
|
||
'accept': '*/*',
|
||
'Authorization': 'Bearer ' + jwt
|
||
}
|
||
});
|
||
return response.json();
|
||
}
|
||
else{
|
||
const response = await fetch("/bot", {
|
||
method: "GET",
|
||
headers: {
|
||
'accept': '*/*'
|
||
}
|
||
});
|
||
return response.json();
|
||
}
|
||
}
|
||
|
||
async function create_bot(jwt, name, visibility, description, llm, sys_prompt){
|
||
const formData = new FormData();
|
||
formData.append("name", name);
|
||
formData.append("visibility", visibility);
|
||
formData.append("description", description);
|
||
formData.append("llm_model", llm);
|
||
formData.append("system_prompt", sys_prompt);
|
||
|
||
const response = await fetch("/bot", {
|
||
method: "POST",
|
||
headers: {
|
||
'accept': '*/*',
|
||
'Authorization': 'Bearer ' + jwt
|
||
},
|
||
body: formData
|
||
});
|
||
return response.json();
|
||
}
|
||
|
||
async function* ask_question(bot_id, question, system_prompt=""){
|
||
let socket;
|
||
let room = null;
|
||
//let evt_listener = null;
|
||
let dom_ele = document.head;
|
||
const evt_name = "tokenstream";
|
||
try{
|
||
socket = io();
|
||
socket.on('backend response', data =>{
|
||
console.log(data);
|
||
if(data.room){
|
||
room = data.room;
|
||
socket.off('backend response');
|
||
}
|
||
});
|
||
|
||
let done = false;
|
||
//let last_timestamp = null;
|
||
|
||
function f(){
|
||
return new Promise((resolve,reject)=>{
|
||
let evt_listener = evt => {
|
||
//if(evt.timeStamp !== last_timestamp){
|
||
//last_timestamp = evt.timeStamp;
|
||
dom_ele.removeEventListener(evt_name, evt_listener);
|
||
resolve(evt.detail);
|
||
//}
|
||
//last_timestamp = evt.timeStamp;
|
||
};
|
||
dom_ele.addEventListener(evt_name, evt_listener);
|
||
});
|
||
}
|
||
|
||
socket.on('backend token', obj =>{
|
||
if(!obj.done){
|
||
dom_ele.dispatchEvent(new CustomEvent(evt_name, { detail: obj.data }));
|
||
}
|
||
else{
|
||
done = true;
|
||
socket.off('backend token');
|
||
}
|
||
});
|
||
socket.emit('client message', {
|
||
question: system_prompt + " " + question,
|
||
bot_id: bot_id,
|
||
room: room
|
||
});
|
||
while(!done){
|
||
yield f();
|
||
}
|
||
}
|
||
catch(e){
|
||
console.error(e);
|
||
}
|
||
finally{
|
||
//socket.off('backend token');
|
||
//dom_ele.removeEventListener(evt_name, evt_listener);
|
||
socket.emit('end');
|
||
socket.close();
|
||
}
|
||
}
|
||
|
||
|
||
window.onload = async ()=>{
|
||
document.documentElement.style.setProperty("--bs-primary-rgb", "45, 124, 172");
|
||
|
||
//chat
|
||
let tA = document.getElementById("user_input");
|
||
let log = document.getElementById("log");
|
||
let submit_btn = document.getElementById("submit_btn");
|
||
let scroll_div = document.getElementById("scroll_div");
|
||
|
||
//settings
|
||
let bot_select = document.getElementById("bot_select");
|
||
let view_select = document.getElementById("view_select");
|
||
let login_btn = document.getElementById("login_btn");
|
||
let logout_btn = document.getElementById("logout_btn");
|
||
let submit_login_btn = document.getElementById("submit_login_btn");
|
||
|
||
//create bot form
|
||
let create_bot_btn = document.getElementById("create_bot_btn");
|
||
let bot_name = document.getElementById("bot_name");
|
||
let bot_visibility_select = document.getElementById("bot_visibility_select");
|
||
let bot_description = document.getElementById("bot_description");
|
||
let bot_llm_select = document.getElementById("bot_llm_select");
|
||
let bot_system_prompt = document.getElementById("bot_system_prompt");
|
||
let alert_spawn = document.getElementById("alert_spawn");
|
||
|
||
let answer_count = 0;
|
||
|
||
function log_msg(nick, msg){
|
||
console.log(nick + ": " + msg);
|
||
log.innerHTML += "<tr><td><b>" + nick + "</b>:</td><td>" + msg + "</td></tr>";
|
||
}
|
||
|
||
function scroll_down(){
|
||
scroll_div.scrollTop = scroll_div.scrollHeight;
|
||
}
|
||
|
||
function get_bot_name(){
|
||
let i = bot_select.selectedIndex;
|
||
if(i===-1) return "Bot";
|
||
return bot_select.options[i].text;
|
||
}
|
||
|
||
function set_bot_list(ls){
|
||
if(ls.length === 0){
|
||
console.error("No bots found!");
|
||
}
|
||
else{
|
||
bot_select.innerHTML = ls.map(bot => `<option value="${bot.id}">${bot.name}</option>`).join("");
|
||
}
|
||
}
|
||
|
||
function clean_bot_create_form(){
|
||
bot_name.value = "";
|
||
bot_description.value = "";
|
||
bot_system_prompt.value = "";
|
||
}
|
||
|
||
function alert_bot_creation(success){
|
||
let msg, s;
|
||
if(success){
|
||
s = "success";
|
||
msg = "<strong>Success!</strong> Bot created!";
|
||
}
|
||
else{
|
||
s = "danger";
|
||
msg = "<strong>Couldn't create bot!</strong> Something killed that bot!";
|
||
}
|
||
alert_spawn.innerHTML = `
|
||
<div class="alert alert-${s} alert-dismissible fade show">
|
||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||
${msg}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function set_ui_loggedin(b){
|
||
if(b){
|
||
//enable create bot button
|
||
create_bot_btn.removeAttribute("disabled");
|
||
login_btn.style.display = "none";
|
||
logout_btn.style.display = "block";
|
||
}
|
||
else{
|
||
//disable create bot button
|
||
create_bot_btn.setAttribute("disabled", "disabled");
|
||
logout_btn.style.display = "none";
|
||
login_btn.style.display = "block";
|
||
}
|
||
}
|
||
|
||
//init: are we logged in on start?
|
||
let jwt = localStorage.getItem("jwt");
|
||
if(jwt === null){
|
||
let ls = await get_bots();
|
||
set_bot_list(ls);
|
||
set_ui_loggedin(false);
|
||
}
|
||
else{
|
||
let ls = await get_bots(jwt);
|
||
set_bot_list(ls);
|
||
set_ui_loggedin(true);
|
||
}
|
||
|
||
//init chat
|
||
log_msg(get_bot_name(), "Ask a question!");
|
||
|
||
//-----init buttons------------
|
||
create_bot_btn.onclick = async ()=>{
|
||
let jwt = localStorage.getItem("jwt");
|
||
if(jwt){
|
||
let name = bot_name.value;
|
||
let visibility = bot_visibility_select.value;
|
||
let description = bot_description.value;
|
||
let llm = bot_llm_select.value;
|
||
let sys_prompt = bot_system_prompt.value;
|
||
|
||
if(!name){
|
||
bot_name.focus();
|
||
return;
|
||
}
|
||
|
||
if(!sys_prompt){
|
||
bot_system_prompt.focus();
|
||
return;
|
||
}
|
||
|
||
try{
|
||
let {bot_id} = await create_bot(jwt, name, visibility, description, llm, sys_prompt);
|
||
alert_bot_creation(true);
|
||
clean_bot_create_form();
|
||
|
||
//update bot list
|
||
let ls = await get_bots(jwt);
|
||
set_bot_list(ls);
|
||
|
||
}
|
||
catch(err){
|
||
console.error(err);
|
||
console.error("Couldn't create bot!");
|
||
alert_bot_creation(false);
|
||
}
|
||
}
|
||
};
|
||
|
||
submit_login_btn.onclick = async ()=>{
|
||
//let nick = prompt("Please enter your email");
|
||
//let pwd = prompt("Please enter your password");
|
||
|
||
let nick_ele = document.getElementById("email");
|
||
let pwd_ele = document.getElementById("pass");
|
||
|
||
if(!nick_ele.value){
|
||
nick_ele.focus();
|
||
return;
|
||
}
|
||
|
||
if(!pwd_ele.value){
|
||
pwd_ele.focus();
|
||
return;
|
||
}
|
||
|
||
let nick = nick_ele.value;
|
||
let pwd = pwd_ele.value;
|
||
|
||
|
||
try{
|
||
let{jwt} = await login(nick, pwd);
|
||
|
||
if(!jwt) throw Error("No JWT!");
|
||
|
||
localStorage.setItem("jwt", jwt);
|
||
set_ui_loggedin(true);
|
||
|
||
let ls = await get_bots(jwt);
|
||
set_bot_list(ls);
|
||
|
||
let myModalEl = document.querySelector('#myModal');
|
||
let myModal = bootstrap.Modal.getOrCreateInstance(myModalEl);
|
||
myModal.hide();
|
||
}
|
||
catch(e){
|
||
console.error("Login failed!");
|
||
}
|
||
};
|
||
|
||
logout_btn.onclick = async ()=>{
|
||
localStorage.removeItem("jwt");
|
||
|
||
set_ui_loggedin(false);
|
||
|
||
let ls = await get_bots();
|
||
set_bot_list(ls);
|
||
};
|
||
|
||
submit_btn.onclick = async evt =>{
|
||
let s = tA.value;
|
||
//if(s.trim() !== '' && room){
|
||
if(s.trim() !== ''){
|
||
answer_count += 1;
|
||
|
||
tA.value = "";
|
||
log_msg('User', s);
|
||
|
||
let tA2 = document.getElementById("system_prompt");
|
||
let acc_text = "";
|
||
|
||
log.innerHTML += `<tr><td><b>${get_bot_name()}</b>:</td><td id="${answer_count}"></td></tr>`;
|
||
|
||
for await (let token of ask_question(bot_select.value, s, tA2.value)){
|
||
console.log(token);
|
||
|
||
acc_text += "" + token;
|
||
//document.getElementById(answer_count).innerHTML += obj.data;
|
||
document.getElementById(answer_count).innerHTML = marked.parse(acc_text);
|
||
scroll_down();
|
||
|
||
}
|
||
|
||
let final_answer = acc_text;
|
||
console.log(final_answer);
|
||
|
||
switch(view_select.value){
|
||
|
||
case "md":
|
||
document.getElementById(answer_count).innerHTML = marked.parse(final_answer);
|
||
break;
|
||
|
||
case "dot":
|
||
final_answer = final_answer.replace("```dot", "").replace("```", "");
|
||
//let layout = "fdp";
|
||
let layout = "dot";
|
||
document.getElementById(answer_count).innerHTML = `<dot-graph layout="${layout}" style="width:100%; height:100%;">${final_answer}</dot-graph>`;
|
||
break;
|
||
|
||
default:
|
||
document.getElementById(answer_count).innerHTML = `<pre>${final_answer}</pre>`;
|
||
break;
|
||
}
|
||
|
||
//answer_count += 1;
|
||
scroll_down();
|
||
|
||
}
|
||
};
|
||
|
||
};
|
||
</script>
|
||
</body>
|
||
</html>
|