| 1 |
{% macro panel(base, a, csrf, what) %} |
| 2 |
<section class="panel" id="assistants"> |
| 3 |
<h2>AI assistants</h2> |
| 4 |
<p class="small mute">{{ what }} An assistant reviews a proposal when asked, or drafts its description; its answers are shown as the person who asked, via the assistant. It never approves.</p> |
| 5 |
{% if !a.inherited.is_empty() %} |
| 6 |
<h3>Inherited</h3> |
| 7 |
<ul class="hooks"> |
| 8 |
{% for i in a.inherited %} |
| 9 |
<li class="hook"> |
| 10 |
<div class="hook-head"><strong>{{ i.assistant.name }}</strong> <span class="tag">{{ i.assistant.model }}</span> |
| 11 |
{% if i.on() %}<span class="tag head">on</span>{% else if i.off_above %}<span class="tag dim">off above</span>{% else %}<span class="tag dim">off</span>{% endif %} |
| 12 |
<span class="small mute">from {{ i.from }}</span></div> |
| 13 |
</li> |
| 14 |
{% endfor %} |
| 15 |
</ul> |
| 16 |
{% endif %} |
| 17 |
{% if a.own.is_empty() %}<p class="mute small">{% if a.inherited.is_empty() %}None.{% else %}None of its own.{% endif %}</p>{% else %} |
| 18 |
<ul class="hooks"> |
| 19 |
{% for o in a.own %}{% let r = o.assistant %} |
| 20 |
<li class="hook"> |
| 21 |
<div class="hook-head"> |
| 22 |
<strong>{{ r.name }}</strong> <span class="tag">{{ r.model }}</span> <span class="tag">{{ r.api }}</span> |
| 23 |
{% if o.on() %}<span class="tag head">on</span>{% else if o.off_above %}<span class="tag dim" title="An assistant of this name is turned off at a level above">off above</span>{% else %}<span class="tag dim">off</span>{% endif %} |
| 24 |
</div> |
| 25 |
<p class="small mute"><code>{{ r.endpoint }}</code> · token {% if r.has_token() %}set{% else %}none{% endif %} · context {{ r.budget }} characters</p> |
| 26 |
<form class="tiny" method="post" action="{{ base }}/assistants"><input type="hidden" name="_csrf" value="{{ csrf }}"><input type="hidden" name="id" value="{{ r.id }}"><button name="action" value="toggle">{% if r.enabled %}turn off{% else %}turn on{% endif %}</button> <button name="action" value="test">test</button> <button name="action" value="delete" class="danger-btn" onclick="return confirm('Delete {{ r.name }}?')">delete</button></form> |
| 27 |
<details class="hook-edit"><summary class="small">edit</summary> |
| 28 |
<form class="card" method="post" action="{{ base }}/assistants"> |
| 29 |
<input type="hidden" name="_csrf" value="{{ csrf }}"><input type="hidden" name="id" value="{{ r.id }}"><input type="hidden" name="action" value="update"> |
| 30 |
<label for="an{{ r.id }}">Name</label><input type="text" id="an{{ r.id }}" name="name" value="{{ r.name }}"> |
| 31 |
<label for="ae{{ r.id }}">Endpoint</label><input type="text" id="ae{{ r.id }}" name="endpoint" value="{{ r.endpoint }}" autocomplete="off"> |
| 32 |
<label for="aa{{ r.id }}">API</label> |
| 33 |
<select id="aa{{ r.id }}" name="api">{% for api in a.apis %}<option value="{{ api }}"{% if r.api == *api %} selected{% endif %}>{{ api }}</option>{% endfor %}</select> |
| 34 |
<label for="am{{ r.id }}">Model</label><input type="text" id="am{{ r.id }}" name="model" value="{{ r.model }}"> |
| 35 |
<label for="at{{ r.id }}">Token</label><input type="password" id="at{{ r.id }}" name="token" placeholder="{% if r.has_token() %}unchanged{% else %}none{% endif %}" autocomplete="new-password"> |
| 36 |
<label for="ah{{ r.id }}">Extra headers</label><textarea id="ah{{ r.id }}" name="headers" rows="2" placeholder="Name: value">{{ r.headers }}</textarea> |
| 37 |
<label for="ab{{ r.id }}">Context budget (characters)</label><input type="text" id="ab{{ r.id }}" name="budget" value="{{ r.budget }}"> |
| 38 |
<div class="choice"><label><input type="checkbox" name="enabled" value="1"{% if r.enabled %} checked{% endif %}> on</label></div> |
| 39 |
<button type="submit">SAVE</button> |
| 40 |
</form> |
| 41 |
</details> |
| 42 |
</li> |
| 43 |
{% endfor %} |
| 44 |
</ul>{% endif %} |
| 45 |
<form class="card" method="post" action="{{ base }}/assistants"> |
| 46 |
<input type="hidden" name="_csrf" value="{{ csrf }}"><input type="hidden" name="action" value="add"> |
| 47 |
<h3>Add an assistant</h3> |
| 48 |
<label for="aname">Name</label><input type="text" id="aname" name="name" placeholder="Claude" required> |
| 49 |
<label for="aendpoint">Endpoint</label><input type="text" id="aendpoint" name="endpoint" placeholder="https://api.anthropic.com/v1/messages" required autocomplete="off"> |
| 50 |
<label for="aapi">API</label> |
| 51 |
<select id="aapi" name="api">{% for api in a.apis %}<option value="{{ api }}">{{ api }}{% if *api == "chat-completions" %} (OpenAI, DeepSeek, Ollama, most others){% else %} (Anthropic){% endif %}</option>{% endfor %}</select> |
| 52 |
<label for="amodel">Model</label><input type="text" id="amodel" name="model" placeholder="claude-opus-5-5" required> |
| 53 |
<label for="atoken">Token</label><input type="password" id="atoken" name="token" autocomplete="new-password"> |
| 54 |
<p class="small mute">Sent as a bearer token, or as the API key for Anthropic; kept sealed on the server and never shown again. Leave it empty for an endpoint that needs none.</p> |
| 55 |
<label for="aheaders">Extra headers</label><textarea id="aheaders" name="headers" rows="2" placeholder="Name: value"></textarea> |
| 56 |
<label for="abudget">Context budget (characters)</label><input type="text" id="abudget" name="budget" value="{{ a.default_budget }}"> |
| 57 |
<div class="choice"><label><input type="checkbox" name="enabled" value="1" checked> on</label></div> |
| 58 |
<button type="submit">ADD</button> |
| 59 |
</form> |
| 60 |
<form class="card" method="post" action="{{ base }}/assistants"> |
| 61 |
<input type="hidden" name="_csrf" value="{{ csrf }}"><input type="hidden" name="action" value="prompt"> |
| 62 |
<h3>Prompt</h3> |
| 63 |
{% for (from, text) in a.inherited_prompts %}<details><summary class="small mute">From {{ from }}</summary><pre class="snippet">{{ text }}</pre></details>{% endfor %} |
| 64 |
<label for="aprompt">What every assistant is told here{% if !a.inherited_prompts.is_empty() %}, after the prompts above{% endif %}</label> |
| 65 |
<textarea id="aprompt" name="prompt" rows="6" placeholder="Review for correctness first. This project is written in Rust; the style guide is in docs/style.md.">{{ a.prompt }}</textarea> |
| 66 |
<button type="submit">SAVE PROMPT</button> |
| 67 |
</form> |
| 68 |
</section> |
| 69 |
{% endmacro %} |