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,
|
#json,
|
||||||
#search,
|
#search,
|
||||||
#scrolling,
|
#scrolling,
|
||||||
|
#import,
|
||||||
|
#export,
|
||||||
#clear,
|
#clear,
|
||||||
#add,
|
#add,
|
||||||
#send,
|
#send,
|
||||||
@@ -749,6 +751,8 @@ label[for="reasoning-tokens"] {
|
|||||||
#json,
|
#json,
|
||||||
#search,
|
#search,
|
||||||
#scrolling,
|
#scrolling,
|
||||||
|
#import,
|
||||||
|
#export,
|
||||||
#clear {
|
#clear {
|
||||||
position: unset !important;
|
position: unset !important;
|
||||||
}
|
}
|
||||||
@@ -777,6 +781,14 @@ label[for="reasoning-tokens"] {
|
|||||||
background-image: url(icons/search-on.svg);
|
background-image: url(icons/search-on.svg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#import {
|
||||||
|
background-image: url(icons/import.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#export {
|
||||||
|
background-image: url(icons/export.svg);
|
||||||
|
}
|
||||||
|
|
||||||
#clear {
|
#clear {
|
||||||
background-image: url(icons/trash.svg);
|
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>
|
<button id="scrolling" title="Turn on auto-scrolling"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="option">
|
<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>
|
<button id="clear" title="Clear the entire chat"></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -15,10 +15,13 @@
|
|||||||
$add = document.getElementById("add"),
|
$add = document.getElementById("add"),
|
||||||
$send = document.getElementById("send"),
|
$send = document.getElementById("send"),
|
||||||
$scrolling = document.getElementById("scrolling"),
|
$scrolling = document.getElementById("scrolling"),
|
||||||
|
$export = document.getElementById("export"),
|
||||||
|
$import = document.getElementById("import"),
|
||||||
$clear = document.getElementById("clear");
|
$clear = document.getElementById("clear");
|
||||||
|
|
||||||
const messages = [],
|
const messages = [],
|
||||||
models = {};
|
models = {},
|
||||||
|
modelList = [];
|
||||||
|
|
||||||
let autoScrolling = false,
|
let autoScrolling = false,
|
||||||
searchAvailable = false,
|
searchAvailable = false,
|
||||||
@@ -899,6 +902,8 @@
|
|||||||
$model.innerHTML = "";
|
$model.innerHTML = "";
|
||||||
|
|
||||||
for (const model of data.models) {
|
for (const model of data.models) {
|
||||||
|
modelList.push(model);
|
||||||
|
|
||||||
const el = document.createElement("option");
|
const el = document.createElement("option");
|
||||||
|
|
||||||
el.value = model.id;
|
el.value = model.id;
|
||||||
@@ -917,7 +922,15 @@
|
|||||||
return data;
|
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");
|
$role.value = loadValue("role", "user");
|
||||||
$model.value = loadValue("model", modelList[0].id);
|
$model.value = loadValue("model", modelList[0].id);
|
||||||
$prompt.value = loadValue("prompt", "normal");
|
$prompt.value = loadValue("prompt", "normal");
|
||||||
@@ -971,6 +984,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$message.value = "";
|
$message.value = "";
|
||||||
|
storeValue("message", "");
|
||||||
|
|
||||||
return new Message($role.value, "", text);
|
return new Message($role.value, "", text);
|
||||||
}
|
}
|
||||||
@@ -1086,9 +1100,53 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let x = messages.length - 1; x >= 0; x--) {
|
clearMessages();
|
||||||
messages[x].delete();
|
});
|
||||||
|
|
||||||
|
$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", () => {
|
$scrolling.addEventListener("click", () => {
|
||||||
@@ -1123,8 +1181,8 @@
|
|||||||
dropdown($prompt);
|
dropdown($prompt);
|
||||||
dropdown($reasoningEffort);
|
dropdown($reasoningEffort);
|
||||||
|
|
||||||
loadData().then((data) => {
|
loadData().then(() => {
|
||||||
restore(data?.models || []);
|
restore();
|
||||||
|
|
||||||
document.body.classList.remove("loading");
|
document.body.classList.remove("loading");
|
||||||
});
|
});
|
||||||
|
@@ -74,3 +74,66 @@ function formatMilliseconds(ms) {
|
|||||||
function fixed(num, decimals = 0) {
|
function fixed(num, decimals = 0) {
|
||||||
return num.toFixed(decimals).replace(/\.?0+$/m, "");
|
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