From 2de023b755e13bf9eb628da10180a5b79b9bf8c7 Mon Sep 17 00:00:00 2001 From: naheyansheikh Date: Thu, 5 Dec 2024 17:02:52 -0800 Subject: [PATCH] Transcription endpoint to Upload Photo implemeneted --- frontend/package.json | 3 +- frontend/src/main.jsx | 4 + .../load_transcription/LoadTranscription.css | 28 ++++++ .../load_transcription/LoadTranscription.jsx | 30 +++++++ .../pages/loading_screen/LoadingScreen.css | 46 ++++++++++ .../pages/loading_screen/LoadingScreen.jsx | 85 +++++++++++++++++++ frontend/src/pages/log_code/LogCode.jsx | 3 +- .../src/pages/upload_photo/UploadPhoto.jsx | 67 +++++++++++---- frontend/vite.config.js | 8 ++ transcription/app.py | 6 +- 10 files changed, 259 insertions(+), 21 deletions(-) create mode 100644 frontend/src/pages/load_transcription/LoadTranscription.css create mode 100644 frontend/src/pages/load_transcription/LoadTranscription.jsx create mode 100644 frontend/src/pages/loading_screen/LoadingScreen.css create mode 100644 frontend/src/pages/loading_screen/LoadingScreen.jsx diff --git a/frontend/package.json b/frontend/package.json index 981438d9..6e4872a7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -44,5 +44,6 @@ "jest": "^29.7.0", "react-test-renderer": "^18.3.1", "vite": "^5.4.1" - } + }, + "proxy": "http://localhost:5000" } diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 80038774..ac87b8cb 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -11,6 +11,8 @@ import LogHistory from "./pages/log_history/LogHistory.jsx"; import UploadPhoto from "./pages/upload_photo/UploadPhoto.jsx"; import ManualEntry from "./pages/manual_entry/ManualEntry.jsx"; import LogCode from "./pages/log_code/LogCode.jsx"; +import LoadTranscription from "./pages/load_transcription/LoadTranscription.jsx"; +import LoadingScreen from "./pages/loading_screen/LoadingScreen.jsx"; import "./index.css"; createRoot(document.getElementById("root")).render( @@ -26,6 +28,8 @@ createRoot(document.getElementById("root")).render( } /> } /> } /> + } /> + } /> } /> diff --git a/frontend/src/pages/load_transcription/LoadTranscription.css b/frontend/src/pages/load_transcription/LoadTranscription.css new file mode 100644 index 00000000..aaa056ec --- /dev/null +++ b/frontend/src/pages/load_transcription/LoadTranscription.css @@ -0,0 +1,28 @@ +.transcription-container { + padding: 20px; +} + +.transcription-header { + display: flex; + align-items: center; + gap: 16px; + margin-bottom: 20px; +} + +.back-button { + background: none; + border: none; + cursor: pointer; +} + +.back-icon { + width: 24px; + height: 24px; +} + +.transcription-content { + background: white; + padding: 20px; + border-radius: 8px; + white-space: pre-wrap; +} diff --git a/frontend/src/pages/load_transcription/LoadTranscription.jsx b/frontend/src/pages/load_transcription/LoadTranscription.jsx new file mode 100644 index 00000000..dc1a7615 --- /dev/null +++ b/frontend/src/pages/load_transcription/LoadTranscription.jsx @@ -0,0 +1,30 @@ +import { useLocation, useNavigate } from "react-router-dom"; +import { NavContentWrapper } from "../../components/NavContentWrapper/NavContentWrapper"; +import { ChevronLeftIcon } from "@heroicons/react/24/solid"; +import "./LoadTranscription.css"; + +export default function LoadTranscription() { + const location = useLocation(); + const navigate = useNavigate(); + const transcription = location.state?.transcription; + + return ( + +
+
+ +

Transcription Result

+
+ +
+ {transcription || "No transcription data available"} +
+
+
+ ); +} diff --git a/frontend/src/pages/loading_screen/LoadingScreen.css b/frontend/src/pages/loading_screen/LoadingScreen.css new file mode 100644 index 00000000..7b39f5a1 --- /dev/null +++ b/frontend/src/pages/loading_screen/LoadingScreen.css @@ -0,0 +1,46 @@ +.loading-screen { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + text-align: center; +} + +.loading-screen h2, h3 { + margin: 5px 0; + color: #333; +} + +.progress-bar-container { + width: 60%; + height: 20px; + border-radius: 30px; + border: 1px solid #244B94; + background-color: #FFFF; + margin: 40px 0; + position: relative; +} + +.progress-bar { + height: 100%; + background: #244B94; + border-radius: 20px; + transition: width 0.3s ease; +} + +.progress-text { + position: absolute; + right: -40px; + top: -2px; + color: #000000; +} + +.cancel-icon { + margin-right: 8px; + font-size: 18px; +} + +.cancel-button:hover { + background: #f5f5f5; +} diff --git a/frontend/src/pages/loading_screen/LoadingScreen.jsx b/frontend/src/pages/loading_screen/LoadingScreen.jsx new file mode 100644 index 00000000..ae48ce89 --- /dev/null +++ b/frontend/src/pages/loading_screen/LoadingScreen.jsx @@ -0,0 +1,85 @@ +import { useEffect, useState } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; +import { CLButtonSecondary } from "../../components/Buttons/CLButtons"; +import "./LoadingScreen.css"; + +export default function LoadingScreen() { + const location = useLocation(); + const navigate = useNavigate(); + const [progress, setProgress] = useState(0); + const imageFile = location.state?.imageFile; + + useEffect(() => { + if (!imageFile) { + navigate("/upload-photo"); + return; + } + + const handleTranscription = async () => { + try { + const formData = new FormData(); + formData.append("image", imageFile); + + // Progress simulation + let currentProgress = 0; + const progressInterval = setInterval(() => { + const increment = Math.random() * 3 + 1; // Random increment between 1-4 + currentProgress = Math.min(currentProgress + increment, 85); + setProgress(Math.round(currentProgress)); + }, 300); + + const response = await fetch("/api/transcribe", { + method: "POST", + body: formData, + }); + + clearInterval(progressInterval); + + const data = await response.json(); + + if (!response.ok) { + throw new Error( + data.error || `HTTP error! status: ${response.status}` + ); + } + + // Complete the progress bar + setProgress(100); + + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Navigate to results + navigate("/load-transcription", { + state: { transcription: data.transcription }, + replace: true, + }); + } catch (error) { + console.error("Error during transcription:", error); + alert("Failed to transcribe image: " + error.message); + navigate(-1); + } + }; + + handleTranscription(); + }, [imageFile, navigate]); + + return ( +
+

Transcribing photos

+

to standardized template...

+ +
+
+ {progress}% +
+ + navigate("/upload-photo")} + width={"250px"} + > + × + Cancel Transcription + +
+ ); +} diff --git a/frontend/src/pages/log_code/LogCode.jsx b/frontend/src/pages/log_code/LogCode.jsx index 4a2c6467..17681fc3 100644 --- a/frontend/src/pages/log_code/LogCode.jsx +++ b/frontend/src/pages/log_code/LogCode.jsx @@ -7,7 +7,7 @@ import Logo from "../../assets/images/logo.png"; import "./LogCode.css"; const LogCode = () => { - const [setLogbookCode] = useState(""); + const [logbookCode, setLogbookCode] = useState(""); const [termsAccepted, setTermsAccepted] = useState(false); const [loading, setLoading] = useState(false); const navigate = useNavigate(); @@ -18,6 +18,7 @@ const LogCode = () => { return alert("Please accept the terms and conditions"); } try { + console.log("Logbook Code:", logbookCode); // exmaple use for now navigate("/home"); } catch { alert("Something went wrong. Please try again."); diff --git a/frontend/src/pages/upload_photo/UploadPhoto.jsx b/frontend/src/pages/upload_photo/UploadPhoto.jsx index cc91ad6f..1f1b93db 100644 --- a/frontend/src/pages/upload_photo/UploadPhoto.jsx +++ b/frontend/src/pages/upload_photo/UploadPhoto.jsx @@ -9,31 +9,55 @@ import { ChevronRightIcon, ChevronDoubleLeftIcon, } from "@heroicons/react/24/solid"; +import LoadingScreen from "../loading_screen/LoadingScreen"; export default function UploadPhoto() { const navigate = useNavigate(); + const [files, setFiles] = useState([]); + const [isLoading] = useState(false); + const [progress] = useState(0); const handleTranscribe = () => { - navigate("/load-transcription"); + if (files.length === 0) { + alert("Please upload an image first"); + return; + } + + navigate("/loading-screen", { + state: { imageFile: files[0].file }, + }); }; return ( <> - - - -
- -
+ {isLoading ? ( + + ) : ( + <> + + + +
+ +
+ + )} ); } -function MainContent({ handleTranscribe }) { +function MainContent({ files, setFiles, handleTranscribe }) { const navigate = useNavigate(); - const [files, setFiles] = useState([]); const [showPreview, setShowPreview] = useState(false); /** Allowed file types */ @@ -41,12 +65,21 @@ function MainContent({ handleTranscribe }) { /** Handle files from input or drop */ const handleFiles = (newFiles) => { - const filesWithPreview = newFiles.map((file) => ({ - ...file, - timestamp: Date.now(), - preview: URL.createObjectURL(file), - })); - setFiles((prev) => [...filesWithPreview, ...prev]); // Add new files to beginning + console.log("Received files:", newFiles); // Debug log + + const filesArray = Array.from(newFiles); + console.log("Files array:", filesArray); // Debug log + + const filesWithPreview = filesArray.map((file) => { + console.log("Processing file:", file); // Debug log + return { + file: file, // Store the actual File object + timestamp: Date.now(), + preview: URL.createObjectURL(file), + }; + }); + + setFiles(filesWithPreview); }; /** Toggle preview visibility */ diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 2dea53a3..b3b20d83 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -4,4 +4,12 @@ import react from '@vitejs/plugin-react' // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], + server: { + proxy: { + '/api': { + target: 'http://localhost:5000', + changeOrigin: true, + } + } + } }) \ No newline at end of file diff --git a/transcription/app.py b/transcription/app.py index bff6161e..f46534b9 100644 --- a/transcription/app.py +++ b/transcription/app.py @@ -1,9 +1,11 @@ from flask import Flask, request, jsonify +from flask_cors import CORS from PIL import Image from transformers import AutoProcessor, AutoModelForCausalLM import torch app = Flask(__name__) +CORS(app) # load model and processor once during init device = "cuda:0" if torch.cuda.is_available() else "cpu" @@ -12,7 +14,7 @@ model = AutoModelForCausalLM.from_pretrained("microsoft/Florence-2-large", torch_dtype=torch_dtype, trust_remote_code=True).to(device) processor = AutoProcessor.from_pretrained("microsoft/Florence-2-large", trust_remote_code=True) -@app.route("/transcribe", methods=["POST"]) +@app.route("/api/transcribe", methods=["POST"]) def transcribe(): if "image" not in request.files: return jsonify({"error": "No image file provided"}), 400 @@ -37,4 +39,4 @@ def transcribe(): return jsonify({"error": str(e)}), 500 if __name__ == "__main__": - app.run(debug=True) \ No newline at end of file + app.run(host='0.0.0.0',port=5000) \ No newline at end of file