From 9aa6c8be875ebea1e09a94e05c9767d28c4779e3 Mon Sep 17 00:00:00 2001 From: Jaronim Pracht Date: Mon, 2 Jun 2025 15:03:39 +0200 Subject: [PATCH 1/3] add progress and file-upload to frontend --- project/backend/coordinator/app.py | 6 +- .../controller/pitch_book_controller.py | 3 + .../coordinator/controller/socketIO.py | 3 + project/backend/coordinator/requirements.txt | 8 + project/docker-compose.yml | 2 +- project/frontend/bun.lockb | Bin 143704 -> 146934 bytes project/frontend/package.json | 3 +- .../components/CircularProgressWithLabel.tsx | 33 +++ .../frontend/src/components/UploadPage.tsx | 268 +++++++++++------- project/frontend/src/components/pdfViewer.tsx | 168 +++++------ project/frontend/src/routeTree.gen.ts | 46 +-- .../src/routes/extractedResult.$pitchBook.tsx | 100 +++++++ .../frontend/src/routes/extractedResult.tsx | 101 ------- project/frontend/src/socket.ts | 6 + 14 files changed, 445 insertions(+), 302 deletions(-) create mode 100644 project/backend/coordinator/controller/socketIO.py create mode 100644 project/frontend/src/components/CircularProgressWithLabel.tsx create mode 100644 project/frontend/src/routes/extractedResult.$pitchBook.tsx delete mode 100644 project/frontend/src/routes/extractedResult.tsx create mode 100644 project/frontend/src/socket.ts 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 68a7bd3706c95b9dcddf3c3e4822bba061c00e44..676011c22448c88f859b20853066f9b21747ae99 100755 GIT binary patch delta 27822 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 SGk4){FWc;GR!^=~{(k{@mxL<- delta 26116 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%0AO*UX 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 + + + {`${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"); From 1b06867d88ad7d6a4a3e584f7f86e5f361f4b8e0 Mon Sep 17 00:00:00 2001 From: Jaronim Pracht Date: Mon, 2 Jun 2025 19:06:25 +0200 Subject: [PATCH 2/3] Fix showing pdfs in production Removed redundant PDF.js worker initialization from PDFViewer component and updated the worker source path in main.tsx. Downgraded react-pdf to v8.0.2 to resolve compatibility issues and fixed missing newline in nginx.conf. --- project/docker-compose.yml | 4 ++-- project/frontend/docker/nginx.conf | 2 +- project/frontend/package.json | 2 +- project/frontend/src/components/pdfViewer.tsx | 7 +------ project/frontend/src/main.tsx | 3 +-- 5 files changed, 6 insertions(+), 12 deletions(-) diff --git a/project/docker-compose.yml b/project/docker-compose.yml index be29228..8892999 100644 --- a/project/docker-compose.yml +++ b/project/docker-compose.yml @@ -19,11 +19,11 @@ services: coordinator: build: context: backend/coordinator - dockerfile: ../../Dockerfile env_file: - .env depends_on: - - db + db: + condition: service_healthy healthcheck: test: wget --spider --no-verbose http://127.0.0.1:5000/health || exit 1 interval: 10s diff --git a/project/frontend/docker/nginx.conf b/project/frontend/docker/nginx.conf index 4bd5b3b..7d028a5 100644 --- a/project/frontend/docker/nginx.conf +++ b/project/frontend/docker/nginx.conf @@ -20,4 +20,4 @@ server { location = /50x.html { root /usr/share/nginx/html; } -} \ No newline at end of file +} diff --git a/project/frontend/package.json b/project/frontend/package.json index c3b04dd..6bb0b68 100644 --- a/project/frontend/package.json +++ b/project/frontend/package.json @@ -26,7 +26,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-material-file-upload": "^0.0.4", - "react-pdf": "^9.2.1", + "react-pdf": "^8.0.2", "socket.io-client": "^4.8.1" }, "devDependencies": { diff --git a/project/frontend/src/components/pdfViewer.tsx b/project/frontend/src/components/pdfViewer.tsx index 5bb6b96..3c25e13 100644 --- a/project/frontend/src/components/pdfViewer.tsx +++ b/project/frontend/src/components/pdfViewer.tsx @@ -1,16 +1,11 @@ import { useEffect, useRef, useState } from "react"; -import { Document, Page, pdfjs } from "react-pdf"; +import { Document, Page } from "react-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, -).toString(); - interface PDFViewerProps { pitchBookId: string; } diff --git a/project/frontend/src/main.tsx b/project/frontend/src/main.tsx index f41d85a..0f5071d 100644 --- a/project/frontend/src/main.tsx +++ b/project/frontend/src/main.tsx @@ -34,9 +34,8 @@ declare module "@tanstack/react-router" { } } -// Initialize PDF.js worker pdfjs.GlobalWorkerOptions.workerSrc = new URL( - "pdfjs-dist/build/pdf.worker.min.mjs", + "pdfjs-dist/build/pdf.worker.min.js", import.meta.url, ).toString(); From d412d5741b71b8b19c7f619fc28bb06c27ab7204 Mon Sep 17 00:00:00 2001 From: Jaronim Pracht Date: Mon, 2 Jun 2025 19:09:16 +0200 Subject: [PATCH 3/3] 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 component --- project/backend/coordinator/Dockerfile | 21 ++ .../coordinator/controller/__init__.py | 2 + .../controller/progress_controller.py | 19 ++ project/frontend/biome.json | 2 +- project/frontend/bun.lockb | Bin 146934 -> 154753 bytes .../frontend/src/components/UploadPage.tsx | 318 +++++++++--------- project/frontend/src/components/pdfViewer.tsx | 155 +++++---- 7 files changed, 279 insertions(+), 238 deletions(-) create mode 100644 project/backend/coordinator/Dockerfile create mode 100644 project/backend/coordinator/controller/progress_controller.py diff --git a/project/backend/coordinator/Dockerfile b/project/backend/coordinator/Dockerfile new file mode 100644 index 0000000..1a8fe43 --- /dev/null +++ b/project/backend/coordinator/Dockerfile @@ -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"] diff --git a/project/backend/coordinator/controller/__init__.py b/project/backend/coordinator/controller/__init__.py index 256d5f2..5b758cb 100644 --- a/project/backend/coordinator/controller/__init__.py +++ b/project/backend/coordinator/controller/__init__.py @@ -1,9 +1,11 @@ from controller.spacy_contoller import spacy_controller from controller.kpi_setting_controller import kpi_setting_controller from controller.pitch_book_controller import pitch_book_controller +from controller.progress_controller import progress_controller def register_routes(app): app.register_blueprint(kpi_setting_controller) app.register_blueprint(pitch_book_controller) app.register_blueprint(spacy_controller) + app.register_blueprint(progress_controller) diff --git a/project/backend/coordinator/controller/progress_controller.py b/project/backend/coordinator/controller/progress_controller.py new file mode 100644 index 0000000..90db4df --- /dev/null +++ b/project/backend/coordinator/controller/progress_controller.py @@ -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"}) diff --git a/project/frontend/biome.json b/project/frontend/biome.json index 55240aa..2e91202 100644 --- a/project/frontend/biome.json +++ b/project/frontend/biome.json @@ -12,7 +12,7 @@ }, "formatter": { "enabled": true, - "indentStyle": "space" + "indentStyle": "tab" }, "organizeImports": { "enabled": true diff --git a/project/frontend/bun.lockb b/project/frontend/bun.lockb index 676011c22448c88f859b20853066f9b21747ae99..d5b340930964353aaf516044ae493772a55f22d3 100755 GIT binary patch delta 35255 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

  • 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%0AC@i4(cpmDfj}WDi}Aw32+5ye_9TBhq%wc zTY%ivspUt6C`*c=>e2Nc;gP^6fOfcpL20*3#ZvC%CEtMa0PTo109D}+fO7T7{ia4! zc9ac8(T>Fipe+(@m8<|upcG&M&<>^qK)V56Q(X7zhIV0AGMwf)oRQK!DNPwiJ)$!b`+Z-PEIM)f2)@h0Z>56C@OuYoeZyg_;C=p(bcl z#8Bg?r-(^0zFu0i+u`DT`b<iM|pv1Ewx@d~6E^?X_NkT0d2T;!)7MXR0%hA4k8srA)UZyPFyjY%=GW$|)1 zt7nZN#uu#-jJe26ZZGv{XS`@~#oZeOo}e{4EG454J%R`qB$WJeaEQPoq0 z)x+GRf&!=@_1t0gxHl}WcA=VWE2S3fq(o%;b^nJ ztuS)L@_-2nHOo_vt&^$+W00G2!m@g>ol;@Z*PpMNFO>87+cx#3Jk_I?Ei!(s?|AE% z_ehD^gD;;ilo!1$`0@D|PUMC!|pi zZ&pvxLvD&&w#L*$gHt_Y54lk%;-`|PGnMJbd^ct&cjz1|&08)I){6EvywoDWUaVrn zT^E7|*zlSQg-9{VhR3cK9C*$m!9na`!?z;RNj-%5{*m~)7oLr;jiQ5PLk8IJ=ahVe z4IfY0jkn>Ieiu5MFGYQvQQScr{>$$|b*;qn7t4h*-=mZo@aM~f(tj0-cWt@*BEj#^ z)sYMN+srVTUZe6;%JBD;PR%@Gu@InS@}rX3W%=^OP*y8Hsw}^`Sm^w3Dk&D0<%5!n}(x3(*#p7W&v z_`;4)Uy5v0P4!gk#-pcM+}u;^3N$evkjKcH_M)!?FaO661Hap**urQ2A(Sp+NF}u+ z)bqE?eyz}8$BHw*N?nLCg&D<>Km7x>Rhv-u09zikObDTNOkXB+{obC`Sw*rJm)h|1 z%LP}8uZwmGQV+fErqQ2z=zhtT48R27?-i&X+PtUmm&qyP{EJDZNfW!$rcUz7*_yoZ z+vaC(e|uoNqyb0%ZagnoE|eBKIPs_dyCMtA%G1%#m3j5bw{CDosSMile_f=~-uB{r zC%%6L8sdyI_gab8QVbUV(%0V|5K# zz!Pl{gPM`K?&bMcD#N!tw_hdqH5%8J$F34g#c3|QD}+JnvEcK1^vdb`wSzN~WAUIJ zjCyeR(3OcNs)x4NBo*mH%c0=Pk0Y&9n4A2Y&a|nA9WzokmXI{CWa8tU8+Td_m%RiH z%$j-E)-Sb;Z?#}HH2mca#vM1_5*p$&H{N5l5E(S9g6!N)*L}Njt>X|%8^-m|Y#N)SFMR^X4R6!ir1yDj^fG;1?;gOt_}Q;Wu{7)HjC z1}J%8>Q+xHf15gah^_g;iilCRK%FY{R7$I!U;g-g+|@2NW&zMp8dyEa{833)hY~|7 z`ymFNNKJpS3a`5b*4?YZORWR-tjeDwuYXh1SrvHlCTQNM%JpkOpI7Bi!8@sko!4v8 zrbV-{jJ!G6OR`PLo6lb>1aPNWf}^PBA*U^C>Zei5SE-V#S}!N{Nc3rCHav73zO|=h zJw_S!*46mhhViu}$JjR2bc+K(bLpIuYp~5pJx;yw>#^nT0T%c@TiQ0E zZDozJraqM0jjgasnV{6^#tBEP)0RK4!7r}uye{AJKw3Ze2Rz8S2LElnkT1G;a-R+8 z1@(0JvNm@k-A6Uv1x>U=kXOj}y`B9YZq#h$)a$hf4b+NJ+U%nAj zVXzlJN%0Y0{2}D#YNghB@$%#-Xqe|aQ91SOcTLyrd!LNYpt+u!5KgGNgX&UF-h4Jv z2C2uN9+nX}imdmxJ;i4W`-{@v?gX+6B(2!T0?SFR1zq-6wp10jBM2J1S`Si^~ z&HvevX-+JOLGy10iCTtid)e~Xt-{SpqkZ=~*?gVxz!EE}Tw_1YFkk*F<);1&o;Kx# z<)BfK92x;$ezX`a_vO2{2$3z-Q}z8P#`oJbWA;E%=ne<8K(2#9Z9yM}8I6bhn(L6( z7yC!wfY3>b#s>R+S5BV%dK)pcSMm*|pV&KZ75rNM^OW<~8d(V?vG1mx<-Z+{(g2c& zgRQGS=3({y2bT34Ubd|LBt*8|-z!LU{9>eqq2G8LRvg*Ss*ANe`IT+(uj;}4>oyE< z^<@C%{TmwZ_nTBfvQe7EnuPGk?Px*ul>LLnTk?kYQ+Au;Lz`b9xqn&+&x1ygdM>~H z$B(|P4hJRv&`?kApIOdl^O*S)YsIOaKVUH{J7@I%pN6f2Go=R!e z7c?Avo98lMejD~fLw$vVnYG?yqww3&A2Bs*@~ucK{$>7Zo7hbnB&(HZC3fLvk1f;%IVVF&Y zQ7iW!dmswf!}-##h1Me!7WDTnOq+~gyTW#6JG^alG*@Y@|sRlMl-9*$Ye5H$Mz0)@fSpUlyc7L)izMnxSOlre_b0``x+~ zh@me4iy02g`lH}k{~s}LTJW!^ktjE*z39_|`yIhDzN0zsb3|};QeUd`!8)<$*MMVF zrB0AOh`emc*B(JP4vymDQ6WMrwKc!KO(?@-j|vsk(a5JC#b;@B&gr9qpY7in!R?R1 zD~?3**2mC&7ozy6WB8^)8O;AO6wx65t^)?Rb`C%VGLK_LQ{UvV#kOqePUEHyF~#?* z0n&h5*^2MPgb<{@`y(p<>C?KiPksHNA-^6(lXu>4%eS#F@f7X%w8ekorA{EN`a+R4 zl}$VO+#hc9LqmP#NXYa4ElOF9JoO_c{wLlMX`P1uB>iNb_rCMgK>^bbfBT`a=qEm& za#vq#(&;&E)nmtaPT>JxV z%CT*vsY-nb%YaU%EBa*hr_ZX&_n}eoyw+JXbS!FSg_`-<{ZXZ#?|_lepijcsaHhxe zbZ7((hK4mXGHU*mU$gul+oas3x~p$+nP2_dn4Binnks2gv-$CSJLSF}X=!6JqT$Z| zBmI-pp`m=5Jrd7fp2hI_=Ndjr;C0U-kMKl!PWoU`VO^QJv!7#KhlJ{^lp=2G%6~nF z!3(!M!dZ_vPpj@`C4k2c}QIa8&wg#|Ph(`IE+1 zXoxn+d;n3UG;vW6zLIz~{?B|KeJsnnh%aqJQt>k&az3)grD>fWO@>N6fn9T`Mtv(y z71y%CE(6+J{Sl+SwOcflGCTpzwY_|!t?2uFtp^aqU5iK_{mE`X{Y_^@`tMktcTRQqqZzOZQuJ;4O?M< zI{$hJzM^XU>;3>5kp)=9?K6040eVS&nNH&csmm|A6e}(Y_z8!Gj7EJ8kJZfK?C{{z z7O(){Z;-hl5SwIh^UH9q;7qQ+3_d)Qw<8|(7vfiC@rB@n z)c5qnWmKwNw}d5qZjwH`;cKgGd09NwZpq0euV>wnzE77H{Z=`=)D@VazMAKcg0g|T z-ZsY^@9PyRZ5Y&-_gKE-lU?*DX8(wpl*2nBt&{q?pHo3=>g_oGvhxp(eK~wQH1z5V zf;Q(T+Bf>G$DSV=$~%Pe+Kj6^WO>t@6@SFE+bjQ&Ib|=udqt?r-LDEhj-%pazZn)1 zcvR?9>gz#8sdMcOnOB8LraI}DPOt24{dkL8g1uFWv1g)ZVz0#1Y|r$}wDc@)brU}` z#@rMd^K&=xrwb=<3R*t*rr^bGZ{bfJF1``Yai4mkDSvfKXlE<`d`Y>&y#*X@TdF|60;NIvpwBiIq&+NpM^kidpVz;F1?m2OuT{F{C zJkyQwJ&oNGQ+m=n*F58sjagYq@u{eQr(Aqac2Y7#NSNr^twdsWc1~()VzQ^P8@IkL zly~{gqM=ZmqO2!=MByWz2tAzSp9CpddcmhB{T3%x5j>yvSg26(&&5OY&jMtHzdo@M z-9jmctZclGDn-doW{@R0EUZq1ooQVaovvA)@oCh4so8wuW8oveCyM3x(T74vxgt?n z$f_vIa~}wm-TssjH2%bUVhC{G`sl;-M}iNp^+*WN#U(>4F4;4Hue1;=y2`(_kw|*! zsAnKP0s2x6?oJ`o7?7t^7^~H6_H;EycB_f{wyzUdB68Cwn{NmU2wXjT!FRt96mo4>hWL9MY9bpJ_-%-!{ZIXiub*X3F?HoSbW3ulH&DZ zT<{T{xzByUZbN??v8TUz@9xIz-rYQNGL!k;hGI)?_v~y;Yt([]); - const [pageId, setPageId] = useState(null); - const [loadingState, setLoadingState] = useState(null); - 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(); - const uploadFile = useCallback(async () => { - const formData = new FormData(); - formData.append("file", files[0]); - const response = await fetch("http://localhost:5050/api/pitch_book", { - method: "POST", - body: formData, - }); + const uploadFile = useCallback(async () => { + 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); + 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]); + !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 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); + 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], - ); + 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]); + 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" })}> - - - - - - - - - - - - ); + return ( + <> + {PROGRESS && ( + ({ color: "#fff", zIndex: theme.zIndex.drawer + 1 })} + open={pageId !== null && loadingState !== null} + > + + + )} + + + navigate({ to: "/config" })}> + + + + + + + + + + + + ); } diff --git a/project/frontend/src/components/pdfViewer.tsx b/project/frontend/src/components/pdfViewer.tsx index 3c25e13..87e1edc 100644 --- a/project/frontend/src/components/pdfViewer.tsx +++ b/project/frontend/src/components/pdfViewer.tsx @@ -7,88 +7,87 @@ import ArrowCircleRightIcon from "@mui/icons-material/ArrowCircleRight"; import { Box, IconButton } from "@mui/material"; interface PDFViewerProps { - pitchBookId: string; + pitchBookId: string; } - 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 [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)} - > - {containerWidth && ( - - )} - - - - setPageNumber((p) => p - 1)} - > - - - - {pageNumber} / {numPages} - - = (numPages || 1)} - onClick={() => setPageNumber((p) => p + 1)} - > - - - - - ); + return ( + + + + 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)} + > + + + + + ); }