1
0
mirror of https://github.com/coalaura/whiskr.git synced 2025-12-02 20:22:52 +00:00
This commit is contained in:
Laura
2025-09-12 14:34:08 +02:00
parent 5998f61823
commit ea65f7fd60
7 changed files with 143 additions and 29 deletions

View File

@@ -334,6 +334,9 @@ body:not(.loading) #loading {
margin-left: 12px;
}
.message:not(.has-tool) .tool,
.message:not(.has-reasoning) .reasoning,
.message:not(.has-images) .images,
.message:not(.has-text) .text,
.message:not(.has-tags) .tags {
display: none;
@@ -405,6 +408,18 @@ body:not(.loading) #loading {
border: 2px solid #ed8796;
}
.images .image {
display: block;
font-size: 0;
}
.images .image img {
max-width: 100%;
width: 420px;
border-radius: 6px;
border: 2px solid #363a4f;
}
.tool .result pre,
.reasoning-text pre {
background: #1b1d2a;
@@ -433,8 +448,7 @@ body:not(.loading) #loading {
.message.has-reasoning:not(.has-text):not(.errored) div.text,
.message.has-tool:not(.has-text):not(.errored) div.text,
.message.has-files:not(.has-text):not(.errored) div.text,
.message:not(.has-tool) .tool,
.message:not(.has-reasoning) .reasoning {
.message.has-images:not(.has-text):not(.errored) div.text {
display: none;
}

View File

@@ -137,6 +137,7 @@
#role;
#reasoning;
#text;
#images = [];
#files = [];
#tool;
@@ -158,10 +159,11 @@
#_reasoning;
#_text;
#_edit;
#_images;
#_tool;
#_statistics;
constructor(role, reasoning, text, files = [], collapsed = false) {
constructor(role, reasoning, text, tool = false, files = [], images = [], tags = [], collapsed = false) {
this.#id = uid();
this.#role = role;
this.#reasoning = reasoning || "";
@@ -172,15 +174,25 @@
this.#build(collapsed);
this.#render();
if (tool) {
this.setTool(tool);
}
for (const file of files) {
this.addFile(file);
}
for (const image of images) {
this.addImage(image);
}
for (const tag of tags) {
this.addTag(tag);
}
messages.push(this);
if (this.#reasoning || this.#text) {
this.#save();
}
this.#save();
}
#build(collapsed) {
@@ -273,6 +285,11 @@
this.updateEditHeight();
});
// message images
this.#_images = make("div", "images");
_body.appendChild(this.#_images);
// message tool
this.#_tool = make("div", "tool");
@@ -514,6 +531,38 @@
this.#_message.classList.toggle("has-tags", this.#tags.length > 0);
}
if (!only || only === "images") {
for (let x = 0; x < this.#images.length; x++) {
if (this.#_images.querySelector(`.i-${x}`)) {
continue;
}
const image = this.#images[x],
blob = dataBlob(image),
url = URL.createObjectURL(blob);
const _link = make("a", "image", `i-${x}`);
_link.download = `image-${x + 1}`;
_link.target = "_blank";
_link.href = url;
this.#_images.appendChild(_link);
const _image = make("img");
_image.src = url;
_link.appendChild(_image);
}
this.#_message.classList.toggle("has-images", !!this.#images.length);
noScroll || scroll();
updateScrollButton();
}
if (!only || only === "tool") {
if (this.#tool) {
const { name, args, result, cost, invalid } = this.#tool;
@@ -650,6 +699,10 @@
data.reasoning = this.#reasoning;
}
if (this.#images.length && full) {
data.images = this.#images;
}
if (this.#error && full) {
data.error = this.#error;
}
@@ -666,7 +719,7 @@
data.collapsed = true;
}
if (!data.files?.length && !data.reasoning && !data.text && !data.tool) {
if (!data.images?.length && !data.files?.length && !data.reasoning && !data.text && !data.tool) {
return false;
}
@@ -790,6 +843,13 @@
this.#save();
}
addImage(image) {
this.#images.push(image);
this.#render("images");
this.#save();
}
addReasoning(chunk) {
this.#reasoning += chunk;
@@ -995,7 +1055,7 @@
$temperature.classList.remove("invalid");
}
let iterations = parseInt($iterations.value);
let iterations = parseInt($iterations.value, 10);
if (Number.isNaN(iterations) || iterations < 1 || iterations > 50) {
iterations = 3;
@@ -1006,7 +1066,7 @@
const effort = $reasoningEffort.value;
let tokens = parseInt($reasoningTokens.value);
let tokens = parseInt($reasoningTokens.value, 10);
if (!effort && (Number.isNaN(tokens) || tokens <= 0 || tokens > 1024 * 1024)) {
tokens = 1024;
@@ -1146,6 +1206,10 @@
finish();
}
break;
case "image":
message.addImage(chunk.text);
break;
case "reason":
message.setState("reasoning");
@@ -1354,22 +1418,12 @@
}
loadValue("messages", []).forEach(message => {
const obj = new Message(message.role, message.reasoning, message.text, message.files || [], message.collapsed);
const obj = new Message(message.role, message.reasoning, message.text, message.tool, message.files || [], message.images || [], message.tags || [], message.collapsed);
if (message.error) {
obj.showError(message.error);
}
if (message.tags) {
message.tags.forEach(tag => {
obj.addTag(tag);
});
}
if (message.tool) {
obj.setTool(message.tool);
}
if (message.statistics) {
obj.setStatistics(message.statistics);
}
@@ -1689,7 +1743,7 @@
},
json: jsonMode,
search: searchTool,
messages: messages.map(message => message.getData()).filter(Boolean),
messages: messages.map(message => message.getData(true)).filter(Boolean),
});
download("chat.json", "application/json", data);

View File

@@ -86,6 +86,36 @@ function formatMilliseconds(ms) {
return `${Math.round(ms / 1000)}s`;
}
function dataBlob(dataUrl) {
const [header, data] = dataUrl.split(","),
mime = header.match(/data:(.*?)(;|$)/)[1];
let blob;
if (header.includes(";base64")) {
const bytes = atob(data),
numbers = new Array(bytes.length);
for (let i = 0; i < bytes.length; i++) {
numbers[i] = bytes.charCodeAt(i);
}
const byteArray = new Uint8Array(numbers);
blob = new Blob([byteArray], {
type: mime,
});
} else {
const text = decodeURIComponent(data);
blob = new Blob([text], {
type: mime,
});
}
return blob;
}
function fixed(num, decimals = 0) {
return num.toFixed(decimals).replace(/\.?0+$/m, "");
}