Add AI weekly and monthly summaries with archive UI and backup support
This commit is contained in:
+168
-34
@@ -3,47 +3,181 @@
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<p class="eyebrow">Archiv</p>
|
||||
<h3>Alle gespeicherten Tage</h3>
|
||||
<h3>KI-Rückblicke und gespeicherte Tage</h3>
|
||||
</div>
|
||||
<span class="chart-chip"><?= e((string) count($entries)) ?> Einträge</span>
|
||||
</div>
|
||||
|
||||
<?php if ($entries === []): ?>
|
||||
<p class="empty-state">Noch keine Einträge vorhanden. Auf der Tracking-Seite kannst du den ersten Tag anlegen.</p>
|
||||
<?php else: ?>
|
||||
<div class="archive-items">
|
||||
<?php foreach ($entries as $entry): ?>
|
||||
<article class="archive-item <?= $selectedEntry !== null && $selectedEntry['date'] === $entry['date'] ? 'active' : '' ?>">
|
||||
<div>
|
||||
<strong><?= e(format_display_date($entry['date'], false)) ?></strong>
|
||||
<span><?= e($entry['evaluation']['label']) ?></span>
|
||||
<?php if ((int) $entry['sport_minutes'] > 0 && !empty($entry['sport_type_meta'])): ?>
|
||||
<span class="sport-pill-group">
|
||||
<?php foreach ($entry['sport_type_meta'] as $sportType): ?>
|
||||
<span class="sport-pill">
|
||||
<img src="<?= e(sport_icon_path($sportType['icon'])) ?>" alt="">
|
||||
<span><?= e($sportType['label']) ?><?= !empty($sportType['location']) ? ' · ' . e(sport_location_label((string) $sportType['location'])) : '' ?></span>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="archive-item__meta">
|
||||
<span><?= e(format_points((float) $entry['evaluation']['total'])) ?></span>
|
||||
<span>Stimmung <?= e((string) $entry['mood']) ?>/10</span>
|
||||
</div>
|
||||
<div class="archive-item__actions">
|
||||
<a class="ghost-link archive-action" href="/archive?date=<?= e(rawurlencode($entry['date'])) ?>">Ansehen</a>
|
||||
<a class="ghost-link archive-action" href="/track?date=<?= e(rawurlencode($entry['date'])) ?>">Bearbeiten</a>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
<section class="archive-summary-section">
|
||||
<div class="section-head section-head--compact">
|
||||
<div>
|
||||
<p class="eyebrow">KI</p>
|
||||
<h4>Monatszusammenfassungen</h4>
|
||||
</div>
|
||||
<?php if (empty($aiAvailable)): ?>
|
||||
<span class="chart-chip chart-chip--muted">API nicht bereit</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($monthlyArchive === []): ?>
|
||||
<p class="empty-state">Sobald genügend Wochenzusammenfassungen vorliegen, erscheinen hier die Monatsrückblicke.</p>
|
||||
<?php else: ?>
|
||||
<div class="archive-summary-grid">
|
||||
<?php foreach ($monthlyArchive as $month): ?>
|
||||
<article class="archive-summary-card">
|
||||
<div class="archive-summary-card__head">
|
||||
<div>
|
||||
<span class="summary-badge">KI</span>
|
||||
<strong><?= e($month['label']) ?></strong>
|
||||
</div>
|
||||
<?php if (!empty($month['has_summary'])): ?>
|
||||
<span class="chart-chip">vorhanden</span>
|
||||
<?php else: ?>
|
||||
<span class="chart-chip chart-chip--muted">offen</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<p class="helper-text"><?= e($month['date_from']) ?> bis <?= e($month['date_to']) ?></p>
|
||||
<p class="helper-text"><?= e((string) $month['weekly_summary_count']) ?> KI-Wochenzusammenfassungen im Monat verfügbar</p>
|
||||
|
||||
<?php if (!empty($month['summary'])): ?>
|
||||
<p class="helper-text">Erstellt am <?= e(format_display_datetime((string) $month['summary']['created_at'])) ?></p>
|
||||
<?php else: ?>
|
||||
<p class="helper-text">Mindestens 2 KI-Wochenzusammenfassungen nötig.</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="archive-item__actions archive-item__actions--stack">
|
||||
<?php if (!empty($month['summary'])): ?>
|
||||
<a class="ghost-link archive-action" href="/archive?summary_kind=monthly&summary_key=<?= e(rawurlencode((string) $month['summary_key'])) ?>">Öffnen</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="/archive">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="form_name" value="generate_monthly_summary">
|
||||
<input type="hidden" name="month_key" value="<?= e((string) $month['summary_key']) ?>">
|
||||
<button class="ghost-button ghost-button--small" type="submit" <?= !$month['can_generate'] || empty($aiAvailable) ? 'disabled' : '' ?>>
|
||||
<?= !empty($month['has_summary']) ? 'Neu generieren' : 'KI-Monatszusammenfassung erzeugen' ?>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="archive-summary-section">
|
||||
<div class="section-head section-head--compact">
|
||||
<div>
|
||||
<p class="eyebrow">KI</p>
|
||||
<h4>Wochenzusammenfassungen</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($weeklyArchive === []): ?>
|
||||
<p class="empty-state">Noch keine Wochen verfügbar. Sobald Einträge vorliegen, kannst du hier Wochenrückblicke erzeugen.</p>
|
||||
<?php else: ?>
|
||||
<div class="archive-summary-grid">
|
||||
<?php foreach ($weeklyArchive as $week): ?>
|
||||
<article class="archive-summary-card">
|
||||
<div class="archive-summary-card__head">
|
||||
<div>
|
||||
<span class="summary-badge">KI</span>
|
||||
<strong><?= e($week['label']) ?></strong>
|
||||
</div>
|
||||
<?php if (!empty($week['has_summary'])): ?>
|
||||
<span class="chart-chip">vorhanden</span>
|
||||
<?php else: ?>
|
||||
<span class="chart-chip chart-chip--muted">offen</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<p class="helper-text"><?= e($week['date_from']) ?> bis <?= e($week['date_to']) ?></p>
|
||||
<p class="helper-text"><?= e((string) $week['note_entries_count']) ?> Texteinträge · <?= e((string) $week['tracked_days']) ?> getrackte Tage</p>
|
||||
|
||||
<?php if (!empty($week['summary'])): ?>
|
||||
<p class="helper-text">Erstellt am <?= e(format_display_datetime((string) $week['summary']['created_at'])) ?></p>
|
||||
<?php else: ?>
|
||||
<p class="helper-text">Mindestens 3 Texteinträge nötig.</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="archive-item__actions archive-item__actions--stack">
|
||||
<?php if (!empty($week['summary'])): ?>
|
||||
<a class="ghost-link archive-action" href="/archive?summary_kind=weekly&summary_key=<?= e(rawurlencode((string) $week['summary_key'])) ?>">Öffnen</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="/archive">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="form_name" value="generate_weekly_summary">
|
||||
<input type="hidden" name="week_key" value="<?= e((string) $week['summary_key']) ?>">
|
||||
<button class="ghost-button ghost-button--small" type="submit" <?= !$week['can_generate'] || empty($aiAvailable) ? 'disabled' : '' ?>>
|
||||
<?= !empty($week['has_summary']) ? 'Neu generieren' : 'KI-Wochenzusammenfassung erzeugen' ?>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="archive-summary-section">
|
||||
<div class="section-head section-head--compact">
|
||||
<div>
|
||||
<p class="eyebrow">Tage</p>
|
||||
<h4>Alle gespeicherten Tage</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($entries === []): ?>
|
||||
<p class="empty-state">Noch keine Einträge vorhanden. Auf der Tracking-Seite kannst du den ersten Tag anlegen.</p>
|
||||
<?php else: ?>
|
||||
<div class="archive-items">
|
||||
<?php foreach ($entries as $entry): ?>
|
||||
<article class="archive-item <?= $selectedEntry !== null && $selectedEntry['date'] === $entry['date'] ? 'active' : '' ?>">
|
||||
<div>
|
||||
<strong><?= e(format_display_date($entry['date'], false)) ?></strong>
|
||||
<span><?= e($entry['evaluation']['label']) ?></span>
|
||||
<?php if ((int) $entry['sport_minutes'] > 0 && !empty($entry['sport_type_meta'])): ?>
|
||||
<span class="sport-pill-group">
|
||||
<?php foreach ($entry['sport_type_meta'] as $sportType): ?>
|
||||
<span class="sport-pill">
|
||||
<img src="<?= e(sport_icon_path($sportType['icon'])) ?>" alt="">
|
||||
<span><?= e($sportType['label']) ?><?= !empty($sportType['location']) ? ' · ' . e(sport_location_label((string) $sportType['location'])) : '' ?></span>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="archive-item__meta">
|
||||
<span><?= e(format_points((float) $entry['evaluation']['total'])) ?></span>
|
||||
<span>Stimmung <?= e((string) $entry['mood']) ?>/10</span>
|
||||
</div>
|
||||
<div class="archive-item__actions">
|
||||
<a class="ghost-link archive-action" href="/archive?date=<?= e(rawurlencode($entry['date'])) ?>">Ansehen</a>
|
||||
<a class="ghost-link archive-action" href="/track?date=<?= e(rawurlencode($entry['date'])) ?>">Bearbeiten</a>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
<aside class="stack-column">
|
||||
<?php if ($selectedEntry !== null): ?>
|
||||
<?php if ($selectedSummary !== null): ?>
|
||||
<article class="glass-panel detail-card">
|
||||
<p class="eyebrow">KI-Zusammenfassung</p>
|
||||
<h3><?= e($selectedSummary['title']) ?></h3>
|
||||
<p class="hero-label"><?= e($selectedSummary['date_from']) ?> bis <?= e($selectedSummary['date_to']) ?></p>
|
||||
<p class="helper-text">Erstellt am <?= e(format_display_datetime((string) $selectedSummary['created_at'])) ?></p>
|
||||
|
||||
<div class="note-box note-box--summary">
|
||||
<h4>Text</h4>
|
||||
<p><?= e($selectedSummary['text']) ?></p>
|
||||
</div>
|
||||
</article>
|
||||
<?php elseif ($selectedEntry !== null): ?>
|
||||
<article class="glass-panel detail-card">
|
||||
<p class="eyebrow">Ausgewählt</p>
|
||||
<h3><?= e(format_display_date($selectedEntry['date'])) ?></h3>
|
||||
@@ -91,7 +225,7 @@
|
||||
<article class="glass-panel detail-card">
|
||||
<p class="eyebrow">Details</p>
|
||||
<h3>Archivansicht</h3>
|
||||
<p>Wähle links einen Tag aus, um alle Werte anzuzeigen oder direkt in die Bearbeitung zu springen.</p>
|
||||
<p>Wähle links einen Tag oder eine KI-Zusammenfassung aus. Wochenrückblicke benötigen mindestens 3 Texteinträge, Monatsrückblicke mindestens 2 vorhandene KI-Wochenzusammenfassungen.</p>
|
||||
</article>
|
||||
<?php endif; ?>
|
||||
</aside>
|
||||
|
||||
@@ -390,6 +390,37 @@
|
||||
</article>
|
||||
|
||||
<?php if (!empty($authUser['is_admin'])): ?>
|
||||
<article class="glass-panel detail-card">
|
||||
<p class="eyebrow">KI</p>
|
||||
<h3>OpenAI für Zusammenfassungen</h3>
|
||||
<p class="helper-text">Diese Einstellung gilt zentral für alle Nutzer. Der API-Key bleibt in der Umgebung des Servers und wird hier bewusst nicht gespeichert.</p>
|
||||
|
||||
<?php if (!empty($aiStatus)): ?>
|
||||
<div class="user-list">
|
||||
<div class="user-row">
|
||||
<strong>API-Key</strong>
|
||||
<span><?= !empty($aiStatus['has_api_key']) ? 'vorhanden' : 'fehlt' ?></span>
|
||||
</div>
|
||||
<div class="user-row">
|
||||
<strong>Aktuelles Modell</strong>
|
||||
<span><?= e((string) ($aiStatus['model'] ?? '')) ?></span>
|
||||
</div>
|
||||
<div class="user-row">
|
||||
<strong>Timeout</strong>
|
||||
<span><?= e((string) ($aiStatus['timeout'] ?? '')) ?> s</span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="/options" class="stack-form">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="form_name" value="ai_config">
|
||||
<label><span>OpenAI-Modell</span><input type="text" name="ai[model]" value="<?= e((string) ($aiConfig['model'] ?? 'gpt-4o-mini')) ?>" required></label>
|
||||
<label><span>Timeout in Sekunden</span><input type="number" name="ai[timeout]" value="<?= e((string) ($aiConfig['timeout'] ?? 25)) ?>" min="5" max="120" required></label>
|
||||
<button class="primary-button" type="submit">KI-Konfiguration speichern</button>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<article class="glass-panel detail-card">
|
||||
<p class="eyebrow">Mehrere Accounts</p>
|
||||
<h3>Neuen Nutzer anlegen</h3>
|
||||
|
||||
Reference in New Issue
Block a user