bifroest/app/templates/usage.html
2026-10-27 08:34:17 +01:00

90 lines
6.8 KiB
HTML

{% extends "base.html" %}
{% block title %}Nutzung und Kosten{% endblock %}
{% block content %}
<div class="stack stack-sm">
<h1>Nutzung und Kosten{% if workspace %}: {{ workspace.name }}{% endif %}</h1>
<p class="text-muted">Tokens und Kosten je Modellaufruf, {{ start.strftime('%d.%m.%Y') }} bis {{ end.strftime('%d.%m.%Y') }}. Kosten nur für Modelle mit hinterlegtem Preis{% if not workspace %} (<a href="/admin/models">Modelle</a>){% endif %}; Verbrauch bleibt erhalten, auch wenn Chats gelöscht werden.</p>
</div>
<section class="card"><div class="card-body stack">
<form method="get" class="cluster aiw-filters">
<label class="text-sm">Zeitraum
<select name="period">{% for k, v in periods.items() %}<option value="{{ k }}" {% if period == k %}selected{% endif %}>{{ v }}</option>{% endfor %}</select>
</label>
<label class="text-sm">von <input type="date" name="start" value="{{ start }}"></label>
<label class="text-sm">bis <input type="date" name="end" value="{{ end }}"></label>
<label class="text-sm">Gruppiert nach
<select name="group">{% for k, v in groups.items() if not workspace or k not in ('team', 'workspace') %}<option value="{{ k }}" {% if group == k %}selected{% endif %}>{{ v }}</option>{% endfor %}</select>
</label>
<button class="btn btn-secondary btn-sm" type="submit">Anzeigen</button>
</form>
<p class="text-xs text-muted">Für eigene Daten „Zeitraum“ wählen.</p>
<div class="aiw-stats">
<div class="card"><div class="card-body"><p class="text-xs text-muted">Kosten</p><p class="text-xl">{{ total.cost|money }} {{ currency }}</p>
{% if total.unpriced %}<p class="text-xs text-muted">{{ total.unpriced|number }} Aufrufe ohne Preis</p>{% endif %}</div></div>
<div class="card"><div class="card-body"><p class="text-xs text-muted">Tokens</p><p class="text-xl">{{ total.total_tokens|number }}</p>
<p class="text-xs text-muted">{{ total.input_tokens|number }} ein · {{ total.output_tokens|number }} aus</p></div></div>
<div class="card"><div class="card-body"><p class="text-xs text-muted">Modellaufrufe</p><p class="text-xl">{{ total.calls|number }}</p>
<p class="text-xs text-muted">in {{ total.runs|number }} Runs</p></div></div>
<div class="card"><div class="card-body"><p class="text-xs text-muted">Runs nach Status</p>
<p class="text-sm">{% for s, n in stats|dictsort %}<a href="{{ base }}/runs?period={{ period }}&start={{ start }}&end={{ end }}&status={{ s }}">{{ labels.get(s, s) }}: {{ n }}</a>{% if not loop.last %} · {% endif %}{% else %}keine{% endfor %}</p></div></div>
</div>
{% if rows %}
<div class="table-wrap"><table>
<thead><tr><th>{{ groups[group] }}</th><th class="aiw-number">Aufrufe</th><th class="aiw-number">Runs</th><th class="aiw-number">Eingabe</th><th class="aiw-number">davon Cache</th><th class="aiw-number">Ausgabe</th><th class="aiw-number">davon Reasoning</th><th class="aiw-number">Kosten ({{ currency }})</th></tr></thead>
<tbody>
{% for r in rows %}
<tr>
<td>{{ r.label }}</td>
<td class="aiw-number">{{ r.calls|number }}</td>
<td class="aiw-number">{{ r.runs|number }}</td>
<td class="aiw-number">{{ r.input_tokens|number }}</td>
<td class="aiw-number">{{ r.cached_tokens|number }}</td>
<td class="aiw-number">{{ r.output_tokens|number }}</td>
<td class="aiw-number">{{ r.reasoning_tokens|number }}</td>
<td class="aiw-number">{{ r.cost|money }}{% if r.unpriced %} <span class="text-xs text-muted" title="Aufrufe ohne Preis">+{{ r.unpriced }} ohne Preis</span>{% endif %}</td>
</tr>
{% endfor %}
</tbody>
<tfoot><tr><th>Summe</th><th class="aiw-number">{{ total.calls|number }}</th><th class="aiw-number">{{ total.runs|number }}</th><th class="aiw-number">{{ total.input_tokens|number }}</th><th class="aiw-number">{{ total.cached_tokens|number }}</th><th class="aiw-number">{{ total.output_tokens|number }}</th><th class="aiw-number">{{ total.reasoning_tokens|number }}</th><th class="aiw-number">{{ total.cost|money }}</th></tr></tfoot>
</table></div>
{% else %}<p class="text-muted">Im Zeitraum keine Modellaufrufe.</p>{% endif %}
</div></section>
<section class="card" id="feedback">
<div class="card-header aiw-chats-head">
<h2 class="text-lg">Rückmeldungen zu Antworten</h2>
{% if feedback.total %}<a class="btn btn-ghost btn-sm" href="{{ base }}/usage/feedback.csv?period={{ period }}&start={{ start }}&end={{ end }}" download>Als CSV herunterladen</a>{% endif %}
</div>
<div class="card-body stack">
{% if feedback.total %}
<div class="aiw-stats">
<div class="card"><div class="card-body"><p class="text-xs text-muted">Hilfreich</p><p class="text-xl">{{ feedback.share }} %</p>
<p class="text-xs text-muted">{{ feedback.up|number }} 👍 · {{ feedback.down|number }} 👎</p></div></div>
<div class="card"><div class="card-body"><p class="text-xs text-muted">Rückmeldungen</p><p class="text-xl">{{ feedback.total|number }}</p>
<p class="text-xs text-muted">{% if total.runs %}zu {{ (100 * feedback.total / total.runs)|round|int }} % der Runs{% endif %}</p></div></div>
</div>
{% if feedback.by_model|length > 1 %}
<div class="table-wrap"><table>
<thead><tr><th>Modell</th><th class="aiw-number">👍</th><th class="aiw-number">👎</th><th class="aiw-number">Hilfreich</th></tr></thead>
<tbody>{% for model, up, down in feedback.by_model %}<tr><td>{{ model }}</td><td class="aiw-number">{{ up }}</td><td class="aiw-number">{{ down }}</td><td class="aiw-number">{{ (100 * up / (up + down))|round|int }} %</td></tr>{% endfor %}</tbody>
</table></div>
{% endif %}
<h3 class="text-base">Einzelne Rückmeldungen</h3>
<p class="text-xs text-muted">Zuerst die mit Kommentar, dann die negativen{% if feedback.total > feedback.recent|length %}; hier {{ feedback.recent|length }} von {{ feedback.total }}, alle im CSV{% endif %}.</p>
<ul class="aiw-feedback-list">
{% for r in feedback.recent %}
<li>
<span class="aiw-feedback-mark" aria-label="{{ 'hilfreich' if r.rating == 1 else 'nicht hilfreich' }}">{{ '👍' if r.rating == 1 else '👎' }}</span>
<div class="min-w-0">
{% if r.comment %}<p class="text-sm">{{ r.comment }}</p>{% endif %}
<p class="text-xs text-muted"><a href="/chats/{{ r.chat_id }}#message-{{ r.message_id }}">{{ r.chat_title }}</a> · {{ r.user }}{% if not workspace %} · {{ r.workspace }}{% endif %}{% if r.model %} · {{ r.model }}{% endif %} · {{ r.tools }} Tool-Aufruf{{ 'e' if r.tools != 1 }} · {{ r.time.astimezone().strftime('%d.%m.%Y %H:%M') }}</p>
</div>
</li>
{% endfor %}
</ul>
{% else %}<p class="text-muted">Im Zeitraum keine Rückmeldungen. Unter jeder Antwort fragt Bifröst „Hilfreich?“.</p>{% endif %}
</div>
</section>
{% endblock %}