diff --git a/static/css/chat.css b/static/css/chat.css
index 364a48d..71f9e83 100644
--- a/static/css/chat.css
+++ b/static/css/chat.css
@@ -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);
}
diff --git a/static/css/icons/export.svg b/static/css/icons/export.svg
new file mode 100644
index 0000000..f226b7b
--- /dev/null
+++ b/static/css/icons/export.svg
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/static/css/icons/import.svg b/static/css/icons/import.svg
new file mode 100644
index 0000000..118ded1
--- /dev/null
+++ b/static/css/icons/import.svg
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/static/index.html b/static/index.html
index 3466135..a0d305d 100644
--- a/static/index.html
+++ b/static/index.html
@@ -77,6 +77,8 @@
+
+
diff --git a/static/js/chat.js b/static/js/chat.js
index 879af05..1ce4af9 100644
--- a/static/js/chat.js
+++ b/static/js/chat.js
@@ -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");
});
diff --git a/static/js/lib.js b/static/js/lib.js
index fb0eca7..6bd7a1c 100644
--- a/static/js/lib.js
+++ b/static/js/lib.js
@@ -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();
+ });
+}