From fdb4ac55a1d84c7c8d672c8a3e6df1e54c9120e8 Mon Sep 17 00:00:00 2001 From: m Date: Mon, 30 Mar 2026 11:26:01 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20frontend=20AI=20tab=20=E2=80=94=20KI-St?= =?UTF-8?q?rategie,=20KI-Entwurf,=20Aehnliche=20Faelle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New "KI" tab on case detail page with three sub-panels: - KI-Strategie: one-click strategic analysis with next steps, risks, timeline - KI-Entwurf: document drafting with template selection, language, instructions - Aehnliche Faelle: UPC similar case search with relevance scores Components: CaseStrategy, DocumentDrafter, SimilarCaseFinder Types: StrategyRecommendation, DocumentDraft, SimilarCase, etc. --- frontend/src/app/(app)/cases/[id]/ki/page.tsx | 51 ++++ frontend/src/app/(app)/cases/[id]/layout.tsx | 3 + frontend/src/components/ai/CaseStrategy.tsx | 226 ++++++++++++++++++ .../src/components/ai/DocumentDrafter.tsx | 198 +++++++++++++++ .../src/components/ai/SimilarCaseFinder.tsx | 183 ++++++++++++++ frontend/src/lib/types.ts | 78 ++++++ 6 files changed, 739 insertions(+) create mode 100644 frontend/src/app/(app)/cases/[id]/ki/page.tsx create mode 100644 frontend/src/components/ai/CaseStrategy.tsx create mode 100644 frontend/src/components/ai/DocumentDrafter.tsx create mode 100644 frontend/src/components/ai/SimilarCaseFinder.tsx diff --git a/frontend/src/app/(app)/cases/[id]/ki/page.tsx b/frontend/src/app/(app)/cases/[id]/ki/page.tsx new file mode 100644 index 0000000..98be0a9 --- /dev/null +++ b/frontend/src/app/(app)/cases/[id]/ki/page.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { useState } from "react"; +import { useParams } from "next/navigation"; +import { Brain, FileText, Search } from "lucide-react"; +import { CaseStrategy } from "@/components/ai/CaseStrategy"; +import { DocumentDrafter } from "@/components/ai/DocumentDrafter"; +import { SimilarCaseFinder } from "@/components/ai/SimilarCaseFinder"; + +type AITab = "strategy" | "draft" | "similar"; + +const TABS: { id: AITab; label: string; icon: typeof Brain }[] = [ + { id: "strategy", label: "KI-Strategie", icon: Brain }, + { id: "draft", label: "KI-Entwurf", icon: FileText }, + { id: "similar", label: "Aehnliche Faelle", icon: Search }, +]; + +export default function CaseAIPage() { + const { id } = useParams<{ id: string }>(); + const [activeTab, setActiveTab] = useState("strategy"); + + return ( +
+ {/* Sub-tabs */} +
+ {TABS.map((tab) => { + const isActive = activeTab === tab.id; + return ( + + ); + })} +
+ + {/* Content */} + {activeTab === "strategy" && } + {activeTab === "draft" && } + {activeTab === "similar" && } +
+ ); +} diff --git a/frontend/src/app/(app)/cases/[id]/layout.tsx b/frontend/src/app/(app)/cases/[id]/layout.tsx index 857f570..7037917 100644 --- a/frontend/src/app/(app)/cases/[id]/layout.tsx +++ b/frontend/src/app/(app)/cases/[id]/layout.tsx @@ -17,6 +17,7 @@ import { StickyNote, AlertTriangle, ScrollText, + Brain, } from "lucide-react"; import { format } from "date-fns"; import { de } from "date-fns/locale"; @@ -48,6 +49,7 @@ const TABS = [ { segment: "mitarbeiter", label: "Mitarbeiter", icon: UserCheck }, { segment: "notizen", label: "Notizen", icon: StickyNote }, { segment: "protokoll", label: "Protokoll", icon: ScrollText }, + { segment: "ki", label: "KI", icon: Brain }, ] as const; const TAB_LABELS: Record = { @@ -58,6 +60,7 @@ const TAB_LABELS: Record = { mitarbeiter: "Mitarbeiter", notizen: "Notizen", protokoll: "Protokoll", + ki: "KI", }; function CaseDetailSkeleton() { diff --git a/frontend/src/components/ai/CaseStrategy.tsx b/frontend/src/components/ai/CaseStrategy.tsx new file mode 100644 index 0000000..1c98c36 --- /dev/null +++ b/frontend/src/components/ai/CaseStrategy.tsx @@ -0,0 +1,226 @@ +"use client"; + +import { useMutation } from "@tanstack/react-query"; +import { api } from "@/lib/api"; +import type { StrategyRecommendation } from "@/lib/types"; +import { + Loader2, + Brain, + AlertTriangle, + ArrowRight, + Shield, + Calendar, + RefreshCw, +} from "lucide-react"; + +interface CaseStrategyProps { + caseId: string; +} + +const PRIORITY_STYLES = { + high: "bg-red-50 text-red-700 border-red-200", + medium: "bg-amber-50 text-amber-700 border-amber-200", + low: "bg-emerald-50 text-emerald-700 border-emerald-200", +} as const; + +const IMPORTANCE_STYLES = { + critical: "border-l-red-500", + important: "border-l-amber-500", + routine: "border-l-neutral-300", +} as const; + +export function CaseStrategy({ caseId }: CaseStrategyProps) { + const mutation = useMutation({ + mutationFn: () => + api.post("/ai/case-strategy", { + case_id: caseId, + }), + }); + + if (!mutation.data && !mutation.isPending && !mutation.isError) { + return ( +
+
+ +
+
+

+ KI-Strategieanalyse +

+

+ Claude analysiert die Akte und gibt strategische Empfehlungen. +

+
+ +
+ ); + } + + if (mutation.isPending) { + return ( +
+ +

+ Claude analysiert die Akte... +

+

+ Dies kann bis zu 30 Sekunden dauern. +

+
+ ); + } + + if (mutation.isError) { + return ( +
+
+ +
+

Analyse fehlgeschlagen

+ +
+ ); + } + + const data = mutation.data!; + + return ( +
+
+

+ KI-Strategieanalyse +

+ +
+ + {/* Summary */} +
+ {data.summary} +
+ + {/* Next Steps */} + {data.next_steps?.length > 0 && ( +
+

+ + Naechste Schritte +

+
+ {data.next_steps.map((step, i) => ( +
+
+ + {step.priority === "high" + ? "Hoch" + : step.priority === "medium" + ? "Mittel" + : "Niedrig"} + +
+

+ {step.action} +

+

+ {step.reasoning} +

+ {step.deadline && ( +

+ Frist: {step.deadline} +

+ )} +
+
+
+ ))} +
+
+ )} + + {/* Risk Assessment */} + {data.risk_assessment?.length > 0 && ( +
+

+ + Risikobewertung +

+
+ {data.risk_assessment.map((risk, i) => ( +
+
+ + {risk.level === "high" + ? "Hoch" + : risk.level === "medium" + ? "Mittel" + : "Niedrig"} + +
+

+ {risk.risk} +

+

+ Massnahme: {risk.mitigation} +

+
+
+
+ ))} +
+
+ )} + + {/* Timeline */} + {data.timeline?.length > 0 && ( +
+

+ + Zeitplan +

+
+ {data.timeline.map((item, i) => ( +
+
+ + {item.date} + + {item.event} +
+
+ ))} +
+
+ )} +
+ ); +} diff --git a/frontend/src/components/ai/DocumentDrafter.tsx b/frontend/src/components/ai/DocumentDrafter.tsx new file mode 100644 index 0000000..548f1a8 --- /dev/null +++ b/frontend/src/components/ai/DocumentDrafter.tsx @@ -0,0 +1,198 @@ +"use client"; + +import { useState } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { api } from "@/lib/api"; +import type { DocumentDraft, DraftDocumentRequest } from "@/lib/types"; +import { FileText, Loader2, Copy, Check, Download } from "lucide-react"; + +const TEMPLATES = { + klageschrift: "Klageschrift", + klageerwiderung: "Klageerwiderung", + abmahnung: "Abmahnung", + schriftsatz: "Schriftsatz", + berufung: "Berufungsschrift", + antrag: "Antrag", + stellungnahme: "Stellungnahme", + gutachten: "Gutachten", + vertrag: "Vertrag", + vollmacht: "Vollmacht", + upc_claim: "UPC Statement of Claim", + upc_defence: "UPC Statement of Defence", + upc_counterclaim: "UPC Counterclaim for Revocation", + upc_injunction: "UPC Provisional Measures", +} as const; + +const LANGUAGES = [ + { value: "de", label: "Deutsch" }, + { value: "en", label: "English" }, + { value: "fr", label: "Francais" }, +] as const; + +const inputClass = + "w-full rounded-md border border-neutral-200 bg-white px-3 py-2 text-sm text-neutral-900 outline-none transition-colors focus:border-neutral-400 focus:ring-1 focus:ring-neutral-400"; + +interface DocumentDrafterProps { + caseId: string; +} + +export function DocumentDrafter({ caseId }: DocumentDrafterProps) { + const [templateType, setTemplateType] = useState(""); + const [instructions, setInstructions] = useState(""); + const [language, setLanguage] = useState("de"); + const [copied, setCopied] = useState(false); + + const mutation = useMutation({ + mutationFn: (req: DraftDocumentRequest) => + api.post("/ai/draft-document", req), + }); + + function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + if (!templateType) return; + mutation.mutate({ + case_id: caseId, + template_type: templateType, + instructions, + language, + }); + } + + function handleCopy() { + if (mutation.data?.content) { + navigator.clipboard.writeText(mutation.data.content); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + } + + function handleDownload() { + if (!mutation.data?.content) return; + const blob = new Blob([mutation.data.content], { type: "text/plain;charset=utf-8" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `${templateType}_entwurf.txt`; + a.click(); + URL.revokeObjectURL(url); + } + + return ( +
+
+
+ + +
+ +
+ + +
+ +
+ +