Backend: - Setting + GeneratedPdf models, Alembic migration with default settings seed - PDF generation service (WeasyPrint + Jinja2 with autoescape) - Health report HTML template with memory entries + document excerpts - Admin user management: list, create, update (role/max_chats/is_active) - Admin settings: self_registration_enabled, default_max_chats - Self-registration check wired into auth register endpoint - default_max_chats applied to new user registrations - AI tool: generate_pdf creates health compilation PDFs - PDF compile/list/download API endpoints - WeasyPrint system deps added to Dockerfile Frontend: - PDF reports page with generate + download - Admin users page with create/edit/activate/deactivate - Admin settings page with self-registration toggle + max chats - Extended sidebar with PDF reports + admin users/settings links - English + Russian translations for all new UI Review fixes applied: - Jinja2 autoescape enabled (XSS prevention in PDFs) - db.refresh after flush (created_at populated correctly) - storage_path removed from API response (no internal path leak) - Role field uses Literal["user", "admin"] validation - React hooks called before conditional returns (rules of hooks) - default_max_chats setting now applied during registration Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
71 lines
2.4 KiB
HTML
71 lines
2.4 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<style>
|
|
body { font-family: 'Helvetica', 'Arial', sans-serif; font-size: 12pt; color: #333; margin: 40px; }
|
|
h1 { color: #1a7a8a; border-bottom: 2px solid #1a7a8a; padding-bottom: 8px; }
|
|
h2 { color: #2a5a6a; margin-top: 24px; }
|
|
.header { margin-bottom: 24px; }
|
|
.header p { margin: 2px 0; color: #666; }
|
|
.section { margin-bottom: 20px; }
|
|
.memory-entry { background: #f5f9fa; padding: 10px 14px; border-radius: 6px; margin-bottom: 8px; }
|
|
.memory-entry .category { font-size: 10pt; color: #888; text-transform: uppercase; }
|
|
.memory-entry .title { font-weight: bold; }
|
|
.memory-entry .importance { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 9pt; }
|
|
.importance-critical { background: #fee; color: #c33; }
|
|
.importance-high { background: #fff3e0; color: #e65100; }
|
|
.importance-medium { background: #e3f2fd; color: #1565c0; }
|
|
.importance-low { background: #f5f5f5; color: #666; }
|
|
.document { border: 1px solid #ddd; padding: 12px; border-radius: 6px; margin-bottom: 10px; }
|
|
.document .filename { font-weight: bold; color: #1a7a8a; }
|
|
.document .excerpt { font-size: 10pt; color: #555; white-space: pre-wrap; max-height: 200px; overflow: hidden; }
|
|
.footer { margin-top: 40px; padding-top: 12px; border-top: 1px solid #ddd; font-size: 9pt; color: #999; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1>{{ title }}</h1>
|
|
<p>Patient: {{ user_name }}</p>
|
|
<p>Generated: {{ generated_at }}</p>
|
|
</div>
|
|
|
|
{% if memories %}
|
|
<div class="section">
|
|
<h2>Health Profile</h2>
|
|
{% for m in memories %}
|
|
<div class="memory-entry">
|
|
<span class="category">{{ m.category }}</span>
|
|
<span class="importance importance-{{ m.importance }}">{{ m.importance }}</span>
|
|
<div class="title">{{ m.title }}</div>
|
|
<div>{{ m.content }}</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if documents %}
|
|
<div class="section">
|
|
<h2>Document Summaries</h2>
|
|
{% for d in documents %}
|
|
<div class="document">
|
|
<div class="filename">{{ d.original_filename }} ({{ d.doc_type }})</div>
|
|
<div class="excerpt">{{ d.excerpt }}</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if ai_summary %}
|
|
<div class="section">
|
|
<h2>AI Summary</h2>
|
|
<p>{{ ai_summary }}</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="footer">
|
|
Generated by AI Assistant • This document is for informational purposes only.
|
|
</div>
|
|
</body>
|
|
</html>
|