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:
@@ -404,6 +404,10 @@ body:not(.loading) #loading {
|
||||
color: #ed8796;
|
||||
}
|
||||
|
||||
.message .text .error a {
|
||||
color: #ee99a0;
|
||||
}
|
||||
|
||||
.message.errored {
|
||||
border: 2px solid #ed8796;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
156
static/js/storage.js
Normal 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);
|
||||
})();
|
||||
Reference in New Issue
Block a user