Add Dockerfile for coordinator service and progress controller
Add progress tracking functionality to frontend and backend - Add progress controller endpoint to handle progress updates - Implement socket.io progress updates in UploadPage - Update import path for CircularProgressWithLabel componentpull/48/head
parent
1b06867d88
commit
d412d5741b
|
|
@ -0,0 +1,21 @@
|
||||||
|
# 1. Python-Image verwenden
|
||||||
|
FROM python:3.11-alpine
|
||||||
|
|
||||||
|
# 2. Arbeitsverzeichnis im Container setzen
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 3. production-style server mit gunicorn
|
||||||
|
RUN pip install gunicorn eventlet
|
||||||
|
|
||||||
|
# 4. requirements.txt kopieren und Pakete installieren
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
|
||||||
|
# 5. Quellcode kopieren (z.B. app.py)
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
CMD ["gunicorn", "--worker-class", "eventlet", "-w", "1", "--bind", "0.0.0.0:5000", "app:app"]
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
from controller.spacy_contoller import spacy_controller
|
from controller.spacy_contoller import spacy_controller
|
||||||
from controller.kpi_setting_controller import kpi_setting_controller
|
from controller.kpi_setting_controller import kpi_setting_controller
|
||||||
from controller.pitch_book_controller import pitch_book_controller
|
from controller.pitch_book_controller import pitch_book_controller
|
||||||
|
from controller.progress_controller import progress_controller
|
||||||
|
|
||||||
|
|
||||||
def register_routes(app):
|
def register_routes(app):
|
||||||
app.register_blueprint(kpi_setting_controller)
|
app.register_blueprint(kpi_setting_controller)
|
||||||
app.register_blueprint(pitch_book_controller)
|
app.register_blueprint(pitch_book_controller)
|
||||||
app.register_blueprint(spacy_controller)
|
app.register_blueprint(spacy_controller)
|
||||||
|
app.register_blueprint(progress_controller)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
from flask import Blueprint, request, jsonify
|
||||||
|
|
||||||
|
from controller.socketIO import socketio
|
||||||
|
progress_controller = Blueprint("progress", __name__, url_prefix="/api/progress")
|
||||||
|
|
||||||
|
|
||||||
|
@progress_controller.route("/", methods=["POST"])
|
||||||
|
def progress():
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
if 'id' not in data or 'progress' not in data:
|
||||||
|
return jsonify({"error": "Missing required fields. [id, progress]"}), 400
|
||||||
|
|
||||||
|
if not isinstance(data['progress'], (int, float)) or data['progress'] < 0 or data['progress'] >= 100:
|
||||||
|
return jsonify({"error": "Invalid progress value"}), 400
|
||||||
|
|
||||||
|
socketio.emit("progress", {"id": data["id"], "progress": data["progress"]})
|
||||||
|
# Process the data and return a response
|
||||||
|
return jsonify({"message": "Progress updated"})
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
},
|
},
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"indentStyle": "space"
|
"indentStyle": "tab"
|
||||||
},
|
},
|
||||||
"organizeImports": {
|
"organizeImports": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -4,175 +4,175 @@ import { useNavigate } from "@tanstack/react-router";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import FileUpload from "react-material-file-upload";
|
import FileUpload from "react-material-file-upload";
|
||||||
import { socket } from "../socket";
|
import { socket } from "../socket";
|
||||||
import { CircularProgressWithLabel } from "./circularProgressWithLabel";
|
import { CircularProgressWithLabel } from "./CircularProgressWithLabel";
|
||||||
|
|
||||||
const PROGRESS = false;
|
const PROGRESS = false;
|
||||||
|
|
||||||
export default function UploadPage() {
|
export default function UploadPage() {
|
||||||
const [files, setFiles] = useState<File[]>([]);
|
const [files, setFiles] = useState<File[]>([]);
|
||||||
const [pageId, setPageId] = useState<string | null>(null);
|
const [pageId, setPageId] = useState<string | null>(null);
|
||||||
const [loadingState, setLoadingState] = useState<number | null>(null);
|
const [loadingState, setLoadingState] = useState<number | null>(null);
|
||||||
const fileTypes = ["pdf"];
|
const fileTypes = ["pdf"];
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const uploadFile = useCallback(async () => {
|
const uploadFile = useCallback(async () => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", files[0]);
|
formData.append("file", files[0]);
|
||||||
const response = await fetch("http://localhost:5050/api/pitch_book", {
|
const response = await fetch("http://localhost:5050/api/pitch_book", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
console.log("File uploaded successfully");
|
console.log("File uploaded successfully");
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log(data);
|
console.log(data);
|
||||||
setPageId(data.id);
|
setPageId(data.id);
|
||||||
setLoadingState(0);
|
setLoadingState(0);
|
||||||
|
|
||||||
!PROGRESS &&
|
!PROGRESS &&
|
||||||
navigate({
|
navigate({
|
||||||
to: "/extractedResult/$pitchBook",
|
to: "/extractedResult/$pitchBook",
|
||||||
params: { pitchBook: data.id },
|
params: { pitchBook: data.id },
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to upload file");
|
console.error("Failed to upload file");
|
||||||
}
|
}
|
||||||
}, [files, navigate]);
|
}, [files, navigate]);
|
||||||
|
|
||||||
const onConnection = useCallback(() => {
|
const onConnection = useCallback(() => {
|
||||||
console.log("connected");
|
console.log("connected");
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onProgress = useCallback(
|
const onProgress = useCallback(
|
||||||
(progress: { id: number; progress: number }) => {
|
(progress: { id: number; progress: number }) => {
|
||||||
console.log("Progress:", progress);
|
console.log("Progress:", progress);
|
||||||
console.log(pageId);
|
console.log(pageId);
|
||||||
if (Number(pageId) === progress.id) {
|
if (Number(pageId) === progress.id) {
|
||||||
setLoadingState(progress.progress);
|
setLoadingState(progress.progress);
|
||||||
|
|
||||||
if (progress.progress === 100) {
|
if (progress.progress === 100) {
|
||||||
navigate({
|
navigate({
|
||||||
to: "/extractedResult/$pitchBook",
|
to: "/extractedResult/$pitchBook",
|
||||||
params: { pitchBook: progress.id.toString() },
|
params: { pitchBook: progress.id.toString() },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[pageId, navigate],
|
[pageId, navigate],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
socket.on("connect", onConnection);
|
socket.on("connect", onConnection);
|
||||||
socket.on("progress", onProgress);
|
socket.on("progress", onProgress);
|
||||||
return () => {
|
return () => {
|
||||||
socket.off("connect", onConnection);
|
socket.off("connect", onConnection);
|
||||||
socket.off("progress", onProgress);
|
socket.off("progress", onProgress);
|
||||||
};
|
};
|
||||||
}, [onConnection, onProgress]);
|
}, [onConnection, onProgress]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{PROGRESS && (
|
{PROGRESS && (
|
||||||
<Backdrop
|
<Backdrop
|
||||||
sx={(theme) => ({ color: "#fff", zIndex: theme.zIndex.drawer + 1 })}
|
sx={(theme) => ({ color: "#fff", zIndex: theme.zIndex.drawer + 1 })}
|
||||||
open={pageId !== null && loadingState !== null}
|
open={pageId !== null && loadingState !== null}
|
||||||
>
|
>
|
||||||
<CircularProgressWithLabel
|
<CircularProgressWithLabel
|
||||||
color="inherit"
|
color="inherit"
|
||||||
value={loadingState || 0}
|
value={loadingState || 0}
|
||||||
size={60}
|
size={60}
|
||||||
/>
|
/>
|
||||||
</Backdrop>
|
</Backdrop>
|
||||||
)}
|
)}
|
||||||
<Box
|
<Box
|
||||||
display="flex"
|
display="flex"
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
height="100vh"
|
height="100vh"
|
||||||
bgcolor="white"
|
bgcolor="white"
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
width="100%"
|
width="100%"
|
||||||
maxWidth="1300px"
|
maxWidth="1300px"
|
||||||
display="flex"
|
display="flex"
|
||||||
justifyContent="flex-end"
|
justifyContent="flex-end"
|
||||||
px={2}
|
px={2}
|
||||||
>
|
>
|
||||||
<IconButton onClick={() => navigate({ to: "/config" })}>
|
<IconButton onClick={() => navigate({ to: "/config" })}>
|
||||||
<SettingsIcon fontSize="large" />
|
<SettingsIcon fontSize="large" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
<Paper
|
<Paper
|
||||||
elevation={3}
|
elevation={3}
|
||||||
sx={{
|
sx={{
|
||||||
width: 900,
|
width: 900,
|
||||||
height: 500,
|
height: 500,
|
||||||
backgroundColor: "#eeeeee",
|
backgroundColor: "#eeeeee",
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
height: "100%",
|
height: "100%",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
maxWidth: "100%",
|
maxWidth: "100%",
|
||||||
margin: "0px",
|
margin: "0px",
|
||||||
padding: "0px",
|
padding: "0px",
|
||||||
"& .MuiBox-root": {
|
"& .MuiBox-root": {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
border: "none",
|
border: "none",
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FileUpload
|
<FileUpload
|
||||||
value={files}
|
value={files}
|
||||||
onChange={setFiles}
|
onChange={setFiles}
|
||||||
accept={`.${fileTypes.join(", .")}`}
|
accept={`.${fileTypes.join(", .")}`}
|
||||||
title="Hier Dokument hinziehen"
|
title="Hier Dokument hinziehen"
|
||||||
buttonText="Datei auswählen"
|
buttonText="Datei auswählen"
|
||||||
sx={{
|
sx={{
|
||||||
height: "100%",
|
height: "100%",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
padding: "0px",
|
padding: "0px",
|
||||||
"& svg": {
|
"& svg": {
|
||||||
color: "#9e9e9e",
|
color: "#9e9e9e",
|
||||||
},
|
},
|
||||||
"& .MuiOutlinedInput-notchedOutline": {
|
"& .MuiOutlinedInput-notchedOutline": {
|
||||||
border: "none",
|
border: "none",
|
||||||
},
|
},
|
||||||
"& .MuiButton-root": {
|
"& .MuiButton-root": {
|
||||||
backgroundColor: "#9e9e9e",
|
backgroundColor: "#9e9e9e",
|
||||||
},
|
},
|
||||||
"& .MuiTypography-root": {
|
"& .MuiTypography-root": {
|
||||||
fontSize: "1.25rem",
|
fontSize: "1.25rem",
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
marginBottom: 1,
|
marginBottom: 1,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
sx={{
|
sx={{
|
||||||
mt: 4,
|
mt: 4,
|
||||||
backgroundColor: "#383838",
|
backgroundColor: "#383838",
|
||||||
}}
|
}}
|
||||||
disabled={files.length === 0}
|
disabled={files.length === 0}
|
||||||
onClick={uploadFile}
|
onClick={uploadFile}
|
||||||
>
|
>
|
||||||
Kennzahlen extrahieren
|
Kennzahlen extrahieren
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,88 +7,87 @@ import ArrowCircleRightIcon from "@mui/icons-material/ArrowCircleRight";
|
||||||
import { Box, IconButton } from "@mui/material";
|
import { Box, IconButton } from "@mui/material";
|
||||||
|
|
||||||
interface PDFViewerProps {
|
interface PDFViewerProps {
|
||||||
pitchBookId: string;
|
pitchBookId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PDFViewer({ pitchBookId }: PDFViewerProps) {
|
export default function PDFViewer({ pitchBookId }: PDFViewerProps) {
|
||||||
const [numPages, setNumPages] = useState<number | null>(null);
|
const [numPages, setNumPages] = useState<number | null>(null);
|
||||||
const [pageNumber, setPageNumber] = useState(1);
|
const [pageNumber, setPageNumber] = useState(1);
|
||||||
const [containerWidth, setContainerWidth] = useState<number | null>(null);
|
const [containerWidth, setContainerWidth] = useState<number | null>(null);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
|
const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
|
||||||
setNumPages(numPages);
|
setNumPages(numPages);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateWidth = () => {
|
const updateWidth = () => {
|
||||||
if (containerRef.current) {
|
if (containerRef.current) {
|
||||||
setContainerWidth(containerRef.current.offsetWidth);
|
setContainerWidth(containerRef.current.offsetWidth);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateWidth();
|
updateWidth();
|
||||||
window.addEventListener("resize", updateWidth);
|
window.addEventListener("resize", updateWidth);
|
||||||
return () => window.removeEventListener("resize", updateWidth);
|
return () => window.removeEventListener("resize", updateWidth);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
display="flex"
|
display="flex"
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
p={2}
|
p={2}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
maxHeight: "90vh",
|
maxHeight: "90vh",
|
||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Document
|
<Document
|
||||||
file={`http://localhost:5050/api/pitch_book/${pitchBookId}/download`}
|
file={`http://localhost:5050/api/pitch_book/${pitchBookId}/download`}
|
||||||
onLoadSuccess={onDocumentLoadSuccess}
|
onLoadSuccess={onDocumentLoadSuccess}
|
||||||
onLoadError={(error) =>
|
onLoadError={(error) =>
|
||||||
console.error("Es gab ein Fehler beim Laden des PDFs:", error)
|
console.error("Es gab ein Fehler beim Laden des PDFs:", error)
|
||||||
}
|
}
|
||||||
onSourceError={(error) => console.error("Ungültige PDF:", error)}
|
onSourceError={(error) => console.error("Ungültige PDF:", error)}
|
||||||
>
|
>
|
||||||
{containerWidth && (
|
{containerWidth && (
|
||||||
<Page pageNumber={pageNumber} width={containerWidth * 0.8} />
|
<Page pageNumber={pageNumber} width={containerWidth * 0.8} />
|
||||||
)}
|
)}
|
||||||
</Document>
|
</Document>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
mt={2}
|
mt={2}
|
||||||
display="flex"
|
display="flex"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
gap={1}
|
gap={1}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={pageNumber <= 1}
|
disabled={pageNumber <= 1}
|
||||||
onClick={() => setPageNumber((p) => p - 1)}
|
onClick={() => setPageNumber((p) => p - 1)}
|
||||||
>
|
>
|
||||||
<ArrowCircleLeftIcon fontSize="large" />
|
<ArrowCircleLeftIcon fontSize="large" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<span>
|
<span>
|
||||||
{pageNumber} / {numPages}
|
{pageNumber} / {numPages}
|
||||||
</span>
|
</span>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={pageNumber >= (numPages || 1)}
|
disabled={pageNumber >= (numPages || 1)}
|
||||||
onClick={() => setPageNumber((p) => p + 1)}
|
onClick={() => setPageNumber((p) => p + 1)}
|
||||||
>
|
>
|
||||||
<ArrowCircleRightIcon fontSize="large" />
|
<ArrowCircleRightIcon fontSize="large" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue