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;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 14px 12px;
|
||||
padding: 0 12px;
|
||||
padding-bottom: 20px;
|
||||
overflow-anchor: none;
|
||||
}
|
||||
|
||||
#messages::before {
|
||||
content: "";
|
||||
display: block;
|
||||
height: 14px;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: -40px;
|
||||
}
|
||||
|
||||
#messages:empty::before {
|
||||
content: "whiskr - no messages";
|
||||
color: var(--c-subtext);
|
||||
font-style: italic;
|
||||
align-self: center;
|
||||
margin-top: 5px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#message,
|
||||
@@ -242,9 +250,11 @@ body:not(.loading) #loading {
|
||||
max-width: min(700px, 100%);
|
||||
min-width: 280px;
|
||||
width: max-content;
|
||||
padding-top: 28px;
|
||||
background: var(--c-surface0);
|
||||
background: transparent;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.message::after {
|
||||
@@ -259,6 +269,7 @@ body:not(.loading) #loading {
|
||||
bottom: 0;
|
||||
transition: opacity 150ms;
|
||||
border-radius: 6px;
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
.message.marked::after {
|
||||
@@ -273,16 +284,25 @@ body:not(.loading) #loading {
|
||||
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 {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
}
|
||||
|
||||
.statistics .provider::after,
|
||||
@@ -425,8 +445,7 @@ body:not(.loading) #loading {
|
||||
|
||||
.message .body {
|
||||
position: relative;
|
||||
border-bottom-left-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
padding: 14px 12px;
|
||||
display: flex;
|
||||
@@ -584,12 +603,11 @@ body:not(.loading) #loading {
|
||||
.message .options {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 6px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: 150ms;
|
||||
background: var(--c-surface0);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.message.editing .options,
|
||||
|
||||
@@ -253,10 +253,15 @@
|
||||
// main message div
|
||||
this.#_message = make("div", "message", this.#role, collapsed ? "collapsed" : "");
|
||||
|
||||
// header
|
||||
const _header = make("div", "header");
|
||||
|
||||
this.#_message.appendChild(_header);
|
||||
|
||||
// message role (wrapper)
|
||||
const _wrapper = make("div", "role", this.#role);
|
||||
|
||||
this.#_message.appendChild(_wrapper);
|
||||
_header.appendChild(_wrapper);
|
||||
|
||||
observer.observe(_wrapper);
|
||||
|
||||
@@ -272,6 +277,130 @@
|
||||
|
||||
_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
|
||||
const _body = make("div", "body");
|
||||
|
||||
@@ -408,130 +537,6 @@
|
||||
|
||||
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
|
||||
this.#_statistics = make("div", "statistics");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user