mirror of
https://github.com/coalaura/whiskr.git
synced 2025-09-09 09:19:54 +00:00
morphdom & better images
This commit is contained in:
@@ -52,7 +52,7 @@ body {
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 1100px;
|
max-width: 1200px;
|
||||||
height: calc(100% - 30px);
|
height: calc(100% - 30px);
|
||||||
border-top-left-radius: 6px;
|
border-top-left-radius: 6px;
|
||||||
border-top-right-radius: 6px;
|
border-top-right-radius: 6px;
|
||||||
|
@@ -70,14 +70,10 @@
|
|||||||
text-decoration-color: rgba(183, 189, 248, 0.6);
|
text-decoration-color: rgba(183, 189, 248, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown .image {
|
.markdown img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
margin: 12px auto;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown blockquote {
|
.markdown blockquote {
|
||||||
|
@@ -62,6 +62,7 @@
|
|||||||
|
|
||||||
<script src="lib/highlight.min.js"></script>
|
<script src="lib/highlight.min.js"></script>
|
||||||
<script src="lib/marked.min.js"></script>
|
<script src="lib/marked.min.js"></script>
|
||||||
|
<script src="lib/morphdom.min.js"></script>
|
||||||
<script src="js/lib.js"></script>
|
<script src="js/lib.js"></script>
|
||||||
<script src="js/markdown.js"></script>
|
<script src="js/markdown.js"></script>
|
||||||
<script src="js/dropdown.js"></script>
|
<script src="js/dropdown.js"></script>
|
||||||
|
@@ -14,17 +14,20 @@
|
|||||||
|
|
||||||
const messages = [];
|
const messages = [];
|
||||||
|
|
||||||
let autoScrolling = false, interacted = false;
|
let autoScrolling = false,
|
||||||
|
interacted = false;
|
||||||
|
|
||||||
function scroll(force = false) {
|
function scroll(force = false) {
|
||||||
if (!autoScrolling && !force) {
|
if (!autoScrolling && !force) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
$messages.scroll({
|
$messages.scroll({
|
||||||
top: $messages.scrollHeight + 200,
|
top: $messages.scrollHeight + 200,
|
||||||
behavior: "smooth",
|
behavior: "smooth",
|
||||||
});
|
});
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Message {
|
class Message {
|
||||||
@@ -37,6 +40,10 @@
|
|||||||
#expanded = false;
|
#expanded = false;
|
||||||
#state = false;
|
#state = false;
|
||||||
|
|
||||||
|
#_diff;
|
||||||
|
#pending = {};
|
||||||
|
#patching = {};
|
||||||
|
|
||||||
#_message;
|
#_message;
|
||||||
#_role;
|
#_role;
|
||||||
#_reasoning;
|
#_reasoning;
|
||||||
@@ -49,10 +56,16 @@
|
|||||||
this.#reasoning = reasoning || "";
|
this.#reasoning = reasoning || "";
|
||||||
this.#text = text || "";
|
this.#text = text || "";
|
||||||
|
|
||||||
|
this.#_diff = document.createElement("div");
|
||||||
|
|
||||||
this.#build();
|
this.#build();
|
||||||
this.#render();
|
this.#render();
|
||||||
|
|
||||||
messages.push(this);
|
messages.push(this);
|
||||||
|
|
||||||
|
if (this.#reasoning || this.#text) {
|
||||||
|
this.#save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#build() {
|
#build() {
|
||||||
@@ -165,38 +178,81 @@
|
|||||||
scroll();
|
scroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#handleImages(element) {
|
||||||
|
element.querySelectorAll("img:not(.image)").forEach((img) => {
|
||||||
|
img.classList.add("image");
|
||||||
|
|
||||||
|
img.addEventListener("load", () => {
|
||||||
|
scroll(!interacted);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#patch(name, element, md, after = false) {
|
||||||
|
if (!element.firstChild) {
|
||||||
|
element.innerHTML = render(md);
|
||||||
|
|
||||||
|
this.#handleImages(element);
|
||||||
|
|
||||||
|
after?.();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#pending[name] = md;
|
||||||
|
|
||||||
|
if (this.#patching[name]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#patching[name] = true;
|
||||||
|
|
||||||
|
schedule(() => {
|
||||||
|
const html = render(this.#pending[name]);
|
||||||
|
|
||||||
|
this.#patching[name] = false;
|
||||||
|
|
||||||
|
this.#_diff.innerHTML = html;
|
||||||
|
|
||||||
|
morphdom(element, this.#_diff, {
|
||||||
|
childrenOnly: true,
|
||||||
|
onBeforeElUpdated: (fromEl, toEl) => {
|
||||||
|
return !fromEl.isEqualNode || !fromEl.isEqualNode(toEl);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#_diff.innerHTML = "";
|
||||||
|
|
||||||
|
this.#handleImages(element);
|
||||||
|
|
||||||
|
after?.();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#render(only = false, noScroll = false) {
|
#render(only = false, noScroll = false) {
|
||||||
if (!only || only === "role") {
|
if (!only || only === "role") {
|
||||||
this.#_role.textContent = this.#role;
|
this.#_role.textContent = this.#role;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!only || only === "reasoning") {
|
if (!only || only === "reasoning") {
|
||||||
this.#_reasoning.innerHTML = render(this.#reasoning);
|
this.#patch("reasoning", this.#_reasoning, this.#reasoning, () => {
|
||||||
|
|
||||||
if (this.#reasoning) {
|
|
||||||
this.#_message.classList.add("has-reasoning");
|
|
||||||
} else {
|
|
||||||
this.#_message.classList.remove("has-reasoning");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#_reasoning.style.setProperty(
|
this.#_reasoning.style.setProperty(
|
||||||
"--height",
|
"--height",
|
||||||
`${this.#_reasoning.scrollHeight}px`,
|
`${this.#_reasoning.scrollHeight}px`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
noScroll || scroll();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#_message.classList.toggle("has-reasoning", !!this.#reasoning);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!only || only === "text") {
|
if (!only || only === "text") {
|
||||||
this.#_text.innerHTML = render(this.#text);
|
this.#patch("text", this.#_text, this.#text, () => {
|
||||||
|
noScroll || scroll();
|
||||||
|
});
|
||||||
|
|
||||||
if (this.#text) {
|
this.#_message.classList.toggle("has-text", !!this.#text);
|
||||||
this.#_message.classList.add("has-text");
|
|
||||||
} else {
|
|
||||||
this.#_message.classList.remove("has-text");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!noScroll) {
|
|
||||||
scroll();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,8 +611,6 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug(chunk);
|
|
||||||
|
|
||||||
switch (chunk.type) {
|
switch (chunk.type) {
|
||||||
case "reason":
|
case "reason":
|
||||||
message.setState("reasoning");
|
message.setState("reasoning");
|
||||||
@@ -585,10 +639,6 @@
|
|||||||
interacted = true;
|
interacted = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
addEventListener("image-loaded", () => {
|
|
||||||
scroll(!interacted);
|
|
||||||
});
|
|
||||||
|
|
||||||
dropdown($role);
|
dropdown($role);
|
||||||
dropdown($prompt);
|
dropdown($prompt);
|
||||||
|
|
||||||
|
@@ -28,6 +28,16 @@ function loadValue(key, fallback = false) {
|
|||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function schedule(cb) {
|
||||||
|
if (document.visibilityState === "visible") {
|
||||||
|
requestAnimationFrame(cb);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(cb, 80);
|
||||||
|
}
|
||||||
|
|
||||||
function uid() {
|
function uid() {
|
||||||
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
(() => {
|
(() => {
|
||||||
const timeouts = new WeakMap(),
|
const timeouts = new WeakMap();
|
||||||
images = {};
|
|
||||||
|
|
||||||
marked.use({
|
marked.use({
|
||||||
async: false,
|
async: false,
|
||||||
@@ -36,48 +35,9 @@
|
|||||||
|
|
||||||
return `<pre>${header}<code>${code.text}</code></pre>`;
|
return `<pre>${header}<code>${code.text}</code></pre>`;
|
||||||
},
|
},
|
||||||
|
|
||||||
image(image) {
|
|
||||||
const { href } = image;
|
|
||||||
|
|
||||||
const id = `i_${btoa(href).replace(/=/g, "")}`,
|
|
||||||
style = prepareImage(id, href) || "";
|
|
||||||
|
|
||||||
return `<div class="image ${id}" style="${style}"></div>`;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function prepareImage(id, href) {
|
|
||||||
if (href in images) {
|
|
||||||
return images[href];
|
|
||||||
}
|
|
||||||
|
|
||||||
images[href] = false;
|
|
||||||
|
|
||||||
const image = new Image();
|
|
||||||
|
|
||||||
image.addEventListener("load", () => {
|
|
||||||
const style = `aspect-ratio:${image.naturalWidth}/${image.naturalHeight};width:${image.naturalWidth}px;background-image:url(${href})`;
|
|
||||||
|
|
||||||
images[href] = style;
|
|
||||||
|
|
||||||
document.querySelectorAll(`.image.${id}`).forEach((img) => {
|
|
||||||
img.setAttribute("style", style);
|
|
||||||
});
|
|
||||||
|
|
||||||
window.dispatchEvent(new Event("image-loaded"));
|
|
||||||
});
|
|
||||||
|
|
||||||
image.addEventListener("error", () => {
|
|
||||||
console.error(`Failed to load image: ${href}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
image.src = href;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.body.addEventListener("click", (event) => {
|
document.body.addEventListener("click", (event) => {
|
||||||
const button = event.target,
|
const button = event.target,
|
||||||
header = button.closest(".pre-header"),
|
header = button.closest(".pre-header"),
|
||||||
|
1
static/lib/morphdom.min.js
vendored
Normal file
1
static/lib/morphdom.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user