Initial release
This commit is contained in:
parent
9e3f7b8a93
commit
cd6036eff4
11 changed files with 670 additions and 2 deletions
45
frontend/static/index.html
Normal file
45
frontend/static/index.html
Normal file
|
@ -0,0 +1,45 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Copyright (c) 2023 Julian Müller (ChaoticByte) -->
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/ui/style.css">
|
||||
<title>Eucalyptus</title>
|
||||
</head>
|
||||
<body class="flex">
|
||||
<div class="main flex flex-column">
|
||||
<div id="messages" class="messages flex flex-column"></div>
|
||||
<div class="input-container flex">
|
||||
<textarea id="text-input" class="text-input" rows="1" placeholder="Press Ctrl+Enter to send" autofocus></textarea>
|
||||
<button id="send-btn" class="send-btn icon-button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M120 896V651l302-75-302-77V256l760 320-760 320Z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidepanel flex flex-column">
|
||||
<div class="max-width">Settings</div>
|
||||
<div class="settings flex flex-column">
|
||||
<div class="setting flex">
|
||||
<div>max_tokens</div>
|
||||
<div><input type="number" id="settings-max-tokens" min="16" value="100"></div>
|
||||
</div>
|
||||
<div class="setting flex">
|
||||
<div>temperature</div>
|
||||
<div><input type="number" id="settings-temperature" min="0.0" max="2.0" step="0.01" value="0.8"></div>
|
||||
</div>
|
||||
<div class="setting flex">
|
||||
<div>top_p</div>
|
||||
<div><input type="number" id="settings-top-p" min="0.0" max="1.0" step="0.01" value="0.95"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<button id="reset-history-btn" class="icon-button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="m361 757 119-121 120 121 47-48-119-121 119-121-47-48-120 121-119-121-48 48 120 121-120 121 48 48ZM261 936q-24 0-42-18t-18-42V306h-41v-60h188v-30h264v30h188v60h-41v570q0 24-18 42t-42 18H261Z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/ui/main.js"></script>
|
||||
</body>
|
||||
</html>
|
159
frontend/static/main.js
Normal file
159
frontend/static/main.js
Normal file
|
@ -0,0 +1,159 @@
|
|||
// Copyright (c) 2023 Julian Müller (ChaoticByte)
|
||||
|
||||
(() => {
|
||||
|
||||
// Koala specific keywords
|
||||
const conversationBeginning = "BEGINNING OF CONVERSATION:";
|
||||
const userKeyword = " USER: ";
|
||||
const assistantKeyword = " GPT:";
|
||||
const koalaStopSequence = "</s>";
|
||||
|
||||
// Get frontend config
|
||||
let frontend_config = null;
|
||||
fetch("/config")
|
||||
.then(r => {
|
||||
return r.json();
|
||||
})
|
||||
.then(j => {
|
||||
frontend_config = j;
|
||||
});
|
||||
|
||||
// Message Context
|
||||
let conversation = [conversationBeginning];
|
||||
|
||||
// Elements - Sidebar
|
||||
const settingsMaxTokensElement = document.getElementById("settings-max-tokens");
|
||||
const settingsTemperatureElement = document.getElementById("settings-temperature");
|
||||
const settingsTopPElement = document.getElementById("settings-top-p");
|
||||
const resetHistoryButtonElement = document.getElementById("reset-history-btn");
|
||||
|
||||
// Elements - Main
|
||||
const messageHistoryContainer = document.getElementById("messages");
|
||||
const textInputElement = document.getElementById("text-input");
|
||||
const sendButtonElement = document.getElementById("send-btn");
|
||||
|
||||
// API requests
|
||||
|
||||
async function apiCompletion(prompt, settings) {
|
||||
const bodyData = JSON.stringify({
|
||||
"prompt": prompt,
|
||||
"stop": [koalaStopSequence],
|
||||
"max_tokens": settings.max_tokens,
|
||||
"temperature": settings.temperature,
|
||||
"top_p": settings.top_p
|
||||
});
|
||||
const response = await fetch(frontend_config.api_url + "/v1/completions", {
|
||||
method: "post",
|
||||
cache: "no-cache",
|
||||
body: bodyData,
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
}
|
||||
});
|
||||
const responseData = await response.json();
|
||||
return responseData["choices"][0]["text"];
|
||||
}
|
||||
|
||||
// User-defined settings
|
||||
|
||||
function getSettings() {
|
||||
return {
|
||||
max_tokens: settingsMaxTokensElement.value,
|
||||
temperature: settingsTemperatureElement.value,
|
||||
top_p: settingsTopPElement.value
|
||||
}
|
||||
}
|
||||
|
||||
// Chat
|
||||
|
||||
const MessageType = {
|
||||
USER: {
|
||||
name: "User",
|
||||
class: "message-bg-user"
|
||||
},
|
||||
ASSISTANT: {
|
||||
name: "Koala",
|
||||
class: "message-bg-assistant"
|
||||
}
|
||||
}
|
||||
|
||||
function addMessage(message, type) {
|
||||
if (type == MessageType.USER) {
|
||||
conversation.push(userKeyword + message + assistantKeyword);
|
||||
}
|
||||
else { conversation.push(message); }
|
||||
// UI
|
||||
let messageTypeElem = document.createElement("div");
|
||||
messageTypeElem.classList.add("message-type");
|
||||
messageTypeElem.innerText = type.name;
|
||||
let messageTextElem = document.createElement("div");
|
||||
messageTextElem.classList.add("message-text");
|
||||
messageTextElem.innerText = message;
|
||||
let messageElem = document.createElement("div");
|
||||
messageElem.classList.add("message");
|
||||
messageElem.classList.add(type.class);
|
||||
messageElem.appendChild(messageTypeElem);
|
||||
messageElem.appendChild(messageTextElem);
|
||||
messageHistoryContainer.appendChild(messageElem);
|
||||
}
|
||||
|
||||
function disableInput() {
|
||||
sendButtonElement.disabled = true;
|
||||
textInputElement.disabled = true;
|
||||
}
|
||||
|
||||
function enableInput() {
|
||||
sendButtonElement.disabled = false;
|
||||
textInputElement.disabled = false;
|
||||
// focus text input
|
||||
textInputElement.focus();
|
||||
}
|
||||
|
||||
async function chat() {
|
||||
if (frontend_config == null) {
|
||||
console.log("Couldn't fetch frontend configuration.");
|
||||
}
|
||||
else {
|
||||
disableInput();
|
||||
let input = textInputElement.value.trim();
|
||||
if (input == "") {
|
||||
enableInput();
|
||||
}
|
||||
else {
|
||||
textInputElement.value = "";
|
||||
addMessage(input, MessageType.USER);
|
||||
let prompt = conversation.join("");
|
||||
let settings = getSettings();
|
||||
apiCompletion(prompt, settings).then(r => {
|
||||
addMessage(r, MessageType.ASSISTANT);
|
||||
enableInput();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetHistory() {
|
||||
conversation = [conversationBeginning];
|
||||
messageHistoryContainer.innerText = "";
|
||||
}
|
||||
|
||||
// Event Listeners
|
||||
|
||||
resetHistoryButtonElement.addEventListener("click", resetHistory);
|
||||
sendButtonElement.addEventListener("click", chat);
|
||||
|
||||
textInputElement.addEventListener("keypress", e => {
|
||||
// Send via Ctrl+Enter
|
||||
if (e.key == "Enter" && e.ctrlKey) {
|
||||
chat();
|
||||
}
|
||||
});
|
||||
|
||||
textInputElement.addEventListener("input", e => {
|
||||
// Calculate Line height
|
||||
textInputElement.style.removeProperty("height");
|
||||
let newHeight = textInputElement.scrollHeight;
|
||||
textInputElement.style.height = newHeight.toString() + "px";
|
||||
});
|
||||
|
||||
})();
|
164
frontend/static/style.css
Normal file
164
frontend/static/style.css
Normal file
|
@ -0,0 +1,164 @@
|
|||
/* Copyright (c) 2023 Julian Müller (ChaoticByte) */
|
||||
|
||||
:root {
|
||||
--background: #1f1f1f;
|
||||
--background2: #303030;
|
||||
--background3: #161616;
|
||||
--background4: #131313;
|
||||
--button-bg: #3b3b3b;
|
||||
--button-bg2: #4f4f4f;
|
||||
--icon-button-fill: #ffffff;
|
||||
--send-icon-button-fill: #29c76d;
|
||||
--color: #fafafa;
|
||||
--padding: .5rem;
|
||||
--border-radius: .5rem;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: var(--background);
|
||||
color: var(--color);
|
||||
font-family: sans-serif;
|
||||
flex-direction: row;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
|
||||
input[type="number"] {
|
||||
width: 4rem;
|
||||
}
|
||||
|
||||
.sidepanel {
|
||||
gap: var(--padding);
|
||||
align-items: flex-end;
|
||||
padding: 1rem;
|
||||
padding-left: 0;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
.settings {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
gap: .5rem;
|
||||
}
|
||||
|
||||
.setting {
|
||||
gap: .5rem;
|
||||
}
|
||||
|
||||
.setting > div:first-child {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex-grow: 1;
|
||||
justify-content: flex-end;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.messages {
|
||||
gap: 1.1rem;
|
||||
padding-bottom: var(--padding);
|
||||
overflow-y: scroll;
|
||||
max-height: 89vh;
|
||||
}
|
||||
|
||||
.message {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--padding);
|
||||
padding: var(--padding);
|
||||
border-radius: var(--border-radius);
|
||||
max-width: fit-content;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: .5rem .7rem;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: var(--color);
|
||||
background: var(--button-bg);
|
||||
border-radius: var(--border-radius);
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
button:disabled, input:disabled, textarea:disabled {
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: var(--button-bg2);
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-column {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.max-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.message-bg-assistant {
|
||||
background: var(--background2);
|
||||
}
|
||||
|
||||
.message-bg-user {
|
||||
background: var(--background3);
|
||||
}
|
||||
|
||||
.message-type {
|
||||
min-width: 3.5rem;
|
||||
padding-left: .1rem;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
margin-top: auto;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
}
|
||||
|
||||
.text-input {
|
||||
margin: 0;
|
||||
flex-grow: 1;
|
||||
background-color: var(--background4);
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: .8rem 1.1rem;
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--color);
|
||||
resize: none;
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
padding: .2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.icon-button:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.icon-button > svg {
|
||||
height: 1.8rem;
|
||||
width: auto;
|
||||
fill: var(--icon-button-fill);
|
||||
}
|
||||
|
||||
.icon-button:hover > svg {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.send-btn > svg {
|
||||
height: 2.2rem;
|
||||
fill: var(--send-icon-button-fill);
|
||||
}
|
Reference in a new issue