From 7eeca5c3ba07e0d1f0044e3328eb94218665ccf9 Mon Sep 17 00:00:00 2001 From: s8613 Date: Tue, 1 Jul 2025 10:52:56 +0200 Subject: [PATCH 1/5] Fixed extra frame font and small styling issues. --- project/frontend/src/components/KPIForm.tsx | 81 +++++++++++-------- project/frontend/src/components/pdfViewer.tsx | 18 ++--- 2 files changed, 55 insertions(+), 44 deletions(-) diff --git a/project/frontend/src/components/KPIForm.tsx b/project/frontend/src/components/KPIForm.tsx index f58d88d..13152cc 100644 --- a/project/frontend/src/components/KPIForm.tsx +++ b/project/frontend/src/components/KPIForm.tsx @@ -7,6 +7,7 @@ import type { Kennzahl } from "../types/kpi"; import { typeDisplayMapping } from "../types/kpi"; import Snackbar from "@mui/material/Snackbar"; import MuiAlert from "@mui/material/Alert"; +import { API_HOST } from "../util/api"; interface KPIFormProps { @@ -92,7 +93,7 @@ export function KPIForm({ mode, initialData, onSave, onCancel, loading = false, localStorage.setItem("spacyData", JSON.stringify(updated)); // POST Request an das Flask-Backend - const response = await fetch("http://localhost:5050/api/spacy/append-training-entry", { + const response = await fetch(`${API_HOST}/api/spacy/append-training-entry/`, { method: "POST", headers: { "Content-Type": "application/json" @@ -240,42 +241,54 @@ export function KPIForm({ mode, initialData, onSave, onCancel, loading = false, + - {mode === 'add' && ( - <> - - - updateField('active', e.target.checked)} - sx={{ color: '#383838' }} - /> - } - label="Aktiv" + + updateField('active', e.target.checked)} + sx={{ + color: '#666666', + '&.Mui-checked': { + color: '#333333', + }, + '&:hover': { + backgroundColor: 'rgba(102, 102, 102, 0.04)', + } + }} /> - - Die Kennzahl ist aktiv und wird angezeigt - - - - updateField('mandatory', e.target.checked)} - sx={{ color: '#383838' }} - /> - } - label="Erforderlich" + } + label="Aktiv" + /> + + Die Kennzahl ist aktiv und wird angezeigt + + + + updateField('mandatory', e.target.checked)} + sx={{ + color: '#666666', + '&.Mui-checked': { + color: '#333333', + }, + '&:hover': { + backgroundColor: 'rgba(102, 102, 102, 0.04)', + } + }} /> - - Die Kennzahl erlaubt keine leeren Werte - - - - )} + } + label="Erforderlich" + /> + + Die Kennzahl erlaubt keine leeren Werte + + diff --git a/project/frontend/src/components/pdfViewer.tsx b/project/frontend/src/components/pdfViewer.tsx index b41a433..b88a723 100644 --- a/project/frontend/src/components/pdfViewer.tsx +++ b/project/frontend/src/components/pdfViewer.tsx @@ -188,6 +188,10 @@ export default function PDFViewer({ ); }, []); + const contentWidth = baseWidth ? baseWidth * 0.98 * zoomLevel : 0; + const containerWidth = baseWidth ? baseWidth : 0; + const willOverflow = contentWidth > containerWidth; + return ( Date: Tue, 1 Jul 2025 11:06:20 +0200 Subject: [PATCH 2/5] Removed example for edit mode. --- project/frontend/src/components/KPIForm.tsx | 54 +++++++++++++++++---- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/project/frontend/src/components/KPIForm.tsx b/project/frontend/src/components/KPIForm.tsx index 13152cc..ff9ef86 100644 --- a/project/frontend/src/components/KPIForm.tsx +++ b/project/frontend/src/components/KPIForm.tsx @@ -30,6 +30,7 @@ const emptyKPI: Partial = { export function KPIForm({ mode, initialData, onSave, onCancel, loading = false, resetTrigger }: KPIFormProps) { const [formData, setFormData] = useState>(emptyKPI); + const [originalExamples, setOriginalExamples] = useState>([]); const [isSaving, setIsSaving] = useState(false); const [snackbarOpen, setSnackbarOpen] = useState(false); const [snackbarMessage, setSnackbarMessage] = useState(""); @@ -38,14 +39,20 @@ export function KPIForm({ mode, initialData, onSave, onCancel, loading = false, useEffect(() => { if (mode === 'edit' && initialData) { - setFormData(initialData); + setOriginalExamples(initialData.examples || []); + setFormData({ + ...initialData, + examples: [{ sentence: '', value: '' }] + }); } else if (mode === 'add') { + setOriginalExamples([]); setFormData(emptyKPI); } }, [mode, initialData]); useEffect(() => { if (mode === 'add') { + setOriginalExamples([]); setFormData(emptyKPI); } }, [resetTrigger]); @@ -65,24 +72,30 @@ export function KPIForm({ mode, initialData, onSave, onCancel, loading = false, setSnackbarMessage("Mindestens ein Beispielsatz ist erforderlich"); setSnackbarSeverity("error"); setSnackbarOpen(true); - return; } - for (const ex of formData.examples) { + const newExamples = formData.examples.filter(ex => ex.sentence?.trim() && ex.value?.trim()); + + if (newExamples.length === 0) { + setSnackbarMessage('Mindestens ein vollständiger Beispielsatz ist erforderlich.'); + setSnackbarSeverity("error"); + setSnackbarOpen(true); + return; + } + + for (const ex of newExamples) { if (!ex.sentence?.trim() || !ex.value?.trim()) { setSnackbarMessage('Alle Beispielsätze müssen vollständig sein.'); setSnackbarSeverity("error"); setSnackbarOpen(true); - return; } } - setIsSaving(true); try { - const spacyEntries = generateSpacyEntries(formData); + const spacyEntries = generateSpacyEntries({ ...formData, examples: newExamples }); // Für jeden einzelnen Beispielsatz: for (const entry of spacyEntries) { @@ -110,6 +123,10 @@ export function KPIForm({ mode, initialData, onSave, onCancel, loading = false, console.log("SpaCy-Eintrag gespeichert:", data); } + const allExamples = mode === 'edit' + ? [...originalExamples, ...newExamples] + : newExamples; + // Dann in die DB speichern await onSave({ name: formData.name!, @@ -117,11 +134,18 @@ export function KPIForm({ mode, initialData, onSave, onCancel, loading = false, type: formData.type || 'string', position: formData.position ?? 0, active: formData.active ?? true, - examples: formData.examples ?? [], - is_trained: false, + examples: allExamples, + is_trained: false, }); // Formular zurücksetzen: - setFormData(emptyKPI); + if (mode === 'add') { + setFormData(emptyKPI); + } else { + setFormData(prev => ({ + ...prev, + examples: [{ sentence: '', value: '' }] + })); + } setSnackbarMessage("Beispielsätze gespeichert. Jetzt auf -Neu trainieren- klicken oder weitere Kennzahlen hinzufügen."); @@ -292,6 +316,18 @@ export function KPIForm({ mode, initialData, onSave, onCancel, loading = false, + {/* Hinweistext wie viele Beispielsätzen vorhanden sind*/} + {mode === 'edit' && originalExamples.length > 0 && ( + + + Vorhandene Beispielsätze: {originalExamples.length} + + + Diese Kennzahl hat bereits {originalExamples.length} Beispielsätze. Neue Beispielsätze werden zu den vorhandenen hinzugefügt. + + + )} + {/* Hinweistext vor Beispielsätzen */} -- 2.43.0 From 810827e0bb3bd7e431196a17e8bab02f8b676c1e Mon Sep 17 00:00:00 2001 From: Jaronim Pracht Date: Tue, 1 Jul 2025 18:58:24 +0200 Subject: [PATCH 3/5] fix host url and prepare for deployment --- project/frontend/src/components/KPIForm.tsx | 887 +++++++++++--------- project/frontend/src/main.tsx | 8 +- project/frontend/src/socket.ts | 5 +- 3 files changed, 485 insertions(+), 415 deletions(-) diff --git a/project/frontend/src/components/KPIForm.tsx b/project/frontend/src/components/KPIForm.tsx index ff9ef86..4827a91 100644 --- a/project/frontend/src/components/KPIForm.tsx +++ b/project/frontend/src/components/KPIForm.tsx @@ -1,460 +1,527 @@ import { - Box, Typography, Button, Paper, TextField, FormControlLabel, - Checkbox, Select, MenuItem, FormControl, InputLabel, Divider, CircularProgress + Box, + Button, + Checkbox, + CircularProgress, + Divider, + FormControl, + FormControlLabel, + InputLabel, + MenuItem, + Paper, + Select, + TextField, + Typography, } from "@mui/material"; -import { useState, useEffect } from "react"; +import MuiAlert from "@mui/material/Alert"; +import Snackbar from "@mui/material/Snackbar"; +import { useEffect, useState } from "react"; import type { Kennzahl } from "../types/kpi"; import { typeDisplayMapping } from "../types/kpi"; -import Snackbar from "@mui/material/Snackbar"; -import MuiAlert from "@mui/material/Alert"; import { API_HOST } from "../util/api"; - interface KPIFormProps { - mode: 'add' | 'edit'; - initialData?: Kennzahl | null; - onSave: (data: Partial) => Promise; - onCancel: () => void; - loading?: boolean; - resetTrigger?: number; + mode: "add" | "edit"; + initialData?: Kennzahl | null; + onSave: (data: Partial) => Promise; + onCancel: () => void; + loading?: boolean; + resetTrigger?: number; } const emptyKPI: Partial = { - name: '', - mandatory: false, - type: 'string', - active: true, - examples: [{ sentence: '', value: '' }], + name: "", + mandatory: false, + type: "string", + active: true, + examples: [{ sentence: "", value: "" }], }; +export function KPIForm({ + mode, + initialData, + onSave, + onCancel, + loading = false, +}: KPIFormProps) { + const [formData, setFormData] = useState>(emptyKPI); + const [originalExamples, setOriginalExamples] = useState< + Array<{ sentence: string; value: string }> + >([]); + const [isSaving, setIsSaving] = useState(false); + const [snackbarOpen, setSnackbarOpen] = useState(false); + const [snackbarMessage, setSnackbarMessage] = useState(""); + const [snackbarSeverity, setSnackbarSeverity] = useState< + "success" | "error" | "info" + >("success"); -export function KPIForm({ mode, initialData, onSave, onCancel, loading = false, resetTrigger }: KPIFormProps) { - const [formData, setFormData] = useState>(emptyKPI); - const [originalExamples, setOriginalExamples] = useState>([]); - const [isSaving, setIsSaving] = useState(false); - const [snackbarOpen, setSnackbarOpen] = useState(false); - const [snackbarMessage, setSnackbarMessage] = useState(""); - const [snackbarSeverity, setSnackbarSeverity] = useState<'success' | 'error' | 'info'>("success"); + useEffect(() => { + if (mode === "edit" && initialData) { + setOriginalExamples(initialData.examples || []); + setFormData({ + ...initialData, + examples: [{ sentence: "", value: "" }], + }); + } else if (mode === "add") { + setOriginalExamples([]); + setFormData(emptyKPI); + } + }, [mode, initialData]); - - useEffect(() => { - if (mode === 'edit' && initialData) { - setOriginalExamples(initialData.examples || []); - setFormData({ - ...initialData, - examples: [{ sentence: '', value: '' }] - }); - } else if (mode === 'add') { - setOriginalExamples([]); - setFormData(emptyKPI); - } - }, [mode, initialData]); + const handleSave = async () => { + if (!formData.name?.trim()) { + setSnackbarMessage("Name ist erforderlich"); + setSnackbarSeverity("error"); + setSnackbarOpen(true); - useEffect(() => { - if (mode === 'add') { - setOriginalExamples([]); - setFormData(emptyKPI); - } - }, [resetTrigger]); + return; + } + if (!formData.examples || formData.examples.length === 0) { + setSnackbarMessage("Mindestens ein Beispielsatz ist erforderlich"); + setSnackbarSeverity("error"); + setSnackbarOpen(true); + return; + } + const newExamples = formData.examples.filter( + (ex) => ex.sentence?.trim() && ex.value?.trim(), + ); - const handleSave = async () => { - if (!formData.name?.trim()) { - setSnackbarMessage("Name ist erforderlich"); - setSnackbarSeverity("error"); - setSnackbarOpen(true); + if (newExamples.length === 0) { + setSnackbarMessage( + "Mindestens ein vollständiger Beispielsatz ist erforderlich.", + ); + setSnackbarSeverity("error"); + setSnackbarOpen(true); + return; + } - return; - } + for (const ex of newExamples) { + if (!ex.sentence?.trim() || !ex.value?.trim()) { + setSnackbarMessage("Alle Beispielsätze müssen vollständig sein."); + setSnackbarSeverity("error"); + setSnackbarOpen(true); + return; + } + } - if (!formData.examples || formData.examples.length === 0) { - setSnackbarMessage("Mindestens ein Beispielsatz ist erforderlich"); - setSnackbarSeverity("error"); - setSnackbarOpen(true); - return; - } + setIsSaving(true); + try { + const spacyEntries = generateSpacyEntries({ + ...formData, + examples: newExamples, + }); - const newExamples = formData.examples.filter(ex => ex.sentence?.trim() && ex.value?.trim()); + // Für jeden einzelnen Beispielsatz: + for (const entry of spacyEntries) { + // im localStorage speichern (zum Debuggen oder Vorschau) + const stored = localStorage.getItem("spacyData"); + const existingData = stored ? JSON.parse(stored) : []; + const updated = [...existingData, entry]; + localStorage.setItem("spacyData", JSON.stringify(updated)); - if (newExamples.length === 0) { - setSnackbarMessage('Mindestens ein vollständiger Beispielsatz ist erforderlich.'); - setSnackbarSeverity("error"); - setSnackbarOpen(true); - return; - } + // POST Request an das Flask-Backend + const response = await fetch( + `${API_HOST}/api/spacy/append-training-entry`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(entry), + }, + ); - for (const ex of newExamples) { - if (!ex.sentence?.trim() || !ex.value?.trim()) { - setSnackbarMessage('Alle Beispielsätze müssen vollständig sein.'); - setSnackbarSeverity("error"); - setSnackbarOpen(true); - return; - } - } + const data = await response.json(); - setIsSaving(true); - try { - const spacyEntries = generateSpacyEntries({ ...formData, examples: newExamples }); + if (!response.ok) { + throw new Error( + data.error || "Fehler beim Aufruf von append-training-entry", + ); + } - // Für jeden einzelnen Beispielsatz: - for (const entry of spacyEntries) { - // im localStorage speichern (zum Debuggen oder Vorschau) - const stored = localStorage.getItem("spacyData"); - const existingData = stored ? JSON.parse(stored) : []; - const updated = [...existingData, entry]; - localStorage.setItem("spacyData", JSON.stringify(updated)); + console.log("SpaCy-Eintrag gespeichert:", data); + } - // POST Request an das Flask-Backend - const response = await fetch(`${API_HOST}/api/spacy/append-training-entry/`, { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify(entry) - }); + const allExamples = + mode === "edit" ? [...originalExamples, ...newExamples] : newExamples; - const data = await response.json(); + // Dann in die DB speichern + await onSave({ + name: formData.name ?? "", + mandatory: formData.mandatory ?? false, + type: formData.type || "string", + position: formData.position ?? 0, + active: formData.active ?? true, + examples: allExamples, + is_trained: false, + }); + // Formular zurücksetzen: + if (mode === "add") { + setFormData(emptyKPI); + } else { + setFormData((prev) => ({ + ...prev, + examples: [{ sentence: "", value: "" }], + })); + } - if (!response.ok) { - throw new Error(data.error || "Fehler beim Aufruf von append-training-entry"); - } + setSnackbarMessage( + "Beispielsätze gespeichert. Jetzt auf -Neu trainieren- klicken oder weitere Kennzahlen hinzufügen.", + ); + setSnackbarSeverity("success"); + setSnackbarOpen(true); + } catch (e: any) { + // Prüfe auf 409-Fehler + if (e?.message?.includes("409") || e?.response?.status === 409) { + setSnackbarMessage( + "Diese Kennzahl existiert bereits. Sie können sie unter -Konfiguration- bearbeiten.", + ); + setSnackbarSeverity("info"); + setSnackbarOpen(true); + } else { + setSnackbarMessage(e.message || "Fehler beim Speichern."); + setSnackbarSeverity("error"); + setSnackbarOpen(true); + } + console.error(e); + } finally { + setIsSaving(false); + } + }; - console.log("SpaCy-Eintrag gespeichert:", data); - } + const handleCancel = () => { + setFormData(emptyKPI); + onCancel(); + }; - const allExamples = mode === 'edit' - ? [...originalExamples, ...newExamples] - : newExamples; + const updateField = (field: keyof Kennzahl, value: any) => { + setFormData((prev) => ({ ...prev, [field]: value })); + }; - // Dann in die DB speichern - await onSave({ - name: formData.name!, - mandatory: formData.mandatory ?? false, - type: formData.type || 'string', - position: formData.position ?? 0, - active: formData.active ?? true, - examples: allExamples, - is_trained: false, - }); - // Formular zurücksetzen: - if (mode === 'add') { - setFormData(emptyKPI); - } else { - setFormData(prev => ({ - ...prev, - examples: [{ sentence: '', value: '' }] - })); - } + const updateExample = ( + index: number, + field: "sentence" | "value", + value: string, + ) => { + const newExamples = [...(formData.examples || [])]; + newExamples[index][field] = value; + updateField("examples", newExamples); + }; + const addExample = () => { + const newExamples = [ + ...(formData.examples || []), + { sentence: "", value: "" }, + ]; + updateField("examples", newExamples); + }; - setSnackbarMessage("Beispielsätze gespeichert. Jetzt auf -Neu trainieren- klicken oder weitere Kennzahlen hinzufügen."); - setSnackbarSeverity("success"); - setSnackbarOpen(true); - } catch (e: any) { - // Prüfe auf 409-Fehler - if (e?.message?.includes("409") || e?.response?.status === 409) { - setSnackbarMessage("Diese Kennzahl existiert bereits. Sie können sie unter -Konfiguration- bearbeiten."); - setSnackbarSeverity("info"); - setSnackbarOpen(true); - } else { - setSnackbarMessage(e.message || "Fehler beim Speichern."); - setSnackbarSeverity("error"); - setSnackbarOpen(true); - } - console.error(e); - } finally { - setIsSaving(false); - } + const removeExample = (index: number) => { + const newExamples = [...(formData.examples || [])]; + newExamples.splice(index, 1); + updateField("examples", newExamples); + }; - }; + if (loading) { + return ( + + + + {mode === "edit" ? "Lade KPI Details..." : "Laden..."} + + + ); + } + return ( + <> + + + + Kennzahl + + updateField("name", e.target.value)} + sx={{ mb: 2 }} + required + error={!formData.name?.trim()} + helperText={!formData.name?.trim() ? "Name ist erforderlich" : ""} + /> + - const handleCancel = () => { - setFormData(emptyKPI); - onCancel(); - }; + - const updateField = (field: keyof Kennzahl, value: any) => { - setFormData(prev => ({ ...prev, [field]: value })); - }; + + + Format:{" "} + {typeDisplayMapping[ + formData.type as keyof typeof typeDisplayMapping + ] || formData.type} + + + Typ + + + - const updateExample = (index: number, field: 'sentence' | 'value', value: string) => { - const newExamples = [...(formData.examples || [])]; - newExamples[index][field] = value; - updateField('examples', newExamples); - }; + - const addExample = () => { - const newExamples = [...(formData.examples || []), { sentence: '', value: '' }]; - updateField('examples', newExamples); - }; + + updateField("active", e.target.checked)} + sx={{ + color: "#666666", + "&.Mui-checked": { + color: "#333333", + }, + "&:hover": { + backgroundColor: "rgba(102, 102, 102, 0.04)", + }, + }} + /> + } + label="Aktiv" + /> + + Die Kennzahl ist aktiv und wird angezeigt + + + + updateField("mandatory", e.target.checked)} + sx={{ + color: "#666666", + "&.Mui-checked": { + color: "#333333", + }, + "&:hover": { + backgroundColor: "rgba(102, 102, 102, 0.04)", + }, + }} + /> + } + label="Erforderlich" + /> + + Die Kennzahl erlaubt keine leeren Werte + + - const removeExample = (index: number) => { - const newExamples = [...(formData.examples || [])]; - newExamples.splice(index, 1); - updateField('examples', newExamples); - }; + + {/* Hinweistext wie viele Beispielsätzen vorhanden sind*/} + {mode === "edit" && originalExamples.length > 0 && ( + + + Vorhandene Beispielsätze: {originalExamples.length} + + + Diese Kennzahl hat bereits {originalExamples.length}{" "} + Beispielsätze. Neue Beispielsätze werden zu den vorhandenen + hinzugefügt. + + + )} - if (loading) { - return ( - - - - {mode === 'edit' ? 'Lade KPI Details...' : 'Laden...'} - - - ); - } + {/* Hinweistext vor Beispielsätzen */} + + + Hinweis zur Trainingsqualität + + + Damit das System neue Kennzahlen zuverlässig erkennen kann, + empfehlen wir mindestens 5 Beispielsätze zu + erstellen – je mehr, desto besser. + + + Wichtig: Neue Kennzahlen werden erst in + PDF-Dokumenten erkannt, wenn Sie den Button{" "} + "Neu trainieren" auf der Konfigurationsseite ausführen. + + + Tipp: Sie können jederzeit weitere Beispielsätze + hinzufügen oder vorhandene in der Kennzahlenverwaltung bearbeiten. + + - return ( - <> - - - - Kennzahl - - updateField('name', e.target.value)} - sx={{ mb: 2 }} - required - error={!formData.name?.trim()} - helperText={!formData.name?.trim() ? 'Name ist erforderlich' : ''} - /> - + + + Beispielsätze + + {(formData.examples || []).map((ex, idx) => ( + + updateExample(idx, "sentence", e.target.value)} + required + sx={{ mb: 1 }} + /> - + updateExample(idx, "value", e.target.value)} + required + /> + {(formData.examples?.length || 0) > 1 && ( + + )} + + ))} - - - Format: {typeDisplayMapping[formData.type as keyof typeof typeDisplayMapping] || formData.type} - - - Typ - - - + + - - - - updateField('active', e.target.checked)} - sx={{ - color: '#666666', - '&.Mui-checked': { - color: '#333333', - }, - '&:hover': { - backgroundColor: 'rgba(102, 102, 102, 0.04)', - } - }} - /> - } - label="Aktiv" - /> - - Die Kennzahl ist aktiv und wird angezeigt - - - - updateField('mandatory', e.target.checked)} - sx={{ - color: '#666666', - '&.Mui-checked': { - color: '#333333', - }, - '&:hover': { - backgroundColor: 'rgba(102, 102, 102, 0.04)', - } - }} - /> - } - label="Erforderlich" - /> - - Die Kennzahl erlaubt keine leeren Werte - - - - - - {/* Hinweistext wie viele Beispielsätzen vorhanden sind*/} - {mode === 'edit' && originalExamples.length > 0 && ( - - - Vorhandene Beispielsätze: {originalExamples.length} - - - Diese Kennzahl hat bereits {originalExamples.length} Beispielsätze. Neue Beispielsätze werden zu den vorhandenen hinzugefügt. - - - )} - - {/* Hinweistext vor Beispielsätzen */} - - - Hinweis zur Trainingsqualität - - - Damit das System neue Kennzahlen zuverlässig erkennen kann, empfehlen wir mindestens 5 Beispielsätze zu erstellen – je mehr, desto besser. - - - Wichtig: Neue Kennzahlen werden erst in PDF-Dokumenten erkannt, wenn Sie den Button "Neu trainieren" auf der Konfigurationsseite ausführen. - - - Tipp: Sie können jederzeit weitere Beispielsätze hinzufügen oder vorhandene in der Kennzahlenverwaltung bearbeiten. - - - - - - - Beispielsätze - - {(formData.examples || []).map((ex, idx) => ( - - updateExample(idx, 'sentence', e.target.value)} - required - sx={{ mb: 1 }} - /> - - updateExample(idx, 'value', e.target.value)} - required - /> - {(formData.examples?.length || 0) > 1 && ( - - )} - - ))} - - - - - - - - - - setSnackbarOpen(false)} - anchorOrigin={{ vertical: 'top', horizontal: 'center' }} - > - setSnackbarOpen(false)} - severity={snackbarSeverity} - sx={{ width: '100%', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} - > - {snackbarMessage} - - - - - - ); + + + + + + setSnackbarOpen(false)} + anchorOrigin={{ vertical: "top", horizontal: "center" }} + > + setSnackbarOpen(false)} + severity={snackbarSeverity} + sx={{ + width: "100%", + display: "flex", + justifyContent: "space-between", + alignItems: "center", + }} + > + {snackbarMessage} + + + + + ); } - function generateSpacyEntries(formData: Partial) { - const label = formData.name?.trim().toUpperCase() || ""; - return (formData.examples || []).map(({ sentence, value }) => { - const trimmedValue = value.trim(); - const start = sentence.indexOf(trimmedValue); - if (start === -1) { - throw new Error(`"${trimmedValue}" nicht gefunden in Satz: "${sentence}"`); - } - return { - text: sentence, - entities: [[start, start + trimmedValue.length, label]] - }; - }); + const label = formData.name?.trim().toUpperCase() || ""; + return (formData.examples || []).map(({ sentence, value }) => { + const trimmedValue = value.trim(); + const start = sentence.indexOf(trimmedValue); + if (start === -1) { + throw new Error( + `"${trimmedValue}" nicht gefunden in Satz: "${sentence}"`, + ); + } + return { + text: sentence, + entities: [[start, start + trimmedValue.length, label]], + }; + }); } - - - diff --git a/project/frontend/src/main.tsx b/project/frontend/src/main.tsx index bffa8aa..626676b 100644 --- a/project/frontend/src/main.tsx +++ b/project/frontend/src/main.tsx @@ -1,6 +1,6 @@ import CssBaseline from "@mui/material/CssBaseline"; -import { ThemeProvider, createTheme } from "@mui/material/styles"; -import { RouterProvider, createRouter } from "@tanstack/react-router"; +import { createTheme, ThemeProvider } from "@mui/material/styles"; +import { createRouter, RouterProvider } from "@tanstack/react-router"; import { StrictMode } from "react"; import ReactDOM from "react-dom/client"; import "react-pdf/dist/Page/TextLayer.css"; @@ -10,9 +10,8 @@ import "@fontsource/roboto/400.css"; import "@fontsource/roboto/500.css"; import "@fontsource/roboto/700.css"; -import * as TanStackQueryProvider from "./integrations/tanstack-query/root-provider.tsx"; - import { pdfjs } from "react-pdf"; +import * as TanStackQueryProvider from "./integrations/tanstack-query/root-provider.tsx"; // Import the generated route tree import { routeTree } from "./routeTree.gen"; @@ -27,6 +26,7 @@ const router = createRouter({ scrollRestoration: true, defaultStructuralSharing: true, defaultPreloadStaleTime: 0, + basepath: "/ff", }); // Register the router instance for type safety diff --git a/project/frontend/src/socket.ts b/project/frontend/src/socket.ts index 563ef27..cbba6b5 100644 --- a/project/frontend/src/socket.ts +++ b/project/frontend/src/socket.ts @@ -4,4 +4,7 @@ import { API_HOST } from "./util/api"; // "undefined" means the URL will be computed from the `window.location` object // const URL = process.env.NODE_ENV === 'production' ? undefined : 'http://localhost:4000'; -export const socket = io(`${API_HOST}`); +const url = new URL(API_HOST); +export const socket = io(`${url.host}`, { + path: `${url.pathname.replace(/^\/+/, "")}/socket.io`, +}); -- 2.43.0 From be165144ad383971dfa5fed86b2853b018a09211 Mon Sep 17 00:00:00 2001 From: s8613 Date: Tue, 1 Jul 2025 19:09:48 +0200 Subject: [PATCH 4/5] Fixed min. required example for edit. --- project/frontend/src/components/KPIForm.tsx | 120 +++++++++++--------- 1 file changed, 67 insertions(+), 53 deletions(-) diff --git a/project/frontend/src/components/KPIForm.tsx b/project/frontend/src/components/KPIForm.tsx index 4827a91..134c53e 100644 --- a/project/frontend/src/components/KPIForm.tsx +++ b/project/frontend/src/components/KPIForm.tsx @@ -73,75 +73,85 @@ export function KPIForm({ setSnackbarMessage("Name ist erforderlich"); setSnackbarSeverity("error"); setSnackbarOpen(true); - return; } - if (!formData.examples || formData.examples.length === 0) { - setSnackbarMessage("Mindestens ein Beispielsatz ist erforderlich"); - setSnackbarSeverity("error"); - setSnackbarOpen(true); - return; - } + if (mode === "add") { + if (!formData.examples || formData.examples.length === 0) { + setSnackbarMessage("Mindestens ein Beispielsatz ist erforderlich"); + setSnackbarSeverity("error"); + setSnackbarOpen(true); + return; + } - const newExamples = formData.examples.filter( - (ex) => ex.sentence?.trim() && ex.value?.trim(), - ); - - if (newExamples.length === 0) { - setSnackbarMessage( - "Mindestens ein vollständiger Beispielsatz ist erforderlich.", + const newExamples = formData.examples.filter( + (ex) => ex.sentence?.trim() && ex.value?.trim(), ); - setSnackbarSeverity("error"); - setSnackbarOpen(true); - return; - } - for (const ex of newExamples) { - if (!ex.sentence?.trim() || !ex.value?.trim()) { - setSnackbarMessage("Alle Beispielsätze müssen vollständig sein."); + if (newExamples.length === 0) { + setSnackbarMessage( + "Mindestens ein vollständiger Beispielsatz ist erforderlich.", + ); setSnackbarSeverity("error"); setSnackbarOpen(true); return; } } + const newExamples = (formData.examples || []).filter( + (ex) => ex.sentence?.trim() && ex.value?.trim(), + ); + + if (formData.examples && formData.examples.length > 0) { + for (const ex of formData.examples) { + if (!ex.sentence?.trim() && !ex.value?.trim()) continue; + if (!ex.sentence?.trim() || !ex.value?.trim()) { + setSnackbarMessage("Alle Beispielsätze müssen vollständig sein oder leer gelassen werden."); + setSnackbarSeverity("error"); + setSnackbarOpen(true); + return; + } + } + } + setIsSaving(true); try { - const spacyEntries = generateSpacyEntries({ - ...formData, - examples: newExamples, - }); + if (newExamples.length > 0) { + const spacyEntries = generateSpacyEntries({ + ...formData, + examples: newExamples, + }); - // Für jeden einzelnen Beispielsatz: - for (const entry of spacyEntries) { - // im localStorage speichern (zum Debuggen oder Vorschau) - const stored = localStorage.getItem("spacyData"); - const existingData = stored ? JSON.parse(stored) : []; - const updated = [...existingData, entry]; - localStorage.setItem("spacyData", JSON.stringify(updated)); + // Für jeden einzelnen Beispielsatz: + for (const entry of spacyEntries) { + // im localStorage speichern (zum Debuggen oder Vorschau) + const stored = localStorage.getItem("spacyData"); + const existingData = stored ? JSON.parse(stored) : []; + const updated = [...existingData, entry]; + localStorage.setItem("spacyData", JSON.stringify(updated)); - // POST Request an das Flask-Backend - const response = await fetch( - `${API_HOST}/api/spacy/append-training-entry`, - { - method: "POST", - headers: { - "Content-Type": "application/json", + // POST Request an das Flask-Backend + const response = await fetch( + `${API_HOST}/api/spacy/append-training-entry`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(entry), }, - body: JSON.stringify(entry), - }, - ); - - const data = await response.json(); - - if (!response.ok) { - throw new Error( - data.error || "Fehler beim Aufruf von append-training-entry", ); - } - console.log("SpaCy-Eintrag gespeichert:", data); + const data = await response.json(); + + if (!response.ok) { + throw new Error( + data.error || "Fehler beim Aufruf von append-training-entry", + ); + } + + console.log("SpaCy-Eintrag gespeichert:", data); + } } const allExamples = @@ -167,9 +177,13 @@ export function KPIForm({ })); } - setSnackbarMessage( - "Beispielsätze gespeichert. Jetzt auf -Neu trainieren- klicken oder weitere Kennzahlen hinzufügen.", - ); + const successMessage = newExamples.length > 0 + ? "Beispielsätze gespeichert. Jetzt auf -Neu trainieren- klicken oder weitere Kennzahlen hinzufügen." + : mode === "edit" + ? "Kennzahl erfolgreich aktualisiert." + : "Kennzahl erfolgreich erstellt."; + + setSnackbarMessage(successMessage); setSnackbarSeverity("success"); setSnackbarOpen(true); } catch (e: any) { -- 2.43.0 From ffd30aefee575987c179663ee9d5b7e3195d90aa Mon Sep 17 00:00:00 2001 From: s8613 Date: Tue, 1 Jul 2025 19:32:42 +0200 Subject: [PATCH 5/5] Fixed text and resetting form. --- project/frontend/src/components/KPIForm.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/project/frontend/src/components/KPIForm.tsx b/project/frontend/src/components/KPIForm.tsx index 134c53e..4f71a50 100644 --- a/project/frontend/src/components/KPIForm.tsx +++ b/project/frontend/src/components/KPIForm.tsx @@ -29,13 +29,13 @@ interface KPIFormProps { resetTrigger?: number; } -const emptyKPI: Partial = { +const createEmptyKPI = (): Partial => ({ name: "", mandatory: false, type: "string", active: true, examples: [{ sentence: "", value: "" }], -}; +}); export function KPIForm({ mode, @@ -44,7 +44,7 @@ export function KPIForm({ onCancel, loading = false, }: KPIFormProps) { - const [formData, setFormData] = useState>(emptyKPI); + const [formData, setFormData] = useState>(createEmptyKPI()); const [originalExamples, setOriginalExamples] = useState< Array<{ sentence: string; value: string }> >([]); @@ -64,7 +64,7 @@ export function KPIForm({ }); } else if (mode === "add") { setOriginalExamples([]); - setFormData(emptyKPI); + setFormData(createEmptyKPI()); } }, [mode, initialData]); @@ -167,9 +167,10 @@ export function KPIForm({ examples: allExamples, is_trained: false, }); + // Formular zurücksetzen: if (mode === "add") { - setFormData(emptyKPI); + setFormData(createEmptyKPI()); } else { setFormData((prev) => ({ ...prev, @@ -206,7 +207,7 @@ export function KPIForm({ }; const handleCancel = () => { - setFormData(emptyKPI); + setFormData(createEmptyKPI()); onCancel(); }; @@ -407,7 +408,7 @@ export function KPIForm({ Tipp: Sie können jederzeit weitere Beispielsätze - hinzufügen oder vorhandene in der Kennzahlenverwaltung bearbeiten. + hinzufügen. -- 2.43.0