mirror of
https://github.com/coalaura/whiskr.git
synced 2025-12-02 20:22:52 +00:00
floating sticky header
This commit is contained in:
@@ -215,17 +215,25 @@ body:not(.loading) #loading {
|
|||||||
gap: 40px;
|
gap: 40px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 14px 12px;
|
padding: 0 12px;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
overflow-anchor: none;
|
overflow-anchor: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#messages::before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
height: 14px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-bottom: -40px;
|
||||||
|
}
|
||||||
|
|
||||||
#messages:empty::before {
|
#messages:empty::before {
|
||||||
content: "whiskr - no messages";
|
content: "whiskr - no messages";
|
||||||
color: var(--c-subtext);
|
color: var(--c-subtext);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
margin-top: 5px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#message,
|
#message,
|
||||||
@@ -242,9 +250,11 @@ body:not(.loading) #loading {
|
|||||||
max-width: min(700px, 100%);
|
max-width: min(700px, 100%);
|
||||||
min-width: 280px;
|
min-width: 280px;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
padding-top: 28px;
|
background: transparent;
|
||||||
background: var(--c-surface0);
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message::after {
|
.message::after {
|
||||||
@@ -259,6 +269,7 @@ body:not(.loading) #loading {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
transition: opacity 150ms;
|
transition: opacity 150ms;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
z-index: 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message.marked::after {
|
.message.marked::after {
|
||||||
@@ -273,16 +284,25 @@ body:not(.loading) #loading {
|
|||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message .header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 4px 6px;
|
||||||
|
background: var(--c-surface0);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.message .role {
|
.message .role {
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 12px;
|
line-height: 12px;
|
||||||
top: 6px;
|
|
||||||
left: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.statistics .provider::after,
|
.statistics .provider::after,
|
||||||
@@ -425,8 +445,7 @@ body:not(.loading) #loading {
|
|||||||
|
|
||||||
.message .body {
|
.message .body {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-bottom-left-radius: 6px;
|
border-radius: 6px;
|
||||||
border-bottom-right-radius: 6px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 14px 12px;
|
padding: 14px 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -584,12 +603,11 @@ body:not(.loading) #loading {
|
|||||||
.message .options {
|
.message .options {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
position: absolute;
|
|
||||||
top: 4px;
|
|
||||||
right: 6px;
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: 150ms;
|
transition: 150ms;
|
||||||
|
background: var(--c-surface0);
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message.editing .options,
|
.message.editing .options,
|
||||||
|
|||||||
@@ -253,10 +253,15 @@
|
|||||||
// main message div
|
// main message div
|
||||||
this.#_message = make("div", "message", this.#role, collapsed ? "collapsed" : "");
|
this.#_message = make("div", "message", this.#role, collapsed ? "collapsed" : "");
|
||||||
|
|
||||||
|
// header
|
||||||
|
const _header = make("div", "header");
|
||||||
|
|
||||||
|
this.#_message.appendChild(_header);
|
||||||
|
|
||||||
// message role (wrapper)
|
// message role (wrapper)
|
||||||
const _wrapper = make("div", "role", this.#role);
|
const _wrapper = make("div", "role", this.#role);
|
||||||
|
|
||||||
this.#_message.appendChild(_wrapper);
|
_header.appendChild(_wrapper);
|
||||||
|
|
||||||
observer.observe(_wrapper);
|
observer.observe(_wrapper);
|
||||||
|
|
||||||
@@ -272,6 +277,130 @@
|
|||||||
|
|
||||||
_wrapper.appendChild(this.#_tags);
|
_wrapper.appendChild(this.#_tags);
|
||||||
|
|
||||||
|
// message options
|
||||||
|
const _opts = make("div", "options");
|
||||||
|
|
||||||
|
_header.appendChild(_opts);
|
||||||
|
|
||||||
|
// collapse option
|
||||||
|
const _optCollapse = make("button", "collapse");
|
||||||
|
|
||||||
|
_optCollapse.title = "Collapse/Expand message";
|
||||||
|
|
||||||
|
_opts.appendChild(_optCollapse);
|
||||||
|
|
||||||
|
_optCollapse.addEventListener("click", () => {
|
||||||
|
this.#_message.classList.toggle("collapsed");
|
||||||
|
|
||||||
|
updateScrollButton();
|
||||||
|
|
||||||
|
setFollowTail(distanceFromBottom() <= nearBottom);
|
||||||
|
|
||||||
|
this.#save();
|
||||||
|
});
|
||||||
|
|
||||||
|
// attach option
|
||||||
|
if (this.#role === "user") {
|
||||||
|
const _attach = make("button", "attach");
|
||||||
|
|
||||||
|
_attach.title = "Add files to this message";
|
||||||
|
|
||||||
|
_opts.appendChild(_attach);
|
||||||
|
|
||||||
|
_attach.addEventListener("click", () => {
|
||||||
|
uploadToMessage(_attach, this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy option
|
||||||
|
const _optCopy = make("button", "copy");
|
||||||
|
|
||||||
|
_optCopy.title = "Copy message content";
|
||||||
|
|
||||||
|
_opts.appendChild(_optCopy);
|
||||||
|
|
||||||
|
let timeout;
|
||||||
|
|
||||||
|
_optCopy.addEventListener("click", () => {
|
||||||
|
this.stopEdit();
|
||||||
|
|
||||||
|
clearTimeout(timeout);
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(this.#text);
|
||||||
|
|
||||||
|
_optCopy.classList.add("copied");
|
||||||
|
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
_optCopy.classList.remove("copied");
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
// retry option
|
||||||
|
const _assistant = this.#role === "assistant",
|
||||||
|
_retryLabel = _assistant ? "Delete message and messages after this one and try again" : "Delete messages after this one and try again";
|
||||||
|
|
||||||
|
const _optRetry = make("button", "retry");
|
||||||
|
|
||||||
|
_optRetry.title = _retryLabel;
|
||||||
|
|
||||||
|
_opts.appendChild(_optRetry);
|
||||||
|
|
||||||
|
_optRetry.addEventListener("mouseenter", () => {
|
||||||
|
const index = this.index(_assistant ? 0 : 1);
|
||||||
|
|
||||||
|
mark(index);
|
||||||
|
});
|
||||||
|
|
||||||
|
_optRetry.addEventListener("mouseleave", () => {
|
||||||
|
mark(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
_optRetry.addEventListener("click", () => {
|
||||||
|
const index = this.index(_assistant ? 0 : 1);
|
||||||
|
|
||||||
|
if (index === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
abortNow();
|
||||||
|
|
||||||
|
this.stopEdit();
|
||||||
|
|
||||||
|
while (messages.length > index) {
|
||||||
|
messages[messages.length - 1].delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
mark(false);
|
||||||
|
|
||||||
|
generate(false, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// edit option
|
||||||
|
const _optEdit = make("button", "edit");
|
||||||
|
|
||||||
|
_optEdit.title = "Edit message content";
|
||||||
|
|
||||||
|
_opts.appendChild(_optEdit);
|
||||||
|
|
||||||
|
_optEdit.addEventListener("click", () => {
|
||||||
|
if (this.#_message.classList.contains("collapsed")) {
|
||||||
|
_optCollapse.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggleEdit();
|
||||||
|
});
|
||||||
|
|
||||||
|
// delete option
|
||||||
|
const _optDelete = make("button", "delete");
|
||||||
|
|
||||||
|
_optDelete.title = "Delete message";
|
||||||
|
|
||||||
|
_opts.appendChild(_optDelete);
|
||||||
|
|
||||||
|
_optDelete.addEventListener("click", () => {
|
||||||
|
this.delete();
|
||||||
|
});
|
||||||
|
|
||||||
// message body
|
// message body
|
||||||
const _body = make("div", "body");
|
const _body = make("div", "body");
|
||||||
|
|
||||||
@@ -408,130 +537,6 @@
|
|||||||
|
|
||||||
this.#_tool.appendChild(_callResult);
|
this.#_tool.appendChild(_callResult);
|
||||||
|
|
||||||
// message options
|
|
||||||
const _opts = make("div", "options");
|
|
||||||
|
|
||||||
this.#_message.appendChild(_opts);
|
|
||||||
|
|
||||||
// collapse option
|
|
||||||
const _optCollapse = make("button", "collapse");
|
|
||||||
|
|
||||||
_optCollapse.title = "Collapse/Expand message";
|
|
||||||
|
|
||||||
_opts.appendChild(_optCollapse);
|
|
||||||
|
|
||||||
_optCollapse.addEventListener("click", () => {
|
|
||||||
this.#_message.classList.toggle("collapsed");
|
|
||||||
|
|
||||||
updateScrollButton();
|
|
||||||
|
|
||||||
setFollowTail(distanceFromBottom() <= nearBottom);
|
|
||||||
|
|
||||||
this.#save();
|
|
||||||
});
|
|
||||||
|
|
||||||
// attach option
|
|
||||||
if (this.#role === "user") {
|
|
||||||
const _attach = make("button", "attach");
|
|
||||||
|
|
||||||
_attach.title = "Add files to this message";
|
|
||||||
|
|
||||||
_opts.appendChild(_attach);
|
|
||||||
|
|
||||||
_attach.addEventListener("click", () => {
|
|
||||||
uploadToMessage(_attach, this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy option
|
|
||||||
const _optCopy = make("button", "copy");
|
|
||||||
|
|
||||||
_optCopy.title = "Copy message content";
|
|
||||||
|
|
||||||
_opts.appendChild(_optCopy);
|
|
||||||
|
|
||||||
let timeout;
|
|
||||||
|
|
||||||
_optCopy.addEventListener("click", () => {
|
|
||||||
this.stopEdit();
|
|
||||||
|
|
||||||
clearTimeout(timeout);
|
|
||||||
|
|
||||||
navigator.clipboard.writeText(this.#text);
|
|
||||||
|
|
||||||
_optCopy.classList.add("copied");
|
|
||||||
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
_optCopy.classList.remove("copied");
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
// retry option
|
|
||||||
const _assistant = this.#role === "assistant",
|
|
||||||
_retryLabel = _assistant ? "Delete message and messages after this one and try again" : "Delete messages after this one and try again";
|
|
||||||
|
|
||||||
const _optRetry = make("button", "retry");
|
|
||||||
|
|
||||||
_optRetry.title = _retryLabel;
|
|
||||||
|
|
||||||
_opts.appendChild(_optRetry);
|
|
||||||
|
|
||||||
_optRetry.addEventListener("mouseenter", () => {
|
|
||||||
const index = this.index(_assistant ? 0 : 1);
|
|
||||||
|
|
||||||
mark(index);
|
|
||||||
});
|
|
||||||
|
|
||||||
_optRetry.addEventListener("mouseleave", () => {
|
|
||||||
mark(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
_optRetry.addEventListener("click", () => {
|
|
||||||
const index = this.index(_assistant ? 0 : 1);
|
|
||||||
|
|
||||||
if (index === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
abortNow();
|
|
||||||
|
|
||||||
this.stopEdit();
|
|
||||||
|
|
||||||
while (messages.length > index) {
|
|
||||||
messages[messages.length - 1].delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
mark(false);
|
|
||||||
|
|
||||||
generate(false, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
// edit option
|
|
||||||
const _optEdit = make("button", "edit");
|
|
||||||
|
|
||||||
_optEdit.title = "Edit message content";
|
|
||||||
|
|
||||||
_opts.appendChild(_optEdit);
|
|
||||||
|
|
||||||
_optEdit.addEventListener("click", () => {
|
|
||||||
if (this.#_message.classList.contains("collapsed")) {
|
|
||||||
_optCollapse.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.toggleEdit();
|
|
||||||
});
|
|
||||||
|
|
||||||
// delete option
|
|
||||||
const _optDelete = make("button", "delete");
|
|
||||||
|
|
||||||
_optDelete.title = "Delete message";
|
|
||||||
|
|
||||||
_opts.appendChild(_optDelete);
|
|
||||||
|
|
||||||
_optDelete.addEventListener("click", () => {
|
|
||||||
this.delete();
|
|
||||||
});
|
|
||||||
|
|
||||||
// statistics
|
// statistics
|
||||||
this.#_statistics = make("div", "statistics");
|
this.#_statistics = make("div", "statistics");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user