OXYGENHUB
# 6adcd68b90b1 oxygenhub / templates / assistants_panel.html
7.2 KiB · HTML · last changed Adding an assistant: provider presets and the endpoint's own model list 1 month ago source blame history raw
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="test" title="Send a one-line message and show the answer or the error">TEST CONNECTION</button> <button name="action" value="toggle">{% if r.enabled %}turn off{% else %}turn on{% endif %}</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>
35
        <div class="model-row"><input type="text" id="am{{ r.id }}" name="model" value="{{ r.model }}" list="am{{ r.id }}-list" autocomplete="off"><button type="button" class="small" data-list-models="{{ base }}/assistants/models" data-for="am{{ r.id }}" data-id="{{ r.id }}" data-csrf="{{ csrf }}">LIST MODELS</button></div>
36
        <datalist id="am{{ r.id }}-list"></datalist>
37
        <p class="small mute" data-models-note="am{{ r.id }}"></p>
38
        <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">
39
        <label for="ah{{ r.id }}">Extra headers</label><textarea id="ah{{ r.id }}" name="headers" rows="2" placeholder="Name: value">{{ r.headers }}</textarea>
40
        <label for="ab{{ r.id }}">Context budget (characters)</label><input type="text" id="ab{{ r.id }}" name="budget" value="{{ r.budget }}">
41
        <div class="choice"><label><input type="checkbox" name="enabled" value="1"{% if r.enabled %} checked{% endif %}> on</label></div>
42
        <button type="submit">SAVE</button>
43
      </form>
44
    </details>
45
  </li>
46
  {% endfor %}
47
  </ul>{% endif %}
48
  <form class="card" method="post" action="{{ base }}/assistants">
49
    <input type="hidden" name="_csrf" value="{{ csrf }}"><input type="hidden" name="action" value="add">
50
    <h3>Add an assistant</h3>
51
    <label for="apreset">Provider</label>
52
    <select id="apreset" name="preset" data-preset-select>
53
      {% for p in a.presets %}<option value="{{ p.0 }}" data-name="{{ p.2 }}" data-endpoint="{{ p.3 }}" data-api="{{ p.4 }}">{{ p.1 }}</option>{% endfor %}
54
      <option value="">Other (enter the endpoint)</option>
55
    </select>
56
    <label for="aname">Name</label><input type="text" id="aname" name="name" placeholder="Claude">
57
    <p class="small mute">How it shows on reviews ("you via Claude"); the provider's name when left empty.</p>
58
    <label for="aendpoint">Endpoint</label><input type="text" id="aendpoint" name="endpoint" placeholder="https://api.anthropic.com/v1/messages" autocomplete="off">
59
    <label for="aapi">API</label>
60
    <select id="aapi" name="api">{% for api in a.apis %}<option value="{{ api }}">{{ api }}{% if *api == "chat-completions" %} (OpenAI, Gemini, DeepSeek, Mistral, Ollama, most others){% else %} (Anthropic){% endif %}</option>{% endfor %}</select>
61
    <label for="atoken">Token</label><input type="password" id="atoken" name="token" autocomplete="new-password">
62
    <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>
63
    <label for="amodel">Model</label>
64
    <div class="model-row"><input type="text" id="amodel" name="model" list="amodel-list" placeholder="pick from List models" required autocomplete="off"><button type="button" class="small" data-list-models="{{ base }}/assistants/models" data-for="amodel" data-csrf="{{ csrf }}">LIST MODELS</button></div>
65
    <datalist id="amodel-list"></datalist>
66
    <p class="small mute" data-models-note="amodel">The endpoint's own list, once the provider and token are set. A model it does not list is refused on save.</p>
67
    <label for="aheaders">Extra headers</label><textarea id="aheaders" name="headers" rows="2" placeholder="Name: value"></textarea>
68
    <label for="abudget">Context budget (characters)</label><input type="text" id="abudget" name="budget" value="{{ a.default_budget }}">
69
    <div class="choice"><label><input type="checkbox" name="enabled" value="1" checked> on</label></div>
70
    <button type="submit">ADD AND TEST</button>
71
  </form>
72
  <form class="card" method="post" action="{{ base }}/assistants">
73
    <input type="hidden" name="_csrf" value="{{ csrf }}"><input type="hidden" name="action" value="prompt">
74
    <h3>Prompt</h3>
75
    {% for (from, text) in a.inherited_prompts %}<details><summary class="small mute">From {{ from }}</summary><pre class="snippet">{{ text }}</pre></details>{% endfor %}
76
    <label for="aprompt">What every assistant is told here{% if !a.inherited_prompts.is_empty() %}, after the prompts above{% endif %}</label>
77
    <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>
78
    <button type="submit">SAVE PROMPT</button>
79
  </form>
80
</section>
81
{% endmacro %}