feat(studio): Phase 1 - Studio-Ansicht online erreichbar + info@-Gating

Portiert das experimentelle Studio-Frontend aus der lokalen Variante in den
Online-Monitor, parallel zur klassischen Ansicht. Nur Geruest + Toggle:

- static/studio.html, css/studio.css, js/studio.js kopiert (Claude-basiert,
  ohne lokalen LLM-Unterbau)
- main.py: Route GET /studio liefert studio.html aus
- api.js: fehlende Studio-Methoden ergaenzt (events, ask, uploads, run/stage,
  run-status, freshness, search-status, factcheck-runs, x-accounts); Online-
  exportReport-Signatur unangetastet
- dashboard.html + app.js: Header-Button "Studio" nur fuer info@aegis-sight.de
  sichtbar
- studio.js: init() leitet fremde Logins auf /dashboard um (View-Gating)

Studio-spezifische Backend-Endpunkte folgen in Phase 2/3; fehlende liefern
vorerst 404, studio.js faengt das ab. Read-only Teile (Faelle, Artikel,
Faktenchecks, Karte, Snapshots) laufen ueber vorhandene Online-Endpunkte.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
claude-dev
2026-07-24 22:43:17 +02:00
Ursprung 31e885254a
Commit 4bc842ca1c
7 geänderte Dateien mit 3999 neuen und 0 gelöschten Zeilen

Datei anzeigen

@@ -217,6 +217,78 @@ const API = {
return this._request('GET', `/incidents/${incidentId}/refresh-log?limit=${limit}`);
},
// === Studio: Ereignis-Timeline, RAG-Chat, modulare Bausteine, Uploads, Faktencheck-Verlauf ===
// Backend-Endpunkte folgen phasenweise; fehlende liefern vorerst 404 (studio.js faengt das ab).
getEvents(incidentId, limit = 250) {
return this._request('GET', `/incidents/${incidentId}/events?limit=${encodeURIComponent(limit)}`);
},
// RAG-Chat: inhaltliche Frage über eine konkrete Lage (Studio-UI)
askIncident(incidentId, message, { conversation_id = null, source_filter = null, scope = 'fall' } = {}) {
return this._request('POST', `/incidents/${incidentId}/ask`, {
message,
conversation_id,
source_filter,
scope,
});
},
// Multimodaler Quellen-Ingest (Upload/URL -> Artikel)
createUploads(incidentId, { files = [], url = null } = {}) {
const fd = new FormData();
(files || []).forEach(f => fd.append('files', f));
if (url) fd.append('url', url);
return this.upload(`/incidents/${incidentId}/uploads`, fd);
},
listUploads(incidentId) {
return this._request('GET', `/incidents/${incidentId}/uploads`);
},
deleteUpload(incidentId, uploadId) {
return this._request('DELETE', `/incidents/${incidentId}/uploads/${uploadId}`);
},
async fetchUploadBlobUrl(incidentId, uploadId) {
const token = localStorage.getItem('osint_token');
const headers = {};
if (token) headers['Authorization'] = `Bearer ${token}`;
const res = await fetch(`${this.baseUrl}/incidents/${incidentId}/uploads/${uploadId}/file`, { headers });
if (!res.ok) throw new Error(`Datei konnte nicht geladen werden (${res.status})`);
const blob = await res.blob();
return URL.createObjectURL(blob);
},
// Modulare Pipeline-Bausteine (Studio)
runStage(incidentId, stage) {
return this._request('POST', `/incidents/${incidentId}/run/${stage}`);
},
getRunStatus(incidentId) {
return this._request('GET', `/incidents/${incidentId}/run-status`);
},
// Datenstand je Artefakt (erzeugt? wie alt? wie viele neue Artikel seither?)
getFreshness(incidentId) {
return this._request('GET', `/incidents/${incidentId}/freshness`);
},
// Kann die Websuche gerade Treffer liefern? (online via Claude-WebSearch, Stub)
getSearchStatus() {
return this._request('GET', '/system/search-status');
},
listFactcheckRuns(incidentId) {
return this._request('GET', `/incidents/${incidentId}/factcheck-runs`);
},
getFactcheckRun(incidentId, runId) {
return this._request('GET', `/incidents/${incidentId}/factcheck-runs/${runId}`);
},
// X-Zugänge (Studio; online-Router folgt in spaeterer Phase)
listXAccounts() {
return this._request('GET', '/x/accounts');
},
addXAccount(data) {
return this._request('POST', '/x/accounts', data);
},
deleteXAccount(username) {
return this._request('DELETE', `/x/accounts/${encodeURIComponent(username)}`);
},
// Sources (Quellenverwaltung)
listSources(params = {}) {
const query = new URLSearchParams();

Datei anzeigen

@@ -453,6 +453,12 @@ const App = {
this.user = user;
this._currentUsername = user.email;
// Studio-Button vorerst nur fuer info@aegis-sight.de sichtbar (experimentelle Ansicht)
if (user.email === 'info@aegis-sight.de') {
const studioLink = document.getElementById('studio-link');
if (studioLink) studioLink.style.display = '';
}
// i18n: Sprache anhand der Org laden (default 'de') und DOM uebersetzen
if (window.I18N) {
const targetLang = user.output_language || 'de';

2023
src/static/js/studio.js Normale Datei

Datei-Diff unterdrückt, da er zu groß ist Diff laden