265 lines
16 KiB
HTML
265 lines
16 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">Manage command profiles, channel bindings, business-plan outputs, and translation bridges.</p>
|
|
|
|
<article class="box">
|
|
<h2 class="title is-6">Create Command Profile</h2>
|
|
<p class="help">Create reusable command behavior. Example: <code>#bp#</code> reply command for business-plan extraction.</p>
|
|
<form method="post" aria-label="Create command profile">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="profile_create">
|
|
<div class="columns">
|
|
<div class="column">
|
|
<label class="label is-size-7" for="create_slug">Slug</label>
|
|
<input id="create_slug" class="input is-small" name="slug" placeholder="slug (bp)" value="bp" aria-describedby="create_slug_help">
|
|
<p id="create_slug_help" class="help">Stable command id, e.g. <code>bp</code>.</p>
|
|
</div>
|
|
<div class="column">
|
|
<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">
|
|
<label class="label is-size-7" for="create_trigger_token">Trigger Token</label>
|
|
<input id="create_trigger_token" class="input is-small" name="trigger_token" placeholder="trigger token" value="#bp#">
|
|
</div>
|
|
</div>
|
|
<label class="label is-size-7" for="create_template_text">Template Text</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>Flag Definitions</strong></p>
|
|
<ul>
|
|
<li><strong>enabled</strong>: master on/off switch for this command profile.</li>
|
|
<li><strong>reply required</strong>: command only runs when the trigger message is sent as a reply to another message.</li>
|
|
<li><strong>exact match</strong>: message text must be exactly the trigger token (for example <code>#bp#</code>) with no extra text.</li>
|
|
<li><strong>visibility = status_in_source</strong>: post command status updates back into the source channel.</li>
|
|
<li><strong>visibility = silent</strong>: do not post status updates in the source channel.</li>
|
|
<li><strong>binding direction ingress</strong>: channels where trigger messages are accepted.</li>
|
|
<li><strong>binding direction egress</strong>: channels where command outputs are posted.</li>
|
|
<li><strong>binding direction scratchpad_mirror</strong>: scratchpad/mirror channel used for relay-only behavior.</li>
|
|
<li><strong>action extract_bp</strong>: run AI extraction to produce business plan content.</li>
|
|
<li><strong>action save_document</strong>: save/editable document and revision history.</li>
|
|
<li><strong>action post_result</strong>: fan out generated result to enabled egress bindings.</li>
|
|
<li><strong>position</strong>: execution order (lower runs first).</li>
|
|
</ul>
|
|
{% if profile.slug == "bp" %}
|
|
<p><strong>Supported Triggers (BP)</strong></p>
|
|
<ul>
|
|
<li><code>#bp#</code>: primary BP trigger (uses the standard BP extraction flow).</li>
|
|
<li><code>#bp set#</code>: deterministic no-AI set/update from reply/addendum text.</li>
|
|
<li><code>#bp set range#</code>: deterministic no-AI set/update from reply-anchor to trigger range.</li>
|
|
</ul>
|
|
{% endif %}
|
|
</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 }}">
|
|
<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-2">
|
|
<label class="label is-size-7" for="trigger_token_{{ profile.id }}">Trigger</label>
|
|
<input id="trigger_token_{{ profile.id }}" class="input is-small" name="trigger_token" value="{{ profile.trigger_token }}">
|
|
</div>
|
|
<div class="column is-2">
|
|
<label class="label is-size-7" for="visibility_mode_{{ profile.id }}">Visibility</label>
|
|
<div class="select is-small is-fullwidth">
|
|
<select id="visibility_mode_{{ profile.id }}" name="visibility_mode">
|
|
<option value="status_in_source" {% if profile.visibility_mode == 'status_in_source' %}selected{% endif %}>Show Status In Source Chat</option>
|
|
<option value="silent" {% if profile.visibility_mode == 'silent' %}selected{% endif %}>Silent (No Status Message)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="column is-5">
|
|
<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="5">{{ 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">
|
|
<div class="column">
|
|
<h3 class="title is-7">Channel Bindings</h3>
|
|
<p class="help">A command runs only when the source channel is in <code>ingress</code>. Output is sent to all enabled <code>egress</code> bindings.</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.channel_bindings.all %}
|
|
<tr>
|
|
<td>
|
|
{% if binding.direction == "ingress" %}Ingress (Accept Triggers)
|
|
{% elif binding.direction == "egress" %}Egress (Post Results)
|
|
{% 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 }}">
|
|
<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 }}">
|
|
<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 (Post Results)
|
|
{% 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">
|
|
{% for value in channel_services %}
|
|
<option value="{{ value }}">{{ value }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</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">
|
|
</div>
|
|
<div class="column is-narrow">
|
|
<button class="button is-link is-small" type="submit">Add</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="column">
|
|
<h3 class="title is-7">Actions</h3>
|
|
<p class="help">Enable/disable each step and set execution order with <code>position</code>.</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">Order</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>{{ forloop.counter }}</td>
|
|
<td>
|
|
<div class="buttons are-small" style="margin-bottom: 0.35rem;">
|
|
<form method="post" style="display:inline;" 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">
|
|
<button class="button is-light" type="submit" {% if forloop.first %}disabled{% endif %} aria-label="Move up">Up</button>
|
|
</form>
|
|
<form method="post" style="display:inline;" 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">
|
|
<button class="button is-light" type="submit" {% if forloop.last %}disabled{% endif %} aria-label="Move down">Down</button>
|
|
</form>
|
|
</div>
|
|
<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 }}">
|
|
<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>
|
|
|
|
<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 }}">
|
|
<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">
|
|
<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>
|
|
{% endblock %}
|