Rebuild workspace widgets and behavioral graph views

This commit is contained in:
2026-03-13 16:48:24 +00:00
parent f8a6d1d41c
commit 57269770b5
47 changed files with 2951 additions and 1077 deletions

View File

@@ -96,12 +96,12 @@
.compose-shell .compose-thread {
display: flex;
flex-direction: column;
gap: 0.75rem;
gap: 0.35rem;
flex: 1 1 auto;
min-height: 0;
max-height: none;
overflow-y: auto;
padding: 0.75rem;
padding: 0.5rem 0.625rem;
border: 1px solid var(--bulma-border, #dbdbdb);
background: var(--bulma-scheme-main-bis, #f7f8fa);
}
@@ -112,10 +112,19 @@
font-size: 0.875rem;
}
.compose-shell .compose-history-loader {
margin: 0;
text-align: center;
}
.compose-shell .compose-history-loader.is-hidden {
display: none;
}
.compose-shell .compose-row {
display: flex;
flex-direction: column;
gap: 0.35rem;
gap: 0.15rem;
}
.compose-shell .compose-row.is-in {
@@ -137,10 +146,10 @@
.compose-shell .compose-bubble {
width: fit-content;
max-width: min(42rem, 100%);
padding: 0.75rem 0.875rem;
max-width: min(38rem, 100%);
padding: 0.4rem 0.55rem;
border: 1px solid var(--bulma-border, #dbdbdb);
border-radius: 1rem;
border-radius: 0.8rem;
background: var(--bulma-scheme-main, #fff);
}
@@ -150,8 +159,8 @@
}
.compose-shell .compose-reply-ref {
margin-bottom: 0.5rem;
padding-left: 0.75rem;
margin-bottom: 0.3rem;
padding-left: 0.45rem;
border-left: 3px solid var(--bulma-border, #dbdbdb);
}
@@ -160,7 +169,7 @@
border: 0;
background: transparent;
color: var(--bulma-link, #3273dc);
font-size: 0.75rem;
font-size: 0.6875rem;
text-align: left;
}
@@ -168,35 +177,26 @@
text-decoration: underline;
}
.compose-shell .compose-source-badge-wrap {
margin-bottom: 0.5rem;
}
.compose-shell .compose-source-badge {
font-size: 0.6875rem;
font-weight: 700;
letter-spacing: 0.02em;
}
.compose-shell .compose-media {
margin: 0 0 0.5rem;
margin: 0 0 0.3rem;
}
.compose-shell .compose-media:last-of-type {
margin-bottom: 0.625rem;
margin-bottom: 0.35rem;
}
.compose-shell .compose-image {
display: block;
max-width: min(26rem, 100%);
max-height: 24rem;
border-radius: 0.75rem;
max-height: 22rem;
border-radius: 0.6rem;
}
.compose-shell .compose-body {
margin: 0;
white-space: pre-wrap;
overflow-wrap: anywhere;
line-height: 1.28;
}
.compose-shell .compose-image-fallback.is-hidden {
@@ -209,11 +209,11 @@
.compose-shell .compose-reactions + .compose-msg-meta,
.compose-shell .compose-edit-history + .compose-reactions,
.compose-shell .compose-edit-history + .compose-msg-meta {
margin-top: 0.5rem;
margin-top: 0.3rem;
}
.compose-shell .compose-edit-history {
font-size: 0.75rem;
font-size: 0.6875rem;
}
.compose-shell .compose-edit-history summary {
@@ -221,15 +221,15 @@
}
.compose-shell .compose-edit-history ul {
margin: 0.5rem 0 0;
margin: 0.35rem 0 0;
padding-left: 1rem;
}
.compose-shell .compose-edit-diff {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
margin-top: 0.25rem;
gap: 0.25rem;
margin-top: 0.15rem;
}
.compose-shell .compose-edit-old {
@@ -244,21 +244,21 @@
.compose-shell .compose-reactions {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
gap: 0.2rem;
}
.compose-shell .compose-reaction-chip {
min-height: 1.7rem;
font-size: 0.875rem;
min-height: 1.45rem;
font-size: 0.75rem;
}
.compose-shell .compose-msg-meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.35rem;
margin-top: 0.5rem;
font-size: 0.75rem;
gap: 0.25rem;
margin-top: 0.3rem;
font-size: 0.6875rem;
}
.compose-shell .compose-msg-flag {
@@ -275,7 +275,30 @@
}
.compose-shell .compose-reply-btn {
margin-top: 0.5rem;
display: inline-flex;
align-items: center;
gap: 0.25rem;
margin-top: 0.25rem;
min-height: 1.75rem;
padding-inline: 0.45rem;
}
@media (hover: hover) {
.compose-shell .compose-row .compose-reply-btn {
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 0.12s ease-in-out;
}
.compose-shell .compose-row:hover .compose-reply-btn,
.compose-shell .compose-row.compose-reply-selected .compose-reply-btn,
.compose-shell .compose-row:focus-within .compose-reply-btn,
.compose-shell .compose-reply-btn:focus-visible {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
}
.compose-shell .compose-form {

View File

@@ -130,6 +130,70 @@ body .has-text-grey-light {
color: var(--gia-text);
}
.gia-split-dropdown .dropdown-menu {
min-width: 18rem;
}
.gia-dropdown-nest {
border-top: 1px solid var(--gia-border);
}
.gia-dropdown-nest summary {
list-style: none;
cursor: pointer;
}
.gia-dropdown-nest summary::-webkit-details-marker {
display: none;
}
.gia-dropdown-nest-body {
padding: 0.25rem 0 0.35rem;
}
.gia-dropdown-nest-body .dropdown-item {
padding-left: 1.5rem;
}
.gia-inline-tabs ul {
border-bottom: 0;
}
.gia-behavior-shell {
min-width: 0;
}
.gia-behavior-summary-card,
.gia-behavior-graph-card {
height: 100%;
}
.gia-behavior-chart-shell {
min-height: 11rem;
}
.gia-behavior-chart {
display: block;
width: 100%;
height: 11rem;
}
.gia-behavior-chart-area {
fill: color-mix(in srgb, var(--bulma-link) 16%, transparent);
}
.gia-behavior-chart-line {
fill: none;
stroke: var(--bulma-link);
stroke-width: 1.6;
stroke-linecap: round;
stroke-linejoin: round;
}
.gia-behavior-chart-point {
fill: var(--bulma-link);
}
.input,
.textarea,
.select select {
@@ -554,6 +618,16 @@ html.gia-has-workspace-root {
box-shadow: 0 0 0 2px rgba(50, 115, 220, 0.16);
}
.grid-stack-item.is-gia-anchor .gia-widget-panel {
border-color: rgba(255, 159, 28, 0.55);
box-shadow: 0 0 0 2px rgba(255, 159, 28, 0.12);
}
.grid-stack-item.is-gia-spawned .gia-widget-panel {
border-color: rgba(72, 199, 142, 0.6);
box-shadow: 0 0 0 3px rgba(72, 199, 142, 0.18);
}
.floating-window {
max-height: 300px;
z-index: 9000;
@@ -740,10 +814,11 @@ html.gia-has-workspace-root {
.gia-send-composer {
margin: 0;
padding: 0.75rem;
border: 1px solid var(--bulma-border, #dbdbdb);
border-radius: 0.875rem;
background: var(--bulma-scheme-main-bis, #f7f8fa);
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
.gia-send-composer-row {
@@ -756,11 +831,12 @@ html.gia-has-workspace-root {
}
.gia-send-composer-input {
min-height: 2.75rem;
max-height: 8rem;
min-height: 2.5rem;
max-height: 7rem;
resize: none;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
box-shadow: none;
}
.gia-send-composer-action {
@@ -769,7 +845,7 @@ html.gia-has-workspace-root {
.gia-send-composer-button {
height: 100%;
min-height: 2.75rem;
min-height: 2.5rem;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}

View File

@@ -18,6 +18,7 @@
const replyBanner = config.replyBanner;
const replyBannerText = config.replyBannerText;
const replyClearBtn = config.replyClearBtn;
const historyLoader = config.historyLoader;
const platformSelect = config.platformSelect;
const contactSelect = config.contactSelect;
const hiddenService = config.hiddenService;
@@ -29,6 +30,9 @@
let lastTs = core.toInt(thread.dataset.lastTs);
let beforeContextReset = null;
state.loadingOlder = false;
state.olderExhausted = false;
const nearBottom = function () {
return thread.scrollHeight - thread.scrollTop - thread.clientHeight < 120;
};
@@ -39,6 +43,21 @@
}
};
const setHistoryLoader = function (message, hidden) {
if (!historyLoader) {
return;
}
historyLoader.textContent = String(
message || "Scroll up to load older messages."
);
historyLoader.classList.toggle("is-hidden", !!hidden);
};
const getOldestTs = function () {
const firstRow = thread.querySelector(".compose-row");
return core.toInt(firstRow && firstRow.dataset ? firstRow.dataset.ts : 0);
};
const queryParams = function (extraParams) {
const params = new URLSearchParams();
params.set("service", thread.dataset.service || "");
@@ -204,8 +223,28 @@
});
if (rows.length) {
scrollToBottom(shouldStick);
setHistoryLoader("", false);
}
ensureEmptyState();
if (!thread.querySelector(".compose-row")) {
setHistoryLoader("", true);
}
};
const prependMessageHtml = function (html) {
const rows = parseMessageRows(html);
if (!rows.length) {
return 0;
}
const previousHeight = thread.scrollHeight;
const previousTop = thread.scrollTop;
rows.forEach(function (msg) {
upsertMessageRow(msg);
});
thread.scrollTop = previousTop + (thread.scrollHeight - previousHeight);
setHistoryLoader("", false);
ensureEmptyState();
return rows.length;
};
const applyTyping = function (payload) {
@@ -267,6 +306,47 @@
}
};
const loadOlder = async function () {
if (state.loadingOlder || state.olderExhausted) {
return;
}
const oldestTs = getOldestTs();
if (!oldestTs) {
state.olderExhausted = true;
setHistoryLoader("Start of conversation.", false);
return;
}
state.loadingOlder = true;
setHistoryLoader("Loading older messages...", false);
try {
const response = await fetch(
thread.dataset.pollUrl + "?" + queryParams({ before_ts: String(oldestTs) }),
{
method: "GET",
credentials: "same-origin",
headers: { Accept: "application/json" },
}
);
if (!response.ok) {
setHistoryLoader("Could not load older messages.", false);
return;
}
const payload = await response.json();
const inserted = prependMessageHtml(payload.messages_html || "");
state.olderExhausted = !payload.has_older || inserted === 0;
setHistoryLoader(
state.olderExhausted
? "Start of conversation."
: "Scroll up to load older messages.",
false
);
} catch (_err) {
setHistoryLoader("Could not load older messages.", false);
} finally {
state.loadingOlder = false;
}
};
const setupWebSocket = function () {
const wsPath = String(thread.dataset.wsUrl || "").trim();
if (!wsPath || !window.WebSocket) {
@@ -359,8 +439,14 @@
clearReplyTarget();
closeSocket();
lastTs = 0;
state.loadingOlder = false;
state.olderExhausted = false;
thread.dataset.lastTs = "0";
thread.innerHTML = "";
if (historyLoader) {
thread.appendChild(historyLoader);
}
setHistoryLoader("Loading recent messages...", false);
ensureEmptyState("Loading messages...");
applyTyping({ typing: false });
poll(true);
@@ -466,6 +552,12 @@
setReplyTarget(row.dataset.messageId || "", row.dataset.replySnippet || "");
textarea.focus();
});
thread.addEventListener("scroll", function () {
if (thread.scrollTop <= 48) {
loadOlder();
}
});
};
const init = function () {
@@ -473,6 +565,7 @@
bindContextSelectors();
applyTyping(core.parseJsonSafe(panel.dataset.initialTyping || "{}", {}));
ensureEmptyState();
setHistoryLoader("", !thread.querySelector(".compose-row"));
scrollToBottom(true);
setupWebSocket();
@@ -492,6 +585,9 @@
init: init,
poll: poll,
queryParams: queryParams,
scrollToLatest: function () {
scrollToBottom(true);
},
setBeforeContextReset: function (callback) {
beforeContextReset = callback;
},

View File

@@ -119,6 +119,7 @@
const threadController = threadModule.createController({
contactSelect: document.getElementById(panelId + "-contact-select"),
hiddenIdentifier: document.getElementById(panelId + "-input-identifier"),
historyLoader: document.getElementById(panelId + "-history-loader"),
hiddenPerson: document.getElementById(panelId + "-input-person"),
hiddenReplyTo: form.querySelector('input[name="reply_to_message_id"]'),
hiddenService: document.getElementById(panelId + "-input-service"),
@@ -135,6 +136,7 @@
thread: thread,
typingNode: document.getElementById(panelId + "-typing"),
});
state.threadController = threadController;
const sendController = sendModule.createController({
armInput: form.querySelector('input[name="failsafe_arm"]'),
@@ -177,6 +179,25 @@
},
initAll: initAll,
initPanel: initPanel,
scrollWidgetToLatest: function (widgetId) {
const widgetNode = widgetId ? document.getElementById(String(widgetId)) : null;
if (!widgetNode) {
return;
}
const panel = widgetNode.querySelector("[data-compose-panel]");
const panelId = String(panel && panel.id ? panel.id : "").trim();
if (!panelId) {
return;
}
const state = window.giaComposePanels[panelId];
if (
state
&& state.threadController
&& typeof state.threadController.scrollToLatest === "function"
) {
state.threadController.scrollToLatest();
}
},
};
document.addEventListener("DOMContentLoaded", function () {

File diff suppressed because it is too large Load Diff