Files
GIA/core/templates/pages/command-routing.html

492 lines
27 KiB
HTML

{% extends "base.html" %}
{% block content %}
<section class="section">
<div class="container">
<h1 class="title is-4">Command Routing</h1>
<p class="subtitle is-6">Configure commands, channel bindings, and per-command delivery in a predictable way.</p>
{% if scope_service and scope_identifier %}
<article class="notification is-info is-light">
Scoped to this chat only: <strong>{{ scope_service }}</strong> · <code>{{ scope_identifier }}</code>
</article>
{% endif %}
<article class="box">
<h2 class="title is-6">Create Command Profile</h2>
<p class="help">Create reusable command behavior. <code>bp set</code> and <code>bp set range</code> are fixed bp subcommands and will appear automatically.</p>
<form method="post" aria-label="Create command profile">
{% csrf_token %}
<input type="hidden" name="action" value="profile_create">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<div class="columns is-multiline">
<div class="column is-4">
<label class="label is-size-7" for="create_command_slug">Command</label>
<div class="select is-small is-fullwidth">
<select id="create_command_slug" name="command_slug">
{% for value, label in command_choices %}
<option value="{{ value }}">{{ label }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="column is-4">
<label class="label is-size-7" for="create_name">Name</label>
<input id="create_name" class="input is-small" name="name" placeholder="name" value="Business Plan">
</div>
<div class="column is-4">
<label class="label is-size-7" for="create_trigger_token">Primary Trigger Token</label>
<input id="create_trigger_token" class="input is-small" name="trigger_token" value="#bp#" readonly>
</div>
</div>
<label class="label is-size-7" for="create_template_text">BP Template (used only by <code>bp</code> in AI mode)</label>
<textarea id="create_template_text" class="textarea is-small" name="template_text" rows="4" placeholder="Business plan template"></textarea>
<button class="button is-link is-small" style="margin-top: 0.75rem;" type="submit">Create Profile</button>
</form>
</article>
{% for profile in profiles %}
<article class="box">
<h2 class="title is-6">{{ profile.name }} ({{ profile.slug }})</h2>
<div class="content is-size-7" style="margin-bottom: 0.6rem;">
<p><strong>Help</strong></p>
<ul>
<li><strong>Send plan to egress</strong>: posts generated plan to enabled egress bindings.</li>
<li><strong>Send status to source</strong>: posts a short confirmation message in the source chat.</li>
<li><strong>Send status to egress</strong>: posts a short confirmation to egress channels.</li>
<li><strong>Template support</strong>: only <code>bp</code> uses the template, and only in AI mode.</li>
</ul>
</div>
<form method="post" style="margin-bottom: 0.75rem;" aria-label="Update command profile {{ profile.name }}">
{% csrf_token %}
<input type="hidden" name="action" value="profile_update">
<input type="hidden" name="profile_id" value="{{ profile.id }}">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<div class="columns is-multiline">
<div class="column is-3">
<label class="label is-size-7" for="profile_name_{{ profile.id }}">Name</label>
<input id="profile_name_{{ profile.id }}" class="input is-small" name="name" value="{{ profile.name }}">
</div>
<div class="column is-3">
<label class="label is-size-7" for="trigger_token_{{ profile.id }}">Primary Trigger</label>
<input id="trigger_token_{{ profile.id }}" class="input is-small" name="trigger_token" value="{{ profile.trigger_token }}" readonly>
</div>
<div class="column is-6">
<fieldset>
<legend class="label is-size-7">Flags</legend>
<label class="checkbox is-size-7"><input type="checkbox" name="enabled" value="1" {% if profile.enabled %}checked{% endif %}> enabled</label>
<label class="checkbox is-size-7" style="margin-left: 0.6rem;"><input type="checkbox" name="reply_required" value="1" {% if profile.reply_required %}checked{% endif %}> reply required</label>
<label class="checkbox is-size-7" style="margin-left: 0.6rem;"><input type="checkbox" name="exact_match_only" value="1" {% if profile.exact_match_only %}checked{% endif %}> exact match</label>
</fieldset>
</div>
</div>
<label class="label is-size-7" for="template_text_{{ profile.id }}">BP Template</label>
<textarea id="template_text_{{ profile.id }}" class="textarea is-small" name="template_text" rows="4">{{ profile.template_text }}</textarea>
<div class="buttons" style="margin-top: 0.6rem;">
<button class="button is-link is-small" type="submit">Save Profile</button>
</div>
</form>
<div class="columns is-variable is-5">
<div class="column">
<h3 class="title is-7">Variant Policies</h3>
<p class="help">Delivery switches control where plan/status are posted. Egress bindings define destinations.</p>
<p class="help">Turn off <strong>Save Document</strong> to run/fanout without storing a business plan artifact.</p>
<table class="table is-fullwidth is-striped is-size-7">
<thead>
<tr>
<th>Variant</th>
<th>Trigger</th>
<th>Enabled</th>
<th>Generation</th>
<th>Save Document</th>
<th>Plan -> Egress</th>
<th>Status -> Source</th>
<th>Status -> Egress</th>
<th></th>
</tr>
</thead>
<tbody>
{% for variant in profile.variant_rows %}
<tr>
<form method="post" aria-label="Update variant policy {{ variant.variant_label }} for {{ profile.name }}">
{% csrf_token %}
<input type="hidden" name="action" value="variant_policy_update">
<input type="hidden" name="profile_id" value="{{ profile.id }}">
<input type="hidden" name="variant_key" value="{{ variant.variant_key }}">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<td>
{{ variant.variant_label }}
{% if not variant.template_supported %}
<span class="tag is-warning is-light is-small has-text-dark">no template</span>
{% endif %}
</td>
<td><code>{{ variant.trigger_token }}</code></td>
<td><input type="checkbox" name="enabled" value="1" {% if variant.row.enabled %}checked{% endif %}></td>
<td>
<div class="select is-small">
<select name="generation_mode">
<option value="ai" {% if variant.row.generation_mode == 'ai' %}selected{% endif %}>AI</option>
<option value="verbatim" {% if variant.row.generation_mode == 'verbatim' %}selected{% endif %}>Verbatim</option>
</select>
</div>
</td>
<td><input type="checkbox" name="store_document" value="1" {% if variant.row.store_document %}checked{% endif %}></td>
<td><input type="checkbox" name="send_plan_to_egress" value="1" {% if variant.row.send_plan_to_egress %}checked{% endif %}></td>
<td><input type="checkbox" name="send_status_to_source" value="1" {% if variant.row.send_status_to_source %}checked{% endif %}></td>
<td><input type="checkbox" name="send_status_to_egress" value="1" {% if variant.row.send_status_to_egress %}checked{% endif %}></td>
<td><button class="button is-small is-link is-light" type="submit">Save</button></td>
</form>
</tr>
{% if variant.warn_verbatim_plan %}
<tr>
<td colspan="9">
<article class="command-variant-warning">
<strong>Warning:</strong> <strong>{{ variant.variant_label }}</strong> is in <code>verbatim</code> mode with plan fanout enabled.
Recipients will get raw transcript-style output.
</article>
</td>
</tr>
{% endif %}
{% empty %}
<tr><td colspan="9">No variants configured.</td></tr>
{% endfor %}
</tbody>
</table>
<div class="is-flex is-justify-content-space-between is-align-items-center" style="gap: 0.75rem; margin-top: 0.75rem; flex-wrap: wrap;">
<form method="post" aria-label="Preview delivery for {{ profile.name }}">
{% csrf_token %}
<input type="hidden" name="action" value="variant_preview">
<input type="hidden" name="profile_id" value="{{ profile.id }}">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<button class="button is-small is-info is-light" type="submit">Dry Run Preview</button>
</form>
<form method="post" aria-label="Reset variant defaults for {{ profile.name }}">
{% csrf_token %}
<input type="hidden" name="action" value="variant_policy_reset_defaults">
<input type="hidden" name="profile_id" value="{{ profile.id }}">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<button class="button is-small is-warning is-light" type="submit">Reset Variant Defaults</button>
</form>
</div>
<h4 class="title is-7" style="margin-top: 0.95rem;">Effective Destinations</h4>
{% if profile.enabled_egress_bindings %}
<ul class="command-destination-list is-size-7">
{% for row in profile.enabled_egress_bindings %}
<li class="command-destination-item">
<span class="tag is-link is-light is-rounded is-small">{{ row.service }}</span>
<code>{{ row.channel_identifier }}</code>
</li>
{% endfor %}
</ul>
<p class="command-destination-summary">{{ profile.enabled_egress_bindings|length }} enabled egress destination{{ profile.enabled_egress_bindings|length|pluralize }}.</p>
{% else %}
<article class="notification is-warning is-light is-size-7">No enabled egress destinations. Plan fanout will show sent:0.</article>
{% endif %}
{% if preview_profile_id and preview_profile_id == profile.id|stringformat:"s" %}
<article class="notification is-info is-light is-size-7" style="margin-top: 0.65rem;">
<p><strong>Dry Run Preview</strong></p>
<ul>
{% for variant in profile.variant_rows %}
<li>
{{ variant.variant_label }}: {% if variant.row.enabled %}enabled{% else %}disabled{% endif %}, mode={{ variant.row.generation_mode }},
save_document={{ variant.row.store_document }},
plan->egress={{ variant.row.send_plan_to_egress }},
status->source={{ variant.row.send_status_to_source }},
status->egress={{ variant.row.send_status_to_egress }}
</li>
{% endfor %}
</ul>
</article>
{% endif %}
</div>
<div class="column">
<h3 class="title is-7">Channel Bindings</h3>
<p class="help">Ingress accepts triggers. Egress receives plan/status fanout if enabled in variant policy.</p>
<table class="table is-fullwidth is-striped is-size-7">
<thead>
<tr><th scope="col">Direction</th><th scope="col">Service</th><th scope="col">Channel</th><th scope="col">Actions</th></tr>
</thead>
<tbody>
{% for binding in profile.visible_bindings %}
<tr>
<td>
{% if binding.direction == "ingress" %}Ingress (Accept Triggers)
{% elif binding.direction == "egress" %}Egress (Delivery Destination)
{% else %}Scratchpad Mirror
{% endif %}
</td>
<td>{{ binding.service }}</td>
<td>{{ binding.channel_identifier }}</td>
<td>
<form method="post" aria-label="Delete binding {{ binding.direction }} {{ binding.service }} {{ binding.channel_identifier }}">
{% csrf_token %}
<input type="hidden" name="action" value="binding_delete">
<input type="hidden" name="binding_id" value="{{ binding.id }}">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<button class="button is-danger is-light is-small" type="submit">Delete</button>
</form>
</td>
</tr>
{% empty %}
<tr><td colspan="4">No bindings yet.</td></tr>
{% endfor %}
</tbody>
</table>
<form method="post" aria-label="Add channel binding for {{ profile.name }}">
{% csrf_token %}
<input type="hidden" name="action" value="binding_create">
<input type="hidden" name="profile_id" value="{{ profile.id }}">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<div class="columns">
<div class="column">
<label class="label is-size-7" for="binding_direction_{{ profile.id }}">Direction</label>
<div class="select is-small is-fullwidth">
<select id="binding_direction_{{ profile.id }}" name="direction">
{% for value in directions %}
<option value="{{ value }}">
{% if value == "ingress" %}Ingress (Accept Triggers)
{% elif value == "egress" %}Egress (Delivery Destination)
{% else %}Scratchpad Mirror
{% endif %}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="column">
<label class="label is-size-7" for="binding_service_{{ profile.id }}">Service</label>
<div class="select is-small is-fullwidth">
<select id="binding_service_{{ profile.id }}" name="service" {% if scope_service %}disabled{% endif %}>
{% for value in channel_services %}
<option value="{{ value }}" {% if value == scope_service %}selected{% endif %}>{{ value }}</option>
{% endfor %}
</select>
</div>
{% if scope_service %}
<input type="hidden" name="service" value="{{ scope_service }}">
{% endif %}
</div>
<div class="column">
<label class="label is-size-7" for="binding_channel_identifier_{{ profile.id }}">Channel Identifier</label>
<input id="binding_channel_identifier_{{ profile.id }}" class="input is-small" name="channel_identifier" placeholder="channel identifier" value="{{ scope_identifier }}" {% if scope_identifier %}readonly{% endif %}>
</div>
<div class="column is-narrow">
<button class="button is-link is-small" type="submit">Add</button>
</div>
</div>
</form>
</div>
</div>
{% if profile.show_actions_editor %}
<div class="columns">
<div class="column">
<h3 class="title is-7">Actions</h3>
<p class="help">Enable/disable each step and use the reorder capsule to change execution order.</p>
<table class="table is-fullwidth is-striped is-size-7">
<thead>
<tr><th scope="col">Type</th><th scope="col">Enabled</th><th scope="col">Reorder</th><th scope="col">Actions</th></tr>
</thead>
<tbody>
{% for action_row in profile.actions.all %}
<tr>
<td>
{% if action_row.action_type == "extract_bp" %}Extract Business Plan
{% elif action_row.action_type == "save_document" %}Save Document
{% elif action_row.action_type == "post_result" %}Post Result
{% else %}{{ action_row.action_type }}
{% endif %}
</td>
<td>{{ action_row.enabled }}</td>
<td>
<span class="command-order-capsule">
<form method="post" class="command-order-capsule-form" aria-label="Move action {{ action_row.action_type }} up for {{ profile.name }}">
{% csrf_token %}
<input type="hidden" name="action" value="action_move">
<input type="hidden" name="command_action_id" value="{{ action_row.id }}">
<input type="hidden" name="direction" value="up">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<button class="button is-small is-light command-order-btn" type="submit" {% if forloop.first %}disabled{% endif %} aria-label="Move up"></button>
</form>
<form method="post" class="command-order-capsule-form" aria-label="Move action {{ action_row.action_type }} down for {{ profile.name }}">
{% csrf_token %}
<input type="hidden" name="action" value="action_move">
<input type="hidden" name="command_action_id" value="{{ action_row.id }}">
<input type="hidden" name="direction" value="down">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<button class="button is-small is-light command-order-btn" type="submit" {% if forloop.last %}disabled{% endif %} aria-label="Move down"></button>
</form>
</span>
</td>
<td>
<form method="post" aria-label="Update action {{ action_row.action_type }} for {{ profile.name }}">
{% csrf_token %}
<input type="hidden" name="action" value="action_update">
<input type="hidden" name="command_action_id" value="{{ action_row.id }}">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<label class="checkbox is-size-7"><input type="checkbox" name="enabled" value="1" {% if action_row.enabled %}checked{% endif %}> enabled</label>
<button class="button is-link is-light is-small" type="submit">Save</button>
</form>
</td>
</tr>
{% empty %}
<tr><td colspan="4">No actions.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<form method="post" style="margin-top: 0.75rem;" aria-label="Delete profile {{ profile.name }}">
{% csrf_token %}
<input type="hidden" name="action" value="profile_delete">
<input type="hidden" name="profile_id" value="{{ profile.id }}">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<button class="button is-danger is-light is-small" type="submit" aria-label="Delete profile {{ profile.name }}">Delete Profile</button>
</form>
</article>
{% empty %}
<article class="notification is-light">No command profiles configured.</article>
{% endfor %}
<article class="box" id="bp-documents">
<h2 class="title is-6">Business Plan Documents</h2>
<table class="table is-fullwidth is-striped is-size-7">
<thead>
<tr><th scope="col">Title</th><th scope="col">Status</th><th scope="col">Source</th><th scope="col">Updated</th><th scope="col">Actions</th></tr>
</thead>
<tbody>
{% for doc in documents %}
<tr>
<td>{{ doc.title }}</td>
<td>{{ doc.status }}</td>
<td>{{ doc.source_service }} · {{ doc.source_channel_identifier }}</td>
<td>{{ doc.updated_at }}</td>
<td><a class="button is-small is-link is-light" href="{% url 'business_plan_editor' doc_id=doc.id %}" aria-label="Open business plan document {{ doc.title }}">Open</a></td>
</tr>
{% empty %}
<tr><td colspan="5">No business plan documents yet.</td></tr>
{% endfor %}
</tbody>
</table>
</article>
</div>
</section>
<style>
.command-order-capsule {
display: inline-flex;
flex-direction: column;
border: 1px solid #dbdbdb;
border-radius: 6px;
overflow: hidden;
white-space: nowrap;
flex-wrap: nowrap;
width: 2.15rem;
min-width: 2.15rem;
}
.command-order-capsule-form {
margin: 0;
line-height: 1;
}
.command-order-btn {
border: 0;
border-radius: 0;
width: 100%;
min-width: 2.15rem;
height: 1.35rem;
padding: 0;
font-size: 0.72rem;
line-height: 1;
}
.command-order-capsule-form + .command-order-capsule-form .command-order-btn {
border-top: 1px solid #dbdbdb;
}
.command-variant-warning {
border: 1px solid rgba(171, 109, 17, 0.45);
background: linear-gradient(180deg, rgba(255, 246, 226, 0.98), rgba(255, 238, 204, 0.95));
color: #6e450e;
border-radius: 8px;
padding: 0.48rem 0.62rem;
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;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.28rem;
}
.command-destination-item {
display: inline-flex;
align-items: center;
gap: 0.42rem;
background: rgba(244, 248, 255, 0.85);
border: 1px solid rgba(58, 103, 165, 0.2);
border-radius: 7px;
padding: 0.26rem 0.38rem;
width: fit-content;
max-width: 100%;
}
.command-destination-summary {
margin-top: 0.44rem;
display: inline-flex;
align-items: center;
border-radius: 999px;
background: rgba(239, 247, 255, 0.95);
border: 1px solid rgba(58, 103, 165, 0.25);
padding: 0.16rem 0.52rem;
font-size: 0.73rem;
color: #284d7c;
}
</style>
{% endblock %}