Tobias Weise 08812c6d94
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 5s
async gen, ui improvements
2024-08-01 03:05:11 +02:00

673 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html>
<head>
<title>Ollama Chatbot</title>
<meta charset="utf-8">
<link rel="icon" href="favicon.svg">
<script src="viz.js"></script>
<script src="viz_widget.js"></script>
<script src="tabs.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<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>
<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>
<!--
<input size="8" class="form-control" list="bot_select" name="bots" placeholder="bot">
<datalist id="bot_select">
<option value="Superman">
</datalist>
-->
</span>
<!--
<input id="user_input" type="text" class="form-control" placeholder="What is...">
-->
<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>
<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{
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>