Export: Zusammenfassung-Sektion, Checkbox-Auswahl, neue Reihenfolge
Research-Briefings: - Neue Sektion ZUSAMMENFASSUNG mit Bullet-Points als erstes Element - UEBERBLICK entfernt, durch ZUSAMMENFASSUNG ersetzt - Inkrementelles Briefing ebenfalls angepasst Export-System: - Zusammenfassung wird direkt aus dem Bericht extrahiert (kein separater KI-Aufruf mehr fuer Research-Lagen) - Reihenfolge: Zusammenfassung > Recherchebericht > Faktencheck > Quellen > Timeline - Sections-basiert statt scope-basiert (rueckwaertskompatibel) - Checkbox-Dialog statt Radio-Buttons im Frontend - Bereiche: Zusammenfassung, Recherchebericht, Faktencheck, Quellen, Timeline, Karte - PDF und DOCX Templates angepasst - Backend akzeptiert sections-Parameter (kommagetrennt)
Dieser Commit ist enthalten in:
@@ -66,8 +66,8 @@ AUFTRAG:
|
||||
Erstelle ein strukturiertes Briefing auf {output_language} mit folgenden Abschnitten. Sei so ausführlich wie nötig, um alle Aspekte gründlich abzudecken.
|
||||
Verwende durchgehend Inline-Quellenverweise [1], [2], [3] etc. im Text.
|
||||
|
||||
## ÜBERBLICK
|
||||
Kurze Einordnung des Themas (2-3 Sätze)
|
||||
## ZUSAMMENFASSUNG
|
||||
Kompakte Übersicht als Aufzählung (4-8 Bullet Points mit "- "). Jeder Punkt fasst einen Kernaspekt des Themas in 1-2 Sätzen zusammen. Der Leser soll nach dieser Sektion das Wesentliche erfasst haben, ohne den Rest lesen zu müssen.
|
||||
|
||||
## HINTERGRUND
|
||||
Historischer Kontext, relevante Vorgeschichte
|
||||
@@ -171,7 +171,7 @@ NEUE QUELLEN SEIT DEM LETZTEN UPDATE:
|
||||
AUFTRAG:
|
||||
Aktualisiere das Briefing mit den neuen Erkenntnissen. Sei so ausführlich wie nötig. Behalte die Struktur bei:
|
||||
|
||||
## ÜBERBLICK
|
||||
## ZUSAMMENFASSUNG
|
||||
## HINTERGRUND
|
||||
## AKTEURE
|
||||
## AKTUELLE LAGE
|
||||
|
||||
@@ -171,6 +171,55 @@ def _strip_citation_numbers(text: str) -> str:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _extract_zusammenfassung(summary_text: str) -> tuple[str, str]:
|
||||
"""Extrahiert die ZUSAMMENFASSUNG-Sektion aus einem Research-Briefing.
|
||||
|
||||
Returns:
|
||||
(zusammenfassung_html, remaining_summary)
|
||||
zusammenfassung_html: HTML-formatierte Bullet Points
|
||||
remaining_summary: Der Rest des Berichts ohne die Zusammenfassung
|
||||
"""
|
||||
if not summary_text:
|
||||
return "", summary_text
|
||||
|
||||
# Suche nach ## ZUSAMMENFASSUNG ... bis zur naechsten ## Ueberschrift
|
||||
pattern = r"(## ZUSAMMENFASSUNG\s*\n)(.*?)(?=\n## |\Z)"
|
||||
match = re.search(pattern, summary_text, re.DOTALL)
|
||||
if not match:
|
||||
return "", summary_text
|
||||
|
||||
zusammenfassung_raw = match.group(2).strip()
|
||||
# Rest des Berichts ohne die Zusammenfassung-Sektion
|
||||
remaining = summary_text[:match.start()] + summary_text[match.end():]
|
||||
remaining = remaining.strip()
|
||||
|
||||
# Bullet Points als HTML formatieren
|
||||
lines = []
|
||||
for line in zusammenfassung_raw.split("\n"):
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("- "):
|
||||
clean = _strip_citation_numbers(stripped[2:].strip())
|
||||
if clean:
|
||||
lines.append(clean)
|
||||
elif stripped.startswith("* "):
|
||||
clean = _strip_citation_numbers(stripped[2:].strip())
|
||||
if clean:
|
||||
lines.append(clean)
|
||||
elif stripped and not stripped.startswith("#"):
|
||||
clean = _strip_citation_numbers(stripped)
|
||||
if clean:
|
||||
lines.append(clean)
|
||||
|
||||
if lines:
|
||||
html = "<ul>\n" + "\n".join(f"<li>{line}</li>" for line in lines) + "\n</ul>"
|
||||
else:
|
||||
html = f"<p>{_strip_citation_numbers(zusammenfassung_raw)}</p>"
|
||||
|
||||
return html, remaining
|
||||
|
||||
|
||||
async def generate_executive_summary(summary_text: str) -> str:
|
||||
"""KI-verdichtetes Executive Summary aus dem Lagebild."""
|
||||
if not summary_text or len(summary_text.strip()) < 50:
|
||||
@@ -246,8 +295,31 @@ LAGEBILD:
|
||||
async def generate_pdf(
|
||||
incident: dict, articles: list, fact_checks: list, snapshots: list,
|
||||
scope: str, creator: str, executive_summary_html: str,
|
||||
sections: set[str] | None = None,
|
||||
) -> bytes:
|
||||
"""PDF-Report via WeasyPrint generieren."""
|
||||
# Sections aus scope ableiten wenn nicht explizit angegeben
|
||||
if sections is None:
|
||||
if scope == "summary":
|
||||
sections = {"zusammenfassung"}
|
||||
elif scope == "report":
|
||||
sections = {"zusammenfassung", "bericht", "faktencheck", "quellen"}
|
||||
else: # full
|
||||
sections = {"zusammenfassung", "bericht", "faktencheck", "quellen", "timeline"}
|
||||
|
||||
# Fuer Research-Lagen: Zusammenfassung aus dem Bericht extrahieren
|
||||
is_research = incident.get("type") == "research"
|
||||
zusammenfassung_html = executive_summary_html
|
||||
bericht_summary = incident.get("summary", "")
|
||||
zusammenfassung_title = "Executive Summary"
|
||||
|
||||
if is_research and bericht_summary:
|
||||
extracted_html, remaining = _extract_zusammenfassung(bericht_summary)
|
||||
if extracted_html:
|
||||
zusammenfassung_html = extracted_html
|
||||
zusammenfassung_title = "Zusammenfassung"
|
||||
bericht_summary = remaining
|
||||
|
||||
env = Environment(loader=FileSystemLoader(str(TEMPLATE_DIR)))
|
||||
template = env.get_template("report.html")
|
||||
|
||||
@@ -260,13 +332,12 @@ async def generate_pdf(
|
||||
report_date=now.strftime("%d.%m.%Y, %H:%M Uhr"),
|
||||
creator=creator,
|
||||
logo_base64=_get_logo_base64(),
|
||||
executive_summary=executive_summary_html,
|
||||
executive_summary=zusammenfassung_html,
|
||||
zusammenfassung_title=zusammenfassung_title,
|
||||
sections=sections,
|
||||
scope=scope,
|
||||
lagebild_html=_markdown_to_html(
|
||||
_strip_citation_numbers(
|
||||
_truncate_lagebild(incident.get("summary", ""), 4000) if scope == "report"
|
||||
else incident.get("summary", "")
|
||||
)
|
||||
_strip_citation_numbers(bericht_summary)
|
||||
),
|
||||
lagebild_timestamp=(incident.get("updated_at") or "")[:16].replace("T", " "),
|
||||
sources=_prepare_sources(incident)[:30] if scope == "report" else _prepare_sources(incident),
|
||||
@@ -292,10 +363,33 @@ async def generate_pdf(
|
||||
async def generate_docx(
|
||||
incident: dict, articles: list, fact_checks: list, snapshots: list,
|
||||
scope: str, creator: str, executive_summary_text: str,
|
||||
sections: set[str] | None = None,
|
||||
) -> bytes:
|
||||
"""Word-Report via python-docx generieren."""
|
||||
doc = Document()
|
||||
|
||||
# Sections aus scope ableiten wenn nicht explizit angegeben
|
||||
if sections is None:
|
||||
if scope == "summary":
|
||||
sections = {"zusammenfassung"}
|
||||
elif scope == "report":
|
||||
sections = {"zusammenfassung", "bericht", "faktencheck", "quellen"}
|
||||
else: # full
|
||||
sections = {"zusammenfassung", "bericht", "faktencheck", "quellen", "timeline"}
|
||||
|
||||
# Fuer Research-Lagen: Zusammenfassung aus dem Bericht extrahieren
|
||||
is_research = incident.get("type") == "research"
|
||||
zusammenfassung_text = executive_summary_text
|
||||
bericht_summary = incident.get("summary") or "Keine Zusammenfassung verfuegbar."
|
||||
zusammenfassung_title = "Executive Summary"
|
||||
|
||||
if is_research and bericht_summary:
|
||||
extracted_html, remaining = _extract_zusammenfassung(bericht_summary)
|
||||
if extracted_html:
|
||||
zusammenfassung_text = extracted_html
|
||||
zusammenfassung_title = "Zusammenfassung"
|
||||
bericht_summary = remaining
|
||||
|
||||
# Styles
|
||||
style = doc.styles['Normal']
|
||||
style.font.size = Pt(10)
|
||||
@@ -347,23 +441,21 @@ async def generate_docx(
|
||||
|
||||
doc.add_page_break()
|
||||
|
||||
# --- Executive Summary ---
|
||||
doc.add_heading("Executive Summary", level=1)
|
||||
# --- Zusammenfassung / Executive Summary ---
|
||||
if "zusammenfassung" in sections:
|
||||
doc.add_heading(zusammenfassung_title, level=1)
|
||||
|
||||
# HTML-Tags entfernen und als Bullet Points
|
||||
clean_text = re.sub(r'<[^>]+>', '', executive_summary_text)
|
||||
lines = [line.strip().lstrip("- ").lstrip("* ") for line in clean_text.strip().split("\n") if line.strip()]
|
||||
for line in lines:
|
||||
if line:
|
||||
doc.add_paragraph(line, style='List Bullet')
|
||||
# HTML-Tags entfernen und als Bullet Points
|
||||
clean_text = re.sub(r'<[^>]+>', '', zusammenfassung_text)
|
||||
lines = [line.strip().lstrip("- ").lstrip("* ") for line in clean_text.strip().split("\n") if line.strip()]
|
||||
for line in lines:
|
||||
if line:
|
||||
doc.add_paragraph(line, style='List Bullet')
|
||||
|
||||
if scope in ("report", "full"):
|
||||
# --- Lagebild ---
|
||||
doc.add_heading("Recherchebericht" if incident.get("type") == "research" else "Lagebild", level=1)
|
||||
raw_summary = incident.get("summary") or "Keine Zusammenfassung verfügbar."
|
||||
summary = _strip_citation_numbers(
|
||||
_truncate_lagebild(raw_summary, 4000) if scope == "report" else raw_summary
|
||||
)
|
||||
if "bericht" in sections:
|
||||
# --- Lagebild / Recherchebericht ---
|
||||
doc.add_heading("Recherchebericht" if is_research else "Lagebild", level=1)
|
||||
summary = _strip_citation_numbers(bericht_summary)
|
||||
# Markdown-Formatierung entfernen
|
||||
clean_summary = re.sub(r'\*\*(.+?)\*\*', r'\1', summary)
|
||||
clean_summary = re.sub(r'\[([^\]]+)\]\([^)]+\)', r'\1', clean_summary)
|
||||
@@ -379,8 +471,9 @@ async def generate_docx(
|
||||
else:
|
||||
doc.add_paragraph(para_text)
|
||||
|
||||
if "faktencheck" in sections:
|
||||
# --- Faktencheck ---
|
||||
report_fcs = fact_checks[:20] if scope == 'report' else fact_checks
|
||||
report_fcs = fact_checks
|
||||
if report_fcs:
|
||||
doc.add_heading("Faktencheck", level=1)
|
||||
table = doc.add_table(rows=1, cols=3)
|
||||
@@ -400,10 +493,9 @@ async def generate_docx(
|
||||
row[1].text = FC_STATUS_LABELS.get(fc.get("status", ""), fc.get("status", ""))
|
||||
row[2].text = str(fc.get("sources_count", 0))
|
||||
|
||||
if "quellen" in sections:
|
||||
# --- Quellenstatistik ---
|
||||
source_stats = _prepare_source_stats(articles)
|
||||
if scope == 'report':
|
||||
source_stats = source_stats[:20]
|
||||
if source_stats:
|
||||
doc.add_heading("Quellenstatistik", level=1)
|
||||
table = doc.add_table(rows=1, cols=3)
|
||||
@@ -423,7 +515,7 @@ async def generate_docx(
|
||||
row[1].text = str(stat["count"])
|
||||
row[2].text = stat["languages"]
|
||||
|
||||
if scope == "full":
|
||||
if "timeline" in sections:
|
||||
# --- Artikelverzeichnis ---
|
||||
if articles:
|
||||
doc.add_page_break()
|
||||
|
||||
@@ -78,17 +78,27 @@ tr:nth-child(even) { background: #f8f9fa; }
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Executive Summary -->
|
||||
<!-- Zusammenfassung / Executive Summary -->
|
||||
{% if 'zusammenfassung' in sections %}
|
||||
<div class="section">
|
||||
<h2>Executive Summary</h2>
|
||||
<h2>{{ zusammenfassung_title }}</h2>
|
||||
<div class="exec-summary">
|
||||
{{ executive_summary | safe }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Recherchebericht / Lagebild -->
|
||||
{% if 'bericht' in sections %}
|
||||
<div class="section">
|
||||
<h2>{% if incident.type == "research" %}Recherchebericht{% else %}Lagebild{% endif %}</h2>
|
||||
{% if lagebild_timestamp %}<p style="font-size:9pt;color:#888;margin-bottom:10px;">Aktualisiert: {{ lagebild_timestamp }}</p>{% endif %}
|
||||
<div class="lagebild-content">{{ lagebild_html | safe }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if scope in ('report', 'full') %}
|
||||
<!-- Faktencheck -->
|
||||
{% if fact_checks %}
|
||||
{% if 'faktencheck' in sections and fact_checks %}
|
||||
<div class="section">
|
||||
<h2>Faktencheck</h2>
|
||||
<table>
|
||||
@@ -106,10 +116,12 @@ tr:nth-child(even) { background: #f8f9fa; }
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Quellenstatistik -->
|
||||
{% if source_stats %}
|
||||
<!-- Quellenverzeichnis -->
|
||||
{% if 'quellen' in sections and sources %}
|
||||
<div class="section">
|
||||
<h2>Quellenstatistik</h2>
|
||||
<h2>Quellenverzeichnis</h2>
|
||||
{% if source_stats %}
|
||||
<h3>Quellenstatistik</h3>
|
||||
<table>
|
||||
<thead><tr><th>Quelle</th><th>Artikel</th><th>Sprache</th></tr></thead>
|
||||
<tbody>
|
||||
@@ -118,18 +130,8 @@ tr:nth-child(even) { background: #f8f9fa; }
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Lagebild -->
|
||||
<div class="section">
|
||||
<h2>{% if incident.type == "research" %}Recherchebericht{% else %}Lagebild{% endif %}</h2>
|
||||
{% if lagebild_timestamp %}<p style="font-size:9pt;color:#888;margin-bottom:10px;">Aktualisiert: {{ lagebild_timestamp }}</p>{% endif %}
|
||||
<div class="lagebild-content">{{ lagebild_html | safe }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Quellenverzeichnis -->
|
||||
{% if sources %}
|
||||
<div class="section">
|
||||
<h2>Quellenverzeichnis</h2>
|
||||
{% endif %}
|
||||
<h3>Quellen</h3>
|
||||
<table class="quellen-table">
|
||||
<thead><tr><th style="width:30px">#</th><th style="width:120px">Quelle</th><th>URL</th></tr></thead>
|
||||
<tbody>
|
||||
@@ -141,12 +143,8 @@ tr:nth-child(even) { background: #f8f9fa; }
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if scope == 'full' %}
|
||||
<!-- Timeline -->
|
||||
{% if timeline %}
|
||||
{% if 'timeline' in sections and timeline %}
|
||||
<div class="section" style="page-break-before:always;">
|
||||
<h2>Ereignis-Timeline</h2>
|
||||
{% for event in timeline %}
|
||||
@@ -160,7 +158,7 @@ tr:nth-child(even) { background: #f8f9fa; }
|
||||
{% endif %}
|
||||
|
||||
<!-- Artikelverzeichnis -->
|
||||
{% if articles %}
|
||||
{% if 'timeline' in sections and articles %}
|
||||
<div class="section" style="page-break-before:always;">
|
||||
<h2>Artikelverzeichnis ({{ articles | length }} Artikel)</h2>
|
||||
<table>
|
||||
@@ -178,7 +176,6 @@ tr:nth-child(even) { background: #f8f9fa; }
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div class="report-footer">
|
||||
Erstellt mit AegisSight Monitor — aegis-sight.de — {{ report_date }}
|
||||
|
||||
@@ -713,12 +713,21 @@ async def export_incident(
|
||||
incident_id: int,
|
||||
format: str = Query("pdf", pattern="^(pdf|docx)$"),
|
||||
scope: str = Query("report", pattern="^(summary|report|full)$"),
|
||||
sections: str = Query(None),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
db: aiosqlite.Connection = Depends(db_dependency),
|
||||
):
|
||||
"""Lage als PDF oder Word exportieren."""
|
||||
from report_generator import generate_pdf, generate_docx, generate_executive_summary
|
||||
|
||||
# Sections aus Komma-getrenntem String parsen
|
||||
VALID_SECTIONS = {"zusammenfassung", "bericht", "faktencheck", "quellen", "timeline", "karte"}
|
||||
sections_set = None
|
||||
if sections:
|
||||
sections_set = {s.strip() for s in sections.split(",") if s.strip() in VALID_SECTIONS}
|
||||
if not sections_set:
|
||||
sections_set = None
|
||||
|
||||
tenant_id = current_user.get("tenant_id")
|
||||
row = await _check_incident_access(db, incident_id, current_user["id"], tenant_id)
|
||||
incident = dict(row)
|
||||
@@ -765,18 +774,28 @@ async def export_incident(
|
||||
date_str = datetime.now(TIMEZONE).strftime("%Y%m%d")
|
||||
slug = _slugify(incident["title"])
|
||||
scope_labels = {"summary": "executive_summary", "report": "lagebericht", "full": "vollstaendig"}
|
||||
# Wenn sections explizit angegeben, passenden Label waehlen
|
||||
if sections_set:
|
||||
if sections_set == {"zusammenfassung"}:
|
||||
scope_labels_key = "executive_summary"
|
||||
elif "timeline" in sections_set:
|
||||
scope_labels_key = "vollstaendig"
|
||||
else:
|
||||
scope_labels_key = "lagebericht"
|
||||
else:
|
||||
scope_labels_key = scope_labels.get(scope, "lagebericht")
|
||||
|
||||
if format == "pdf":
|
||||
pdf_bytes = await generate_pdf(incident, articles, fact_checks, snapshots, scope, creator, exec_summary)
|
||||
filename = f"{slug}_{scope_labels[scope]}_{date_str}.pdf"
|
||||
pdf_bytes = await generate_pdf(incident, articles, fact_checks, snapshots, scope, creator, exec_summary, sections=sections_set)
|
||||
filename = f"{slug}_{scope_labels_key}_{date_str}.pdf"
|
||||
return StreamingResponse(
|
||||
io.BytesIO(pdf_bytes),
|
||||
media_type="application/pdf",
|
||||
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
|
||||
)
|
||||
else:
|
||||
docx_bytes = await generate_docx(incident, articles, fact_checks, snapshots, scope, creator, exec_summary)
|
||||
filename = f"{slug}_{scope_labels[scope]}_{date_str}.docx"
|
||||
docx_bytes = await generate_docx(incident, articles, fact_checks, snapshots, scope, creator, exec_summary, sections=sections_set)
|
||||
filename = f"{slug}_{scope_labels_key}_{date_str}.docx"
|
||||
return StreamingResponse(
|
||||
io.BytesIO(docx_bytes),
|
||||
media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
|
||||
@@ -668,10 +668,13 @@
|
||||
</div>
|
||||
<div class="modal-body" style="padding:20px;">
|
||||
<div style="margin-bottom:16px;">
|
||||
<label style="font-size:11px;text-transform:uppercase;letter-spacing:1px;color:var(--text-secondary);display:block;margin-bottom:8px;">Umfang</label>
|
||||
<label class="export-radio"><input type="radio" name="export-scope" value="summary" checked><span>Executive Summary</span><span class="export-radio-desc">1-2 Seiten, Kernpunkte</span></label>
|
||||
<label class="export-radio"><input type="radio" name="export-scope" value="report"><span>Lagebericht</span><span class="export-radio-desc">Lagebild, Faktencheck, Quellen</span></label>
|
||||
<label class="export-radio"><input type="radio" name="export-scope" value="full"><span>Vollständiger Bericht</span><span class="export-radio-desc">+ Timeline, Artikelverzeichnis</span></label>
|
||||
<label style="font-size:11px;text-transform:uppercase;letter-spacing:1px;color:var(--text-secondary);display:block;margin-bottom:8px;">Bereiche</label>
|
||||
<label class="export-radio"><input type="checkbox" name="export-section" value="zusammenfassung" checked><span>Zusammenfassung</span></label>
|
||||
<label class="export-radio"><input type="checkbox" name="export-section" value="bericht" checked><span>Recherchebericht / Lagebild</span></label>
|
||||
<label class="export-radio"><input type="checkbox" name="export-section" value="faktencheck" checked><span>Faktencheck</span></label>
|
||||
<label class="export-radio"><input type="checkbox" name="export-section" value="quellen" checked><span>Quellen</span></label>
|
||||
<label class="export-radio"><input type="checkbox" name="export-section" value="timeline"><span>Timeline</span></label>
|
||||
<label class="export-radio"><input type="checkbox" name="export-section" value="karte"><span>Karte</span></label>
|
||||
</div>
|
||||
<div style="margin-bottom:16px;">
|
||||
<label style="font-size:11px;text-transform:uppercase;letter-spacing:1px;color:var(--text-secondary);display:block;margin-bottom:8px;">Format</label>
|
||||
|
||||
@@ -237,9 +237,15 @@ const API = {
|
||||
resetTutorialState() {
|
||||
return this._request('DELETE', '/tutorial/state');
|
||||
},
|
||||
exportReport(id, format, scope) {
|
||||
exportReport(id, format, scope, sections) {
|
||||
const token = localStorage.getItem('osint_token');
|
||||
return fetch(`${this.baseUrl}/incidents/${id}/export?format=${format}&scope=${scope}`, {
|
||||
let url = `${this.baseUrl}/incidents/${id}/export?format=${format}`;
|
||||
if (sections && sections.length > 0) {
|
||||
url += `§ions=${sections.join(',')}`;
|
||||
} else if (scope) {
|
||||
url += `&scope=${scope}`;
|
||||
}
|
||||
return fetch(url, {
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
});
|
||||
},
|
||||
|
||||
@@ -2284,16 +2284,21 @@ async handleRefresh() {
|
||||
|
||||
async submitExport() {
|
||||
if (!this.currentIncidentId) return;
|
||||
const scope = document.querySelector('input[name="export-scope"]:checked').value;
|
||||
const checked = document.querySelectorAll('input[name="export-section"]:checked');
|
||||
const sections = Array.from(checked).map(cb => cb.value);
|
||||
if (sections.length === 0) {
|
||||
UI.showToast('Bitte mindestens einen Bereich ausw\u00e4hlen.', 'warning');
|
||||
return;
|
||||
}
|
||||
const format = document.querySelector('input[name="export-format"]:checked').value;
|
||||
|
||||
const btn = document.getElementById('export-submit-btn');
|
||||
const origText = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = scope === 'summary' ? 'KI generiert Executive Summary...' : 'Wird erstellt...';
|
||||
btn.textContent = 'Wird erstellt...';
|
||||
|
||||
try {
|
||||
const response = await API.exportReport(this.currentIncidentId, format, scope);
|
||||
const response = await API.exportReport(this.currentIncidentId, format, null, sections);
|
||||
if (!response.ok) {
|
||||
const err = await response.json().catch(() => ({}));
|
||||
throw new Error(err.detail || 'Fehler ' + response.status);
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren