feat: AcroForm-Fill via Claude CLI, Multi-Source, Kanagawa, Docker-Deploy
Komplettes Rework der AI-Studio-Vorlage zu einem produktiven Werkzeug fuer
deutsche AcroForm-Formulare (Reha-Antraege, Arzt-Befundberichte):
- Backend: Express spawnt headless Claude CLI ('claude -p --output-format json'
via stdin-Pipe). Prompt enthaelt die Feldnamen als Ziel-Schema plus die
Arbeitsregeln (Stichwortstil, feste Zeichen-Kaestchen ohne Leerzeichen,
Vordrucke respektieren, keine geratenen Werte, nur medizinisch).
- PDF-Handling: pdfjs-dist statt pdf-lib — pdf-lib scheitert an verschluesselten
Object-Streams in DRV-Formularen. annotationStorage + saveDocument, kein
Flatten. Worker-Patch zur Laufzeit forciert Auto-Size und schwarze Schrift.
- Multi-Source-Upload: beliebig viele PDFs/Bilder + optional Freitext.
- Design: Kanagawa Design System (Preset aus ../kanagawa-design-system),
Tailwind lokal gebaut statt CDN, Dark/Light-Toggle, Progress-Indicator.
- Deployment: Multi-Stage-Dockerfile, docker-compose in matrix_default-Netz,
Claude-Credentials vom Host per Volume. PLAN.md + AGENTS.md (Alex-Schema).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d6cab4aeb5
commit
3c669fb003
28 changed files with 6756 additions and 934 deletions
80
components/ProcessingIndicator.tsx
Normal file
80
components/ProcessingIndicator.tsx
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Check, FileText, Loader2, ScanText, Sparkles } from 'lucide-react';
|
||||
|
||||
const STAGES = [
|
||||
{ icon: ScanText, label: 'PDF parsen' },
|
||||
{ icon: FileText, label: 'Quelldateien lesen' },
|
||||
{ icon: Sparkles, label: 'Daten extrahieren' },
|
||||
{ icon: Check, label: 'Felder mappen' },
|
||||
];
|
||||
|
||||
export const ProcessingIndicator: React.FC = () => {
|
||||
const [elapsed, setElapsed] = useState(0);
|
||||
const [activeStage, setActiveStage] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const started = Date.now();
|
||||
const id = setInterval(() => {
|
||||
const s = Math.floor((Date.now() - started) / 1000);
|
||||
setElapsed(s);
|
||||
// Rotierende Stage alle 6 Sekunden — nur Deko, kein echter Progress.
|
||||
setActiveStage(Math.min(Math.floor(s / 6), STAGES.length - 1));
|
||||
}, 500);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
const minutes = Math.floor(elapsed / 60);
|
||||
const seconds = elapsed % 60;
|
||||
const elapsedLabel =
|
||||
minutes > 0
|
||||
? `${minutes}:${seconds.toString().padStart(2, '0')} min`
|
||||
: `${seconds}s`;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-20">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 bg-kng-accent blur-xl opacity-20 rounded-kng-full animate-pulse" />
|
||||
<div className="relative bg-kng-bg-elevated p-6 rounded-kng-xl shadow-kng-lg border border-kng-border">
|
||||
<Loader2 className="w-12 h-12 text-kng-accent animate-spin" />
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="mt-8 text-2xl font-bold text-kng-text">
|
||||
Claude verarbeitet die Dokumente …
|
||||
</h3>
|
||||
<p className="mt-2 text-kng-text-secondary max-w-md text-center">
|
||||
Die Claude-CLI liest alle Quellen und mappt die Daten auf die
|
||||
AcroForm-Feldnamen.
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-kng-text-muted font-mono">
|
||||
Läuft seit {elapsedLabel}
|
||||
</p>
|
||||
|
||||
<div className="mt-8 grid grid-cols-1 md:grid-cols-4 gap-3 w-full max-w-3xl">
|
||||
{STAGES.map((stage, idx) => {
|
||||
const Icon = stage.icon;
|
||||
const isActive = idx === activeStage;
|
||||
const isDone = idx < activeStage;
|
||||
return (
|
||||
<div
|
||||
key={stage.label}
|
||||
className={`p-3 rounded-kng-md border flex items-center space-x-2 shadow-kng-sm transition-all duration-300 ${
|
||||
isActive
|
||||
? 'border-kng-accent bg-kng-surface text-kng-text'
|
||||
: isDone
|
||||
? 'border-kng-success/50 bg-kng-bg-elevated text-kng-success'
|
||||
: 'border-kng-border bg-kng-bg-elevated text-kng-text-muted opacity-60'
|
||||
}`}
|
||||
>
|
||||
<Icon
|
||||
className={`w-4 h-4 flex-shrink-0 ${
|
||||
isActive ? 'text-kng-accent animate-pulse' : ''
|
||||
}`}
|
||||
/>
|
||||
<span className="text-xs font-medium truncate">{stage.label}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue