1
0
mirror of https://github.com/coalaura/whiskr.git synced 2025-09-08 00:29:54 +00:00

title generation

This commit is contained in:
Laura
2025-08-25 22:45:03 +02:00
parent 3eac1a0795
commit 82e91cfc3e
14 changed files with 392 additions and 31 deletions

View File

@@ -133,12 +133,13 @@ body:not(.loading) #loading {
gap: 5px;
background: #1e2030;
margin: auto;
margin-top: 30px;
margin-top: 40px;
width: 100%;
max-width: 1200px;
height: calc(100% - 30px);
height: calc(100% - 40px);
border-top-left-radius: 6px;
border-top-right-radius: 6px;
position: relative;
}
.hidden {
@@ -150,6 +151,40 @@ body:not(.loading) #loading {
display: none !important;
}
#title {
display: flex;
align-items: center;
gap: 6px;
position: absolute;
top: -22px;
left: -4px;
font-style: italic;
padding-left: 22px;
transition: 150ms opacity;
}
#title-text {
transition: 150ms;
}
#title #title-refresh {
background-image: url(icons/refresh.svg);
width: 16px;
height: 16px;
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
}
#title.refreshing #title-refresh {
animation: rotating-y 1.2s linear infinite;
}
#title.refreshing #title-text {
filter: blur(3px);
}
#messages {
display: flex;
flex-direction: column;
@@ -717,6 +752,7 @@ select {
}
body.loading #version,
#title-refresh,
#loading .inner::after,
.modal.loading .content::after,
.reasoning .toggle::before,

View File

@@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>

After

Width:  |  Height:  |  Size: 879 B

View File

@@ -26,7 +26,13 @@
</div>
<div id="page">
<div id="title" class="hidden">
<button id="title-refresh"></button>
<div id="title-text"></div>
</div>
<div id="messages"></div>
<div id="chat">
<button id="top" class="hidden" title="Scroll to top"></button>
<button id="bottom" class="hidden" title="Scroll to bottom"></button>

View File

@@ -1,6 +1,9 @@
(() => {
const $version = document.getElementById("version"),
$total = document.getElementById("total"),
$title = document.getElementById("title"),
$titleRefresh = document.getElementById("title-refresh"),
$titleText = document.getElementById("title-text"),
$messages = document.getElementById("messages"),
$chat = document.getElementById("chat"),
$message = document.getElementById("message"),
@@ -37,7 +40,8 @@
let autoScrolling = false,
jsonMode = false,
searchTool = false;
searchTool = false,
chatTitle = false;
let searchAvailable = false,
activeMessage = null,
@@ -51,6 +55,14 @@
$total.textContent = formatMoney(totalCost);
}
function updateTitle() {
$title.classList.toggle("hidden", !messages.length);
$titleText.textContent = chatTitle || (messages.length ? "New Chat" : "");
storeValue("title", chatTitle);
}
function updateScrollButton() {
const bottom = $messages.scrollHeight - ($messages.scrollTop + $messages.offsetHeight);
@@ -266,6 +278,8 @@
let timeout;
_optCopy.addEventListener("click", () => {
this.stopEdit();
clearTimeout(timeout);
navigator.clipboard.writeText(this.#text);
@@ -304,6 +318,8 @@
return;
}
this.stopEdit();
while (messages.length > index) {
messages[messages.length - 1].delete();
}
@@ -759,8 +775,6 @@
}
}
let controller;
async function json(url) {
try {
const response = await fetch(url);
@@ -778,6 +792,8 @@
}
async function stream(url, options, callback) {
let aborted;
try {
const response = await fetch(url, options);
@@ -834,29 +850,32 @@
}
}
} catch (err) {
if (err.name !== "AbortError") {
callback({
type: "error",
text: err.message,
});
if (err.name === "AbortError") {
aborted = true;
return;
}
callback({
type: "error",
text: err.message,
});
} finally {
callback(false);
callback(aborted ? "aborted" : "done");
}
}
let chatController;
function generate(cancel = false) {
if (controller) {
controller.abort();
if (chatController) {
chatController.abort();
if (cancel) {
return;
}
}
if (!$temperature.value) {
}
let temperature = parseFloat($temperature.value);
if (Number.isNaN(temperature) || temperature < 0 || temperature > 2) {
@@ -888,7 +907,7 @@
pushMessage();
controller = new AbortController();
chatController = new AbortController();
$chat.classList.add("completing");
@@ -908,14 +927,16 @@
let message, generationID;
function finish() {
function finish(aborted = false) {
if (!message) {
return;
}
message.setState(false);
setTimeout(message.loadGenerationData.bind(message), 750, generationID);
if (!aborted) {
setTimeout(message.loadGenerationData.bind(message), 750, generationID);
}
message = null;
generationID = null;
@@ -945,16 +966,24 @@
"Content-Type": "application/json",
},
body: JSON.stringify(body),
signal: controller.signal,
signal: chatController.signal,
},
chunk => {
if (!chunk) {
controller = null;
if (chunk === "aborted") {
finish(true);
return;
} else if (chunk === "done") {
chatController = null;
finish();
$chat.classList.remove("completing");
if (!chatTitle && !titleController) {
refreshTitle();
}
return;
}
@@ -999,6 +1028,65 @@
);
}
let titleController;
async function refreshTitle() {
if (titleController) {
titleController.abort();
}
titleController = new AbortController();
const body = {
title: chatTitle || null,
messages: messages.map(message => message.getData()).filter(Boolean),
};
if (!body.messages.length) {
updateTitle();
return;
}
$title.classList.add("refreshing");
try {
const response = await fetch("/-/title", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
signal: titleController.signal,
}),
result = await response.json();
if (result.cost) {
totalCost += result.cost;
updateTotalCost();
}
if (!response.ok || !result?.title) {
throw new Error(result?.error || response.statusText);
}
chatTitle = result.title;
} catch (err) {
if (err.name === "AbortError") {
return;
}
alert(err.message);
}
titleController = null;
updateTitle();
$title.classList.remove("refreshing");
}
async function login() {
const username = $username.value.trim(),
password = $password.value.trim();
@@ -1147,6 +1235,10 @@
}
});
chatTitle = loadValue("title");
updateTitle();
scroll();
// small fix, sometimes when hard reloading we don't scroll all the way
@@ -1235,11 +1327,12 @@
const message = new Message($role.value, "", text, attachments);
clearAttachments();
updateTitle();
return message;
}
$total.addEventListener("auxclick", (event) => {
$total.addEventListener("auxclick", event => {
if (event.button !== 1) {
return;
}
@@ -1249,6 +1342,10 @@
updateTotalCost();
});
$titleRefresh.addEventListener("click", () => {
refreshTitle();
});
$messages.addEventListener("scroll", () => {
updateScrollButton();
});
@@ -1417,6 +1514,10 @@
}
clearMessages();
chatTitle = false;
updateTitle();
});
$export.addEventListener("click", () => {