1
0
mirror of https://github.com/coalaura/whiskr.git synced 2025-12-02 20:22:52 +00:00

use index db & various fixes

This commit is contained in:
Laura
2025-09-12 20:52:10 +02:00
parent 2d6b618cf1
commit 57418ccbd4
7 changed files with 182 additions and 59 deletions

View File

@@ -404,6 +404,10 @@ body:not(.loading) #loading {
color: #ed8796;
}
.message .text .error a {
color: #ee99a0;
}
.message.errored {
border: 2px solid #ed8796;
}

View File

@@ -129,6 +129,7 @@
<script src="lib/marked.min.js"></script>
<script src="lib/morphdom.min.js"></script>
<script src="js/lib.js"></script>
<script src="js/storage.js"></script>
<script src="js/markdown.js"></script>
<script src="js/dropdown.js"></script>
<script src="js/chat.js"></script>

View File

@@ -723,7 +723,7 @@
data.collapsed = true;
}
if (!data.images?.length && !data.files?.length && !data.reasoning && !data.text && !data.tool) {
if (!data.error && !data.images?.length && !data.files?.length && !data.reasoning && !data.text && !data.tool) {
return false;
}
@@ -868,14 +868,14 @@
this.#save();
}
showError(error) {
this.#error = error;
setError(error) {
this.#error = error || "Something went wrong";
this.#_message.classList.add("errored");
this.#_message.classList.add("errored", "has-text");
const _err = make("div", "error");
_err.textContent = this.#error;
_err.innerHTML = renderInline(this.#error);
this.#_text.appendChild(_err);
@@ -1226,7 +1226,7 @@
break;
case "error":
message.showError(chunk.text);
message.setError(chunk.text);
break;
}
@@ -1326,7 +1326,7 @@
}
async function loadData() {
const data = await json("/-/data");
const [_, data] = await Promise.all([connectDB(), json("/-/data")]);
if (!data) {
notify("Failed to load data.", true);
@@ -1394,6 +1394,12 @@
}
function restore() {
const resizedHeight = loadValue("resized");
if (resizedHeight) {
$chat.style.height = `${resizedHeight}px`;
}
$message.value = loadValue("message", "");
$role.value = loadValue("role", "user");
$model.value = loadValue("model", modelList.length ? modelList[0].id : "");
@@ -1424,13 +1430,13 @@
loadValue("messages", []).forEach(message => {
const obj = new Message(message.role, message.reasoning, message.text, message.tool, message.files || [], message.images || [], message.tags || [], message.collapsed);
if (message.error) {
obj.showError(message.error);
}
if (message.statistics) {
obj.setStatistics(message.statistics);
}
if (message.error) {
obj.setError(message.error);
}
});
chatTitle = loadValue("title");
@@ -1919,12 +1925,6 @@
dropdown($role);
dropdown($reasoningEffort);
const resizedHeight = loadValue("resized");
if (resizedHeight) {
$chat.style.height = `${resizedHeight}px`;
}
loadData().then(() => {
restore();

View File

@@ -1,35 +1,5 @@
/** biome-ignore-all lint/correctness/noUnusedVariables: utility */
function storeValue(key, value = false) {
if (value === null || value === undefined || value === false) {
localStorage.removeItem(key);
return;
}
localStorage.setItem(key, JSON.stringify(value));
}
function loadValue(key, fallback = false) {
const raw = localStorage.getItem(key);
if (raw === null) {
return fallback;
}
try {
const value = JSON.parse(raw);
if (value === null) {
throw new Error("no value");
}
return value;
} catch {}
return fallback;
}
function schedule(cb) {
if (document.visibilityState === "visible") {
requestAnimationFrame(cb);

View File

@@ -158,4 +158,8 @@
window.render = markdown => {
return marked.parse(markdown);
};
window.renderInline = markdown => {
return marked.parseInline(markdown.trim());
};
})();

156
static/js/storage.js Normal file
View File

@@ -0,0 +1,156 @@
/** biome-ignore-all lint/correctness/noUnusedVariables: utility */
(() => {
const DatabaseName = "whiskr",
StorageName = "chat";
function isNull(value) {
return value === null || value === false || value === undefined;
}
class Database {
#database;
#scheduled = new Map();
#writes = new Map();
#cache = new Map();
static async new() {
const db = new Database();
await db.#connect();
await db.#load();
return db;
}
#connect() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DatabaseName, 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.#database = request.result;
resolve();
};
request.onupgradeneeded = event => {
const db = event.target.result;
if (db.objectStoreNames.contains(StorageName)) {
return;
}
db.createObjectStore(StorageName);
};
});
}
#load() {
return new Promise((resolve, reject) => {
const transaction = this.#database.transaction(StorageName, "readonly"),
store = transaction.objectStore(StorageName),
request = store.openCursor();
request.onerror = () => reject(request.error);
let total = 0;
request.onsuccess = event => {
const cursor = event.target.result;
if (cursor) {
if (!isNull(cursor.value)) {
this.#cache.set(cursor.key, cursor.value);
total++;
}
cursor.continue();
} else {
console.info(`Loaded ${total} items from IndexedDB`);
resolve();
}
};
});
}
#write(key, retry) {
if (this.#writes.has(key)) {
if (retry) {
this.#schedule(key);
}
return;
}
this.#writes.set(key, true);
try {
const transaction = this.#database.transaction(StorageName, "readwrite"),
store = transaction.objectStore(StorageName);
const value = this.#cache.get(key);
if (isNull(value)) {
store.delete(key);
} else {
store.put(value, key);
}
return new Promise((resolve, reject) => {
transaction.oncomplete = () => resolve();
transaction.onerror = () => reject(transaction.error);
});
} catch (error) {
console.error(`Failed to write to IndexedDB: ${error}`);
} finally {
this.#writes.delete(key);
}
}
#schedule(key) {
if (this.#scheduled.has(key)) {
clearTimeout(this.#scheduled.get(key));
}
const timeout = setTimeout(() => {
this.#scheduled.delete(key);
this.#write(key, true);
}, 500);
this.#scheduled.set(key, timeout);
}
store(key, value = false) {
if (isNull(value)) {
this.#cache.delete(key);
} else {
this.#cache.set(key, value);
}
this.#schedule(key);
}
load(key, fallback = false) {
if (!this.#cache.has(key)) {
return fallback;
}
return this.#cache.get(key);
}
}
let db;
window.connectDB = async () => {
db = await Database.new();
};
window.storeValue = (key, value = false) => db.store(key, value);
window.loadValue = (key, fallback = false) => db.load(key, fallback);
})();