112 lines
7.3 KiB
HTML
112 lines
7.3 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Daten · {{ workspace.name }}{% endblock %}
|
|
{% block content %}
|
|
<div class="stack stack-sm">
|
|
<h1>Daten</h1>
|
|
<p class="text-muted">Worauf die KI in diesem Workspace zugreifen kann, immer nur lesend. Änderungen an Repositories schlägt sie als Freigabeantrag vor.{% if not ready %} Der Chat startet, sobald eine Quelle erfolgreich getestet oder eine Datei verarbeitet ist.{% endif %}</p>
|
|
</div>
|
|
<section class="card" id="sources">
|
|
<div class="card-header cluster">
|
|
<h2 class="text-lg flex-1">Datenquellen</h2>
|
|
{% if membership.is_admin %}
|
|
<details class="dropdown aiw-menu-details">
|
|
<summary class="btn btn-secondary btn-sm">Verbinden</summary>
|
|
<div class="aiw-menu-panel">
|
|
<a class="dropdown-item" href="/workspaces/{{ workspace.id }}/sources/new">Git-Repository</a>
|
|
<a class="dropdown-item" href="/workspaces/{{ workspace.id }}/settings?tab=mcp#databases">Datenbank (unter MCP-Server)</a>
|
|
</div>
|
|
</details>
|
|
{% endif %}
|
|
</div>
|
|
<div class="card-body">
|
|
{% if sources %}
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead><tr><th>Name</th><th>Ziel</th><th>Status</th><th></th></tr></thead>
|
|
<tbody>
|
|
{% for s in sources %}{% include "_source_row.html" %}{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<p class="text-muted text-sm">Noch keine Datenquelle.{% if membership.is_admin %} Über „Verbinden“ ein Repository oder eine Datenbank anbinden.{% endif %}</p>
|
|
{% endif %}
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card" id="uploads">
|
|
<div class="card-header aiw-storage-head">
|
|
<h2 class="text-lg">Dateien zur Analyse</h2>
|
|
<div class="aiw-storage {{ 'aiw-storage-high' if storage.percent >= 80 }}">
|
|
<meter min="0" max="100" low="70" high="90" optimum="0" value="{{ storage.percent }}" aria-label="Speicher belegt"></meter>
|
|
<span class="text-xs text-muted">{{ (storage.used / 1048576)|round(1) }} von {{ (storage.limit / 1048576)|round|int }} MB belegt</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-body stack">
|
|
{% if storage.free <= 0 %}<div class="alert alert-warning" role="status"><div class="alert-content"><p>Das Speicherkontingent ist ausgeschöpft. Nicht mehr benötigte Dateien löschen oder den Plattform-Admin um mehr Speicher bitten.</p></div></div>
|
|
{% elif storage.percent >= 80 %}<div class="alert alert-info" role="status"><div class="alert-content"><p>Das Speicherkontingent ist zu {{ storage.percent }} % belegt.</p></div></div>{% endif %}
|
|
{% if uploads %}
|
|
<ul class="aiw-file-list">
|
|
{% for u in uploads %}
|
|
<li>
|
|
<div class="flex-1 min-w-0">
|
|
<code>{{ u.table_name }}</code> <span class="text-sm text-muted">{{ u.filename }}</span>
|
|
<p class="text-xs text-muted">{% if u.status == 'ok' %}{{ u.row_count|number }} Zeilen, {{ u.columns|length }} Spalten · {% endif %}{{ (u.stored_size / 1048576)|round(1) }} MB als Tabelle · {{ u.created.strftime('%d.%m.%Y') }}</p>
|
|
{% if u.status == 'ok' %}<details><summary class="text-xs">Spalten</summary><p class="text-xs">{% for c in u.columns %}<code>{{ c.name }}</code> {{ c.type }}{% if not loop.last %}, {% endif %}{% endfor %}</p></details>
|
|
{% else %}<span class="badge badge-danger">Fehler</span> <span class="text-xs text-muted">{{ u.error }}</span>{% endif %}
|
|
</div>
|
|
{% if membership.is_admin or u.uploaded_by == p.user_id %}
|
|
<form method="post" action="/uploads/{{ u.id }}/delete" data-confirm="Datei {{ u.filename }} löschen?">
|
|
<input type="hidden" name="_csrf" value="{{ p.csrf }}">
|
|
<button class="btn btn-ghost btn-sm" type="submit">Löschen</button>
|
|
</form>{% endif %}
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% endif %}
|
|
<form method="post" action="/workspaces/{{ workspace.id }}/uploads" enctype="multipart/form-data" class="cluster">
|
|
<input type="hidden" name="_csrf" value="{{ p.csrf }}">
|
|
<input type="file" name="file" required accept=".csv,.tsv,.json,.jsonl,.ndjson,.parquet" aria-label="Datei">
|
|
<button class="btn btn-secondary btn-sm" type="submit">Hochladen</button>
|
|
</form>
|
|
<p class="text-xs text-muted">CSV, TSV, JSON, JSON Lines oder Parquet bis {{ upload_max_mb }} MB je Datei. Aufs Kontingent zählt die Größe als Tabelle: mindestens etwa 0,5 MB, bei großen Dateien meist weniger als das Original. Jede Datei wird zu einer Tabelle, die die KI nur lesend mit SQL abfragt. Abfrageergebnisse gehen für die Antwort an das Modell.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card" id="folders">
|
|
<div class="card-header"><h2 class="text-lg">Abgeglichene Ordner</h2></div>
|
|
<div class="card-body stack">
|
|
<p class="text-sm text-muted">Dokumente von deinem Rechner, die sich regelmäßig ändern: Bifröst gleicht einen Ordner ab (nur Neues und Geändertes) und speichert den ausgelesenen Text. Office, PDF und Bilder liest der zentrale Dokumentendienst aus. Die KI sucht und liest darin mit <code>docs_search</code> und <code>docs_read</code>.</p>
|
|
{% if folders %}
|
|
<ul class="aiw-file-list">
|
|
{% for f in folders %}{% set s = folder_summaries[f.id] %}
|
|
<li>
|
|
<div class="flex-1 min-w-0">
|
|
<a href="/folders/{{ f.id }}">{{ f.name }}</a> <span class="badge {{ 'badge-default' if f.scope == 'private' else 'badge-info' }}">{{ scopes[f.scope] }}</span>
|
|
<p class="text-xs text-muted">{{ s.files }} Datei{{ 'en' if s.files != 1 }}{% if s.pending %} · {{ s.pending }} {{ 'wird' if s.pending == 1 else 'werden' }} ausgelesen{% endif %}{% if s.failed %} · {{ s.failed }} nicht lesbar{% endif %} · {{ s.size|filesizeformat }} Text · {% if f.synced %}abgeglichen {{ f.synced.astimezone().strftime('%d.%m.%Y %H:%M') }}{% else %}noch nicht abgeglichen{% endif %}</p>
|
|
</div>
|
|
<a class="btn btn-ghost btn-sm" href="/folders/{{ f.id }}">{{ 'Abgleichen' if f.owner_id == p.user_id else 'Ansehen' }}</a>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% endif %}
|
|
<details>
|
|
<summary class="btn btn-secondary btn-sm">Ordner verbinden</summary>
|
|
<form method="post" action="/workspaces/{{ workspace.id }}/folders" class="stack stack-sm aiw-folder-form">
|
|
<input type="hidden" name="_csrf" value="{{ p.csrf }}">
|
|
<div class="form-group">
|
|
<label for="folder-name">Name</label>
|
|
<input id="folder-name" name="name" required maxlength="100" placeholder="z. B. Projektunterlagen">
|
|
</div>
|
|
<fieldset class="stack stack-sm">
|
|
<legend class="text-sm">Wer darf die KI darauf zugreifen lassen?</legend>
|
|
<label class="cluster"><input type="radio" name="scope" value="private" checked> <span><strong>Nur ich</strong>: nur in meinen eigenen Chats. Antworten der KI stehen im Chat, und den sehen alle Mitglieder des Workspace.</span></label>
|
|
<label class="cluster"><input type="radio" name="scope" value="workspace"> <span><strong>Ganzer Workspace</strong>: alle Mitglieder in allen Chats.</span></label>
|
|
</fieldset>
|
|
<label class="cluster"><input type="checkbox" name="exposure_confirmed" value="1"> <span>Nur bei „Ganzer Workspace“: Ich bestätige, dass alle Mitglieder dieses Workspace die Inhalte über die KI sehen dürfen.</span></label>
|
|
<div><button class="btn btn-primary btn-sm" type="submit">Anlegen und Ordner wählen</button></div>
|
|
</form>
|
|
</details>
|
|
</div>
|
|
</section>
|
|
{% endblock %}
|