Continue AI features and improve protocol support

This commit is contained in:
2026-02-15 16:57:32 +00:00
parent ab91c7dba1
commit 9a70f86088
62 changed files with 5472 additions and 441 deletions

View File

@@ -0,0 +1,110 @@
{% extends "base.html" %}
{% load static %}
{% block content %}
<div class="columns is-multiline">
<div class="column is-12">
<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">{{ 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>
</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>
<div class="buttons">
<a class="button is-light" href="{{ graphs_url }}">
<span class="icon is-small"><i class="fa-solid fa-chart-line"></i></span>
<span>All Graphs</span>
</a>
<a class="button is-light" href="{{ help_url }}">
<span class="icon is-small"><i class="fa-solid fa-circle-question"></i></span>
<span>Scoring Help</span>
</a>
</div>
</div>
<div class="column is-7">
<div class="box">
<p class="heading">History</p>
<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 %}
</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 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>
{% endblock %}

View File

@@ -0,0 +1,115 @@
{% extends "base.html" %}
{% load static %}
{% block content %}
<div class="columns is-multiline">
<div class="column is-12">
<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>
</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 }}
</p>
<div class="buttons are-small" style="margin-top: 0.6rem;">
<a class="button is-light" href="{{ help_url }}">
<span class="icon is-small"><i class="fa-solid fa-circle-question"></i></span>
<span>Scoring Help</span>
</a>
</div>
</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 }} point{{ graph.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>
{% endblock %}

View File

@@ -0,0 +1,43 @@
{% extends "base.html" %}
{% block content %}
<div class="columns is-multiline">
<div class="column is-12">
<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>
</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.
</p>
</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">
<p><strong>{{ metric.title }}</strong>: {{ metric.value|default:"-" }}</p>
<p><strong>Calculation:</strong> {{ metric.calculation }}</p>
<p><strong>Psychological Read:</strong> {{ metric.psychology }}</p>
</div>
</article>
{% endif %}
{% endfor %}
</div>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@@ -8,4 +8,13 @@
hx-trigger="load"
hx-swap="afterend"
style="display: none;"></div>
{% if selected_person_id %}
<div
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'ai_workspace_person' type='widget' person_id=selected_person_id %}"
hx-target="#widgets-here"
hx-trigger="load delay:250ms"
hx-swap="afterend"
style="display: none;"></div>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends "base.html" %}
{% block content %}
<div class="columns is-centered">
<div class="column is-10-tablet is-9-desktop is-8-widescreen">
<div id="compose-page-panel">
{% include "partials/compose-panel.html" %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -3,9 +3,9 @@
{% block load_widgets %}
<div
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'signal_accounts' type='widget' %}"
hx-get="{% url accounts_url_name type='widget' %}"
hx-target="#widgets-here"
hx-trigger="load"
hx-swap="afterend"
style="display: none;"></div>
{% endblock %}
{% endblock %}