Improve tasks and backdate insights

This commit is contained in:
2026-03-03 17:21:06 +00:00
parent 9c14e51b43
commit 2898d9e832
18 changed files with 1617 additions and 264 deletions

View File

@@ -13,7 +13,7 @@
<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.</p>
<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>

View File

@@ -14,7 +14,7 @@
<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 }}
Historical metrics for workspace {{ workspace_conversation.id }}. Points come from deterministic message-history snapshots (not only mitigation runs).
</p>
{% include "partials/ai-insight-nav.html" with active_tab="graphs" %}
</div>

View File

@@ -15,7 +15,8 @@
<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.
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>

View File

@@ -23,102 +23,45 @@
<button class="button is-link is-small" type="submit">Save</button>
</form>
<form method="get" class="box">
<h2 class="title is-6">Timeline Filters</h2>
<div class="columns is-multiline">
<div class="column is-3">
<label class="label is-size-7">Person</label>
<div class="select is-small is-fullwidth">
<select name="person">
<option value="">All</option>
{% for person in people %}
<option value="{{ person.id }}" {% if filters.person == person.id|stringformat:"s" %}selected{% endif %}>{{ person.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="column is-2">
<label class="label is-size-7">Service</label>
<div class="select is-small is-fullwidth">
<select name="service">
<option value="">All</option>
{% for item in service_choices %}
<option value="{{ item }}" {% if filters.service == item %}selected{% endif %}>{{ item }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="column is-2">
<label class="label is-size-7">State</label>
<div class="select is-small is-fullwidth">
<select name="state">
<option value="">All</option>
{% for item in state_choices %}
<option value="{{ item }}" {% if filters.state == item %}selected{% endif %}>{{ item }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="column is-2">
<label class="label is-size-7">Source</label>
<div class="select is-small is-fullwidth">
<select name="source_kind">
<option value="">All</option>
{% for item in source_kind_choices %}
<option value="{{ item }}" {% if filters.source_kind == item %}selected{% endif %}>{{ item }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="column is-2">
<label class="label is-size-7">Start (ISO)</label>
<input class="input is-small" type="datetime-local" name="start" value="{{ filters.start }}">
</div>
<div class="column is-2">
<label class="label is-size-7">End (ISO)</label>
<input class="input is-small" type="datetime-local" name="end" value="{{ filters.end }}">
</div>
</div>
<button class="button is-light is-small" type="submit">Apply</button>
</form>
<div class="box">
<h2 class="title is-6">Availability Events</h2>
<h2 class="title is-6">Availability Event Statistics Per Contact</h2>
<table class="table is-fullwidth is-striped is-size-7">
<thead><tr><th>ts</th><th>person</th><th>service</th><th>source</th><th>state</th><th>confidence</th></tr></thead>
<thead>
<tr>
<th>Contact</th>
<th>Service</th>
<th>Total</th>
<th>Available</th>
<th>Fading</th>
<th>Unavailable</th>
<th>Unknown</th>
<th>Native</th>
<th>Read</th>
<th>Typing</th>
<th>Msg Activity</th>
<th>Timeout</th>
<th>Last Event TS</th>
</tr>
</thead>
<tbody>
{% for row in events %}
{% for row in contact_stats %}
<tr>
<td>{{ row.ts }}</td>
<td>{{ row.person.name }}</td>
<td>{{ row.person__name }}</td>
<td>{{ row.service }}</td>
<td>{{ row.source_kind }}</td>
<td>{{ row.availability_state }}</td>
<td>{{ row.confidence|floatformat:2 }}</td>
<td>{{ row.total_events }}</td>
<td>{{ row.available_events }}</td>
<td>{{ row.fading_events }}</td>
<td>{{ row.unavailable_events }}</td>
<td>{{ row.unknown_events }}</td>
<td>{{ row.native_presence_events }}</td>
<td>{{ row.read_receipt_events }}</td>
<td>{{ row.typing_events }}</td>
<td>{{ row.message_activity_events }}</td>
<td>{{ row.inferred_timeout_events }}</td>
<td>{{ row.last_event_ts }}</td>
</tr>
{% empty %}
<tr><td colspan="6">No events in range.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="box">
<h2 class="title is-6">Availability Spans</h2>
<table class="table is-fullwidth is-striped is-size-7">
<thead><tr><th>person</th><th>service</th><th>state</th><th>start</th><th>end</th><th>confidence</th></tr></thead>
<tbody>
{% for row in spans %}
<tr>
<td>{{ row.person.name }}</td>
<td>{{ row.service }}</td>
<td>{{ row.state }}</td>
<td>{{ row.start_ts }}</td>
<td>{{ row.end_ts }}</td>
<td>{{ row.confidence_start|floatformat:2 }} -> {{ row.confidence_end|floatformat:2 }}</td>
</tr>
{% empty %}
<tr><td colspan="6">No spans in range.</td></tr>
<tr><td colspan="13">No availability events found.</td></tr>
{% endfor %}
</tbody>
</table>

View File

@@ -448,6 +448,13 @@
font-size: 0.78rem;
line-height: 1.35;
}
.command-variant-warning strong {
color: #3f2a09;
}
.command-variant-warning code {
color: #5b3a0c;
background: rgba(255, 255, 255, 0.55);
}
.command-destination-list {
list-style: none;
margin: 0;

View File

@@ -3,6 +3,47 @@
<section class="section"><div class="container">
<h1 class="title is-4">Group Tasks: {{ channel_display_name }}</h1>
<p class="subtitle is-6">{{ service_label }} · {{ identifier }}</p>
<article class="box">
<h2 class="title is-6">Create Or Map Project</h2>
<form method="post" style="margin-bottom: 0.7rem;">
{% csrf_token %}
<input type="hidden" name="action" value="group_project_create">
<div class="columns is-multiline">
<div class="column is-5">
<label class="label is-size-7">Project Name</label>
<input class="input is-small" name="project_name" placeholder="Project name">
</div>
<div class="column is-5">
<label class="label is-size-7">Initial Epic (optional)</label>
<input class="input is-small" name="epic_name" placeholder="Epic name">
</div>
<div class="column is-2" style="display:flex; align-items:flex-end;">
<button class="button is-small is-link is-light" type="submit">Create + Map</button>
</div>
</div>
</form>
<form method="post">
{% csrf_token %}
<input type="hidden" name="action" value="group_map_existing_project">
<div class="columns is-multiline">
<div class="column is-9">
<label class="label is-size-7">Existing Project</label>
<div class="select is-small is-fullwidth">
<select name="project_id">
{% for project in projects %}
<option value="{{ project.id }}">{{ project.name }}</option>
{% empty %}
<option value="">No projects available</option>
{% endfor %}
</select>
</div>
</div>
<div class="column is-3" style="display:flex; align-items:flex-end;">
<button class="button is-small is-light" type="submit">Map Existing</button>
</div>
</div>
</form>
</article>
{% if not tasks %}
<article class="box">
<h2 class="title is-6">No Tasks Yet</h2>
@@ -19,23 +60,46 @@
{% endif %}
<article class="box">
<h2 class="title is-6">Mappings</h2>
<ul>
{% for row in mappings %}
<li>{{ row.project.name }}{% if row.epic %} / {{ row.epic.name }}{% endif %}</li>
{% empty %}
<li>No mappings for this group.</li>
{% endfor %}
</ul>
<table class="table is-fullwidth is-striped is-size-7">
<thead><tr><th>Project</th><th>Epic</th><th>Channel</th><th>Enabled</th><th></th></tr></thead>
<tbody>
{% for row in mappings %}
<tr>
<td>{{ row.project.name }}</td>
<td>{% if row.epic %}{{ row.epic.name }}{% else %}-{% endif %}</td>
<td>
<div><code>{{ row.service }} · {{ row.channel_identifier }}</code></div>
{% if channel_display_name %}
<p class="is-size-7 has-text-dark" style="margin-top:0.2rem;">{{ channel_display_name }}</p>
{% endif %}
</td>
<td>{{ row.enabled }}</td>
<td><a class="button is-small is-light" href="{% url 'tasks_project' project_id=row.project_id %}">Open Project</a></td>
</tr>
{% empty %}
<tr><td colspan="5">No mappings for this group.</td></tr>
{% endfor %}
</tbody>
</table>
</article>
<article class="box">
<h2 class="title is-6">Derived Tasks</h2>
<ul>
{% for row in tasks %}
<li><a href="{% url 'tasks_task' task_id=row.id %}">#{{ row.reference_code }} {{ row.title }}</a></li>
{% empty %}
<li>No tasks yet.</li>
{% endfor %}
</ul>
<table class="table is-fullwidth is-striped is-size-7">
<thead><tr><th>Ref</th><th>Title</th><th>Project</th><th>Status</th><th></th></tr></thead>
<tbody>
{% for row in tasks %}
<tr>
<td>#{{ row.reference_code }}</td>
<td>{{ row.title }}</td>
<td>{{ row.project.name }}{% if row.epic %} / {{ row.epic.name }}{% endif %}</td>
<td>{{ row.status_snapshot }}</td>
<td><a class="button is-small is-light" href="{% url 'tasks_task' task_id=row.id %}">Open</a></td>
</tr>
{% empty %}
<tr><td colspan="5">No tasks yet.</td></tr>
{% endfor %}
</tbody>
</table>
</article>
</div></section>
{% endblock %}

View File

@@ -4,20 +4,130 @@
<div class="container">
<h1 class="title is-4">Tasks</h1>
<p class="subtitle is-6">Immutable tasks derived from chat activity.</p>
<div class="buttons">
<div class="buttons" style="margin-bottom: 0.75rem;">
<a class="button is-small is-link is-light" href="{% url 'tasks_settings' %}">Task Settings</a>
</div>
<div class="columns">
<div class="columns is-variable is-5">
<div class="column is-4">
<article class="box">
<h2 class="title is-6">Projects</h2>
<ul>
<div class="is-flex is-justify-content-space-between is-align-items-center" style="gap: 0.5rem; margin-bottom: 0.6rem;">
<h2 class="title is-6" style="margin: 0;">Projects</h2>
<span class="tag task-stat-tag">{{ projects|length }}</span>
</div>
<p class="help" style="margin-bottom: 0.45rem;">Create the project first, then map linked identifiers below in one click.</p>
<form method="post" style="margin-bottom: 0.75rem;">
{% csrf_token %}
<input type="hidden" name="action" value="project_create">
<input type="hidden" name="person" value="{{ scope.person_id }}">
<input type="hidden" name="service" value="{{ scope.service }}">
<input type="hidden" name="identifier" value="{{ scope.identifier }}">
<div class="field has-addons">
<div class="control is-expanded">
<input class="input is-small" name="name" placeholder="New project name">
</div>
<div class="control">
<button class="button is-small is-link is-light" type="submit">Add</button>
</div>
</div>
</form>
{% if scope.person %}
<article class="message is-light" style="margin-bottom: 0.75rem;">
<div class="message-body is-size-7">
Setup scope: <strong>{{ scope.person.name }}</strong>
{% if scope.service and scope.identifier %}
· {{ scope.service }} · {{ scope.identifier }}
{% endif %}
</div>
</article>
<div style="margin-bottom: 0.75rem;">
<label class="label is-size-7">Map Linked Identifiers To Project</label>
<form method="get">
<input type="hidden" name="person" value="{{ scope.person_id }}">
<input type="hidden" name="service" value="{{ scope.service }}">
<input type="hidden" name="identifier" value="{{ scope.identifier }}">
<div class="field has-addons">
<div class="control is-expanded">
<div class="select is-small is-fullwidth">
<select name="project">
<option value="">Select project</option>
{% for project in projects %}
<option value="{{ project.id }}" {% if selected_project and selected_project.id == project.id %}selected{% endif %}>{{ project.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="control">
<button class="button is-small is-light" type="submit">Select</button>
</div>
</div>
</form>
</div>
<table class="table is-fullwidth is-striped is-size-7" style="margin-bottom:0.9rem;">
<thead><tr><th>Identifier</th><th>Service</th><th></th></tr></thead>
<tbody>
{% for row in person_identifiers %}
<tr>
<td><code>{{ row.identifier }}</code></td>
<td>{{ row.service }}</td>
<td class="has-text-right">
{% if selected_project %}
{% if tuple(selected_project.id, row.service, row.identifier) in mapping_pairs %}
<span class="tag task-stat-tag">Linked</span>
{% else %}
<form method="post">
{% csrf_token %}
<input type="hidden" name="action" value="project_map_identifier">
<input type="hidden" name="project_id" value="{{ selected_project.id }}">
<input type="hidden" name="person_identifier_id" value="{{ row.id }}">
<input type="hidden" name="person" value="{{ scope.person_id }}">
<input type="hidden" name="service" value="{{ scope.service }}">
<input type="hidden" name="identifier" value="{{ scope.identifier }}">
<button class="button is-small is-light" type="submit">Link</button>
</form>
{% endif %}
{% else %}
<span class="has-text-grey">Select project</span>
{% endif %}
</td>
</tr>
{% empty %}
<tr><td colspan="3">No linked identifiers for this person yet.</td></tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="help" style="margin-bottom: 0.75rem;">
Open this page from Compose to map a persons linked identifiers in one click.
</p>
{% endif %}
<table class="table is-fullwidth is-striped is-size-7">
<thead><tr><th>Project</th><th>Stats</th><th></th></tr></thead>
<tbody>
{% for project in projects %}
<li><a href="{% url 'tasks_project' project_id=project.id %}">{{ project.name }}</a> <span class="has-text-grey">({{ project.task_count }})</span></li>
<tr>
<td>
<a href="{% url 'tasks_project' project_id=project.id %}">{{ project.name }}</a>
</td>
<td>
<span class="tag task-stat-tag">{{ project.task_count }} task{{ project.task_count|pluralize }}</span>
<span class="tag task-stat-tag">{{ project.epic_count }} epic{{ project.epic_count|pluralize }}</span>
</td>
<td class="has-text-right">
<form method="post">
{% csrf_token %}
<input type="hidden" name="action" value="project_delete">
<input type="hidden" name="project_id" value="{{ project.id }}">
<button class="button is-small is-danger is-light" type="submit">Delete</button>
</form>
</td>
</tr>
{% empty %}
<li>No projects yet.</li>
<tr><td colspan="3">No projects yet.</td></tr>
{% endfor %}
</ul>
</tbody>
</table>
</article>
</div>
<div class="column">
@@ -44,4 +154,15 @@
</div>
</div>
</section>
<style>
.task-stat-tag {
background: #f5f5f5;
border: 1px solid #dbdbdb;
color: #1f1f1f !important;
font-size: 0.75rem;
line-height: 1.5;
padding: 0.25em 0.75em;
font-weight: 500;
}
</style>
{% endblock %}

View File

@@ -1,27 +1,75 @@
{% extends "base.html" %}
{% block content %}
<section class="section"><div class="container">
<h1 class="title is-4">Project: {{ project.name }}</h1>
<div class="buttons"><a class="button is-small is-light" href="{% url 'tasks_hub' %}">Back</a></div>
<article class="box">
<h2 class="title is-6">Epics</h2>
<ul>
{% for epic in epics %}
<li><a href="{% url 'tasks_epic' epic_id=epic.id %}">{{ epic.name }}</a></li>
{% empty %}
<li>No epics.</li>
{% endfor %}
</ul>
</article>
<article class="box">
<h2 class="title is-6">Tasks</h2>
<ul>
{% for row in tasks %}
<li><a href="{% url 'tasks_task' task_id=row.id %}">#{{ row.reference_code }} {{ row.title }}</a></li>
{% empty %}
<li>No tasks.</li>
{% endfor %}
</ul>
</article>
</div></section>
<section class="section">
<div class="container">
<h1 class="title is-4">Project: {{ project.name }}</h1>
<div class="buttons" style="margin-bottom: 0.75rem;">
<a class="button is-small is-light" href="{% url 'tasks_hub' %}">Back</a>
<form method="post">
{% csrf_token %}
<input type="hidden" name="action" value="project_delete">
<button class="button is-small is-danger is-light" type="submit">Delete Project</button>
</form>
</div>
<article class="box">
<div class="is-flex is-justify-content-space-between is-align-items-center" style="gap: 0.5rem; margin-bottom: 0.6rem;">
<h2 class="title is-6" style="margin: 0;">Epics</h2>
<span class="tag is-light">{{ epics|length }}</span>
</div>
<form method="post" style="margin-bottom: 0.75rem;">
{% csrf_token %}
<input type="hidden" name="action" value="epic_create">
<div class="field has-addons">
<div class="control is-expanded">
<input class="input is-small" name="name" placeholder="New epic name">
</div>
<div class="control">
<button class="button is-small is-link is-light" type="submit">Add Epic</button>
</div>
</div>
</form>
<table class="table is-fullwidth is-striped is-size-7">
<thead><tr><th>Epic</th><th>Tasks</th><th></th></tr></thead>
<tbody>
{% for epic in epics %}
<tr>
<td><a href="{% url 'tasks_epic' epic_id=epic.id %}">{{ epic.name }}</a></td>
<td><span class="tag is-light">{{ epic.task_count }}</span></td>
<td class="has-text-right">
<form method="post">
{% csrf_token %}
<input type="hidden" name="action" value="epic_delete">
<input type="hidden" name="epic_id" value="{{ epic.id }}">
<button class="button is-small is-danger is-light" type="submit">Delete</button>
</form>
</td>
</tr>
{% empty %}
<tr><td colspan="3">No epics.</td></tr>
{% endfor %}
</tbody>
</table>
</article>
<article class="box">
<h2 class="title is-6">Tasks</h2>
<table class="table is-fullwidth is-striped is-size-7">
<thead><tr><th>Ref</th><th>Title</th><th>Epic</th><th></th></tr></thead>
<tbody>
{% for row in tasks %}
<tr>
<td>#{{ row.reference_code }}</td>
<td>{{ row.title }}</td>
<td>{% if row.epic %}{{ row.epic.name }}{% else %}-{% endif %}</td>
<td><a class="button is-small is-light" href="{% url 'tasks_task' task_id=row.id %}">Open</a></td>
</tr>
{% empty %}
<tr><td colspan="4">No tasks.</td></tr>
{% endfor %}
</tbody>
</table>
</article>
</div>
</section>
{% endblock %}

View File

@@ -335,6 +335,7 @@
<input type="hidden" name="provider" value="codex_cli">
<label class="checkbox"><input type="checkbox" name="enabled" value="1" {% if codex_provider_config and codex_provider_config.enabled %}checked{% endif %}> Enable Codex CLI provider</label>
<p class="help">Codex task-sync runs in a dedicated worker (<code>python manage.py codex_worker</code>).</p>
<p class="help">This provider syncs task updates to Codex; it does not mirror whole chat threads in this phase.</p>
<div class="field" style="margin-top:0.5rem;">
<label class="label is-size-7">Command</label>
<input class="input is-small" name="command" value="{{ codex_provider_settings.command }}" placeholder="codex">
@@ -361,40 +362,69 @@
<div class="column is-12">
<section class="tasks-panel">
<h3 class="title is-7">External Chat Links</h3>
<p class="help">Map a contact to an external Codex chat/session ID for task-sync metadata.</p>
<p class="help">Map a GIA contact identifier to one Codex conversation/session so task-sync updates are routed to the correct Codex thread.</p>
{% if external_link_scoped %}
<article class="message is-info is-light tasks-link-scope-note">
<div class="message-body">
Scoped to <strong>{{ external_link_scope_label }}</strong>. Only matching identifiers are available below.
</div>
</article>
{% endif %}
<form method="post" class="block">
{% csrf_token %}
<input type="hidden" name="action" value="external_chat_link_upsert">
<div class="columns tasks-settings-inline-columns">
<div class="column is-2">
<label class="label is-size-7">Provider</label>
<div class="select is-small is-fullwidth">
<select name="provider">
<option value="codex_cli" selected>codex_cli</option>
</select>
<input type="hidden" name="prefill_service" value="{{ prefill_service }}">
<input type="hidden" name="prefill_identifier" value="{{ prefill_identifier }}">
<div class="columns is-multiline is-variable is-2 tasks-external-link-columns">
<div class="column is-12-mobile is-4-tablet is-2-desktop">
<div class="field">
<label class="label is-size-7">Provider</label>
<div class="control">
<div class="select is-small is-fullwidth">
<select name="provider">
<option value="codex_cli" selected>codex_cli</option>
</select>
</div>
</div>
</div>
</div>
<div class="column is-5">
<label class="label is-size-7">Contact Identifier</label>
<div class="select is-small is-fullwidth">
<select name="person_identifier_id">
<option value="">Unlinked</option>
{% for row in person_identifiers %}
<option value="{{ row.id }}">{{ row.person.name }} · {{ row.service }} · {{ row.identifier }}</option>
{% endfor %}
</select>
<div class="column is-12-mobile is-8-tablet is-5-desktop">
<div class="field">
<label class="label is-size-7">Contact Identifier</label>
<div class="control">
<div class="select is-small is-fullwidth">
<select name="person_identifier_id">
<option value="">Unlinked</option>
{% for row in external_link_person_identifiers %}
<option value="{{ row.id }}">{{ row.person.name }} · {{ row.service }} · {{ row.identifier }}</option>
{% endfor %}
</select>
</div>
</div>
<p class="help">Choose which contact/group in GIA this Codex chat mapping belongs to.</p>
</div>
</div>
<div class="column is-3">
<label class="label is-size-7">External Chat ID</label>
<input class="input is-small" name="external_chat_id" placeholder="codex-chat-...">
<div class="column is-12-mobile is-8-tablet is-3-desktop">
<div class="field">
<label class="label is-size-7">External Chat ID</label>
<div class="control">
<input class="input is-small" name="external_chat_id" placeholder="codex-chat-...">
</div>
<p class="help">Use the Codex conversation/session ID (the stable ID Codex worker should target for task updates).</p>
</div>
</div>
<div class="column is-2">
<label class="label is-size-7">Enabled</label>
<label class="checkbox"><input type="checkbox" name="enabled" value="1" checked> Active</label>
<div class="column is-6-mobile is-4-tablet is-2-desktop">
<div class="field">
<label class="label is-size-7">Enabled</label>
<label class="checkbox"><input type="checkbox" name="enabled" value="1" checked> Active</label>
</div>
</div>
</div>
<div class="field">
<div class="control">
<button class="button is-small is-link is-light" type="submit">Save Link</button>
</div>
</div>
<button class="button is-small is-link is-light" type="submit">Save Link</button>
</form>
<table class="table is-fullwidth is-striped is-size-7">
<thead><tr><th>Provider</th><th>Person</th><th>Identifier</th><th>External Chat</th><th>Enabled</th><th></th></tr></thead>
@@ -452,6 +482,12 @@
.tasks-settings-page .tasks-settings-list {
margin-top: 0.75rem;
}
.tasks-settings-page .tasks-link-scope-note {
margin-bottom: 0.75rem;
}
.tasks-settings-page .tasks-external-link-columns .field {
margin-bottom: 0.5rem;
}
.tasks-settings-page .prefix-chip {
margin-right: 0.25rem;
margin-bottom: 0.25rem;