Improve insights and continue WhatsApp implementation

This commit is contained in:
2026-02-15 23:02:51 +00:00
parent b23af9bc7f
commit 88224d972c
13 changed files with 628 additions and 81 deletions

View File

@@ -500,6 +500,82 @@
});
};
window.giaPrepareWindowAnchor = function (trigger) {
if (!trigger || !trigger.getBoundingClientRect) {
window.giaWindowAnchor = null;
return;
}
const rect = trigger.getBoundingClientRect();
window.giaWindowAnchor = {
left: rect.left,
right: rect.right,
top: rect.top,
bottom: rect.bottom,
width: rect.width,
height: rect.height,
ts: Date.now(),
};
};
window.giaPositionFloatingWindow = function (windowEl) {
if (!windowEl) {
return;
}
const isMobile = window.matchMedia("(max-width: 768px)").matches;
const margin = 12;
const rect = windowEl.getBoundingClientRect();
const anchor = window.giaWindowAnchor || null;
windowEl.style.position = "fixed";
if (isMobile) {
const centeredLeftViewport = Math.max(
margin,
Math.round((window.innerWidth - rect.width) / 2)
);
const centeredTopViewport = Math.max(
margin,
Math.round((window.innerHeight - rect.height) / 2)
);
windowEl.style.left = centeredLeftViewport + "px";
windowEl.style.top = centeredTopViewport + "px";
windowEl.style.right = "auto";
windowEl.style.bottom = "auto";
windowEl.style.transform = "none";
windowEl.setAttribute("tabindex", "-1");
if (typeof windowEl.focus === "function") {
windowEl.focus({preventScroll: true});
}
if (typeof windowEl.scrollIntoView === "function") {
windowEl.scrollIntoView({block: "center", inline: "center", behavior: "smooth"});
}
window.giaWindowAnchor = null;
return;
}
if (!anchor || (Date.now() - anchor.ts) > 10000) {
return;
}
const desiredLeftViewport = anchor.left;
const desiredTopViewport = anchor.bottom + 6;
const maxLeftViewport = window.innerWidth - rect.width - margin;
const maxTopViewport = window.innerHeight - rect.height - margin;
const boundedLeftViewport = Math.max(
margin,
Math.min(desiredLeftViewport, maxLeftViewport)
);
const boundedTopViewport = Math.max(
margin,
Math.min(desiredTopViewport, maxTopViewport)
);
windowEl.style.left = boundedLeftViewport + "px";
windowEl.style.top = boundedTopViewport + "px";
windowEl.style.right = "auto";
windowEl.style.bottom = "auto";
windowEl.style.transform = "none";
window.giaWindowAnchor = null;
};
document.addEventListener("click", function (event) {
const trigger = event.target.closest(".js-widget-spawn-trigger");
if (!trigger) {
@@ -515,6 +591,15 @@
document.body.addEventListener("htmx:afterSwap", function (event) {
const target = (event && event.target) || document;
window.giaEnableWidgetSpawnButtons(target);
const targetId = (target && target.id) || "";
if (targetId === "windows-here") {
const floatingWindow = target.querySelector(".floating-window");
if (floatingWindow) {
window.setTimeout(function () {
window.giaPositionFloatingWindow(floatingWindow);
}, 0);
}
}
});
</script>
{% block outer_content %}
@@ -527,7 +612,7 @@
{% endblock %}
<div id="modals-here">
</div>
<div id="windows-here">
<div id="windows-here" style="z-index: 120;">
</div>
<div id="widgets-here" style="display: none;">
</div>

View File

@@ -1,6 +1,9 @@
<div id="widget">
<div id="widget-{{ unique }}" class="grid-stack-item" {% block widget_options %}{% if widget_options is None %}gs-w="6" gs-h="1" gs-y="20" gs-x="0"{% else %}{% autoescape off %}{{ widget_options }}{% endautoescape %}{% endif %}{% endblock %}>
<div class="grid-stack-item-content">
<span class="gia-widget-focus-indicator" aria-hidden="true">
<i class="{{ widget_icon|default:'fa-solid fa-circle' }}"></i>
</span>
<nav class="panel">
<p class="panel-heading" style="padding: .2em; line-height: .5em;">
@@ -27,6 +30,36 @@
</div>
</div>
<style>
#widget-{{ unique }} .grid-stack-item-content {
position: relative;
}
#widget-{{ unique }} .gia-widget-focus-indicator {
position: absolute;
left: 0.32rem;
top: 50%;
width: 1rem;
height: 1rem;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
color: #2f5f9f;
background: rgba(235, 244, 255, 0.92);
border: 1px solid rgba(47, 95, 159, 0.3);
opacity: 0;
transform: translateY(-50%) scale(0.92);
transition: opacity 120ms ease, transform 120ms ease;
pointer-events: none;
z-index: 3;
}
#widget-{{ unique }}.is-widget-active .gia-widget-focus-indicator {
opacity: 1;
transform: translateY(-50%) scale(1);
}
</style>
<script>
{% block custom_script %}
{% endblock %}
@@ -34,43 +67,22 @@
document.dispatchEvent(widget_event);
(function () {
var widgetRoot = document.getElementById("widget-{{ unique }}");
var iconClass = "{{ widget_icon|default:'fa-solid fa-arrows-minimize'|escapejs }}";
function decorateHandle() {
if (!widgetRoot) {
return true;
}
var handles = widgetRoot.querySelectorAll(".ui-resizable-se");
if (!handles.length) {
handles = widgetRoot.querySelectorAll(".ui-resizable-handle");
}
if (!handles.length) {
return false;
}
handles.forEach(function (handle) {
if (handle.dataset.iconApplied === "1") {
return;
}
handle.dataset.iconApplied = "1";
handle.style.display = "flex";
handle.style.alignItems = "center";
handle.style.justifyContent = "center";
handle.style.overflow = "hidden";
var icon = document.createElement("i");
icon.className = iconClass + " has-text-grey-light";
icon.style.fontSize = "0.65rem";
icon.style.opacity = "0.65";
icon.style.pointerEvents = "none";
handle.appendChild(icon);
});
return true;
if (!widgetRoot) {
return;
}
var attempts = 0;
var timer = window.setInterval(function () {
attempts += 1;
if (decorateHandle() || attempts > 10) {
window.clearInterval(timer);
}
}, 80);
function setActive() {
document.querySelectorAll(".grid-stack-item.is-widget-active").forEach(function (node) {
if (node !== widgetRoot) {
node.classList.remove("is-widget-active");
}
});
widgetRoot.classList.add("is-widget-active");
}
widgetRoot.addEventListener("mousedown", setActive);
widgetRoot.addEventListener("focusin", setActive);
window.setTimeout(setActive, 0);
})();
</script>
{% block custom_end %}

View File

@@ -30,9 +30,22 @@
{% if metric.group == group_key %}
<article class="message is-light" style="margin-bottom: 0.6rem;">
<div class="message-body">
<p><strong>{{ metric.title }}</strong>: {{ metric.value|default:"-" }}</p>
<p><strong>Calculation:</strong> {{ metric.calculation }}</p>
<p><strong>Psychological Read:</strong> {{ metric.psychology }}</p>
<h3 class="is-size-6 has-text-weight-semibold" style="margin-bottom: 0.45rem;">{{ metric.title }}</h3>
<p class="is-size-7 has-text-grey has-text-weight-semibold" style="margin-bottom: 0.15rem;">
Current Value
</p>
<p style="margin-bottom: 0.55rem;">{{ metric.value|default:"-" }}</p>
<p class="is-size-7 has-text-grey has-text-weight-semibold" style="margin-bottom: 0.15rem;">
How It Is Calculated
</p>
<p style="margin-bottom: 0.55rem;">{{ metric.calculation }}</p>
<p class="is-size-7 has-text-grey has-text-weight-semibold" style="margin-bottom: 0.15rem;">
Psychological Interpretation
</p>
<p>{{ metric.psychology }}</p>
</div>
</article>
{% endif %}

View File

@@ -52,7 +52,7 @@
{% if render_mode == "page" %}
<a class="button is-light is-rounded" href="{{ compose_workspace_url }}">
<span class="icon is-small"><i class="fa-solid fa-table-cells-large"></i></span>
<span>Open In Workspace</span>
<span>Chat Workspace</span>
</a>
{% endif %}
</div>
@@ -204,6 +204,7 @@
class="compose-image"
src="{{ image_url }}"
alt="Attachment"
referrerpolicy="no-referrer"
loading="lazy"
decoding="async">
</figure>
@@ -214,6 +215,7 @@
class="compose-image"
src="{{ msg.image_url }}"
alt="Attachment"
referrerpolicy="no-referrer"
loading="lazy"
decoding="async">
</figure>
@@ -390,7 +392,7 @@
#{{ panel_id }}-lightbox.compose-lightbox {
position: fixed;
inset: 0;
z-index: 160;
z-index: 12050;
background: rgba(10, 12, 16, 0.82);
display: flex;
align-items: center;
@@ -661,7 +663,7 @@
width: min(40rem, calc(100% - 1rem));
margin-top: 0;
z-index: 35;
overflow: auto;
overflow: visible;
}
#{{ panel_id }} .compose-ai-popover-backdrop {
position: absolute;
@@ -687,6 +689,7 @@
background: #fff;
padding: 0.65rem;
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.08);
overflow: visible;
}
#{{ panel_id }} .compose-ai-card.is-active {
display: block;
@@ -863,9 +866,9 @@
word-break: break-word;
}
#{{ panel_id }} .compose-qi-doc-dot {
width: 0.5rem;
height: 0.5rem;
min-width: 0.5rem;
width: 0.64rem;
height: 0.64rem;
min-width: 0.64rem;
border-radius: 50%;
border: 0;
padding: 0;
@@ -874,13 +877,65 @@
cursor: help;
opacity: 0.85;
transform: translateY(0.02rem);
position: relative;
z-index: 1;
}
#{{ panel_id }} .compose-qi-doc-dot::after {
content: attr(data-tooltip);
position: absolute;
left: 50%;
bottom: calc(100% + 0.42rem);
transform: translate(-50%, 0.18rem);
width: min(21rem, 75vw);
max-width: 21rem;
padding: 0.42rem 0.5rem;
border-radius: 7px;
background: rgba(31, 39, 53, 0.96);
color: #f5f8ff;
font-size: 0.67rem;
line-height: 1.3;
text-align: left;
white-space: normal;
box-shadow: 0 8px 22px rgba(7, 10, 17, 0.28);
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 85ms ease, transform 85ms ease, visibility 85ms linear;
transition-delay: 30ms;
}
#{{ panel_id }} .compose-qi-doc-dot::before {
content: "";
position: absolute;
left: 50%;
bottom: calc(100% + 0.1rem);
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 0.3rem solid transparent;
border-right: 0.3rem solid transparent;
border-top: 0.36rem solid rgba(31, 39, 53, 0.96);
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 85ms ease, visibility 85ms linear;
transition-delay: 30ms;
}
#{{ panel_id }} .compose-qi-doc-dot:hover,
#{{ panel_id }} .compose-qi-doc-dot:focus-visible {
background: #9ab1cc;
opacity: 1;
outline: 1px solid rgba(52, 101, 164, 0.45);
outline-offset: 1px;
}
#{{ panel_id }} .compose-qi-doc-dot:hover::after,
#{{ panel_id }} .compose-qi-doc-dot:focus-visible::after,
#{{ panel_id }} .compose-qi-doc-dot:hover::before,
#{{ panel_id }} .compose-qi-doc-dot:focus-visible::before {
opacity: 1;
visibility: visible;
transform: translate(-50%, 0);
transition-delay: 0ms;
}
#{{ panel_id }} .compose-qi-row-meta {
display: inline-flex;
align-items: center;
@@ -1242,6 +1297,7 @@
img.className = "compose-image";
img.src = String(candidateUrl);
img.alt = "Attachment";
img.referrerPolicy = "no-referrer";
img.loading = "lazy";
img.decoding = "async";
figure.appendChild(img);
@@ -1290,11 +1346,7 @@
}
img.dataset.fallbackBound = "1";
img.addEventListener("error", function () {
const figure = img.closest(".compose-media");
if (figure) {
figure.remove();
}
refresh();
img.classList.add("is-image-load-failed");
});
img.addEventListener("load", function () {
if (fallback) {
@@ -1921,7 +1973,7 @@
const dot = document.createElement("button");
dot.type = "button";
dot.className = "compose-qi-doc-dot";
dot.title = String(tooltipText || "");
dot.setAttribute("data-tooltip", String(tooltipText || ""));
dot.setAttribute("aria-label", "Explain " + String(titleText || "metric"));
dot.addEventListener("click", function (ev) {
ev.preventDefault();

View File

@@ -168,6 +168,7 @@
hx-get="{{ action.url }}"
hx-target="{{ action.target }}"
hx-swap="innerHTML"
{% if action.target == "#windows-here" %}onclick="window.giaPrepareWindowAnchor(this);"{% endif %}
title="{{ action.title }}">
<span class="icon"><i class="{{ action.icon }}"></i></span>
</button>

View File

@@ -4,6 +4,13 @@
{% if object.warning %}
<p class="is-size-7" style="margin-top: 0.6rem;">{{ object.warning }}</p>
{% endif %}
{% if object.debug_lines %}
<article class="notification is-light" style="margin-top: 0.6rem; margin-bottom: 0;">
<p><strong>Runtime Debug</strong></p>
<pre class="is-size-7" style="white-space: pre-wrap; margin: 0.4rem 0 0;">{% for line in object.debug_lines %}{{ line }}
{% endfor %}</pre>
</article>
{% endif %}
{% else %}
<article class="notification is-warning is-light" style="margin-bottom: 0;">
<p><strong>WhatsApp QR Not Ready.</strong></p>
@@ -26,6 +33,13 @@
<input type="hidden" name="refresh" value="1" />
</form>
{% endif %}
{% if object.debug_lines %}
<article class="notification is-light" style="margin-top: 0.6rem; margin-bottom: 0;">
<p><strong>Runtime Debug</strong></p>
<pre class="is-size-7" style="white-space: pre-wrap; margin: 0.4rem 0 0;">{% for line in object.debug_lines %}{{ line }}
{% endfor %}</pre>
</article>
{% endif %}
</article>
{% endif %}
</div>