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

import and export

This commit is contained in:
Laura
2025-08-16 14:54:27 +02:00
parent 566996a728
commit 66cf5011a5
6 changed files with 155 additions and 6 deletions

View File

@@ -625,6 +625,8 @@ body.loading #version,
#json,
#search,
#scrolling,
#import,
#export,
#clear,
#add,
#send,
@@ -749,6 +751,8 @@ label[for="reasoning-tokens"] {
#json,
#search,
#scrolling,
#import,
#export,
#clear {
position: unset !important;
}
@@ -777,6 +781,14 @@ label[for="reasoning-tokens"] {
background-image: url(icons/search-on.svg);
}
#import {
background-image: url(icons/import.svg);
}
#export {
background-image: url(icons/export.svg);
}
#clear {
background-image: url(icons/trash.svg);
}

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: 651 B

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: 819 B

View File

@@ -77,6 +77,8 @@
<button id="scrolling" title="Turn on auto-scrolling"></button>
</div>
<div class="option">
<button id="export" title="Export the entire chat as a JSON file"></button>
<button id="import" title="Import a chat form a JSON file"></button>
<button id="clear" title="Clear the entire chat"></button>
</div>
</div>

View File

@@ -15,10 +15,13 @@
$add = document.getElementById("add"),
$send = document.getElementById("send"),
$scrolling = document.getElementById("scrolling"),
$export = document.getElementById("export"),
$import = document.getElementById("import"),
$clear = document.getElementById("clear");
const messages = [],
models = {};
models = {},
modelList = [];
let autoScrolling = false,
searchAvailable = false,
@@ -899,6 +902,8 @@
$model.innerHTML = "";
for (const model of data.models) {
modelList.push(model);
const el = document.createElement("option");
el.value = model.id;
@@ -917,7 +922,15 @@
return data;
}
function restore(modelList) {
function clearMessages() {
while (messages.length) {
console.log("delete", messages.length)
messages[0].delete();
}
}
function restore() {
$message.value = loadValue("message", "");
$role.value = loadValue("role", "user");
$model.value = loadValue("model", modelList[0].id);
$prompt.value = loadValue("prompt", "normal");
@@ -971,6 +984,7 @@
}
$message.value = "";
storeValue("message", "");
return new Message($role.value, "", text);
}
@@ -1086,9 +1100,53 @@
return;
}
for (let x = messages.length - 1; x >= 0; x--) {
messages[x].delete();
clearMessages();
});
$export.addEventListener("click", () => {
const data = JSON.stringify({
message: $message.value,
role: $role.value,
model: $model.value,
prompt: $prompt.value,
temperature: $temperature.value,
reasoning: {
effort: $reasoningEffort.value,
tokens: $reasoningTokens.value,
},
json: jsonMode,
search: searchTool,
messages: messages.map((message) => message.getData()).filter(Boolean),
});
download("chat.json", "application/json", data);
});
$import.addEventListener("click", async () => {
if (!modelList.length) {
return;
}
const data = await selectFile("application/json");
if (!data) {
return;
}
clearMessages();
storeValue("message", data.message);
storeValue("role", data.role);
storeValue("model", data.model);
storeValue("prompt", data.prompt);
storeValue("temperature", data.temperature);
storeValue("reasoning", data.reasoning);
storeValue("reasoning", data.reasoning);
storeValue("json", data.json);
storeValue("search", data.search);
storeValue("messages", data.messages);
restore();
});
$scrolling.addEventListener("click", () => {
@@ -1123,8 +1181,8 @@
dropdown($prompt);
dropdown($reasoningEffort);
loadData().then((data) => {
restore(data?.models || []);
loadData().then(() => {
restore();
document.body.classList.remove("loading");
});

View File

@@ -74,3 +74,66 @@ function formatMilliseconds(ms) {
function fixed(num, decimals = 0) {
return num.toFixed(decimals).replace(/\.?0+$/m, "");
}
function download(name, type, data) {
let blob;
if (data instanceof Blob) {
blob = data;
} else {
blob = new Blob([data], {
type: type,
});
}
const a = document.createElement("a"),
url = URL.createObjectURL(blob);
a.setAttribute("download", name);
a.style.display = "none";
a.href = url;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function selectFile(accept) {
return new Promise((resolve) => {
const input = make("input");
input.type = "file";
input.accept = accept;
input.onchange = () => {
const file = input.files[0];
if (!file) {
resolve(false);
return;
}
const reader = new FileReader();
reader.onload = () => {
try {
const data = JSON.parse(reader.result);
resolve(data);
} catch {
resolve(false);
}
};
reader.onerror = () => resolve(false);
reader.readAsText(file);
};
input.click();
});
}