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

@@ -43,7 +43,7 @@
<link rel="preload" href="{% static 'vendor/fontawesome/webfonts/fa-regular-400.woff2' %}" as="font" type="font/woff2" integrity="sha512-qioT43fXB5q4Bbpn8sPQE9OIZLjKD0c0lVmpm6KmT8k34LM6gkRcOOMi1BOl2lohFG/7p9tzKfTP5G563BQq1g==" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/bulma.min.css' %}" integrity="sha512-yh2RE0wZCVZeysGiqTwDTO/dKelCbS9bP2L94UvOFtl/FKXcNAje3Y2oBg/ZMZ3LS1sicYk4dYVGtDex75fvvA==" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'vendor/fontawesome/css/all.css' %}" integrity="sha512-UKBBxJ5N3/MYiSsYTlEsARsp4vELKVRIklED4Mb6wpuVFOgy5Blt+sXUdz1TDReqWsm64xxBA2QoBJRCxI0x5Q==" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/gia-theme.css' %}" integrity="sha512-17wNDv0gA1saxAIzoySMcOef4/8dKEo2eZcWGhVHUjKolxhbfYVia9i/wExDqEw8MhfP4Kk8BrMajcOHngqJJg==" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/gia-theme.css' %}" integrity="sha512-Lnmy74rBeyPt8DwWBqsRj9hFqTXuXZqkIgIeIm+inhoXpOfGjsvlealaDwRNVv/ou0Bta6h/RmOkjOJYOkALCw==" crossorigin="anonymous">
{% block extra_head_assets %}{% endblock %}
<script src="{% static 'js/htmx.min.js' %}" integrity="sha512-CGXFnDNv5q48ciFeIyWFcfZhqYW0sSBiPO+HZDO3XLM+p8xjhezz5CCxtkXVDKfCbvF+iUhel7xoeSp19o7x7g==" crossorigin="anonymous"></script>
<script defer src="{% static 'js/remove-me.js' %}" integrity="sha512-uhE4kDw2+tXdJPDKSttOEYhVnwYq310+d5DMQnTjafJ58QLlYPyx0RTCNbjcrTiGfCjAhaQob4AumEUa2m3TaQ==" crossorigin="anonymous"></script>
@@ -163,6 +163,49 @@
});
});
var closeGiaDropdowns = function (exceptNode) {
document.querySelectorAll(".dropdown[data-gia-dropdown].is-active").forEach(function (node) {
if (exceptNode && node === exceptNode) {
return;
}
node.classList.remove("is-active");
var trigger = node.querySelector(".js-gia-dropdown-toggle");
if (trigger) {
trigger.setAttribute("aria-expanded", "false");
}
});
};
document.addEventListener("click", function (event) {
var toggle = event.target.closest(".js-gia-dropdown-toggle");
if (toggle) {
event.preventDefault();
event.stopPropagation();
var dropdown = toggle.closest(".dropdown[data-gia-dropdown]");
if (!dropdown) {
return;
}
var nextState = !dropdown.classList.contains("is-active");
closeGiaDropdowns(nextState ? dropdown : null);
dropdown.classList.toggle("is-active", nextState);
toggle.setAttribute("aria-expanded", nextState ? "true" : "false");
return;
}
if (!event.target.closest(".dropdown[data-gia-dropdown]")) {
closeGiaDropdowns(null);
return;
}
if (event.target.closest(".dropdown[data-gia-dropdown] .dropdown-item") && !event.target.closest("summary")) {
closeGiaDropdowns(null);
}
});
document.addEventListener("keydown", function (event) {
if (event.key === "Escape") {
closeGiaDropdowns(null);
}
});
document.body.addEventListener("htmx:afterRequest", function (event) {
const detail = (event && event.detail) || null;
const source = detail && detail.elt ? detail.elt : null;

View File

@@ -24,7 +24,7 @@
aria-label="Snap assistant">
<p class="panel-heading gia-snap-assistant-heading">
<span class="icon is-small"><i class="fa-solid fa-table-columns"></i></span>
<span>Snap Right</span>
<span class="gia-snap-assistant-title">Snap Right</span>
<button
type="button"
class="delete is-small js-gia-snap-assistant-close"
@@ -32,7 +32,7 @@
</p>
<div class="panel-block">
<div class="content is-small">
<p>Choose a second window for the right side.</p>
<p class="gia-snap-assistant-message">Choose a second window for the right side.</p>
</div>
</div>
<div class="panel-block is-active gia-snap-assistant-body">

View File

@@ -3,7 +3,7 @@
data-gia-widget-shell="1"
{% if widget_style_hrefs %}data-gia-style-hrefs="{{ widget_style_hrefs|join:'|' }}"{% endif %}
{% if widget_script_srcs %}data-gia-script-srcs="{{ widget_script_srcs|join:'|' }}"{% endif %}>
<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 id="widget-{{ unique }}" class="grid-stack-item" gs-id="widget-{{ unique }}" {% 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">
<section class="gia-widget-panel">
<header class="gia-widget-heading">
@@ -34,6 +34,14 @@
aria-label="Snap window left">
<span class="icon is-small"><i class="fa-solid fa-arrow-left"></i></span>
</button>
<button
type="button"
class="button is-light js-gia-widget-action"
data-gia-action="snap-top"
data-gia-widget-id="widget-{{ unique }}"
aria-label="Snap window top">
<span class="icon is-small"><i class="fa-solid fa-arrow-up"></i></span>
</button>
<button
type="button"
class="button is-light js-gia-widget-action"
@@ -42,6 +50,14 @@
aria-label="Snap window right">
<span class="icon is-small"><i class="fa-solid fa-arrow-right"></i></span>
</button>
<button
type="button"
class="button is-light js-gia-widget-action"
data-gia-action="snap-bottom"
data-gia-widget-id="widget-{{ unique }}"
aria-label="Snap window bottom">
<span class="icon is-small"><i class="fa-solid fa-arrow-down"></i></span>
</button>
<button
type="button"
class="button is-light js-gia-widget-action"

View File

@@ -1,98 +1,22 @@
{% extends "base.html" %}
{% block content %}
<div class="columns is-multiline">
<div class="column is-12">
<section class="section">
<div class="container">
<nav class="breadcrumb is-small" aria-label="breadcrumbs">
<ul>
<li><a href="{{ workspace_url }}">AI Workspace</a></li>
<li class="is-active"><a aria-current="page">Information</a></li>
<li class="is-active"><a aria-current="page">MS / PS Information</a></li>
</ul>
</nav>
</div>
<div class="column is-12">
<h1 class="title is-4" style="margin-bottom: 0.35rem;">Information: {{ person.name }}</h1>
<p class="is-size-7 has-text-grey">Commitment directionality and underlying metric factors from deterministic message-history snapshots.</p>
{% include "partials/ai-insight-nav.html" with active_tab="information" %}
</div>
<div class="column is-5">
<div class="box">
<p class="heading">Conversation Overview</p>
{% if overview_rows %}
{% for row in overview_rows %}
<article class="message is-light" style="margin-bottom: 0.45rem;">
<div class="message-body" style="padding: 0.45rem 0.55rem;">
<p class="is-size-7 has-text-grey" style="margin-bottom: 0.12rem;">
{{ row.group_title }}
</p>
<p style="margin-bottom: 0.3rem;">
<strong>{{ row.title }}:</strong> {{ row.value|default:"-" }}
</p>
<a
class="tag is-light"
href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric=row.slug %}">
View Detail
</a>
</div>
</article>
{% endfor %}
{% else %}
<p class="is-size-7 has-text-grey">No conversation metadata available yet.</p>
{% endif %}
</div>
<div class="box">
<p class="heading">Commitment Directionality</p>
<p class="title is-5" style="margin-bottom: 0.35rem;">{{ directionality.direction_label }}</p>
<p><strong>Commit In:</strong> {{ directionality.commit_in|default:"-" }}</p>
<p><strong>Commit Out:</strong> {{ directionality.commit_out|default:"-" }}</p>
<p><strong>Delta:</strong> {{ directionality.delta|default:"-" }}</p>
<p><strong>Magnitude:</strong> {{ directionality.magnitude|default:"-" }}</p>
<p><strong>Confidence:</strong> {{ directionality.confidence|default:"-" }}</p>
<article class="message is-light" style="margin-top: 0.65rem;">
<div class="message-body">
{{ directionality.conclusion }}
</div>
</article>
<div class="mb-4">
<h1 class="title is-4 mb-1">MS / PS Information: {{ person.name }}</h1>
<p class="is-size-7 has-text-grey">
Current message-state and presence-state signals derived from timing data.
</p>
{% include "partials/ai-insight-nav.html" with active_tab="information" %}
</div>
{% include "partials/ai-workspace-behavioral-information.html" %}
</div>
<div class="column is-7">
<div class="box">
<p class="heading">Factor Inputs</p>
<div class="columns is-multiline" style="margin: 0 -0.2rem;">
{% for factor in directionality.factors %}
<div class="column is-12-mobile is-6-tablet" style="padding: 0.2rem;">
<article class="box" style="margin-bottom: 0; border: 1px solid rgba(0, 0, 0, 0.14); box-shadow: none;">
<p class="is-size-7 has-text-weight-semibold" style="margin-bottom: 0.2rem;">
<span class="icon is-small"><i class="{{ factor.icon }}"></i></span>
<span>{{ factor.title }}</span>
</p>
<p class="is-size-7"><strong>Weight:</strong> {{ factor.weight }}</p>
<p class="is-size-7"><strong>Value:</strong> {{ factor.value|default:"-" }}</p>
<p style="margin-top: 0.35rem;">
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric=factor.slug %}">
View Metric
</a>
</p>
</article>
</div>
{% endfor %}
</div>
</div>
<div class="box">
<p class="heading">Linked Graphs</p>
<div class="tags">
{% for ref in directionality.graph_refs %}
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric=ref.slug %}">
{{ ref.title }}: {{ ref.value|default:"-" }}
</a>
{% endfor %}
</div>
</div>
</div>
</div>
</section>
{% endblock %}

View File

@@ -1,113 +1,23 @@
{% extends "base.html" %}
{% load static %}
{% block content %}
<div class="columns is-multiline">
<div class="column is-12">
<section class="section">
<div class="container">
<nav class="breadcrumb is-small" aria-label="breadcrumbs">
<ul>
<li><a href="{{ workspace_url }}">AI Workspace</a></li>
<li><a href="{{ graphs_url }}">Insight Graphs</a></li>
<li><a href="{{ graphs_url }}">Behavioral Graphs</a></li>
<li class="is-active"><a aria-current="page">{{ metric.title }}</a></li>
</ul>
</nav>
</div>
<div class="column is-12">
<h1 class="title is-4" style="margin-bottom: 0.35rem;">{{ metric.title }}: {{ person.name }}</h1>
<p class="is-size-7 has-text-grey">Conversation {{ workspace_conversation.id }}</p>
{% include "partials/ai-insight-nav.html" %}
</div>
<div class="column is-5">
<div class="box">
<p class="heading">{{ metric_group.title }}</p>
<p class="title is-5" style="margin-bottom: 0.5rem;">{{ metric.title }}</p>
<p><strong>Current Value:</strong> {{ metric_value|default:"-" }}</p>
<p style="margin-top: 0.65rem;"><strong>How It Is Calculated</strong></p>
<p>{{ metric.calculation }}</p>
<p style="margin-top: 0.65rem;"><strong>Psychological Interpretation</strong></p>
<p>{{ metric.psychology }}</p>
{% if metric_psychology_hint %}
<article class="message is-info is-light" style="margin-top: 0.75rem;">
<div class="message-body">
{{ metric_psychology_hint }}
</div>
</article>
{% endif %}
<div class="mb-4">
<h1 class="title is-4 mb-1">{{ metric.title }}: {{ person.name }}</h1>
<p class="is-size-7 has-text-grey">
Detailed MS/PS graph for this contact.
</p>
{% include "partials/ai-insight-nav.html" %}
</div>
{% include "partials/ai-workspace-behavioral-graph-detail.html" %}
</div>
<div class="column is-7">
<div class="box">
<p class="heading">History</p>
{% if graph_applicable %}
<div style="height: 360px;">
<canvas id="metric-detail-chart"></canvas>
</div>
{% if not graph_points %}
<p class="is-size-7 has-text-grey" style="margin-top: 0.65rem;">
No historical points yet for this metric.
</p>
{% endif %}
{% else %}
<article class="message is-light">
<div class="message-body">
This is a point-in-time metric and is not charted.
</div>
</article>
{% endif %}
</div>
</div>
</div>
{{ graph_points|json_script:"metric-detail-points" }}
<script src="{% static 'js/chart.js' %}"></script>
<script>
(function() {
var node = document.getElementById("metric-detail-points");
if (!node) {
return;
}
var points = JSON.parse(node.textContent || "[]");
var shouldRender = {{ graph_applicable|yesno:"true,false" }};
if (!shouldRender) {
return;
}
var labels = points.map(function(row) {
var dt = new Date(row.x);
return dt.toLocaleString();
});
var values = points.map(function(row) {
return row.y;
});
var ctx = document.getElementById("metric-detail-chart");
if (!ctx) {
return;
}
new Chart(ctx.getContext("2d"), {
type: "line",
data: {
labels: labels,
datasets: [
{
label: "{{ metric.title|escapejs }}",
data: values,
borderColor: "#3273dc",
backgroundColor: "rgba(50,115,220,0.12)",
borderWidth: 2,
pointRadius: 2,
tension: 0.28,
spanGaps: true
}
]
},
options: {
maintainAspectRatio: false,
plugins: {
legend: {
display: true
}
}
}
});
})();
</script>
</section>
{% endblock %}

View File

@@ -1,129 +1,22 @@
{% extends "base.html" %}
{% load static %}
{% block content %}
<div class="columns is-multiline">
<div class="column is-12">
<section class="section">
<div class="container">
<nav class="breadcrumb is-small" aria-label="breadcrumbs">
<ul>
<li><a href="{{ workspace_url }}">AI Workspace</a></li>
<li class="is-active"><a aria-current="page">Insight Graphs</a></li>
<li class="is-active"><a aria-current="page">Behavioral Graphs</a></li>
</ul>
</nav>
</div>
<div class="column is-12">
<h1 class="title is-4" style="margin-bottom: 0.35rem;">Insight Graphs: {{ person.name }}</h1>
<p class="is-size-7 has-text-grey">
Historical metrics for workspace {{ workspace_conversation.id }}. Points are range-downsampled server-side with high-resolution recent data and progressively sparser older ranges.
</p>
<div class="buttons are-small" style="margin: 0.5rem 0 0.25rem;">
<a
class="button {% if graph_density == 'low' %}is-dark{% else %}is-light{% endif %}"
href="?density=low">
Density: Low (max {{ graph_density_caps.low }})
</a>
<a
class="button {% if graph_density == 'medium' %}is-dark{% else %}is-light{% endif %}"
href="?density=medium">
Density: Medium (max {{ graph_density_caps.medium }})
</a>
<a
class="button {% if graph_density == 'high' %}is-dark{% else %}is-light{% endif %}"
href="?density=high">
Density: High (max {{ graph_density_caps.high }})
</a>
<div class="mb-4">
<h1 class="title is-4 mb-1">Behavioral Graphs: {{ person.name }}</h1>
<p class="is-size-7 has-text-grey">
Rebuilt from message and event timing data using the MS/PS model rather than workspace snapshots.
</p>
{% include "partials/ai-insight-nav.html" with active_tab="graphs" %}
</div>
{% include "partials/ai-insight-nav.html" with active_tab="graphs" %}
{% include "partials/ai-workspace-behavioral-graphs.html" %}
</div>
{% for graph in graph_cards %}
<div class="column is-6">
<div class="box">
<div class="is-flex is-justify-content-space-between is-align-items-center" style="margin-bottom: 0.45rem;">
<div>
<p class="heading">{{ graph.group_title }}</p>
<p class="title is-6" style="margin-bottom: 0.2rem;">{{ graph.title }}</p>
<p class="is-size-7 has-text-grey">
{{ graph.count }} displayed of {{ graph.raw_count }} source point{{ graph.raw_count|pluralize }}
</p>
</div>
<div class="buttons are-small" style="margin: 0;">
<a class="button is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric=graph.slug %}">
Detail
</a>
<a class="button is-light" href="{{ help_url }}#group-{{ graph.group }}">
How It Works
</a>
</div>
</div>
<div style="height: 250px;">
<canvas id="graph-{{ graph.slug }}"></canvas>
</div>
</div>
</div>
{% endfor %}
</div>
{{ graph_cards|json_script:"insight-graph-cards" }}
<script src="{% static 'js/chart.js' %}"></script>
<script>
(function() {
var node = document.getElementById("insight-graph-cards");
if (!node) {
return;
}
var graphs = JSON.parse(node.textContent || "[]");
var palette = ["#3273dc", "#23d160", "#ffdd57", "#ff3860", "#7957d5", "#00d1b2"];
graphs.forEach(function(graph, idx) {
var canvas = document.getElementById("graph-" + graph.slug);
if (!canvas) {
return;
}
var labels = (graph.points || []).map(function(row) {
return new Date(row.x).toLocaleString();
});
var values = (graph.points || []).map(function(row) {
return row.y;
});
var color = palette[idx % palette.length];
var yScale = {};
if (graph.y_min !== null && graph.y_min !== undefined) {
yScale.min = graph.y_min;
}
if (graph.y_max !== null && graph.y_max !== undefined) {
yScale.max = graph.y_max;
}
new Chart(canvas.getContext("2d"), {
type: "line",
data: {
labels: labels,
datasets: [
{
label: graph.title,
data: values,
borderColor: color,
backgroundColor: color + "33",
borderWidth: 2,
pointRadius: 2,
tension: 0.24,
spanGaps: true
}
]
},
options: {
maintainAspectRatio: false,
scales: {
y: yScale
},
plugins: {
legend: {
display: false
}
}
}
});
});
})();
</script>
</section>
{% endblock %}

View File

@@ -1,58 +1,23 @@
{% extends "base.html" %}
{% block content %}
<div class="columns is-multiline">
<div class="column is-12">
<section class="section">
<div class="container">
<nav class="breadcrumb is-small" aria-label="breadcrumbs">
<ul>
<li><a href="{{ workspace_url }}">AI Workspace</a></li>
<li><a href="{{ graphs_url }}">Insight Graphs</a></li>
<li class="is-active"><a aria-current="page">Scoring Help</a></li>
<li><a href="{{ graphs_url }}">Behavioral Graphs</a></li>
<li class="is-active"><a aria-current="page">MS / PS Help</a></li>
</ul>
</nav>
</div>
<div class="column is-12">
<h1 class="title is-4" style="margin-bottom: 0.35rem;">Scoring Help: {{ person.name }}</h1>
<p class="is-size-7 has-text-grey">
Combined explanation for each metric collection group and what it can
imply in relationship dynamics. Scoring is deterministic from message
history and can be backfilled via metric history reconciliation.
</p>
{% include "partials/ai-insight-nav.html" with active_tab="help" %}
</div>
{% for group_key, group in groups.items %}
<div class="column is-12" id="group-{{ group_key }}">
<div class="box">
<p class="heading">{{ group.title }}</p>
<p style="margin-bottom: 0.75rem;">{{ group.summary }}</p>
{% for metric in metrics %}
{% if metric.group == group_key %}
<article class="message is-light" style="margin-bottom: 0.6rem;">
<div class="message-body">
<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 %}
{% endfor %}
</div>
<div class="mb-4">
<h1 class="title is-4 mb-1">MS / PS Help: {{ person.name }}</h1>
<p class="is-size-7 has-text-grey">
Definitions and psychological interpretation for the timing signal system.
</p>
{% include "partials/ai-insight-nav.html" with active_tab="help" %}
</div>
{% endfor %}
</div>
{% include "partials/ai-workspace-behavioral-help.html" %}
</div>
</section>
{% endblock %}

View File

@@ -3,18 +3,18 @@
class="tag {% if active_tab == 'graphs' %}is-dark{% else %}is-link is-light{% endif %}"
href="{{ graphs_url }}">
<span class="icon is-small"><i class="fa-solid fa-chart-line"></i></span>
<span>Insight Graphs</span>
<span>Behavioral Graphs</span>
</a>
<a
class="tag {% if active_tab == 'information' %}is-dark{% else %}is-link is-light{% endif %}"
href="{{ information_url }}">
<span class="icon is-small"><i class="fa-solid fa-circle-info"></i></span>
<span>Information View</span>
<span>MS / PS Information</span>
</a>
<a
class="tag {% if active_tab == 'help' %}is-dark{% else %}is-link is-light{% endif %}"
href="{{ help_url }}">
<span class="icon is-small"><i class="fa-solid fa-circle-question"></i></span>
<span>Scoring Guide</span>
<span>MS / PS Help</span>
</a>
</div>

View File

@@ -0,0 +1,36 @@
<div class="gia-behavior-shell">
<div class="is-flex is-justify-content-space-between is-align-items-center is-flex-wrap-wrap mb-3">
<div>
<p class="is-size-7 has-text-weight-semibold has-text-grey mb-1">{{ metric.state_label }} · {{ metric.group|upper }}</p>
<h3 class="title is-5 mb-1">{{ metric.title }}</h3>
<p class="is-size-7">{{ metric.calculation }}</p>
</div>
{% include "partials/behavioral-range-tabs.html" %}
</div>
<div class="columns is-multiline">
<div class="column is-12">
{% include "partials/behavioral-graph-card.html" with graph=metric person=person graphs_widget_url=graphs_widget_url %}
</div>
<div class="column is-12-tablet is-6-desktop">
<article class="message is-light">
<div class="message-body">
<p class="is-size-7 has-text-weight-semibold has-text-grey mb-1">Psychological Reading</p>
<p>{{ metric.psychology }}</p>
</div>
</article>
</div>
<div class="column is-12-tablet is-6-desktop">
<article class="message is-light">
<div class="message-body">
<p class="is-size-7 has-text-weight-semibold has-text-grey mb-1">Coverage</p>
<p class="mb-2">{{ coverage.message_count }} messages · {{ coverage.event_count }} events</p>
<p class="is-size-7">Latest value: {{ metric.current_value_label }}</p>
{% if metric.delta_label %}
<p class="is-size-7">Latest shift: {{ metric.delta_label }}</p>
{% endif %}
</div>
</article>
</div>
</div>
</div>

View File

@@ -0,0 +1,36 @@
<div class="gia-behavior-shell">
<div class="is-flex is-justify-content-space-between is-align-items-center is-flex-wrap-wrap mb-3">
<div>
<p class="is-size-7 has-text-weight-semibold has-text-grey mb-1">Behavioral Graphs</p>
<p class="mb-0">{{ coverage.message_count }} messages · {{ coverage.event_count }} events · {{ coverage.session_count }} sessions</p>
</div>
{% include "partials/behavioral-range-tabs.html" %}
</div>
<div class="columns is-multiline">
{% for card in summary_cards %}
<div class="column is-12-mobile is-6-tablet is-4-desktop">
{% include "partials/behavioral-summary-card.html" with card=card %}
</div>
{% endfor %}
</div>
{% for group_key, group in behavioral_groups.items %}
<section class="mb-4">
<div class="mb-3">
<p class="is-size-7 has-text-weight-semibold has-text-grey mb-1">{{ group.eyebrow }}</p>
<h3 class="title is-5 mb-1">{{ group.title }}</h3>
<p class="is-size-7">{{ group.summary }}</p>
</div>
<div class="columns is-multiline">
{% for graph in graph_cards %}
{% if graph.group == group_key %}
<div class="column is-12-mobile is-6-desktop">
{% include "partials/behavioral-graph-card.html" with graph=graph person=person graphs_widget_url=graphs_widget_url %}
</div>
{% endif %}
{% endfor %}
</div>
</section>
{% endfor %}
</div>

View File

@@ -0,0 +1,33 @@
<div class="gia-behavior-shell">
<div class="is-flex is-justify-content-space-between is-align-items-center is-flex-wrap-wrap mb-3">
<div>
<p class="is-size-7 has-text-weight-semibold has-text-grey mb-1">MS / PS Help</p>
<p class="mb-0">Signal definitions and psychological interpretation for the timing system.</p>
</div>
{% include "partials/behavioral-range-tabs.html" %}
</div>
{% for group_key, group in groups.items %}
<section class="box mb-4">
<p class="is-size-7 has-text-weight-semibold has-text-grey mb-1">{{ group.eyebrow }}</p>
<h3 class="title is-5 mb-2">{{ group.title }}</h3>
<p class="mb-3">{{ group.summary }}</p>
{% for metric in metrics %}
{% if metric.group == group_key %}
<article class="message is-light mb-3">
<div class="message-body">
<p class="is-size-7 has-text-weight-semibold has-text-grey mb-1">{{ metric.state_label }}</p>
<p class="title is-6 mb-2">
<span class="icon is-small mr-1"><i class="{{ metric.icon }}"></i></span>
<span>{{ metric.title }}</span>
</p>
<p class="mb-2"><strong>Current:</strong> {{ metric.current_value_label }}</p>
<p class="mb-2"><strong>How it is calculated:</strong> {{ metric.calculation }}</p>
<p><strong>What it can mean:</strong> {{ metric.psychology }}</p>
</div>
</article>
{% endif %}
{% endfor %}
</section>
{% endfor %}
</div>

View File

@@ -0,0 +1,51 @@
<div class="gia-behavior-shell">
<div class="is-flex is-justify-content-space-between is-align-items-center is-flex-wrap-wrap mb-3">
<div>
<p class="is-size-7 has-text-weight-semibold has-text-grey mb-1">MS / PS Information</p>
<p class="mb-0">Current timing signals for {{ person.name }} across message state and presence state.</p>
</div>
{% include "partials/behavioral-range-tabs.html" %}
</div>
<div class="columns is-multiline">
{% for card in summary_cards %}
<div class="column is-12-mobile is-6-tablet is-4-desktop">
{% include "partials/behavioral-summary-card.html" with card=card %}
</div>
{% endfor %}
</div>
{% for group_key, group in behavioral_groups.items %}
<section class="box mb-4">
<p class="is-size-7 has-text-weight-semibold has-text-grey mb-1">{{ group.eyebrow }}</p>
<h3 class="title is-5 mb-2">{{ group.title }}</h3>
<p class="mb-3">{{ group.summary }}</p>
<div class="table-container">
<table class="table is-fullwidth is-hoverable">
<thead>
<tr>
<th>Metric</th>
<th>Current</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
{% for graph in graph_cards %}
{% if graph.group == group_key %}
<tr>
<td>
<a href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric=graph.slug %}?range={{ range_key }}">
{{ graph.title }}
</a>
</td>
<td>{{ graph.current_value_label }}</td>
<td>{{ graph.psychology }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endfor %}
</div>

View File

@@ -9,35 +9,18 @@
<div style="margin-bottom: 0.75rem; padding: 0.5rem 0.25rem; border-bottom: 1px solid rgba(0, 0, 0, 0.12);">
<p class="is-size-7 has-text-weight-semibold">Selected Person</p>
<h3 class="title is-5" style="margin-bottom: 0.25rem;">{{ person.name }}</h3>
<div class="tags" style="margin-top: 0.35rem;">
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='platform' %}">Platform {{ workspace_conversation.platform_type|title }}</a>
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='thread' %}">Thread {{ workspace_conversation.platform_thread_id|default:"-" }}</a>
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='workspace_created' %}">Workspace Created {{ workspace_conversation.created_at|default:"-" }}</a>
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='stability_state' %}">Stability {{ workspace_conversation.stability_state|title }}</a>
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='stability_score' %}">Stability Score {{ workspace_conversation.stability_score|default:"-" }}</a>
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='stability_confidence' %}">Confidence {{ workspace_conversation.stability_confidence }}</a>
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='sample_messages' %}">Sample Msg {{ workspace_conversation.stability_sample_messages }}</a>
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='sample_days' %}">Sample Days {{ workspace_conversation.stability_sample_days }}</a>
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='stability_computed' %}">Stability Computed {{ workspace_conversation.stability_last_computed_at|default:"-" }}</a>
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='commitment_inbound' %}">Commit In {{ workspace_conversation.commitment_inbound_score|default:"-" }}</a>
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='commitment_outbound' %}">Commit Out {{ workspace_conversation.commitment_outbound_score|default:"-" }}</a>
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='commitment_confidence' %}">Commit Confidence {{ workspace_conversation.commitment_confidence }}</a>
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='commitment_computed' %}">Commitment Computed {{ workspace_conversation.commitment_last_computed_at|default:"-" }}</a>
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='last_event' %}">Last Event {{ workspace_conversation.last_event_ts|default:"-" }}</a>
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='last_ai_run' %}">Last AI Run {{ workspace_conversation.last_ai_run_at|default:"-" }}</a>
</div>
<p class="is-size-7 has-text-grey" style="margin-top: 0.35rem; margin-bottom: 0;">
Open MS / PS graphs, information, and help from the controls below.
</p>
<div class="buttons are-small" style="margin-top: 0.35rem; margin-bottom: 0;">
<a class="button is-light" href="{% url 'ai_workspace_insight_graphs' type='page' person_id=person.id %}">
<span class="icon is-small"><i class="fa-solid fa-chart-line"></i></span>
<span>Insight Graphs</span>
</a>
{% include "partials/behavioral-graph-launcher.html" with button_label="Graphs" show_widget_actions=behavioral_show_widget_actions default_widget_url=behavioral_graphs_widget_url default_page_url=behavioral_graphs_page_url graph_groups=behavioral_graph_groups %}
<a class="button is-light" href="{% url 'ai_workspace_information' type='page' person_id=person.id %}">
<span class="icon is-small"><i class="fa-solid fa-circle-info"></i></span>
<span>Information</span>
<span>MS / PS</span>
</a>
<a class="button is-light" href="{% url 'ai_workspace_insight_help' type='page' person_id=person.id %}">
<span class="icon is-small"><i class="fa-solid fa-circle-question"></i></span>
<span>Scoring Help</span>
<span>Help</span>
</a>
</div>
{% with participants=workspace_conversation.participants.all %}
@@ -81,6 +64,7 @@
id="ai-manual-widget-btn-{{ person.id }}"
type="button"
class="button is-light is-small js-widget-spawn-trigger is-hidden"
data-gia-widget-id="{{ compose_widget_id }}"
data-widget-url="{{ compose_widget_url }}"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{{ compose_widget_url }}"

View File

@@ -0,0 +1,71 @@
<article class="card gia-behavior-graph-card">
<header class="card-header">
<div class="card-header-title is-flex is-justify-content-space-between is-align-items-flex-start">
<div>
<p class="is-size-7 has-text-weight-semibold has-text-grey mb-1">
{{ graph.state_label }} · {{ graph.group|upper }}
</p>
<p class="mb-0">
<span class="icon is-small mr-1"><i class="{{ graph.icon }}"></i></span>
<span>{{ graph.title }}</span>
</p>
</div>
<span class="tag is-light gia-badge">{{ graph.current_value_label }}</span>
</div>
</header>
<div class="card-content">
<p class="is-size-7 has-text-grey mb-2">
{{ graph.raw_count }} sample{{ graph.raw_count|pluralize }} in this range{% if graph.delta_label %} · {{ graph.delta_label }}{% endif %}
</p>
{% if graph.has_data %}
<div class="gia-behavior-chart-shell">
<svg
class="gia-behavior-chart"
viewBox="0 0 100 48"
preserveAspectRatio="none"
role="img"
aria-label="{{ graph.title }} graph">
{% if graph.area_path %}
<path class="gia-behavior-chart-area" d="{{ graph.area_path }}"></path>
{% endif %}
{% if graph.polyline %}
<polyline class="gia-behavior-chart-line" points="{{ graph.polyline }}"></polyline>
{% endif %}
{% for marker in graph.markers|slice:"-1:" %}
<circle class="gia-behavior-chart-point" cx="{{ marker.x }}" cy="{{ marker.y }}" r="1.8"></circle>
{% endfor %}
</svg>
<div class="is-flex is-justify-content-space-between is-size-7 has-text-grey mt-1">
<span>{{ graph.y_min_label }}</span>
<span>{{ graph.latest_bucket_label }}</span>
<span>{{ graph.y_max_label }}</span>
</div>
</div>
{% else %}
<article class="message is-light">
<div class="message-body is-size-7">
No samples for this metric in the selected range.
</div>
</article>
{% endif %}
<p class="is-size-7 mt-3">{{ graph.psychology }}</p>
<div class="buttons are-small mt-3 mb-0">
<a
class="button is-light"
href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric=graph.slug %}?range={{ range_key }}">
Page
</a>
{% if behavioral_show_widget_actions %}
<button
type="button"
class="button is-light js-widget-spawn-trigger"
data-widget-url="{% url 'ai_workspace_insight_detail' type='widget' person_id=person.id metric=graph.slug %}?range={{ range_key }}"
hx-get="{% url 'ai_workspace_insight_detail' type='widget' person_id=person.id metric=graph.slug %}?range={{ range_key }}"
hx-target="#widgets-here"
hx-swap="beforeend">
Widget
</button>
{% endif %}
</div>
</div>
</article>

View File

@@ -0,0 +1,71 @@
<div class="dropdown is-right gia-split-dropdown" data-gia-dropdown>
<div class="dropdown-trigger">
<div class="buttons has-addons are-small mb-0">
{% if show_widget_actions %}
<button
type="button"
class="button is-light js-widget-spawn-trigger"
data-widget-url="{{ default_widget_url }}"
hx-get="{{ default_widget_url }}"
hx-target="#widgets-here"
hx-swap="beforeend">
<span class="icon is-small"><i class="fa-solid fa-chart-line"></i></span>
<span>{{ button_label|default:"Graphs" }}</span>
</button>
{% else %}
<a class="button is-light" href="{{ default_page_url }}">
<span class="icon is-small"><i class="fa-solid fa-chart-line"></i></span>
<span>{{ button_label|default:"Graphs" }}</span>
</a>
{% endif %}
<button
type="button"
class="button is-light js-gia-dropdown-toggle"
aria-haspopup="true"
aria-expanded="false">
<span class="icon is-small"><i class="fa-solid fa-angle-down"></i></span>
</button>
</div>
</div>
<div class="dropdown-menu" role="menu">
<div class="dropdown-content">
<a class="dropdown-item" href="{{ default_page_url }}">
<span class="icon is-small"><i class="fa-solid fa-up-right-from-square"></i></span>
<span>Page</span>
</a>
<hr class="dropdown-divider">
<details class="gia-dropdown-nest">
<summary class="dropdown-item">
<span class="icon is-small"><i class="fa-solid fa-sliders"></i></span>
<span>Custom Graph</span>
</summary>
<div class="gia-dropdown-nest-body">
{% for group in graph_groups %}
<p class="dropdown-item has-text-weight-semibold is-size-7 has-text-grey">
{{ group.title }}
</p>
{% for item in group.items %}
{% if show_widget_actions %}
<button
type="button"
class="dropdown-item js-widget-spawn-trigger"
data-widget-url="{{ item.widget_url }}"
hx-get="{{ item.widget_url }}"
hx-target="#widgets-here"
hx-swap="beforeend">
<span class="icon is-small"><i class="{{ item.icon }}"></i></span>
<span>{{ item.title }}</span>
</button>
{% else %}
<a class="dropdown-item" href="{{ item.page_url }}">
<span class="icon is-small"><i class="{{ item.icon }}"></i></span>
<span>{{ item.title }}</span>
</a>
{% endif %}
{% endfor %}
{% endfor %}
</div>
</details>
</div>
</div>
</div>

View File

@@ -0,0 +1,56 @@
<div class="tabs is-toggle is-small gia-inline-tabs">
<ul>
<li{% if range_key == "30d" %} class="is-active"{% endif %}>
{% if behavioral_show_widget_actions %}
<a
class="button is-white is-small js-widget-spawn-trigger"
href="{{ behavioral_range_urls.30d }}"
data-widget-url="{{ behavioral_range_urls.30d }}"
hx-get="{{ behavioral_range_urls.30d }}"
hx-target="#widgets-here"
hx-swap="beforeend">30d</a>
{% else %}
<a href="{{ behavioral_range_urls.30d }}">30d</a>
{% endif %}
</li>
<li{% if range_key == "90d" %} class="is-active"{% endif %}>
{% if behavioral_show_widget_actions %}
<a
class="button is-white is-small js-widget-spawn-trigger"
href="{{ behavioral_range_urls.90d }}"
data-widget-url="{{ behavioral_range_urls.90d }}"
hx-get="{{ behavioral_range_urls.90d }}"
hx-target="#widgets-here"
hx-swap="beforeend">90d</a>
{% else %}
<a href="{{ behavioral_range_urls.90d }}">90d</a>
{% endif %}
</li>
<li{% if range_key == "365d" %} class="is-active"{% endif %}>
{% if behavioral_show_widget_actions %}
<a
class="button is-white is-small js-widget-spawn-trigger"
href="{{ behavioral_range_urls.365d }}"
data-widget-url="{{ behavioral_range_urls.365d }}"
hx-get="{{ behavioral_range_urls.365d }}"
hx-target="#widgets-here"
hx-swap="beforeend">1y</a>
{% else %}
<a href="{{ behavioral_range_urls.365d }}">1y</a>
{% endif %}
</li>
<li{% if range_key == "all" %} class="is-active"{% endif %}>
{% if behavioral_show_widget_actions %}
<a
class="button is-white is-small js-widget-spawn-trigger"
href="{{ behavioral_range_urls.all }}"
data-widget-url="{{ behavioral_range_urls.all }}"
hx-get="{{ behavioral_range_urls.all }}"
hx-target="#widgets-here"
hx-swap="beforeend">All</a>
{% else %}
<a href="{{ behavioral_range_urls.all }}">All</a>
{% endif %}
</li>
</ul>
</div>

View File

@@ -0,0 +1,18 @@
<article class="message is-light gia-behavior-summary-card">
<div class="message-body">
<p class="is-size-7 has-text-weight-semibold has-text-grey mb-1">
{{ card.state_label }} · {{ card.group|upper }}
</p>
<div class="is-flex is-align-items-center is-justify-content-space-between mb-2">
<p class="title is-6 mb-0">
<span class="icon is-small mr-1"><i class="{{ card.icon }}"></i></span>
<span>{{ card.title }}</span>
</p>
<span class="tag is-light gia-badge">{{ card.current_value_label }}</span>
</div>
<p class="is-size-7 mb-1">{{ card.calculation }}</p>
{% if card.delta_label %}
<p class="is-size-7 has-text-grey">Latest shift: {{ card.delta_label }}</p>
{% endif %}
</div>
</article>

View File

@@ -1,5 +1,5 @@
<div class="box is-shadowless gia-send-composer p-2 m-0{% if composer_class %} {{ composer_class }}{% endif %}">
<div class="field has-addons gia-send-composer-row">
<div class="gia-send-composer m-0{% if composer_class %} {{ composer_class }}{% endif %}">
<div class="field has-addons gia-send-composer-row mb-0">
<div class="control is-expanded gia-send-composer-input-wrap">
<textarea
id="{{ textarea_id }}"

View File

@@ -1,5 +1,7 @@
<div class="compose-row {% if msg.outgoing %}is-out{% else %}is-in{% endif %}{% if msg.is_deleted %} is-deleted{% endif %}" data-ts="{{ msg.ts }}" data-message-id="{{ msg.id }}"{% if msg.reply_to_id %} data-reply-to-id="{{ msg.reply_to_id }}"{% endif %} data-reply-snippet="{{ msg.display_text|default:msg.text|default:''|truncatechars:120|escape }}">
<article class="compose-bubble {% if msg.outgoing %}is-out{% else %}is-in{% endif %}">
<article
class="compose-bubble {% if msg.outgoing %}is-out{% else %}is-in{% endif %}"
title="Source: {{ msg.source_label }}{% if msg.author %} · {{ msg.author }}{% endif %}">
{% if msg.reply_to_id %}
<div class="compose-reply-ref" data-reply-target-id="{{ msg.reply_to_id }}">
<button type="button" class="compose-reply-link" title="Jump to referenced message">
@@ -7,9 +9,6 @@
</button>
</div>
{% endif %}
<div class="compose-source-badge-wrap">
<span class="tag is-light gia-badge compose-source-badge source-{{ msg.source_service|default:'web'|lower }}">{{ msg.source_label }}</span>
</div>
{% if msg.image_urls %}
{% for image_url in msg.image_urls %}
<figure class="compose-media">
@@ -34,9 +33,9 @@
</figure>
{% endif %}
{% if not msg.hide_text %}
<p class="compose-body">{{ msg.display_text|default:"(no text)" }}</p>
<p class="compose-body is-size-7">{{ msg.display_text|default:"(no text)" }}</p>
{% else %}
<p class="compose-body compose-image-fallback is-hidden">(no text)</p>
<p class="compose-body compose-image-fallback is-hidden is-size-7">(no text)</p>
{% endif %}
{% if msg.edit_count %}
<details class="compose-edit-history">
@@ -71,7 +70,7 @@
{% endfor %}
</div>
{% endif %}
<p class="compose-msg-meta">
<p class="compose-msg-meta is-size-7" title="Source: {{ msg.source_label }}">
{{ msg.display_ts }}{% if msg.author %} · {{ msg.author }}{% endif %}
{% if msg.is_edited %}
<span class="tag is-light gia-badge compose-msg-flag is-edited" title="Message edited{% if msg.last_edit_display %} at {{ msg.last_edit_display }}{% endif %}">edited</span>

View File

@@ -1,6 +1,6 @@
{% load static %}
<link rel="stylesheet" href="{% static 'css/compose-panel.css' %}">
<script defer src="{% static 'js/compose-panel-core.js' %}"></script>
<script defer src="{% static 'js/compose-panel-thread.js' %}"></script>
<script defer src="{% static 'js/compose-panel-send.js' %}"></script>
<script defer src="{% static 'js/compose-panel.js' %}"></script>
<link rel="stylesheet" href="{% static 'css/compose-panel.css' %}?v={{ compose_asset_version|default:'20260313b' }}">
<script defer src="{% static 'js/compose-panel-core.js' %}?v={{ compose_asset_version|default:'20260313b' }}"></script>
<script defer src="{% static 'js/compose-panel-thread.js' %}?v={{ compose_asset_version|default:'20260313b' }}"></script>
<script defer src="{% static 'js/compose-panel-send.js' %}?v={{ compose_asset_version|default:'20260313b' }}"></script>
<script defer src="{% static 'js/compose-panel.js' %}?v={{ compose_asset_version|default:'20260313b' }}"></script>

View File

@@ -66,6 +66,9 @@
{{ service|title }} · {{ identifier }}
</p>
</div>
{% if behavioral_graphs_page_url %}
{% include "partials/behavioral-graph-launcher.html" with button_label="Graphs" show_widget_actions=behavioral_show_widget_actions default_widget_url=behavioral_graphs_widget_url default_page_url=behavioral_graphs_page_url graph_groups=behavioral_graph_groups %}
{% endif %}
</div>
{% if signal_ingest_warning %}
@@ -90,6 +93,9 @@
data-limit="{{ limit }}"
data-last-ts="{{ last_ts }}"
data-ws-url="{{ compose_ws_url }}">
<p id="{{ panel_id }}-history-loader" class="compose-history-loader is-size-7 has-text-grey mb-2">
Scroll up to load older messages.
</p>
{% include "partials/compose-message-rows.html" with message_rows=serialized_messages show_empty_state=True empty_message="No stored messages for this contact yet." %}
</div>
<p id="{{ panel_id }}-typing" class="compose-typing is-hidden">
@@ -128,7 +134,7 @@
<p class="help is-size-7 has-text-grey">Send disabled: {{ capability_send_reason }}</p>
{% endif %}
</div>
{% include "partials/bulma-send-composer.html" with composer_class="compose-composer-capsule" textarea_id=panel_id|add:"-textarea" textarea_class="compose-textarea" textarea_name="text" textarea_rows="1" textarea_placeholder="Type a message. Enter to send, Shift+Enter for newline." button_class="is-link is-light compose-send-btn" button_type="submit" button_disabled=True button_title=capability_send_reason|default_if_none:"" button_label="Send" button_icon_class=manual_icon_class %}
{% include "partials/bulma-send-composer.html" with composer_class="compose-composer-capsule" textarea_id=panel_id|add:"-textarea" textarea_class="is-small compose-textarea" textarea_name="text" textarea_rows="1" textarea_placeholder="Type a message. Enter to send, Shift+Enter for newline." button_class="is-link is-light is-small compose-send-btn" button_type="submit" button_disabled=True button_title=capability_send_reason|default_if_none:"" button_label="Send" button_icon_class=manual_icon_class %}
</div>
<div id="{{ panel_id }}-reply-banner" class="compose-reply-banner is-hidden">
<span class="compose-reply-banner-label">Replying to:</span>

View File

@@ -22,6 +22,7 @@
{% for row in contact_rows %}
<a
class="panel-block"
data-gia-widget-id="{{ row.compose_widget_id }}"
hx-get="{{ row.compose_widget_url }}"
hx-target="#widgets-here"
hx-swap="beforeend">

View File

@@ -28,6 +28,7 @@
<div class="buttons are-small m-0">
<button
class="button is-small is-link is-light"
data-gia-widget-id="{{ row.compose_widget_id }}"
hx-get="{{ row.compose_widget_url }}"
hx-include="#{{ browser_form_id }}"
hx-target="#widgets-here"

View File

@@ -197,7 +197,7 @@
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{{ action.url }}"
hx-target="{{ action.target }}"
hx-swap="innerHTML"
hx-swap="{{ action.swap|default:'innerHTML' }}"
{% if action.target == "#windows-here" %}onclick="if (window.giaPrepareWindowAnchor) { window.giaPrepareWindowAnchor(this); }"{% endif %}
title="{{ action.title }}">
<span class="icon"><i class="{{ action.icon }}"></i></span>

View File

@@ -109,6 +109,7 @@
{% if item.can_compose %}
<button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
data-gia-widget-id="{{ item.compose_widget_id }}"
hx-get="{{ item.compose_widget_url }}"
hx-trigger="click"
hx-target="#widgets-here"

View File

@@ -43,6 +43,7 @@
<button
type="button"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
data-gia-widget-id="{{ item.compose_widget_id }}"
hx-get="{{ item.compose_widget_url }}"
hx-trigger="click"
hx-target="#widgets-here"

View File

@@ -97,6 +97,7 @@
<button
type="button"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
data-gia-widget-id="{{ item.compose_widget_id }}"
hx-get="{{ item.compose_widget_url }}"
hx-trigger="click"
hx-target="#widgets-here"