mirror of
https://github.com/coalaura/whiskr.git
synced 2025-09-08 17:06:42 +00:00
import and export
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
7
static/css/icons/export.svg
Normal file
7
static/css/icons/export.svg
Normal 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 |
7
static/css/icons/import.svg
Normal file
7
static/css/icons/import.svg
Normal 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 |
@@ -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>
|
||||
|
@@ -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");
|
||||
});
|
||||
|
@@ -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();
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user