diff --git a/project/backend/coordinator/app.py b/project/backend/coordinator/app.py
index 4094652..d634965 100644
--- a/project/backend/coordinator/app.py
+++ b/project/backend/coordinator/app.py
@@ -1,10 +1,14 @@
from flask import Flask
+from flask_cors import CORS
import os
from dotenv import load_dotenv
from controller import register_routes
from model.database import init_db
+from controller.socketIO import socketio
app = Flask(__name__)
+CORS(app)
+socketio.init_app(app)
load_dotenv()
DATABASE_URL = os.getenv("DATABASE_URL")
@@ -25,4 +29,4 @@ def health_check():
# für Docker wichtig: host='0.0.0.0'
if __name__ == "__main__":
- app.run(debug=True, host="0.0.0.0")
+ socketio.run(app,debug=True, host="0.0.0.0", port=5050)
diff --git a/project/backend/coordinator/controller/pitch_book_controller.py b/project/backend/coordinator/controller/pitch_book_controller.py
index afa6042..450b546 100644
--- a/project/backend/coordinator/controller/pitch_book_controller.py
+++ b/project/backend/coordinator/controller/pitch_book_controller.py
@@ -4,6 +4,7 @@ from model.pitch_book_model import PitchBookModel
from io import BytesIO
from werkzeug.utils import secure_filename
import puremagic
+from controller.socketIO import socketio
pitch_book_controller = Blueprint("pitch_books", __name__, url_prefix="/api/pitch_book")
@@ -54,6 +55,7 @@ def upload_file():
db.session.add(new_file)
db.session.commit()
+ socketio.emit("progress", {"id": new_file.id, "progress": 0})
return jsonify(new_file.to_dict()), 201
except Exception as e:
print(e)
@@ -81,6 +83,7 @@ def update_file(id):
print(e)
if "kpi" in request.form:
+ socketio.emit("progress", {"id": id, "progress": 100})
file.kpi = request.form.get("kpi")
db.session.commit()
diff --git a/project/backend/coordinator/controller/socketIO.py b/project/backend/coordinator/controller/socketIO.py
new file mode 100644
index 0000000..2a82575
--- /dev/null
+++ b/project/backend/coordinator/controller/socketIO.py
@@ -0,0 +1,3 @@
+from flask_socketio import SocketIO
+
+socketio = SocketIO(cors_allowed_origins="*")
diff --git a/project/backend/coordinator/requirements.txt b/project/backend/coordinator/requirements.txt
index 7012057..963f260 100644
--- a/project/backend/coordinator/requirements.txt
+++ b/project/backend/coordinator/requirements.txt
@@ -1,3 +1,4 @@
+bidict==0.23.1
black==25.1.0
blinker==1.9.0
cfgv==3.4.0
@@ -6,8 +7,11 @@ distlib==0.3.9
filelock==3.18.0
flake8==7.2.0
Flask==3.1.1
+flask-cors==6.0.0
+Flask-SocketIO==5.5.1
Flask-SQLAlchemy==3.1.1
greenlet==3.2.2
+h11==0.16.0
identify==2.6.12
itsdangerous==2.2.0
Jinja2==3.1.6
@@ -24,8 +28,12 @@ puremagic==1.29
pycodestyle==2.13.0
pyflakes==3.3.2
python-dotenv==1.1.0
+python-engineio==4.12.1
+python-socketio==5.13.0
PyYAML==6.0.2
+simple-websocket==1.1.0
SQLAlchemy==2.0.41
typing_extensions==4.13.2
virtualenv==20.31.2
Werkzeug==3.1.3
+wsproto==1.2.0
diff --git a/project/docker-compose.yml b/project/docker-compose.yml
index e4877ba..be29228 100644
--- a/project/docker-compose.yml
+++ b/project/docker-compose.yml
@@ -30,7 +30,7 @@ services:
timeout: 5s
retries: 5
ports:
- - 5000:5000
+ - 5050:5000
spacy:
build:
diff --git a/project/frontend/bun.lockb b/project/frontend/bun.lockb
index 68a7bd3..676011c 100755
Binary files a/project/frontend/bun.lockb and b/project/frontend/bun.lockb differ
diff --git a/project/frontend/package.json b/project/frontend/package.json
index 87b1e9c..c3b04dd 100644
--- a/project/frontend/package.json
+++ b/project/frontend/package.json
@@ -26,7 +26,8 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-material-file-upload": "^0.0.4",
- "react-pdf": "^9.2.1"
+ "react-pdf": "^9.2.1",
+ "socket.io-client": "^4.8.1"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
diff --git a/project/frontend/src/components/CircularProgressWithLabel.tsx b/project/frontend/src/components/CircularProgressWithLabel.tsx
new file mode 100644
index 0000000..c14698a
--- /dev/null
+++ b/project/frontend/src/components/CircularProgressWithLabel.tsx
@@ -0,0 +1,33 @@
+import Box from "@mui/material/Box";
+import CircularProgress, {
+ type CircularProgressProps,
+} from "@mui/material/CircularProgress";
+import Typography from "@mui/material/Typography";
+
+export function CircularProgressWithLabel(
+ props: CircularProgressProps & { value: number },
+) {
+ return (
+
+
+
+ {`${Math.round(props.value)}%`}
+
+
+ );
+}
diff --git a/project/frontend/src/components/UploadPage.tsx b/project/frontend/src/components/UploadPage.tsx
index 6916b27..c4a57d0 100644
--- a/project/frontend/src/components/UploadPage.tsx
+++ b/project/frontend/src/components/UploadPage.tsx
@@ -1,100 +1,178 @@
-import { useState } from 'react'
-import FileUpload from 'react-material-file-upload'
-import {Box, Button, IconButton, Paper} from '@mui/material'
-import { useNavigate } from '@tanstack/react-router'
-import SettingsIcon from '@mui/icons-material/Settings';
+import SettingsIcon from "@mui/icons-material/Settings";
+import { Backdrop, Box, Button, IconButton, Paper } from "@mui/material";
+import { useNavigate } from "@tanstack/react-router";
+import { useCallback, useEffect, useState } from "react";
+import FileUpload from "react-material-file-upload";
+import { socket } from "../socket";
+import { CircularProgressWithLabel } from "./circularProgressWithLabel";
+
+const PROGRESS = false;
export default function UploadPage() {
- const [files, setFiles] = useState([])
- const fileTypes = ["pdf"];
- const navigate = useNavigate()
+ const [files, setFiles] = useState([]);
+ const [pageId, setPageId] = useState(null);
+ const [loadingState, setLoadingState] = useState(null);
+ const fileTypes = ["pdf"];
+ const navigate = useNavigate();
- return (
- {
+ const formData = new FormData();
+ formData.append("file", files[0]);
+ const response = await fetch("http://localhost:5050/api/pitch_book", {
+ method: "POST",
+ body: formData,
+ });
+
+ if (response.ok) {
+ console.log("File uploaded successfully");
+ const data = await response.json();
+ console.log(data);
+ setPageId(data.id);
+ setLoadingState(0);
+
+ !PROGRESS &&
+ navigate({
+ to: "/extractedResult/$pitchBook",
+ params: { pitchBook: data.id },
+ });
+ } else {
+ console.error("Failed to upload file");
+ }
+ }, [files, navigate]);
+
+ const onConnection = useCallback(() => {
+ console.log("connected");
+ }, []);
+
+ const onProgress = useCallback(
+ (progress: { id: number; progress: number }) => {
+ console.log("Progress:", progress);
+ console.log(pageId);
+ if (Number(pageId) === progress.id) {
+ setLoadingState(progress.progress);
+
+ if (progress.progress === 100) {
+ navigate({
+ to: "/extractedResult/$pitchBook",
+ params: { pitchBook: progress.id.toString() },
+ });
+ }
+ }
+ },
+ [pageId, navigate],
+ );
+
+ useEffect(() => {
+ socket.on("connect", onConnection);
+ socket.on("progress", onProgress);
+ return () => {
+ socket.off("connect", onConnection);
+ socket.off("progress", onProgress);
+ };
+ }, [onConnection, onProgress]);
+
+ return (
+ <>
+ {PROGRESS && (
+ ({ color: "#fff", zIndex: theme.zIndex.drawer + 1 })}
+ open={pageId !== null && loadingState !== null}
>
-
- navigate({ to: '/config' })}>
-
-
-
-
-
-
-
-
-
+
+
+ )}
+
+
+ navigate({ to: "/config" })}>
+
+
- )
-}
\ No newline at end of file
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/project/frontend/src/components/pdfViewer.tsx b/project/frontend/src/components/pdfViewer.tsx
index 2e8f7d1..5bb6b96 100644
--- a/project/frontend/src/components/pdfViewer.tsx
+++ b/project/frontend/src/components/pdfViewer.tsx
@@ -1,91 +1,99 @@
+import { useEffect, useRef, useState } from "react";
import { Document, Page, pdfjs } from "react-pdf";
-import { useState, useRef, useEffect } from 'react';
-import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
-import 'react-pdf/dist/esm/Page/TextLayer.css';
-import { Box, IconButton } from '@mui/material';
-import ArrowCircleLeftIcon from '@mui/icons-material/ArrowCircleLeft';
-import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight';
-import testPDF from '/example.pdf';
+import "react-pdf/dist/esm/Page/AnnotationLayer.css";
+import "react-pdf/dist/esm/Page/TextLayer.css";
+import ArrowCircleLeftIcon from "@mui/icons-material/ArrowCircleLeft";
+import ArrowCircleRightIcon from "@mui/icons-material/ArrowCircleRight";
+import { Box, IconButton } from "@mui/material";
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
- "pdfjs-dist/build/pdf.worker.min.mjs",
- import.meta.url,
+ "pdfjs-dist/build/pdf.worker.min.mjs",
+ import.meta.url,
).toString();
-export default function PDFViewer() {
- const [numPages, setNumPages] = useState(null);
- const [pageNumber, setPageNumber] = useState(1);
- const [containerWidth, setContainerWidth] = useState(null);
+interface PDFViewerProps {
+ pitchBookId: string;
+}
- const containerRef = useRef(null);
+export default function PDFViewer({ pitchBookId }: PDFViewerProps) {
+ const [numPages, setNumPages] = useState(null);
+ const [pageNumber, setPageNumber] = useState(1);
+ const [containerWidth, setContainerWidth] = useState(null);
+ const containerRef = useRef(null);
- const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
- setNumPages(numPages);
+ const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
+ setNumPages(numPages);
+ };
+
+ useEffect(() => {
+ const updateWidth = () => {
+ if (containerRef.current) {
+ setContainerWidth(containerRef.current.offsetWidth);
+ }
};
- useEffect(() => {
- const updateWidth = () => {
- if (containerRef.current) {
- setContainerWidth(containerRef.current.offsetWidth);
- }
- };
+ updateWidth();
+ window.addEventListener("resize", updateWidth);
+ return () => window.removeEventListener("resize", updateWidth);
+ }, []);
- updateWidth();
- window.addEventListener('resize', updateWidth);
- return () => window.removeEventListener('resize', updateWidth);
- }, []);
-
- return (
-
+
+
+ console.error("Es gab ein Fehler beim Laden des PDFs:", error)
+ }
+ onSourceError={(error) => console.error("Ungültige PDF:", error)}
>
-
- console.error('Es gab ein Fehler beim Laden des PDFs:', error)}
- onSourceError={(error) => console.error('Ungültige PDF:', error)}>
- {containerWidth && (
-
- )}
-
-
-
- setPageNumber(p => p - 1)}>
-
-
- {pageNumber} / {numPages}
- = (numPages || 1)}
- onClick={() => setPageNumber(p => p + 1)}
- >
-
-
-
-
- );
-}
\ No newline at end of file
+ {containerWidth && (
+
+ )}
+
+
+
+ setPageNumber((p) => p - 1)}
+ >
+
+
+
+ {pageNumber} / {numPages}
+
+ = (numPages || 1)}
+ onClick={() => setPageNumber((p) => p + 1)}
+ >
+
+
+
+
+ );
+}
diff --git a/project/frontend/src/routeTree.gen.ts b/project/frontend/src/routeTree.gen.ts
index cade514..b3c2de7 100644
--- a/project/frontend/src/routeTree.gen.ts
+++ b/project/frontend/src/routeTree.gen.ts
@@ -11,18 +11,12 @@
// Import Routes
import { Route as rootRoute } from './routes/__root'
-import { Route as ExtractedResultImport } from './routes/extractedResult'
import { Route as ConfigImport } from './routes/config'
import { Route as IndexImport } from './routes/index'
+import { Route as ExtractedResultPitchBookImport } from './routes/extractedResult.$pitchBook'
// Create/Update Routes
-const ExtractedResultRoute = ExtractedResultImport.update({
- id: '/extractedResult',
- path: '/extractedResult',
- getParentRoute: () => rootRoute,
-} as any)
-
const ConfigRoute = ConfigImport.update({
id: '/config',
path: '/config',
@@ -35,6 +29,12 @@ const IndexRoute = IndexImport.update({
getParentRoute: () => rootRoute,
} as any)
+const ExtractedResultPitchBookRoute = ExtractedResultPitchBookImport.update({
+ id: '/extractedResult/$pitchBook',
+ path: '/extractedResult/$pitchBook',
+ getParentRoute: () => rootRoute,
+} as any)
+
// Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' {
@@ -53,11 +53,11 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ConfigImport
parentRoute: typeof rootRoute
}
- '/extractedResult': {
- id: '/extractedResult'
- path: '/extractedResult'
- fullPath: '/extractedResult'
- preLoaderRoute: typeof ExtractedResultImport
+ '/extractedResult/$pitchBook': {
+ id: '/extractedResult/$pitchBook'
+ path: '/extractedResult/$pitchBook'
+ fullPath: '/extractedResult/$pitchBook'
+ preLoaderRoute: typeof ExtractedResultPitchBookImport
parentRoute: typeof rootRoute
}
}
@@ -68,41 +68,41 @@ declare module '@tanstack/react-router' {
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/config': typeof ConfigRoute
- '/extractedResult': typeof ExtractedResultRoute
+ '/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/config': typeof ConfigRoute
- '/extractedResult': typeof ExtractedResultRoute
+ '/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute
}
export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexRoute
'/config': typeof ConfigRoute
- '/extractedResult': typeof ExtractedResultRoute
+ '/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
- fullPaths: '/' | '/config' | '/extractedResult'
+ fullPaths: '/' | '/config' | '/extractedResult/$pitchBook'
fileRoutesByTo: FileRoutesByTo
- to: '/' | '/config' | '/extractedResult'
- id: '__root__' | '/' | '/config' | '/extractedResult'
+ to: '/' | '/config' | '/extractedResult/$pitchBook'
+ id: '__root__' | '/' | '/config' | '/extractedResult/$pitchBook'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
ConfigRoute: typeof ConfigRoute
- ExtractedResultRoute: typeof ExtractedResultRoute
+ ExtractedResultPitchBookRoute: typeof ExtractedResultPitchBookRoute
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
ConfigRoute: ConfigRoute,
- ExtractedResultRoute: ExtractedResultRoute,
+ ExtractedResultPitchBookRoute: ExtractedResultPitchBookRoute,
}
export const routeTree = rootRoute
@@ -117,7 +117,7 @@ export const routeTree = rootRoute
"children": [
"/",
"/config",
- "/extractedResult"
+ "/extractedResult/$pitchBook"
]
},
"/": {
@@ -126,8 +126,8 @@ export const routeTree = rootRoute
"/config": {
"filePath": "config.tsx"
},
- "/extractedResult": {
- "filePath": "extractedResult.tsx"
+ "/extractedResult/$pitchBook": {
+ "filePath": "extractedResult.$pitchBook.tsx"
}
}
}
diff --git a/project/frontend/src/routes/extractedResult.$pitchBook.tsx b/project/frontend/src/routes/extractedResult.$pitchBook.tsx
new file mode 100644
index 0000000..5f5fe37
--- /dev/null
+++ b/project/frontend/src/routes/extractedResult.$pitchBook.tsx
@@ -0,0 +1,100 @@
+import ContentPasteIcon from "@mui/icons-material/ContentPaste";
+import { Box, Button, Paper, Typography } from "@mui/material";
+import { createFileRoute, useNavigate } from "@tanstack/react-router";
+import PDFViewer from "../components/pdfViewer";
+
+export const Route = createFileRoute("/extractedResult/$pitchBook")({
+ component: ExtractedResultsPage,
+});
+
+function ExtractedResultsPage() {
+ const { pitchBook } = Route.useParams();
+ const navigate = useNavigate();
+ const status: "green" | "yellow" | "red" = "red";
+
+ const statusColor = {
+ red: "#f43131",
+ yellow: "#f6ed48",
+ green: "#3fd942",
+ }[status];
+
+ return (
+
+
+
+
+ Kennzahlen extrahiert aus:
+ FONDSNAME: TODO
+
+
+
+
+ To-do: Table hierhin
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/project/frontend/src/routes/extractedResult.tsx b/project/frontend/src/routes/extractedResult.tsx
deleted file mode 100644
index 15ce5b8..0000000
--- a/project/frontend/src/routes/extractedResult.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import { Box, Paper, Typography, Button } from '@mui/material';
-import {createFileRoute, useNavigate} from '@tanstack/react-router';
-import PDFViewer from '../components/pdfViewer';
-import ContentPasteIcon from '@mui/icons-material/ContentPaste';
-
-export const Route = createFileRoute('/extractedResult')({
- component: ExtractedResultsPage,
-});
-
-function ExtractedResultsPage() {
- const navigate = useNavigate();
- const status: 'green' | 'yellow' | 'red' = 'red';
-
- const statusColor = {
- red: '#f43131',
- yellow: '#f6ed48',
- green: '#3fd942',
- }[status];
-
- return (
-
-
-
-
- Kennzahlen extrahiert aus:
FONDSNAME: TODO
-
-
-
-
- To-do: Table hierhin
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/project/frontend/src/socket.ts b/project/frontend/src/socket.ts
new file mode 100644
index 0000000..763c173
--- /dev/null
+++ b/project/frontend/src/socket.ts
@@ -0,0 +1,6 @@
+import { io } from "socket.io-client";
+
+// "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("http://localhost:5050");