diff --git a/backend/src/main/java/com/talkforgeai/backend/assistant/service/AssistantSpringService.java b/backend/src/main/java/com/talkforgeai/backend/assistant/service/AssistantSpringService.java index 3601627f..d7528679 100644 --- a/backend/src/main/java/com/talkforgeai/backend/assistant/service/AssistantSpringService.java +++ b/backend/src/main/java/com/talkforgeai/backend/assistant/service/AssistantSpringService.java @@ -36,6 +36,7 @@ import com.talkforgeai.backend.assistant.repository.AssistantRepository; import com.talkforgeai.backend.assistant.repository.MessageRepository; import com.talkforgeai.backend.assistant.repository.ThreadRepository; +import com.talkforgeai.backend.service.UniqueIdGenerator; import com.talkforgeai.backend.storage.FileStorageService; import com.talkforgeai.backend.transformers.MessageProcessor; import jakarta.transaction.Transactional; diff --git a/backend/src/main/java/com/talkforgeai/backend/assistant/service/UniqueIdGenerator.java b/backend/src/main/java/com/talkforgeai/backend/service/UniqueIdGenerator.java similarity index 93% rename from backend/src/main/java/com/talkforgeai/backend/assistant/service/UniqueIdGenerator.java rename to backend/src/main/java/com/talkforgeai/backend/service/UniqueIdGenerator.java index aa7f68dc..55ec5aa0 100644 --- a/backend/src/main/java/com/talkforgeai/backend/assistant/service/UniqueIdGenerator.java +++ b/backend/src/main/java/com/talkforgeai/backend/service/UniqueIdGenerator.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.talkforgeai.backend.assistant.service; +package com.talkforgeai.backend.service; import java.security.SecureRandom; import org.springframework.stereotype.Component; @@ -45,6 +45,10 @@ public String generateImageId() { return generateUniqueId("img", 20); } + public String generateAudioId() { + return generateUniqueId("aud", 20); + } + public String generateUniqueId(String prefix, int length) { String randomChars = generateRandomString(length); return prefix + "_" + randomChars; diff --git a/backend/src/main/java/com/talkforgeai/backend/storage/FileStorageService.java b/backend/src/main/java/com/talkforgeai/backend/storage/FileStorageService.java index 5ca3ddb8..70807c86 100644 --- a/backend/src/main/java/com/talkforgeai/backend/storage/FileStorageService.java +++ b/backend/src/main/java/com/talkforgeai/backend/storage/FileStorageService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Jean Schmitz. + * Copyright (c) 2023-2024 Jean Schmitz. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,71 +17,77 @@ package com.talkforgeai.backend.storage; import jakarta.annotation.PostConstruct; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; @Service public class FileStorageService { - public static final String TALK_FORGE_DIR = ".talkforgeai"; - public static final Logger LOGGER = LoggerFactory.getLogger(FileStorageService.class); + public static final String TALK_FORGE_DIR = ".talkforgeai"; + public static final Logger LOGGER = LoggerFactory.getLogger(FileStorageService.class); + + @Value("${talkforgeai.datadir:}") + private String configDataDirectory; - @Value("${talkforgeai.datadir:}") - private String configDataDirectory; + private Path dataDirectory; - private Path dataDirectory; + public FileStorageService() { + } - public FileStorageService() { + @PostConstruct + private void postConstruct() { + if (configDataDirectory != null && !configDataDirectory.isEmpty()) { + this.dataDirectory = Path.of(configDataDirectory); + + } else { + this.dataDirectory = Paths.get(System.getProperty("user.home")) + .resolve(TALK_FORGE_DIR) + .normalize(); } - @PostConstruct - private void postConstruct() { - if (configDataDirectory != null && !configDataDirectory.isEmpty()) { - this.dataDirectory = Path.of(configDataDirectory); + LOGGER.info("Data directory set to {}", this.dataDirectory); + } - } else { - this.dataDirectory = Paths.get(System.getProperty("user.home")) - .resolve(TALK_FORGE_DIR) - .normalize(); - } + public Path getTempDirectory() { + return this.dataDirectory.resolve("temp"); + } - LOGGER.info("Data directory set to {}", this.dataDirectory); - } + public Path getDataDirectory() { + return this.dataDirectory; + } - public Path getDataDirectory() { - return this.dataDirectory; - } + public Path getAssistantsDirectory() { + return getDataDirectory().resolve("assistants"); + } - public Path getAssistantsDirectory() { - return getDataDirectory().resolve("assistants"); - } + public Path getThreadDirectory() { + return getDataDirectory().resolve("threads"); + } - public Path getThreadDirectory() { - return getDataDirectory().resolve("threads"); - } + public void createDataDirectories() { + try { + Path createdPath = Files.createDirectories(getDataDirectory()); + LOGGER.info("Created data directory {}", createdPath); - public void createDataDirectories() { - try { - Path createdPath = Files.createDirectories(getDataDirectory()); - LOGGER.info("Created data directory {}", createdPath); + createdPath = Files.createDirectories(getThreadDirectory()); + LOGGER.info("Created threads directory {}", createdPath); - createdPath = Files.createDirectories(getThreadDirectory()); - LOGGER.info("Created threads directory {}", createdPath); + createdPath = Files.createDirectories(getAssistantsDirectory()); + LOGGER.info("Created assistants directory {}", createdPath); - createdPath = Files.createDirectories(getAssistantsDirectory()); - LOGGER.info("Created assistants directory {}", createdPath); + createdPath = Files.createDirectories(getTempDirectory()); + LOGGER.info("Created temp directory {}", createdPath); - LOGGER.info("Directories created successfully"); - } catch (IOException e) { - LOGGER.error("Failed to create directory: " + e.getMessage()); - } + LOGGER.info("Directories created successfully"); + } catch (IOException e) { + LOGGER.error("Failed to create directory: " + e.getMessage()); } + } } diff --git a/backend/src/main/java/com/talkforgeai/backend/util/HashUtils.java b/backend/src/main/java/com/talkforgeai/backend/util/HashUtils.java deleted file mode 100644 index 98183541..00000000 --- a/backend/src/main/java/com/talkforgeai/backend/util/HashUtils.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2023 Jean Schmitz. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.talkforgeai.backend.util; - -import java.util.Random; - -public class HashUtils { - - /** - * Generates a random alphanumeric string of the specified length. - * - * @param length the length of the generated string. - * @return a random alphanumeric string. - */ - public static String generateRandomCode(int length) { - // Define the characters to use in the random string. - String charSet = "abcdefghijklmnopqrstuvwxyz0123456789"; - - StringBuilder randomCode = new StringBuilder(length); - Random random = new Random(); - - // Generate the random string. - for (int i = 0; i < length; i++) { - int randomIndex = random.nextInt(charSet.length()); - randomCode.append(charSet.charAt(randomIndex)); - } - - return randomCode.toString(); - } -} diff --git a/backend/src/main/java/com/talkforgeai/backend/voice/controller/SpeechToTextController.java b/backend/src/main/java/com/talkforgeai/backend/voice/controller/SpeechToTextController.java index 92474030..07c473fd 100644 --- a/backend/src/main/java/com/talkforgeai/backend/voice/controller/SpeechToTextController.java +++ b/backend/src/main/java/com/talkforgeai/backend/voice/controller/SpeechToTextController.java @@ -41,7 +41,6 @@ public SpeechToTextController(SpeechToTextService speechToTextService, @PostMapping("/convert") public ResponseEntity convert(@RequestParam("file") MultipartFile file) { - return speechToTextService.convert(file, - fileStorageService.getDataDirectory().resolve("uploads")); + return speechToTextService.convert(file, fileStorageService.getTempDirectory()); } } diff --git a/backend/src/main/java/com/talkforgeai/backend/voice/service/SpeechToTextService.java b/backend/src/main/java/com/talkforgeai/backend/voice/service/SpeechToTextService.java index e35926ed..547150ef 100644 --- a/backend/src/main/java/com/talkforgeai/backend/voice/service/SpeechToTextService.java +++ b/backend/src/main/java/com/talkforgeai/backend/voice/service/SpeechToTextService.java @@ -16,6 +16,7 @@ package com.talkforgeai.backend.voice.service; +import com.talkforgeai.backend.service.UniqueIdGenerator; import com.talkforgeai.backend.voice.dto.TranscriptionSystem; import java.io.File; import java.io.IOException; @@ -39,16 +40,21 @@ public class SpeechToTextService { private final UniversalTranscriptionService universalTranscriptionService; - public SpeechToTextService(UniversalTranscriptionService universalTranscriptionService) { + private final UniqueIdGenerator uniqueIdGenerator; + + public SpeechToTextService(UniversalTranscriptionService universalTranscriptionService, + UniqueIdGenerator uniqueIdGenerator) { this.universalTranscriptionService = universalTranscriptionService; + this.uniqueIdGenerator = uniqueIdGenerator; } - public ResponseEntity convert(MultipartFile file, Path uploadDirectory) { + public ResponseEntity convert(MultipartFile file, Path tempDirectory) { try { - Files.createDirectories(uploadDirectory); - Path path = uploadDirectory.resolve("audio.wav"); - file.transferTo(path); - String text = transscribeAudio(path.toFile()); + Files.createDirectories(tempDirectory); + Path audioFile = tempDirectory.resolve(uniqueIdGenerator.generateAudioId() + ".wav"); + file.transferTo(audioFile); + String text = transscribeAudio(audioFile.toFile()); + Files.delete(audioFile); return ResponseEntity.ok(text); } catch (IOException e) { LOGGER.error("Failed to create directory or transfer file: {}", e.getMessage()); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 00aadbd9..981b3222 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,6 +11,7 @@ "@huggingface/inference": "^1.6.2", "@popperjs/core": "^2.11.7", "@stomp/stompjs": "^7.0.0", + "@vueuse/core": "^10.9.0", "axios": "^1.6.0", "bootstrap": "^5.3.1", "bootstrap-icons": "^1.10.5", @@ -3131,6 +3132,11 @@ "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", "dev": true }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" + }, "node_modules/@types/webpack-env": { "version": "1.18.0", "resolved": "https://registry.npmmirror.com/@types/webpack-env/-/webpack-env-1.18.0.tgz", @@ -4386,6 +4392,89 @@ "vuetify": "^3.0.0" } }, + "node_modules/@vueuse/core": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.9.0.tgz", + "integrity": "sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.9.0", + "@vueuse/shared": "10.9.0", + "vue-demi": ">=0.14.7" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.9.0.tgz", + "integrity": "sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.9.0.tgz", + "integrity": "sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==", + "dependencies": { + "vue-demi": ">=0.14.7" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.11.6.tgz", diff --git a/frontend/package.json b/frontend/package.json index d6546370..6b1bc1de 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,57 +1,58 @@ { - "name" : "talkforgeai-client", - "version" : "0.2.1", - "private" : true, - "scripts" : { - "serve" : "vue-cli-service serve", - "build" : "vue-cli-service build", - "test:unit" : "vue-cli-service test:unit", - "clean" : "rm -rf node_modules target", - "lint" : "vue-cli-service lint" + "name": "talkforgeai-client", + "version": "0.2.1", + "private": true, + "scripts": { + "serve": "vue-cli-service serve", + "build": "vue-cli-service build", + "test:unit": "vue-cli-service test:unit", + "clean": "rm -rf node_modules target", + "lint": "vue-cli-service lint" }, - "dependencies" : { - "@huggingface/inference" : "^1.6.2", - "@popperjs/core" : "^2.11.7", - "@stomp/stompjs" : "^7.0.0", - "axios" : "^1.6.0", - "bootstrap" : "^5.3.1", - "bootstrap-icons" : "^1.10.5", - "core-js" : "^3.8.3", - "date-fns" : "^2.30.0", - "highlight.js" : "^11.8.0", - "html-to-text" : "^9.0.5", - "katex" : "^0.16.9", - "lodash" : "^4.17.21", - "pinia" : "^2.1.3", - "recordrtc" : "^5.6.2", - "vue" : "^3.2.13", - "vue-class-component" : "^8.0.0-0", - "vue-router" : "^4.0.3", - "vuetify" : "^3.5.7" + "dependencies": { + "@huggingface/inference": "^1.6.2", + "@popperjs/core": "^2.11.7", + "@stomp/stompjs": "^7.0.0", + "@vueuse/core": "^10.9.0", + "axios": "^1.6.0", + "bootstrap": "^5.3.1", + "bootstrap-icons": "^1.10.5", + "core-js": "^3.8.3", + "date-fns": "^2.30.0", + "highlight.js": "^11.8.0", + "html-to-text": "^9.0.5", + "katex": "^0.16.9", + "lodash": "^4.17.21", + "pinia": "^2.1.3", + "recordrtc": "^5.6.2", + "vue": "^3.2.13", + "vue-class-component": "^8.0.0-0", + "vue-router": "^4.0.3", + "vuetify": "^3.5.7" }, - "devDependencies" : { - "@mdi/font" : "^7.4.47", - "@types/html-to-text" : "^9.0.1", - "@types/jest" : "^27.0.1", - "@types/lodash" : "^4.14.198", - "@typescript-eslint/eslint-plugin" : "^5.4.0", - "@typescript-eslint/parser" : "^5.4.0", - "@vue/cli-plugin-babel" : "~5.0.0", - "@vue/cli-plugin-eslint" : "~5.0.0", - "@vue/cli-plugin-router" : "~5.0.0", - "@vue/cli-plugin-typescript" : "~5.0.0", - "@vue/cli-plugin-unit-jest" : "~5.0.0", - "@vue/cli-service" : "~5.0.0", - "@vue/eslint-config-typescript" : "^9.1.0", - "@vue/test-utils" : "^2.0.0-0", - "@vue/vue3-jest" : "^27.0.0-alpha.1", - "babel-jest" : "^27.0.6", - "eslint" : "^7.32.0", - "eslint-plugin-vue" : "^8.0.3", - "jest" : "^27.0.5", - "material-design-icons-iconfont" : "^6.7.0", - "ts-jest" : "^27.0.4", - "typescript" : "~4.7.4", - "webpack-plugin-vuetify" : "^3.0.2" + "devDependencies": { + "@mdi/font": "^7.4.47", + "@types/html-to-text": "^9.0.1", + "@types/jest": "^27.0.1", + "@types/lodash": "^4.14.198", + "@typescript-eslint/eslint-plugin": "^5.4.0", + "@typescript-eslint/parser": "^5.4.0", + "@vue/cli-plugin-babel": "~5.0.0", + "@vue/cli-plugin-eslint": "~5.0.0", + "@vue/cli-plugin-router": "~5.0.0", + "@vue/cli-plugin-typescript": "~5.0.0", + "@vue/cli-plugin-unit-jest": "~5.0.0", + "@vue/cli-service": "~5.0.0", + "@vue/eslint-config-typescript": "^9.1.0", + "@vue/test-utils": "^2.0.0-0", + "@vue/vue3-jest": "^27.0.0-alpha.1", + "babel-jest": "^27.0.6", + "eslint": "^7.32.0", + "eslint-plugin-vue": "^8.0.3", + "jest": "^27.0.5", + "material-design-icons-iconfont": "^6.7.0", + "ts-jest": "^27.0.4", + "typescript": "~4.7.4", + "webpack-plugin-vuetify": "^3.0.2" } } diff --git a/frontend/src/components/common/WhisperComponent.vue b/frontend/src/components/common/WhisperComponent.vue index 7b805979..88f5ad36 100644 --- a/frontend/src/components/common/WhisperComponent.vue +++ b/frontend/src/components/common/WhisperComponent.vue @@ -15,10 +15,13 @@ -->