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(); + }); +}