diff --git a/electron/main/index.html b/electron/main/index.html index 6099453..afb9797 100644 --- a/electron/main/index.html +++ b/electron/main/index.html @@ -7,63 +7,154 @@ + +
+
+ +
+ +

Video to document

+
+ +
+
1. Step
+
2. Step
+
3. Step
+
4. Step
+
5. Step
+
6. Step
+
-
-
- - - - -
- +
+ -

Video to document

+
-
-

Drag and drop video file

- -
No video chosen
-
- +
+
+

Drag and drop video file

+ +
No video chosen
+
+ +
+ + +
+
+ + + + + + + + + + - -
-
- -
- - -
+ -
- - -
- - -
-
- 0% -
-
- diff --git a/electron/main/preload.js b/electron/main/preload.js index b027cde..26aa314 100644 --- a/electron/main/preload.js +++ b/electron/main/preload.js @@ -15,12 +15,22 @@ try { getModuleNames: () => ipcRenderer.invoke('get-module-names') }) - - - ipcRenderer.on("progress", (event, resp) => { - alert(`Finished step ${resp.curstep} of ${resp.totalsteps}`) + contextBridge.exposeInMainWorld('electron', { + progress: (callback) => ipcRenderer.on('progress', callback) }) + contextBridge.exposeInMainWorld('audios', { + speakerAudios: (callback) => ipcRenderer.on('speakerAudios', callback) + }) + contextBridge.exposeInMainWorld("submitSpeaker", { + submitSpeaker: (speaker_names) => {ipcRenderer.send("speaker_submit", speaker_names)} + }) + + contextBridge.exposeInMainWorld("download", { + file_download: () => {ipcRenderer.send("file_download")} + }) + + ipcRenderer.on("error", (event, err) => {alert(err)}) } catch (error) { console.log("Error in preload.js"); diff --git a/electron/main/renderer.js b/electron/main/renderer.js index fcdd894..f972d78 100644 --- a/electron/main/renderer.js +++ b/electron/main/renderer.js @@ -33,7 +33,6 @@ uploadContainer.addEventListener("drop", (e) => { window.addEventListener('load', async (e) => { try { - console.log("test"); loadLanguageOptions(); const value = await window.onStartup.getModuleNames(); loadAiOptions(value.ai_modules); @@ -55,24 +54,16 @@ language_option.addEventListener('change', (e)=>{ }); +//listener for the file explorer search when something got selected videoUpload.addEventListener("change", () => { try { - activateSubmitBtn(videoUpload.files.length > 0); + if (videoUpload.files.length > 0) { + const file = videoUpload.files; + handleFiles(file); + } } catch (error) { console.log(error); } - -}); - -//listener for the file explorer search when something got selected -videoUpload.addEventListener('change', () => { - try { - handleFiles(videoUpload.files); - } catch (error) { - console.log("Error in manualBtn EventListener change"); - console.log(error); - } - }); //listener for the file explorer search @@ -84,4 +75,169 @@ manualUploadBtn.addEventListener('click', () => { console.log(error); } -}); \ No newline at end of file +}); + +stepButtons.forEach(btn => { + btn.addEventListener("click", () => { + try { + const step = parseInt(btn.dataset.step); + showStep(step); + } catch (error) { + + } + }); +}); + +prevBtn.addEventListener("click", () => { + try { + if (currentStep > 1) showStep(currentStep - 1); + } catch (error) { + + } +}); + +nextBtn.addEventListener("click", () => { + try { + if(currentStep < totalSteps) showStep(currentStep + 1); + } catch (error) { + + } +}); + +//Checkboxlistener so that only one can be selected at a time +docFormat.addEventListener("change", (e) =>{ + try { + if(docFormat.checked){ + docFormatSummary1.checked = false; + docFormatSummary2.checked = false; + docFormatSummary3.checked = false; + docFormatCustom.checked = false; + } + } catch (error) { + + } +}); +docFormatSummary1.addEventListener("change", (e) =>{ + try { + if(docFormatSummary1.checked){ + docFormat.checked = false; + docFormatSummary2.checked = false; + docFormatSummary3.checked = false; + docFormatCustom.checked = false; + } + } catch (error) { + + } +}); +docFormatSummary2.addEventListener("change", (e) =>{ + try { + if(docFormatSummary2.checked){ + docFormatSummary1.checked = false; + docFormat.checked = false; + docFormatSummary3.checked = false; + docFormatCustom.checked = false; + } + } catch (error) { + + } +}); +docFormatSummary3.addEventListener("change", (e) =>{ + try { + if(docFormatSummary3.checked){ + docFormatSummary1.checked = false; + docFormatSummary2.checked = false; + docFormat.checked = false; + docFormatCustom.checked = false; + } + } catch (error) { + + } +}); +docFormatCustom.addEventListener("change", (e) =>{ + try { + if(docFormatCustom.checked){ + docFormatSummary1.checked = false; + docFormatSummary2.checked = false; + docFormatSummary3.checked = false; + docFormat.checked = false; + } + } catch (error) { + + } +}); + +//Speaker change listener +cur_speaker.addEventListener("change", (e) =>{ + try { + document.getElementById("speakerAudioViewer").src = valy[e.target.value].source; + } catch (error) { + + } +}); + +window.audios.speakerAudios((event, arg) => { + setSpeakerAudiosValue(arg); + loadSpeakerOptions(arg); +}); + +window.electron.progress((event, arg) => { + if(arg.curstep == 1){ + setCircleOne(); + }else if(arg.curstep == 2){ + setCircleZwo(); + } else if(arg.curstep == 3){ + setCircleThree(); + }else if(arg.curstep == 4){ + setCircleFour(); + } +}); + +function setCircleOne(){ + try { + if(document.getElementById("box1").style.backgroundColor == "green"){ + document.getElementById("box1").style.backgroundColor = "red"; + }else{ + document.getElementById("box1").style.backgroundColor = "green"; + } + } catch (error) { + } + +}; +function setCircleZwo(){ + try { + if(document.getElementById("box2").style.backgroundColor == "green"){ + document.getElementById("box2").style.backgroundColor = "red"; + }else{ + document.getElementById("box2").style.backgroundColor = "green"; + } + } catch (error) { + + } + +}; +function setCircleThree(){ + try { + if(document.getElementById("box3").style.backgroundColor == "green"){ + document.getElementById("box3").style.backgroundColor = "red"; + }else{ + document.getElementById("box3").style.backgroundColor = "green"; + } + } catch (error) { + + } + +}; +function setCircleFour(){ + try { + if(document.getElementById("box4").style.backgroundColor == "green"){ + document.getElementById("box4").style.backgroundColor = "red"; + }else{ + document.getElementById("box4").style.backgroundColor = "green"; + } + } catch (error) { + + } +}; + + + diff --git a/electron/main/script.js b/electron/main/script.js index 51935ed..6500dbc 100644 --- a/electron/main/script.js +++ b/electron/main/script.js @@ -1,4 +1,4 @@ - +let currentVideoPath = null; //function to check if one checkbox is at least klicked function checkBoxes() { try { @@ -25,7 +25,6 @@ function checkBoxes() { var pathTest = window.electronAPI.getFilePath(videoUpload.files[0]); var pathToLower = pathTest.toLowerCase(); if(testEndings.some(e => pathToLower.endsWith(e))){ - document.getElementById("progressbar").style.visibility = "visible"; //assembly of the json for the main const selectedStyles = [checkedCounter]; @@ -37,6 +36,11 @@ function checkBoxes() { iter++; } }); + document.getElementById("testy").style.visibility = "visible" + document.getElementById("box1").style.backgroundColor = "red"; + document.getElementById("box2").style.backgroundColor = "red"; + document.getElementById("box3").style.backgroundColor = "red"; + document.getElementById("box4").style.backgroundColor = "red"; console.log(selectedCheckboxes); const outputType = document.getElementById("output_type"); const transcriptionType = document.getElementById("transkript_type"); @@ -44,15 +48,15 @@ function checkBoxes() { const sendingPackage = { "video": { "module":"extraction-video-to-audio", - "inputVideoPath": pathTest, - "outputType": outputType.value + "inputVideoPath": pathTest }, "transcription": { "module": transcriptionType.value }, "document": { "module":aiType.value, - "styles": selectedStyles + "styles": selectedStyles, + "outputType": outputType.value } }; window.submit.submit(sendingPackage) @@ -105,6 +109,7 @@ function handleFiles(files) { const filePath = window.explorer.onFileDrop(files[0]) videoUpload.files = files; fileName.textContent = `Chosen video: ${file.name}`; + currentVideoPath = filePath; generateThumbnail(filePath); activateSubmitBtn(true); } @@ -167,6 +172,25 @@ function loadTranscriptionOptions(options){ } } +//function to load data type options to the drop down list +function loadDataTypeOptions(options){ + try { + var menu = document.getElementById('output_type'); + var object_holdy; + var choice ; + object_holdy = options + for(i = 0; i < options.length; i++){ + choice = document.createElement('option'); + choice.textContent = object_holdy[i].displayname; + choice.value = object_holdy[i].name; + menu.appendChild(choice); + } + } catch (error) { + console.log("Error in script.js function loadDataTypeOptions"); + console.log(error); + } +} + //function to load language options to the drop down list function loadLanguageOptions(){ try { @@ -180,16 +204,48 @@ function loadLanguageOptions(){ choice.value = object_holdy[i]; menu.appendChild(choice); } - } catch (error) { console.log("Error in script.js loadLanguageOptions function"); console.log(error); } } +//function to load speaker options to the drop down list +function loadSpeakerOptions(options){ + try { + var menu = document.getElementById('speaker_option'); + var object_holdy; + var choice; + object_holdy = options.keys(); + for(i = 0; i < options.length; i++){ + choice = document.createElement('option'); + choice.textContent = options[object_holdy[i]].name; + choice.value = options[object_holdy[i]].name; + menu.appendChild(choice); + } + } catch (error) { + console.log("Error in script.js loadSpeakerOptions function"); + console.log(error); + } +} + +//function to load speaker audio file options to the drop down list +function loadSpeakerAudio(option){ + try { + var menu = document.getElementById('speakerAudioViewer'); + var aud = document.createElement("source"); + aud.src = options; + menu.appendChild(aud); + } catch (error) { + console.log("Error in script.js loadSpeakerAudio function"); + console.log(error); + } +} + + function activateSubmitBtn(hasFile){ try { - submitButton.disabled = !hasFile; + document.getElementById("submitButton").disabled = !hasFile; } catch (error) { console.log(error); } @@ -214,3 +270,68 @@ function generateThumbnail(path){ videoElement.style.maxHeight = 40; videoElement.autoplay = false; } + +//Step-navigation +const steps = document.querySelectorAll(".step"); +const stepButtons = document.querySelectorAll(".step-item"); +let currentStep = 1; +const totalSteps = steps.length; + +function showStep(stepNumber) { + if (stepNumber < 1 || stepNumber > totalSteps){ + console.error("StepNumber out of Bounds", stepNumber); + return; + } + steps.forEach(step => step.style.display = "none"); + document.getElementById("step" + stepNumber).style.display = "block"; + + stepButtons.forEach(btn => btn.classList.remove("active")); + document.querySelector(`.step-item[data-step="${stepNumber}"]`).classList.add("active"); + const activeBtn = document.querySelector(`.step-item[data-step="${stepNumber}"]`); + + if(activeBtn) activeBtn.classList.add("active"); + + prevBtn.disabled = stepNumber == 1; + nextBtn.disabled = stepNumber === totalSteps; + currentStep = stepNumber; +} + + +//Audio value setter +var speakerAudios = {}; +var speakerEndValues = {}; +function setSpeakerAudiosValue(valy){ + try { + speakerAudios = valy; + speakerRewriten = valy; + document.getElementById("speakerAudioViewer").src = valy.speakerA.source; + } catch (error) { + + } +} + +function rewriteSpeakerName(){ + try { + var tempy = document.getElementById("cur_speaker").textContent; + speakerAudios[tempy].name = document.getElementById("newSpeaker").textContent; + document.getElementById("cur_speaker").textContent = document.getElementById("newSpeaker").textContent; + } catch (error) { + + } +} + +function sendSpeakerPackages(){ + try { + window.sendSpeakerPackages(speakerAudios); + } catch (error) { + + } +} + +function fileDownload() { + try { + window.download.file_download(); + } catch (error) { + console.error("Download failed:", error); + } +} diff --git a/electron/main/style.css b/electron/main/style.css index 0912516..cc4eed9 100644 --- a/electron/main/style.css +++ b/electron/main/style.css @@ -6,11 +6,33 @@ body { align-items: center; height: 100vh; background-color: #f2f3f4; - gap: 15px; + gap: 10px; margin: 0; } - +#h1 { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + margin: 0; + z-index: 20; + +} + +#h1-wrapper { + position: relative; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + width: 770px; + height: 60px; + background-color: #FFF; + box-shadow: 0px 4px 10px rgba(0,0,0,0.1); + border-radius: 5px; + margin-bottom: 10px; + display: flex; + align-items: center; +} + .upload-container { background: white; padding: 40px; @@ -91,25 +113,73 @@ input[type="file"] { display: none; } -.checkbox-container{ -margin-top: 8px; -display: flex; -align-items: center; -gap: 5px; -} - .checkbox-group { - margin-top: 15px; - margin-bottom: 15px; + --borderColor: #007bfff5; + --borderWidth: .125em; + } + + .checkbox-group input[type=checkbox] { + -webkit-appearance: none; + appearance: none; + vertical-align: middle; + background: #fff; + font-size: 1.8em; + border-radius: 0.125em; + display: inline-block; + border: var(--borderWidth) solid var(--borderColor); + width: 1em; + height: 1em; + position: relative; + } + .checkbox-group input[type=checkbox]:before, + .checkbox-group input[type=checkbox]:after { + content: ""; + position: absolute; + background: var(--borderColor); + width: calc(var(--borderWidth) * 3); + height: var(--borderWidth); + top: 50%; + left: 10%; + transform-origin: left center; + } + .checkbox-group input[type=checkbox]:before { + transform: rotate(45deg) translate(calc(var(--borderWidth) / -2), calc(var(--borderWidth) / -2)) scaleX(0); + transition: transform 200ms ease-in 200ms; + } + .checkbox-group input[type=checkbox]:after { + width: calc(var(--borderWidth) * 5); + transform: rotate(-45deg) translateY(calc(var(--borderWidth) * 2)) scaleX(0); + transform-origin: left center; + transition: transform 200ms ease-in; + } + .checkbox-group input[type=checkbox]:checked:before { + transform: rotate(45deg) translate(calc(var(--borderWidth) / -2), calc(var(--borderWidth) / -2)) scaleX(1); + transition: transform 200ms ease-in; + } + .checkbox-group input[type=checkbox]:checked:after { + width: calc(var(--borderWidth) * 5); + transform: rotate(-45deg) translateY(calc(var(--borderWidth) * 2)) scaleX(1); + transition: transform 200ms ease-out 200ms; + } + .checkbox-group input[type=checkbox]:focus { + outline: calc(var(--borderWidth) / 2) dotted rgba(0, 0, 0, 0.25); + } + +.checkbox-container{ + margin-top: 8px; display: flex; - flex-direction: column; - gap: 10px; - align-items: flex-start; + justify-items: left; + align-items: center; + gap: 5px; } - + + .submit-btn { + display: flex; + justify-content: center; padding: 10px 20px; - margin-top: 10px; + margin-left: 80px; + margin-top: 30px; margin-bottom: 10px; background-color: #007BFF; color: white; @@ -126,13 +196,12 @@ gap: 5px; } .mitte { - background-color: #FDFCFA; + background-color: #FFF; display: flex; width: 700px; flex-direction: column; align-items: center; - padding: 5% 50px; - margin-top: 20px; + padding: 40px; gap: 10px; border: 0px; border-color: black; @@ -141,14 +210,11 @@ gap: 5px; box-shadow: 0px 4px 10px rgba(0,0,0,0.1); } -h1 { - align-content: center; -} - .progressbar{ position: relative; width: 210px; height: 30px; + margin: 50px 20px 5px 20px; background: rgb(42, 46, 78); border-radius: 5px; overflow: hidden; @@ -173,9 +239,10 @@ h1 { .dropdownMenus { display: flex; - justify-content: flex-end; + justify-content: center; margin-top: 1px; - gap: 150px; + margin-bottom: 30px; + gap: 170px; padding: 2px 10px 2px 10px; } @@ -184,5 +251,229 @@ h1 { } .labelDiv { - gap: 200px; + gap: 60px; + display: flex; + justify-content: center; + overflow-wrap:inherit; + padding-bottom: 20px; + margin-top: 40px; + margin-bottom: 10px; +} + +/*Step bar*/ +.step-nav { + display: flex; + gap: 20px; + justify-content: center; + background: #fff; + padding: 10px 30px; + border-radius: 6px; + box-shadow: 0 4px 10px rgba(0,0,0,0.1); +} + +.step-item { + padding: 10px 25px; + border-radius: 8px; + background: #eee; + cursor: pointer; + font-weight: bold; + transition: 0.2s; +} + +.step-item.active { + background: #007BFF; + color: white; +} + +.step-item:hover { + background: #d9d9d9; +} + +/*panels*/ +.step { + margin-top: 70px; + display: flex; + flex-direction: column; + justify-content: center; + min-height: 400px; +} + +/*Navigation arrows*/ +.step-nav-arrows { + display: flex; + justify-content: space-between; + align-items: center; +} + +.middle-container-wrapper { + display: flex; + align-items: center; + justify-content: center; + gap: 30px; + width: max-content; + height: auto; +} + +.navBtn { + display: flex; + justify-content: center; + padding: 10px 25px; + background-color: #007BFF; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 14px; +} + +.navBtn:disabled { + background-color: #ccc; + cursor: not-allowed; +} + +.testy{ + background-color: #FFF; + display: flex; + width: auto; + flex-direction: row; + align-items: center; + margin-top: 20px; + gap: 10px; + border: 0px; + border-color: black; + border-style: solid; + border-radius: 6px; + visibility: hidden; +} + +.box2 { + background-color: red; + width: 40px; + height: 40px; + padding: 5px; + border: 1px solid black; + margin: 5px; + border-radius: 100px; +} + +li { + color: #FFF; +} + +.p-menu1 { + margin-left: 20px; + z-index: 10; +} + +.hamburger1 { + height: 45px; + margin: 0; + padding-top: 8px; + display: grid; + grid-template-rows: repeat(3, 1fr); + justify-items: center; + z-index: 120; +} + +.hamburger1 div { + background-color: rgb(61, 61, 61); + position: relative; + width: 40px; + height: 5px; + margin-top: 0; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} + +#toggle1 { + display: none; +} + +#toggle1:checked + .hamburger1 .top { + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); + margin-top: 22.5px; +} + +#toggle1:checked + .hamburger1 .meat { + -webkit-transform: rotate(45deg); + transform: rotate(45deg); + margin-top: -5px; +} + +#toggle1:checked + .hamburger1 .bottom { + -webkit-transform: scale(0); + transform: scale(0); +} + +#toggle1:checked ~ .menu1 { + height: 150px; + width: 300px; +} + +/* Menu */ +.menu1 { + position: absolute; + top: 55px; + left: 20px; + width: 240px; + border-radius: 5px; + background-color: #1C3B69; + margin: 0; + display: -ms-grid; + display: grid; + grid-template-rows: 1fr repeat(4, 0.5fr); + grid-row-gap: 25px; + padding: 0; + list-style: none; + clear: both; + width: auto; + text-align: center; + height: 0px; + overflow: hidden; + transition: height 0.3s ease, width 0.3s ease; + z-index: 9999; + -webkit-transition: all 0.3s ease; +} + +.menu1 li:first-child { + margin-top: 30px; +} + +.menu1 li:last-child { + margin-bottom: 30px; +} + +.li1 { + width: 100%; + margin: 0; + padding: 10px 0; + font: 700 20px 'Oswald', sans-serif; +} + +.li1:hover { + background-color: #FFF; + color: rgb(61, 61, 61); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + -webkit-transition: all 0.3s ease; + transition: all 0.3s ease; +} + +#step2, #step3, #step5 { + font-size: larger; +} + +.download-btn { + display: flex; + justify-content: center; + padding: 10px 20px; + margin-left: 80px; + margin-top: 30px; + margin-bottom: 10px; + background-color: #007BFF; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 14px; } \ No newline at end of file diff --git a/main.js b/main.js index 7a8e730..6be3875 100644 --- a/main.js +++ b/main.js @@ -61,7 +61,7 @@ let mainWindow; function createWindow() { mainWindow = new electron.BrowserWindow({ - width: 800, + width: 1200, height: 800, webPreferences: { nodeIntegration: false, @@ -124,10 +124,12 @@ electron.ipcMain.handle('get-module-names', async () => { // mainWindow.webContents.send("modules", module_array) // }) - +var globalArgs = {} +var globalFinalHtmlPath = "" electron.ipcMain.on("file_submit", async (event, args) => { try { + globalArgs = args let curstep = 0 let totalsteps = 3 + args.document.styles.length @@ -185,9 +187,9 @@ electron.ipcMain.on("file_submit", async (event, args) => { for (let i = 0; i < args.document.styles.length; i++) { console.log(`\n\n Running the LLM for Document Style ${i+1}`); - await mapFunctions.get("module-handler").function(args.document.module, {inputTranscriptPath: transcriptpath, documentTypePath: "./storage/documentType/meetingReport.json", language: "en"}).then(resp => { + await mapFunctions.get("module-handler").function(args.document.module, {inputTranscriptPath: transcriptpath, documentTypePath: "./storage/documentType/standard_meeting_report.txt", language: "en"}).then(resp => { console.log(resp); - transcriptpath = resp + globalFinalHtmlPath = resp curstep++ mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps}) }).catch(err => { @@ -225,6 +227,9 @@ electron.ipcMain.on("file_submit", async (event, args) => { } }) +electron.ipcMain.on("file_download", async() => { + await mapFunctions.get("htmlDocumentConverter").convert({inputPath:globalFinalHtmlPath, format: globalArgs.document.outputType, showDialog: true}); +}) let q = diff --git a/requires.js b/requires.js index da3b9aa..6a7e20a 100644 --- a/requires.js +++ b/requires.js @@ -6,6 +6,9 @@ platform = process.platform mainDir = __dirname fs = require("fs") readline = require("readline") + +puppeteer = require("puppeteer") +htmltodocx = require("html-to-docx") config = require("./config/config") ffmpegPath = require('ffmpeg-static'); diff --git a/services/modules/llm-chat_gpt/chatgpt.js b/services/modules/llm-chat_gpt/chatgpt.js index d07a422..4b7c89b 100644 --- a/services/modules/llm-chat_gpt/chatgpt.js +++ b/services/modules/llm-chat_gpt/chatgpt.js @@ -1,7 +1,7 @@ const fs = require('fs'); const path = require('path'); -const outputDir = path.join(__dirname, "../../../storage/documents"); // path for output directory +const outputDir = path.join(__dirname, "../../../storage/documents"); // path for output directory if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); // Create output directory if it doesn't exist @@ -9,8 +9,7 @@ if (!fs.existsSync(outputDir)) { // Ensure SAIA API key is set in environment variables: export SAIA_API_KEY="your_api_key_here" const SAIA_API_KEY = process.env.SAIA_API_KEY; // Ensure SAIA API key is set in environment variables - -const SAIA_URL = "https://chat-ai.academiccloud.de/v1/chat/completions"; //URL for the REST call, used model and action +const SAIA_URL = "https://chat-ai.academiccloud.de/v1/chat/completions"; // URL for the REST call, used model and action const module_exports = { name: "llm-saia_openai_gpt", @@ -19,66 +18,72 @@ const module_exports = { description: "Generates documents using OpenAI GPT OSS 120B via SAIA platform", async function(parameter) { - try { - console.log("SAIA OpenAI GPT module invoked with parameters:", parameter); + return new Promise(async (resolve, reject) => { + try { + // console.log("SAIA OpenAI GPT module invoked with parameters:", parameter); - await this.createDocumentFromTranscript( //Call the function to create document with transcript, document type and language - parameter.inputTranscriptPath, // Path to input transcript file - parameter.documentTypePath, // Path to document type file which is chosen in the front end by the user - parameter.language // Language for the document which is chosen in the front end by the user - ); + resolve(await this.createDocumentFromTranscript( //Call the function to create document with transcript, document type and language + parameter.inputTranscriptPath, // Path to input transcript file + parameter.documentTypePath, // Path to document type file which is chosen in the front end by the user + parameter.language // Language for the document which is chosen in the front end by the user + )); + + } catch (error) { + // console.error("Error in SAIA OpenAI GPT module:", error); + reject(error) + } + }) - } catch (error) { - console.error("Error in SAIA OpenAI GPT module:", error); - } }, createDocumentFromTranscript: async function(transcriptPath, documentTypePath, language = "en") { // default language is English - try { - const transcript = await fs.promises.readFile(transcriptPath, "utf-8"); //read transcript file from Path - const documentType = await fs.promises.readFile(documentTypePath, "utf-8"); //read document type from Path - const promptText = `${documentType}, in language ${language}, transcript:\n\n${transcript}`; //combine doc type, language and transcript - Change prompt here if needed + return new Promise(async(resolve, reject) => { + try { + const transcript = await fs.promises.readFile(transcriptPath, "utf-8"); //read transcript file from Path + const documentType = await fs.promises.readFile(documentTypePath, "utf-8"); //read document type from Path + const promptText = `${documentType}, in language ${language}, transcript:\n\n${transcript}`; //combine doc type, language and transcript - Change prompt here if needed - // --- REST CALL --- - const response = await fetch(SAIA_URL, { - method: "POST", - headers: { - "Authorization": `Bearer ${SAIA_API_KEY}`, - "Accept": "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ - model: "openai-gpt-oss-120b", - messages: [ - { role: "system", content: "You are a helpful assistant that generates HTML documents from transcripts. Output only valid HTML content without any preamble, explanations, or markdown formatting." }, - { role: "user", content: promptText } - ], - temperature: 0 - }) - }); + // --- REST CALL --- + const response = await fetch(SAIA_URL, { //safe model response in variable + method: "POST", + headers: { + "Authorization": `Bearer ${SAIA_API_KEY}`, + "Accept": "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + model: "openai-gpt-oss-120b", + messages: [ + { role: "system", content: "You are a helpful assistant that generates HTML documents from transcripts. Output only valid HTML content without any preamble, explanations, or markdown formatting." }, + { role: "user", content: promptText } + ], + temperature: 0 + }) + }); - if (!response.ok) { //ok is true when a response was successful - const text = await response.text(); - throw new Error(`SAIA API error (${response.status}): ${text}`); + if (!response.ok) { //ok is true when a responce was successfull + const text = await response.text(); + throw new Error(`SAIA API error (${response.status}): ${text}`); + } + + const data = await response.json(); + + // Get generated text from response or default to empty string (if null) + // SAIA uses OpenAI-compatible structure: data.choices[x].message.content + const output = data.choices?.[0]?.message?.content || ""; + let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath)); // Name for the output file + // console.log(inputTranscriptName); + const outPath = path.join(outputDir, `${inputTranscriptName}.html`); // Output file path & name to make naming dynamic. Pulled from input transcript name + fs.writeFileSync(outPath, output, "utf8"); // Write output to file + + // console.log("Generated document written to:", outPath); + resolve(outPath) + + } catch (error) { + // console.error("Error generating SAIA content:", error); + reject(error) } - - const data = await response.json(); - - // Get generated text from response or default to empty string (if null) - // SAIA uses OpenAI-compatible structure: data.choices[x].message.content - const output = data.choices?.[0]?.message?.content || ""; - - let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath)); // Name for the output file - console.log(inputTranscriptName); - - const outPath = path.join(outputDir, `${inputTranscriptName}.html`); // Output file path & name to make naming dynamic. Pulled from input transcript name - fs.writeFileSync(outPath, output, "utf8"); // Write output to file - - console.log("Generated document written to:", outPath); - - } catch (error) { - console.error("Error generating SAIA content:", error); - } + }) } }; diff --git a/services/modules/llm-gemini/gemini.js b/services/modules/llm-gemini/gemini.js index cff1026..beff750 100644 --- a/services/modules/llm-gemini/gemini.js +++ b/services/modules/llm-gemini/gemini.js @@ -71,7 +71,7 @@ const module_exports = { const output = data?.candidates?.[0]?.content?.parts?.[0]?.text || ""; let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath)); // Name for the output file // console.log(inputTranscriptName); - const outPath = path.join(outputDir, `${inputTranscriptName}.md`); // Output file path & name to make naming dynamic. Pulled from input transcript name + const outPath = path.join(outputDir, `${inputTranscriptName}.html`); // Output file path & name to make naming dynamic. Pulled from input transcript name fs.writeFileSync(outPath, output, "utf8"); // Write output to file // console.log("Generated document written to:", outPath); diff --git a/services/modules/quen3/quen3.js b/services/modules/quen3/quen3.js index fd93737..0bc913a 100644 --- a/services/modules/quen3/quen3.js +++ b/services/modules/quen3/quen3.js @@ -18,66 +18,72 @@ const module_exports = { description: "Generates documents using QWEN 3 235B via SAIA platform", async function(parameter) { - try { - console.log("SAIA QWEN 3 235B module invoked with parameters:", parameter); + return new Promise(async (resolve, reject) => { + try { + // console.log("SAIA QWEN 3 235B module invoked with parameters:", parameter); - await this.createDocumentFromTranscript( // Call the function to create document with transcript, document type and language - parameter.inputTranscriptPath, // Path to input transcript file - parameter.documentTypePath, // Path to document type file which is chosen in the front end by the user - parameter.language // Language for the document which is chosen in the front end by the user - ); + resolve(await this.createDocumentFromTranscript( //Call the function to create document with transcript, document type and language + parameter.inputTranscriptPath, // Path to input transcript file + parameter.documentTypePath, // Path to document type file which is chosen in the front end by the user + parameter.language // Language for the document which is chosen in the front end by the user + )); + + } catch (error) { + // console.error("Error in SAIA QWEN 3 235B module:", error); + reject(error) + } + }) - } catch (error) { - console.error("Error in SAIA QWEN 3 235B module:", error); - } }, createDocumentFromTranscript: async function(transcriptPath, documentTypePath, language = "en") { // default language is English - try { - const transcript = await fs.promises.readFile(transcriptPath, "utf-8"); // read transcript file from Path - const documentType = await fs.promises.readFile(documentTypePath, "utf-8"); // read document type from Path - const promptText = `${documentType}, in language ${language}, transcript:\n\n${transcript}`; // combine doc type, language and transcript - Change prompt here if needed + return new Promise(async(resolve, reject) => { + try { + const transcript = await fs.promises.readFile(transcriptPath, "utf-8"); //read transcript file from Path + const documentType = await fs.promises.readFile(documentTypePath, "utf-8"); //read document type from Path + const promptText = `${documentType}, in language ${language}, transcript:\n\n${transcript}`; //combine doc type, language and transcript - Change prompt here if needed - // --- REST CALL --- - const response = await fetch(SAIA_URL, { - method: "POST", - headers: { - "Authorization": `Bearer ${SAIA_API_KEY}`, - "Accept": "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ - model: "qwen3-235b-a22b", - messages: [ - { role: "system", content: "You are a helpful assistant that generates HTML documents from transcripts. Output only valid HTML content without any preamble, explanations, or markdown formatting." }, - { role: "user", content: promptText } - ], - temperature: 0 - }) - }); + // --- REST CALL --- + const response = await fetch(SAIA_URL, { //safe model response in variable + method: "POST", + headers: { + "Authorization": `Bearer ${SAIA_API_KEY}`, + "Accept": "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + model: "qwen3-235b-a22b", + messages: [ + { role: "system", content: "You are a helpful assistant that generates HTML documents from transcripts. Output only valid HTML content without any preamble, explanations, or markdown formatting." }, + { role: "user", content: promptText } + ], + temperature: 0 + }) + }); - if (!response.ok) { // ok is true when a response was successful - const text = await response.text(); - throw new Error(`SAIA API error (${response.status}): ${text}`); + if (!response.ok) { //ok is true when a responce was successfull + const text = await response.text(); + throw new Error(`SAIA API error (${response.status}): ${text}`); + } + + const data = await response.json(); + + // Get generated text from response or default to empty string (if null) + // SAIA uses OpenAI-compatible structure: data.choices[x].message.content + const output = data.choices?.[0]?.message?.content || ""; + let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath)); // Name for the output file + // console.log(inputTranscriptName); + const outPath = path.join(outputDir, `${inputTranscriptName}.html`); // Output file path & name to make naming dynamic. Pulled from input transcript name + fs.writeFileSync(outPath, output, "utf8"); // Write output to file + + // console.log("Generated document written to:", outPath); + resolve(outPath) + + } catch (error) { + // console.error("Error generating SAIA content:", error); + reject(error) } - - const data = await response.json(); - - // Get generated text from response or default to empty string (if null) - // SAIA uses OpenAI-compatible structure: data.choices[x].message.content - const output = data.choices?.[0]?.message?.content || ""; - - let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath)); // Name for the output file - console.log(inputTranscriptName); - - const outPath = path.join(outputDir, `${inputTranscriptName}.html`); // Output file path & name to make naming dynamic. Pulled from input transcript name - fs.writeFileSync(outPath, output, "utf8"); // Write output to file - - console.log("Generated document written to:", outPath); - - } catch (error) { - console.error("Error generating SAIA content:", error); - } + }) } }; diff --git a/storage/.DS_Store b/storage/.DS_Store index 3c42b1f..7cbbcbf 100644 Binary files a/storage/.DS_Store and b/storage/.DS_Store differ diff --git a/storage/documentType/agenda.txt b/storage/documentType/agenda.txt new file mode 100644 index 0000000..7a94443 --- /dev/null +++ b/storage/documentType/agenda.txt @@ -0,0 +1,25 @@ +Du bist ein erfahrener Moderator und Projektmanager. + +AUFGABE: +Erstelle eine sinnvolle Meeting-Agenda basierend auf dem folgenden Transkript. + +ANFORDERUNGEN: +- Rekonstruiere die tatsächlichen Themenblöcke +- Ordne sie logisch und chronologisch +- Fasse ähnliche Diskussionen zusammen +- Keine irrelevanten Details aufnehmen + +STRUKTUR: +- Titel der Agenda +- Ziel des Meetings (1–2 Sätze) +- Agenda-Punkte (nummeriert) + - Thema + - Kurzbeschreibung + - Ziel des Punktes (Information, Entscheidung, Diskussion) + +STIL: +- Klar, kompakt +- Business-orientiert +- Keine Sprecher- oder Zeitangaben + +TRANSKRIPT: diff --git a/storage/documentType/custom_document.txt b/storage/documentType/custom_document.txt new file mode 100644 index 0000000..674c72c --- /dev/null +++ b/storage/documentType/custom_document.txt @@ -0,0 +1,21 @@ +Du bist ein intelligenter Dokumenten-Generator. + +AUFGABE: +Erstelle ein individuelles Dokument basierend auf: +1) dem Meeting-Transkript +2) der zusätzlichen Nutzeranweisung + +WICHTIG: +- Priorisiere die Nutzeranweisung +- Nutze das Transkript als Wissensquelle +- Struktur, Tonalität und Detailgrad anpassen +- Inhalte logisch zusammenführen + +FORMAT: +- Passe Struktur und Stil an den Nutzerwunsch an +- Klare Überschriften +- Keine Sprecher- oder Zeitangaben + +TRANSKRIPT & NUTZERANWEISUNG: + + diff --git a/storage/documentType/meetingReport.json b/storage/documentType/meetingReport.json deleted file mode 100644 index 04626df..0000000 --- a/storage/documentType/meetingReport.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "FORMAT": "markdown", - "GOAL":"Generate a structured meeting report (Markdown). **Output ONLY:** final .md. No meta.", - "STRUCTURE": { - "titlepage": ["title","date","start","end","duration","location","host","participants"], - "toc": "[section](#anchor) — HH:MM:SS", - "section": { - "h2": " — HH:MM:SS", - "summary": "1 sentence", - "key_points": "<=5 bullets, quotes optional", - "decisions": "list: text | owner | due", - "actions": "table: id | task | owner | due | status" - }, - "exec_summary": "3 short sentences", - "consolidated": ["decisions", "actions"], - "appendix": "optional" - }, - "STYLE": { - "tone": "neutral, concise", - "ts_format": "HH:MM:SS", - "no_meta": true - }, - "PROCESS": { - "timestamps": "use if present; else estimate minimal", - "speakers": "use labels; else Speaker X", - "long_transcripts": "chunk → summarize → merge", - "unclear": "UNKLAR:" - }, - - "JSON_OUTPUT_OPTIONAL": true, - - "PROMPT_SNIPPET": "Generate meeting report in markdown using STRUCTURE and STYLE. Output only the report." -} \ No newline at end of file diff --git a/storage/documentType/result_protocol.txt b/storage/documentType/result_protocol.txt new file mode 100644 index 0000000..caf9570 --- /dev/null +++ b/storage/documentType/result_protocol.txt @@ -0,0 +1,25 @@ +Du bist ein professioneller Protokollführer. + +AUFGABE: +Erstelle ein Ergebnisprotokoll basierend auf dem Meeting-Transkript. + +FOKUS: +- Ergebnisse statt Diskussionen +- Entscheidungen, Beschlüsse, Vereinbarungen +- Klare, überprüfbare Aussagen + +STRUKTUR: +1. Meeting-Informationen +2. Ergebnisse je Thema + - Thema + - Ergebnis / Beschluss +3. Entscheidungen +4. Aufgaben & Verantwortlichkeiten +5. Offene Punkte + +REGELN: +- Keine Meinungen oder Spekulationen +- Keine Zeit- oder Sprecherangaben +- Sachlich, formal + +TRANSKRIPT: diff --git a/storage/documentType/sprint_planning_note.txt b/storage/documentType/sprint_planning_note.txt new file mode 100644 index 0000000..df210e8 --- /dev/null +++ b/storage/documentType/sprint_planning_note.txt @@ -0,0 +1,34 @@ +Du bist ein erfahrener Scrum Master. + +AUFGABE: +Erstelle Sprint Planning Notes aus dem folgenden Meeting-Transkript. + +FOKUS: +- Sprint-Ziele +- User Stories / Tasks +- Abhängigkeiten +- Risiken +- Commitments + +STRUKTUR: +1. Sprint Overview + - Sprint-Ziel + - Zeitraum (falls erwähnt) + +2. Geplante Arbeit + - User Story / Task + - Beschreibung + - Akzeptanzkriterien (falls ableitbar) + +3. Abhängigkeiten & Blocker + +4. Risiken & Annahmen + +5. Vereinbarungen / Team-Commitments + +STIL: +- Agile-konform +- Klar & umsetzungsorientiert +- Bullet Points bevorzugen + +TRANSKRIPT: diff --git a/storage/documentType/standard_meeting_report.txt b/storage/documentType/standard_meeting_report.txt index 7a202f1..c6d9d18 100644 --- a/storage/documentType/standard_meeting_report.txt +++ b/storage/documentType/standard_meeting_report.txt @@ -1,217 +1,44 @@ -Generate a structured meeting report in HTML using STRUCTURE and STYLE. -Output ONLY the final .md document — no meta comments, no explanations. +Du bist ein professioneller Meeting-Analyst und Business Writer. -Follow exactly the STRUCTURE defined below. -Follow exactly the STYLE rules. -Use timestamps in HH:MM:SS format. -If information is missing, use: Unclear:. +AUFGABE: +Erstelle einen strukturierten Follow-up Report basierend auf dem folgenden Meeting-Transkript. -==================== STRUCTURE & RULES ==================== +ANFORDERUNGEN: +- Fasse Inhalte sinngemäß zusammen +- Entferne Redundanzen und Smalltalk +- Formuliere klar, präzise und professionell +- Verwende neutrale Business-Sprache +- Keine Zeitstempel oder Sprecher-Namen zitieren +- Leite Entscheidungen und Aufgaben logisch ab, wenn sie implizit sind +- Markiere offene Punkte klar -{ - "FORMAT": "HTML", +STRUKTUR DES DOKUMENTS: +1. Titel & Metadaten + - Meetingtitel (ableiten) + - Datum (falls im Transkript erwähnt, sonst „nicht angegeben“) + - Teilnehmer (zusammengefasst) - "STRUCTURE": { - "titlepage": [ - "title", - "date", - "start", - "end", - "duration", - "location", - "host", - "participants" - ], +2. Executive Summary (max. 5 Bullet Points) - "toc": "[section](#anchor) — HH:MM:SS", +3. Besprochene Themen + - Thema + - Kernaussagen + - Relevante Erkenntnisse - "section": { - "h2": " — HH:MM:SS", - "summary": "exactly 1 concise sentence", - "key_points": "maximum 5 bullet points; quotes optional", - "decisions": "list items formatted as: decision text | owner | due date", - "actions": "HTML table: id | task | owner | due | status" - }, +4. Entscheidungen + - Entscheidung + - Kontext / Begründung - "exec_summary": "exactly 3 short sentences", +5. Action Items + - Aufgabe + - Verantwortlich (falls ableitbar) + - Ziel / Zweck - "consolidated": [ - "decisions", - "actions" - ], +6. Offene Fragen & Risiken - "appendix": "optional" - }, +STIL: +- Überschriften klar strukturiert +- Bullet Points bevorzugen +- Präzise, keine Umgangssprache - "STYLE": { - "tone": "neutral, concise, professional", - "ts_format": "HH:MM:SS", - "no_meta": true - }, - - "PROCESS": { - "timestamps": "use transcript timestamps if present; otherwise estimate minimal", - "speakers": "use names if available; else Speaker X", - "long_transcripts": "split → summarize → merge", - "unclear": "Unclear:" - }, - - "PROMPT_SNIPPET": "Generate meeting report in HTML using STRUCTURE and STYLE. Output only the report." -} - -============================================================ - -Insert all generated content into the following HTML TEMPLATE: - -# {{title}} - -**Date:** {{date}} -**Start:** {{start}} -**End:** {{end}} -**Duration:** {{duration}} -**Location:** {{location}} -**Host:** {{host}} -**Participants:** {{participants}} - ---- - -## Table of Contents -{{toc}} - ---- -Generate a structured meeting report in HTML using STRUCTURE and STYLE. -Output ONLY the final .md document — no meta comments, no explanations. - -Follow exactly the STRUCTURE defined below. -Follow exactly the STYLE rules. -Use timestamps in HH:MM:SS format. -If information is missing, use: UNKLAR:. - -==================== STRUCTURE & RULES ==================== - -{ - "FORMAT": "HTML", - - "STRUCTURE": { - "titlepage": [ - "title", - "date", - "start", - "end", - "duration", - "location", - "host", - "participants" - ], - - "toc": "[section](#anchor) — HH:MM:SS", - - "section": { - "h2": " — HH:MM:SS", - "summary": "exactly 1 concise sentence", - "key_points": "maximum 5 bullet points; quotes optional", - "decisions": "list items formatted as: decision text | owner | due date", - "actions": "HTML table: id | task | owner | due | status" - }, - - "exec_summary": "exactly 3 short sentences", - - "consolidated": [ - "decisions", - "actions" - ], - - "appendix": "optional" - }, - - "STYLE": { - "tone": "neutral, concise, professional", - "ts_format": "HH:MM:SS", - "no_meta": true - }, - - "PROCESS": { - "timestamps": "use transcript timestamps if present; otherwise estimate minimal", - "speakers": "use names if available; else Speaker X", - "long_transcripts": "split → summarize → merge", - "unclear": "UNKLAR:" - }, - - "PROMPT_SNIPPET": "Generate meeting report in HTML using STRUCTURE and STYLE. Output only the report." -} - -============================================================ - -Insert all generated content into the following HTML TEMPLATE: - -# {{title}} - -**Date:** {{date}} -**Start:** {{start}} -**End:** {{end}} -**Duration:** {{duration}} -**Location:** {{location}} -**Host:** {{host}} -**Participants:** {{participants}} - ---- - -## Table of Contents -{{toc}} - ---- - -## Executive Summary -{{exec_summary}} - ---- - -## Sections -{{sections}} - ---- - -## Consolidated Decisions -{{consolidated_decisions}} - ---- - -## Consolidated Actions -{{consolidated_actions}} - ---- - -## Appendix -{{appendix}} - -============================================================ - -Final Requirement: -Output ONLY the completed HTML meeting report. -## Executive Summary -{{exec_summary}} - ---- - -## Sections -{{sections}} - ---- - -## Consolidated Decisions -{{consolidated_decisions}} - ---- - -## Consolidated Actions -{{consolidated_actions}} - ---- - -## Appendix -{{appendix}} - -============================================================ - -Final Requirement: -Output ONLY the completed HTML meeting report. \ No newline at end of file +TRANSKRIPT: