diff --git a/src/main/java/se/su/dsv/seshat/controllers/LandingController.java b/src/main/java/se/su/dsv/seshat/controllers/RootController.java similarity index 68% rename from src/main/java/se/su/dsv/seshat/controllers/LandingController.java rename to src/main/java/se/su/dsv/seshat/controllers/RootController.java index e07debc..7371baa 100644 --- a/src/main/java/se/su/dsv/seshat/controllers/LandingController.java +++ b/src/main/java/se/su/dsv/seshat/controllers/RootController.java @@ -1,15 +1,18 @@ package se.su.dsv.seshat.controllers; -import org.springframework.security.core.Authentication; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller -public class LandingController { +public class RootController { @GetMapping("/") public String showHomePage() { return "redirect:/files/manage"; } + @GetMapping("/about") + public String showAboutPage() { + return "about"; + } } diff --git a/src/main/java/se/su/dsv/seshat/services/StorageService.java b/src/main/java/se/su/dsv/seshat/services/StorageService.java index 7f67d7f..9cea80e 100644 --- a/src/main/java/se/su/dsv/seshat/services/StorageService.java +++ b/src/main/java/se/su/dsv/seshat/services/StorageService.java @@ -99,7 +99,8 @@ public class StorageService { String sanitizedFilename = sanitizeFilename(originalFilename); // Users upload directory - Path userUploadDir = Paths.get(uploadRoot, user.getUsername(), UUID.randomUUID().toString()); + String uuid = UUID.randomUUID().toString(); + Path userUploadDir = Paths.get(uploadRoot, user.getUsername(), uuid); try { if(!Files.exists(userUploadDir)) { @@ -124,7 +125,7 @@ public class StorageService { } metadata.setUploadedAt(LocalDateTime.now()); String fileFolder = fileNameAndUploadedTime(metadata); - metadata.setOutputDirectory(outputRoot + File.separator + user.getUsername() + File.separator + fileFolder); + metadata.setOutputDirectory(outputRoot + File.separator + user.getUsername() + File.separator + uuid + File.separator + fileFolder); metadata.setUser(user); return fileMetadataRepository.save(metadata); } catch (IOException e) { diff --git a/src/main/resources/static/css/styles.css b/src/main/resources/static/css/styles.css index 7f8f0f0..18b79e4 100644 --- a/src/main/resources/static/css/styles.css +++ b/src/main/resources/static/css/styles.css @@ -3,9 +3,16 @@ --bs-btn-bg: #002F5F; } +.header { + padding: 6rem; +} + .header-link { color: white; text-decoration: none; +} + +.header-powered-by { font-size: 0.8rem; } @@ -53,6 +60,10 @@ } footer { margin-top: 1rem; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 6rem; } html, body { diff --git a/src/main/resources/static/js/script.js b/src/main/resources/static/js/script.js index 666e470..2124789 100644 --- a/src/main/resources/static/js/script.js +++ b/src/main/resources/static/js/script.js @@ -57,4 +57,44 @@ function validateFile() { } return true; -} \ No newline at end of file +} + +// Progress bar for file upload +document.getElementById('uploadForm').addEventListener('submit', function(event) { + event.preventDefault(); + const fileInput = document.getElementById('file'); + const file = fileInput.files[0]; + const formData = new FormData(); + formData.append('file', file); + formData.append('language', document.getElementById('language').value); + + const xhr = new XMLHttpRequest(); + xhr.open('POST', '/files/upload', true); + + xhr.upload.addEventListener('progress', function(event) { + if (event.lengthComputable) { + const percentComplete = (event.loaded / event.total) * 100; + const progressBar = document.getElementById('progressBar'); + progressBar.style.width = percentComplete + '%'; + progressBar.setAttribute('aria-valuenow', percentComplete); + progressBar.textContent = Math.round(percentComplete) + '%'; + } + }); + + xhr.addEventListener('load', function() { + if (xhr.status === 200) { + window.location.reload(); + } else { + alert('File upload failed'); + } + document.getElementById('progressContainer').style.display = 'none'; + }); + + xhr.addEventListener('error', function() { + alert('File upload failed'); + document.getElementById('progressContainer').style.display = 'none'; + }); + + document.getElementById('progressContainer').style.display = 'block'; + xhr.send(formData); +}); \ No newline at end of file diff --git a/src/main/resources/templates/about.html b/src/main/resources/templates/about.html new file mode 100644 index 0000000..e397c2d --- /dev/null +++ b/src/main/resources/templates/about.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<html xmlns:th="http://www.thymeleaf.org" lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Seshat Auido Transcriber</title> + <link th:rel="stylesheet" th:href="@{/3p/bootstrap-5.3.3-dist/css/bootstrap.min.css}" /> + <link th:rel="stylesheet" th:href="@{/3p/bootstrap-icons-1.11.3/font/bootstrap-icons.min.css}" /> + <link th:rel="stylesheet" th:href="@{/css/styles.css}" /> +</head> +<body> +<header class="header bg-primary text-white py-3"> + <div class="container d-flex justify-content-between align-items-center"> + <img th:src="@{/images/SU_logotyp_Landscape_Invert.svg}" alt="Stockholm University Logotype" class="logo"> + <h1 class="app-title mb-0"><a href="/files/manage" class="header-link">Seshat Audio Transcriber</a></h1> + <div th:if="${#authentication.name != 'anonymousUser'}" class="dropdown"> + <a class="user-menu text-white text-decoration-none dropdown-toggle" href="#" id="userMenu" role="button" + data-bs-toggle="dropdown" aria-expanded="false"> + <i class="bi bi-person-circle"></i> + <span th:text="${#authentication.getName()}">Username</span> + </a> + <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userMenu"> + <li><a class="dropdown-item" th:href="@{/logout}">Logout</a></li> + </ul> + </div> + </div> + <div class="container text-center"> + <span class="span-small"> + <a href="https://openai.com/index/whisper/" target="_blank" class="header-link header-powered-by">Powered with Whisper AI</a> + </span> + </div> +</header> +<main class="container mt-4"> + <h2>About Seshat Audio Transcriber</h2> + <p>This tool allows you to upload audio files and transcribe them into text using whisperAI.</p> + <p>The application runs a local instance of <a href="https://github.com/openai/whisper" target="_blank">Whisper AI</a>, using the turbo model on one NVIDIA RTX A4000 graphics card.</p> + <p>All processing is done locally, and no data is sent to the cloud, ensuring your privacy and data security.</p> +</main> +<footer class="bg-primary text-white py-4"> + <span>© 2024 Seshat App</span> + <a href="/about" class="text-white text-decoration-none">About</a> +</footer> +<script type="text/javascript" th:src="@{/3p/bootstrap-5.3.3-dist/js/bootstrap.bundle.min.js}"></script> +<script type="text/javascript" th:src="@{/3p/htmx/2.0.4/dist/htmx.min.js}"></script> +<script type="text/javascript" th:src="@{/js/script.js}"></script> +</body> +</html> \ No newline at end of file diff --git a/src/main/resources/templates/file-management.html b/src/main/resources/templates/file-management.html index c16db8e..a2ee258 100644 --- a/src/main/resources/templates/file-management.html +++ b/src/main/resources/templates/file-management.html @@ -12,7 +12,7 @@ <header class="header bg-primary text-white py-3"> <div class="container d-flex justify-content-between align-items-center"> <img th:src="@{/images/SU_logotyp_Landscape_Invert.svg}" alt="Stockholm University Logotype" class="logo"> - <h1 class="app-title mb-0">Seshat Audio Transcriber</h1> + <h1 class="app-title mb-0"><a href="/files/manage" class="header-link">Seshat Audio Transcriber</a></h1> <div th:if="${#authentication.name != 'anonymousUser'}" class="dropdown"> <a class="user-menu text-white text-decoration-none dropdown-toggle" href="#" id="userMenu" role="button" data-bs-toggle="dropdown" aria-expanded="false"> @@ -26,7 +26,7 @@ </div> <div class="container text-center"> <span class="span-small"> - <a href="https://openai.com/index/whisper/" target="_blank" class="header-link">Powered with Whisper AI</a> + <a href="https://openai.com/index/whisper/" target="_blank" class="header-link header-powered-by">Powered with Whisper AI</a> </span> </div> </header> @@ -36,7 +36,7 @@ <!-- File Upload Section --> <section> <h3>Upload File</h3> - <form th:action="@{/files/upload}" method="post" enctype="multipart/form-data"> + <form id="uploadForm" th:action="@{/files/upload}" method="post" enctype="multipart/form-data"> <div class="mb-3"> <label for="file" class="form-label">Choose File</label> <input type="file" id="file" name="file" class="form-control" required @@ -57,6 +57,11 @@ </div> <button type="submit" class="btn btn-primary">Upload</button> </form> + <div id="progressContainer" class="mt-3" style="display: none;"> + <div class="progress"> + <div id="progressBar" class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0%</div> + </div> + </div> </section> <!-- File Status Section --> <section th:if="${fileUploadStatuses != null && !fileUploadStatuses.isEmpty()}" class="mt-5"> @@ -160,6 +165,7 @@ </main> <footer class="bg-primary text-white text-center py-3"> <p>© 2024 Seshat App</p> + <a href="/about" class="text-white text-decoration-none">About</a> </footer> <script type="text/javascript" th:src="@{/3p/bootstrap-5.3.3-dist/js/bootstrap.bundle.min.js}"></script> <script type="text/javascript" th:src="@{/3p/htmx/2.0.4/dist/htmx.min.js}"></script>