mirror of
https://gitlab.rlp.net/proj-wise2526-video2document/video2document.git
synced 2026-06-15 18:01:52 +02:00
Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 948ddb4310 | |||
| 889d455fbe | |||
| 58741f0057 | |||
| a8c284edd2 | |||
| 3a5808d97c | |||
| 4083d5a5cb | |||
| d655925031 | |||
| 405a32098c | |||
| 1683502aa1 | |||
| 789ecd3a31 | |||
| e72d03efbe | |||
| ec57411992 | |||
| 271fe78b7b | |||
| 1e38cc79f4 | |||
| 746fec05d4 | |||
| d647f53790 | |||
| 791dbab739 | |||
| c497fa9532 | |||
| 3ce632bf8d | |||
| 5ea8ec6a1a | |||
| 7cd334645f | |||
| 15e2e35bda | |||
| 53508b175a | |||
| 3dd8485140 | |||
| 68c1f0ed9f | |||
| 2b597add6c | |||
| 3af038d195 | |||
| b546c96238 | |||
| 6512b50d83 | |||
| faee605f12 | |||
| 037d29e656 | |||
| 8c201db539 | |||
| 882b49a489 | |||
| 25d11d74a3 | |||
| 0d3535128d | |||
| e661aefbe8 | |||
| ed94928953 | |||
| 8adf470f18 | |||
| f858f2edbb | |||
| 425e24853e | |||
| 05c04aaef2 | |||
| 84cc94aa34 | |||
| 16f319f89f | |||
| 9ab98825a2 | |||
| 9a0a349813 | |||
| 9dfc05e987 | |||
| a5a60635fc | |||
| af13907fdc | |||
| 812bca8cfb | |||
| 34d644d7d7 | |||
| e73a2a8203 | |||
| 2f5efee9c7 | |||
| 465fe8bd41 | |||
| 78e5cf0503 | |||
| 718664523c | |||
| eb42a1f377 | |||
| 0e7b42cb4a | |||
| 31ddddab6a | |||
| 9082543652 | |||
| b9657e1c95 | |||
| baeb9efd2b | |||
| 8c4d213ed0 | |||
| 474e587ff2 | |||
| 6531f5f51c | |||
| 05449ad8f2 | |||
| 8046fe53d1 | |||
| c2f22b3525 | |||
| 6257ad05a8 | |||
| bf7438b753 | |||
| 868945fb92 | |||
| 0f689e2846 | |||
| b4f2ed561d | |||
| 4ade9575f3 | |||
| 925eb33eab | |||
| 911cba14fd | |||
| 6813659443 | |||
| a0ed2ab7bd | |||
| b87bfd444d | |||
| 97b571b7f9 | |||
| 455147a41b | |||
| a178ccf30f | |||
| 75a454ad60 | |||
| a1d804f463 | |||
| fde4d584ab | |||
| abd72c9b54 | |||
| 8e7e0b5043 | |||
| 4dc53b9d5f | |||
| 5f14d633e3 | |||
| 7b01c4f022 | |||
| 4f57ec25bf | |||
| f8fb71e146 | |||
| ada231cd28 | |||
| 1e3d830ae2 | |||
| ab0e737f33 | |||
| 4b72568ad3 | |||
| 9441699561 | |||
| 76c18fa713 | |||
| 2edc7f8351 | |||
| 6083773f88 | |||
| 4a91f03289 | |||
| 444d408480 | |||
| d9eacafc3a | |||
| ab737f0dc9 | |||
| 1a681eb2b8 |
Binary file not shown.
+15
-1
@@ -964,8 +964,22 @@ app.*.symbols
|
|||||||
!/dev/ci/**/Gemfile.lock
|
!/dev/ci/**/Gemfile.lock
|
||||||
|
|
||||||
# Storage files
|
# Storage files
|
||||||
storage/
|
storage/documents/*
|
||||||
|
storage/transcriptionSummaries/*
|
||||||
|
storage/transcripts/*
|
||||||
|
storage/video/*
|
||||||
|
# The inverse for the .gitkeep files, to make sure the folders are there but not the local files
|
||||||
|
!storage/documents/.gitkeep
|
||||||
|
!storage/transcriptionSummaries/.gitkeep
|
||||||
|
!storage/transcripts/.gitkeep
|
||||||
|
!storage/video/.gitkeep
|
||||||
|
|
||||||
*.mp4
|
*.mp4
|
||||||
|
*.webm
|
||||||
|
*.mp3
|
||||||
|
*.mov
|
||||||
*.wav
|
*.wav
|
||||||
*.flac
|
*.flac
|
||||||
|
|
||||||
|
|
||||||
|
!testvideo.mp4
|
||||||
|
|||||||
+19
-25
@@ -1,33 +1,27 @@
|
|||||||
workflow:
|
image: node:latest
|
||||||
rules:
|
|
||||||
# Run the pipeline for merge requests or when committing to a branch
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
- if: $CI_COMMIT_BRANCH
|
|
||||||
|
|
||||||
image: python:3.14.0
|
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- setup
|
# - install
|
||||||
- test
|
- test
|
||||||
|
|
||||||
setup_environment:
|
# job-install:
|
||||||
stage: setup
|
# stage: install
|
||||||
script:
|
# script:
|
||||||
- pip install --upgrade pip
|
# - npm install
|
||||||
- pip install -r requirements.txt
|
# artifacts:
|
||||||
- echo "Dependencies installed successfully."
|
# untracked: false
|
||||||
|
# when: on_success
|
||||||
|
# access: all
|
||||||
|
# expire_in: "30 days"
|
||||||
|
# paths:
|
||||||
|
# - node_modules
|
||||||
|
|
||||||
only:
|
|
||||||
- main
|
|
||||||
- feature/ci-pipeline-s1-09a-1 # You can add more branches if needed
|
|
||||||
|
|
||||||
test_app:
|
|
||||||
|
job-test:
|
||||||
stage: test
|
stage: test
|
||||||
script:
|
script:
|
||||||
- echo "Running V2D Framework basic test..."
|
- npm install
|
||||||
- python -m unittest discover || echo "No tests found."
|
- echo "ASSEMBLYAI_API_KEY=$apikey_assembly" > .env
|
||||||
|
- echo "GOOGLE_API_KEY=$apikey_gemini" >> .env
|
||||||
only:
|
- npm test
|
||||||
- main
|
|
||||||
- feature/ci-pipeline-s1-09a-1
|
|
||||||
|
|
||||||
@@ -9,43 +9,62 @@
|
|||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="mitte" id="mitte">
|
<div class="mitte" id="mitte">
|
||||||
<div class="flagsBtns" id="flagsBtns">
|
<div class="labelDiv" id="labelDiv">
|
||||||
<button class="de_Btn" id="de_Btn" onclick="changeLanguage('de')"><img src="flags/germany-flag-png-large.jpg" width="25px" height="20px"/></button>
|
<label id="labelKI">Select ki:</label>
|
||||||
<button class="eng_Btn" id="eng_Btn" onclick="changeLanguage('en')"><img src="flags/united-kingdom-flag-png-large.jpg" width="25px" height="20px"/></button>
|
<label id="labelTranscription">Select transcription:</label>
|
||||||
<button class="in_Btn" id="in_Btn" onclick="changeLanguage('in')"><img src="flags/india-flag-png-large.png" width="25px" height="20px"/></button>
|
<label id="labelLanguage">Select language:</label>
|
||||||
|
<img id="labelLanguageFlag" src="flags/united-kingdom-flag-png-large.jpg" width="20" height="10" >
|
||||||
|
</div>
|
||||||
|
<div class="dropdownMenus" id="dropdownMenus">
|
||||||
|
<select name="ai_type" id="ai_type">
|
||||||
|
</select>
|
||||||
|
<select name="transkript_type" id="transkript_type">
|
||||||
|
</select>
|
||||||
|
<select name="output_type" id="output_type">
|
||||||
|
<option value="flac">flac</option>
|
||||||
|
<option value="mp3">mp3</option>
|
||||||
|
<option value="wav">wav</option>
|
||||||
|
</select>
|
||||||
|
<select name="language_option" id="language_option">
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 id="h1">Video to document</h1>
|
<h1 id="h1">Video to document</h1>
|
||||||
|
|
||||||
<div class="upload-container" id="uploadContainer">
|
<div class="upload-container" id="uploadContainer">
|
||||||
<p id="p1">Drag and drop video file</p>
|
<p id="p1">Drag and drop video file</p>
|
||||||
|
<video id="previewThumbnail" autoplay="false">
|
||||||
|
</video>
|
||||||
<div class="file-name" id="fileName">No video chosen</div>
|
<div class="file-name" id="fileName">No video chosen</div>
|
||||||
|
<div id="thumbnailContainer">
|
||||||
|
<img id="thumbnailImage" style="display:none;">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="custom-btn" id="manualUploadBtn">Search video</button>
|
<button class="custom-btn" id="manualUploadBtn">Search video</button>
|
||||||
<input type="file" id="videoUpload" accept="video/*">
|
<input type="file" id="videoUpload" accept="video/*">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="checkbox-group">
|
<div class="checkbox-group">
|
||||||
<label id="checkbox_group" for="checkbox-group">Choose prefered document style:</label>
|
<label id="checkbox_group" for="checkbox-group">Choose prefered document style:</label>
|
||||||
<div class="checkbox-container">
|
<div class="checkbox-container">
|
||||||
<input type="checkbox" name ="docFormat" id="docFormat">
|
<input type="checkbox" name ="docFormat" id="docFormat" value="Meeting report">
|
||||||
<label id="label_format" for="docFormat">Meeting report</label>
|
<label id="label_format" for="docFormat">Meeting report</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="checkbox-container">
|
<div class="checkbox-container">
|
||||||
<input type="checkbox" name="docFormat" id="docFormatSummary">
|
<input type="checkbox" name="docFormat" id="docFormatSummary" value="Summary with timestamps">
|
||||||
<label id="label_summary" for="docFormatSummary">Summary with timestamps</label>
|
<label id="label_summary" for="docFormatSummary">Summary with timestamps</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="submit-btn" id="submitButton" onclick="checkBoxes()">Submit</button>
|
<button class="submit-btn" id="submitButton" onclick="checkBoxes()" disabled>Submit</button>
|
||||||
|
|
||||||
<div class="progressbar">
|
<div class="progressbar" id="progressbar">
|
||||||
<div class="progress_fill"></div>
|
<div class="progress_fill"></div>
|
||||||
<span class="progress_text">0%</span>
|
<span class="progress_text">0%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script src="languages.js"></script>
|
||||||
<script src="script.js"></script>
|
<script src="script.js"></script>
|
||||||
<script src="./renderer.js"></script>
|
<script src="./renderer.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
var languageOptions = {
|
||||||
|
"eng":{
|
||||||
|
"flagPath": "flags/united-kingdom-flag-png-large.jpg",
|
||||||
|
"labelKI": "Select ki:",
|
||||||
|
"labelTranscription": "Select transcription:",
|
||||||
|
"labelLanguage": "Select language:",
|
||||||
|
"title": "Video to document",
|
||||||
|
"h1": "Video to document",
|
||||||
|
"p1": "Drag and drop video file",
|
||||||
|
"fileName": "No video chosen",
|
||||||
|
"manualUploadBtn": "Search video",
|
||||||
|
"checkbox_group": "Choose prefered document style:",
|
||||||
|
"label_format": "Meeting report",
|
||||||
|
"label_summary": "Summary with timestamps",
|
||||||
|
"submitButton": "Submit"
|
||||||
|
},
|
||||||
|
"de":{
|
||||||
|
"flagPath": "flags/germany-flag-png-large.jpg",
|
||||||
|
"labelKI": "Waehle KI:",
|
||||||
|
"labelTranscription": "Waehle Transkription:",
|
||||||
|
"labelLanguage": "Waehle Sprache:",
|
||||||
|
"title": "Video zu Dokument",
|
||||||
|
"h1": "Video zu Dokument",
|
||||||
|
"p1": "Video per Drag & Drop ablegen",
|
||||||
|
"fileName": "Kein Video ausgewaehlt",
|
||||||
|
"manualUploadBtn": "Video suchen",
|
||||||
|
"checkbox_group": "Bevorzugte Dokumentvarianten:",
|
||||||
|
"label_format": "Meeting Bericht",
|
||||||
|
"label_summary": "Zusammenfassung mit Zeitstempeln",
|
||||||
|
"submitButton": "Absenden"
|
||||||
|
},
|
||||||
|
"in":{
|
||||||
|
"flagPath": "flags/india-flag-png-large.png",
|
||||||
|
"labelKI": "की का चयन करें:",
|
||||||
|
"labelTranscription": "प्रतिलेखन चुनें:",
|
||||||
|
"labelLanguage": "भाषा चुने:",
|
||||||
|
"title": "दस्तावेज़ के लिए वीडियो",
|
||||||
|
"h1": "दस्तावेज़ के लिए वीडियो",
|
||||||
|
"p1": "वीडियो फ़ाइल खींचें और छोड़ें",
|
||||||
|
"fileName": "कोई वीडियो नहीं चुना गया",
|
||||||
|
"manualUploadBtn": "वीडियो खोजें",
|
||||||
|
"checkbox_group": "पसंदीदा दस्तावेज़ शैली चुनें:",
|
||||||
|
"label_format": "बैठक रिपोर्ट",
|
||||||
|
"label_summary": "टाइमस्टैम्प के साथ सारांश",
|
||||||
|
"submitButton": "जमा करना"
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
@@ -1,18 +1,27 @@
|
|||||||
|
|
||||||
|
|
||||||
const { contextBridge, ipcRenderer, webUtils } = require('electron')
|
const { contextBridge, ipcRenderer, webUtils } = require('electron')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
contextBridge.exposeInMainWorld("explorer", {
|
contextBridge.exposeInMainWorld("explorer", {
|
||||||
onFileDrop: (file) => webUtils.getPathForFile(file)
|
onFileDrop: (file) => webUtils.getPathForFile(file)
|
||||||
})
|
})
|
||||||
contextBridge.exposeInMainWorld("extractor", {
|
contextBridge.exposeInMainWorld("submit", {
|
||||||
extract: (file) => ipcRenderer.send("extract", file)
|
submit: (meeting_specifications) => {ipcRenderer.send("file_submit", meeting_specifications)}
|
||||||
})
|
})
|
||||||
contextBridge.exposeInMainWorld("electronAPI", {
|
contextBridge.exposeInMainWorld("electronAPI", {
|
||||||
getFilePath: (file) => {return webUtils.getPathForFile(file)}
|
getFilePath: (file) => {return webUtils.getPathForFile(file)}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld("onStartup", {
|
||||||
|
getModuleNames: () => ipcRenderer.invoke('get-module-names')
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ipcRenderer.on("progress", (event, resp) => {
|
||||||
|
alert(`Finished step ${resp.curstep} of ${resp.totalsteps}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcRenderer.on("error", (event, err) => {alert(err)})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error in preload.js");
|
console.log("Error in preload.js");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,16 +16,72 @@ uploadContainer.addEventListener("drop", (e) => {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const files = e.dataTransfer.files
|
const files = e.dataTransfer.files
|
||||||
const filePath = window.explorer.onFileDrop(files[0])
|
const filePath = window.explorer.onFileDrop(files[0])
|
||||||
var holdy = filePath + "";
|
const testEndings = [".mp4", ".mov", ".avi", ".mkv"];
|
||||||
if(holdy.endsWith(".mp4") || holdy.endsWith(".mov") || holdy.endsWith(".avi") || holdy.endsWith( ".mkv")){
|
var pathToLower = filePath.toLowerCase();
|
||||||
console.log(filePath)
|
if(testEndings.some(e => pathToLower.endsWith(e))){
|
||||||
|
document.getElementById("progressbar").style.visibility = "visible";
|
||||||
const files1 = e.dataTransfer.files;
|
const files1 = e.dataTransfer.files;
|
||||||
handleFiles(files1);
|
handleFiles(files1);
|
||||||
|
}else{
|
||||||
|
alert('The given file is not compatible. These are the available types: [".mp4", ".mov", ".avi", ".mkv"].');
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error in renderer.js with the listerner for the drop function");
|
console.log("Error in renderer.js with the listerner for the drop function");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
window.addEventListener('load', async (e) => {
|
||||||
|
try {
|
||||||
|
console.log("test");
|
||||||
|
loadLanguageOptions();
|
||||||
|
const value = await window.onStartup.getModuleNames();
|
||||||
|
loadAiOptions(value.ai_modules);
|
||||||
|
loadTranscriptionOptions(value.transcription_modules);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
language_option.addEventListener('change', (e)=>{
|
||||||
|
try {
|
||||||
|
const select = document.getElementById('language_option');
|
||||||
|
changeLanguage(select.value);
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
videoUpload.addEventListener("change", () => {
|
||||||
|
try {
|
||||||
|
activateSubmitBtn(videoUpload.files.length > 0);
|
||||||
|
} 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
|
||||||
|
manualUploadBtn.addEventListener('click', () => {
|
||||||
|
try {
|
||||||
|
videoUpload.click();
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error in manualBtn EventListener click");
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
+158
-57
@@ -1,90 +1,100 @@
|
|||||||
|
|
||||||
//listener for the file explorer search
|
|
||||||
manualUploadBtn.addEventListener('click', () => {
|
|
||||||
try {
|
|
||||||
videoUpload.click();
|
|
||||||
} catch (error) {
|
|
||||||
console.log("Error in manualBtn EventListener click");
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
//function to check if one checkbox is at least klicked
|
//function to check if one checkbox is at least klicked
|
||||||
function checkBoxes() {
|
function checkBoxes() {
|
||||||
try {
|
try {
|
||||||
const checkboxes = document.querySelectorAll('input[name="docFormat"]');
|
const checkboxes = document.querySelectorAll('input[name="docFormat"]');
|
||||||
let isChecked = false;
|
let isChecked = false;
|
||||||
|
var checkedCounter = 0;
|
||||||
checkboxes.forEach(function(checkbox){
|
checkboxes.forEach(function(checkbox){
|
||||||
if(checkbox.checked){
|
if(checkbox.checked){
|
||||||
isChecked = true;
|
isChecked = true;
|
||||||
|
checkedCounter++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
//Code to submit the video
|
//Code to submit the video
|
||||||
var pathTest = window.electronAPI.getFilePath(videoUpload.files[0]);
|
var selectedCheckboxes = {};
|
||||||
if(pathTest.endsWith(".mp4") || holdy.endsWith(".mov") || holdy.endsWith(".avi") || holdy.endsWith( ".mkv")){
|
checkboxes.forEach(function(checkbox){
|
||||||
window.extractor.extract({inputVideoPath: pathTest, outputType:"wav"})
|
if(checkbox.checked){
|
||||||
|
selectedCheckboxes[checkbox.nextElementSibling.textContent] = "";
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const testEndings = [".mp4", ".mov", ".avi", ".mkv"];
|
||||||
|
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];
|
||||||
|
var iter = 0;
|
||||||
|
checkboxes.forEach(function(checkbox){
|
||||||
|
if(checkbox.checked){
|
||||||
|
console.log(checkbox.value);
|
||||||
|
selectedStyles[iter] = {iter: checkbox.value};
|
||||||
|
iter++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(selectedCheckboxes);
|
||||||
|
const outputType = document.getElementById("output_type");
|
||||||
|
const transcriptionType = document.getElementById("transkript_type");
|
||||||
|
const aiType = document.getElementById("ai_type");
|
||||||
|
const sendingPackage = {
|
||||||
|
"video": {
|
||||||
|
"module":"extraction-video-to-audio",
|
||||||
|
"inputVideoPath": pathTest,
|
||||||
|
"outputType": outputType.value
|
||||||
|
},
|
||||||
|
"transcription": {
|
||||||
|
"module": transcriptionType.value
|
||||||
|
},
|
||||||
|
"document": {
|
||||||
|
"module":aiType.value,
|
||||||
|
"styles": selectedStyles
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.submit.submit(sendingPackage)
|
||||||
|
}else{
|
||||||
|
alert('The given file is not compatible. These are the available types: [".mp4", ".mov", ".avi", ".mkv"].');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
//language only english at the moment
|
//language only english at the moment
|
||||||
alert('Please select at least one document type.');
|
alert('Please select at least one document type.');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log("Error in script.js checkBoxes function");
|
||||||
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./a.mp4", outputType:"wav"})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//language changing feature
|
//language changing feature
|
||||||
function changeLanguage(language) {
|
function changeLanguage(language) {
|
||||||
if (language === 'en') {
|
|
||||||
document.getElementById('title').textContent = 'Video to document';
|
|
||||||
document.getElementById('h1').textContent = 'Video to document';
|
|
||||||
document.getElementById('p1').textContent = 'Drag and drop video file';
|
|
||||||
document.getElementById('fileName').textContent = 'No video chosen';
|
|
||||||
document.getElementById('manualUploadBtn').textContent = 'Search video';
|
|
||||||
document.getElementById('checkbox_group').textContent = 'Choose prefered document style:';
|
|
||||||
document.getElementById('label_format').textContent = 'Meeting report';
|
|
||||||
document.getElementById('label_summary').textContent = 'Summary with timestamps';
|
|
||||||
document.getElementById('submitButton').textContent = 'Submit';
|
|
||||||
} else if (language === 'de') {
|
|
||||||
document.getElementById('title').textContent = 'Video zu Dokument';
|
|
||||||
document.getElementById('h1').textContent = 'Video zu Dokument';
|
|
||||||
document.getElementById('p1').textContent = 'Video per Drag & Drop ablegen';
|
|
||||||
document.getElementById('fileName').textContent = 'Kein Video ausgewaehlt';
|
|
||||||
document.getElementById('manualUploadBtn').textContent = 'Video suchen';
|
|
||||||
document.getElementById('checkbox_group').textContent = 'Bevorzugte Dokumentvarianten:';
|
|
||||||
document.getElementById('label_format').textContent = 'Meeting Bericht';
|
|
||||||
document.getElementById('label_summary').textContent = 'Zusammenfassung mit Zeitstempeln';
|
|
||||||
document.getElementById('submitButton').textContent = 'Absenden';
|
|
||||||
} else if(language == "in") {
|
|
||||||
document.getElementById('title').textContent = 'दस्तावेज़ के लिए वीडियो';
|
|
||||||
document.getElementById('h1').textContent = 'दस्तावेज़ के लिए वीडियो';
|
|
||||||
document.getElementById('p1').textContent = 'वीडियो फ़ाइल खींचें और छोड़ें';
|
|
||||||
document.getElementById('fileName').textContent = 'कोई वीडियो नहीं चुना गया';
|
|
||||||
document.getElementById('manualUploadBtn').textContent = 'वीडियो खोजें';
|
|
||||||
document.getElementById('checkbox_group').textContent = 'पसंदीदा दस्तावेज़ शैली चुनें:';
|
|
||||||
document.getElementById('label_format').textContent = 'बैठक रिपोर्ट';
|
|
||||||
document.getElementById('label_summary').textContent = 'टाइमस्टैम्प के साथ सारांश';
|
|
||||||
document.getElementById('submitButton').textContent = 'जमा करना';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//listener for the file explorer search when something got selected
|
|
||||||
videoUpload.addEventListener('change', () => {
|
|
||||||
try {
|
try {
|
||||||
handleFiles(videoUpload.files);
|
document.getElementById('labelLanguageFlag').src = languageOptions[language].flagPath;
|
||||||
|
document.getElementById('labelKI').textContent = languageOptions[language].labelKI;
|
||||||
|
document.getElementById('labelTranscription').textContent = languageOptions[language].labelTranscription;
|
||||||
|
document.getElementById('labelLanguage').textContent = languageOptions[language].labelLanguage;
|
||||||
|
document.getElementById('title').textContent = languageOptions[language].title;
|
||||||
|
document.getElementById('h1').textContent = languageOptions[language].h1;
|
||||||
|
document.getElementById('p1').textContent = languageOptions[language].p1;
|
||||||
|
document.getElementById('fileName').textContent = languageOptions[language].fileName;
|
||||||
|
document.getElementById('manualUploadBtn').textContent = languageOptions[language].manualUploadBtn;
|
||||||
|
document.getElementById('checkbox_group').textContent = languageOptions[language].checkbox_group;
|
||||||
|
document.getElementById('label_format').textContent = languageOptions[language].label_format;
|
||||||
|
document.getElementById('label_summary').textContent = languageOptions[language].label_summary;
|
||||||
|
document.getElementById('submitButton').textContent = languageOptions[language].submitButton;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error in manualBtn EventListener change");
|
console.log("Error in script.js changeLanguage function");
|
||||||
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//function to display the file path in the drop down box
|
//function to display the file path in the drop down box
|
||||||
function handleFiles(files) {
|
function handleFiles(files) {
|
||||||
@@ -92,12 +102,16 @@ function handleFiles(files) {
|
|||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
const file = files[0];
|
const file = files[0];
|
||||||
if (file.type.startsWith('video/')) {
|
if (file.type.startsWith('video/')) {
|
||||||
|
const filePath = window.explorer.onFileDrop(files[0])
|
||||||
videoUpload.files = files;
|
videoUpload.files = files;
|
||||||
fileName.textContent = `Chosen video: ${file.name}`;
|
fileName.textContent = `Chosen video: ${file.name}`;
|
||||||
|
generateThumbnail(filePath);
|
||||||
|
activateSubmitBtn(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error in script.js handleFiles function");
|
console.log("Error in script.js handleFiles function");
|
||||||
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -110,6 +124,93 @@ function updateProgressBar(bar, value){
|
|||||||
bar.querySelector(".progress_text").textContent = `${value}%`;
|
bar.querySelector(".progress_text").textContent = `${value}%`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error in scripts.js updateProgressBar function");
|
console.log("Error in scripts.js updateProgressBar function");
|
||||||
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//function to load ai options to the drop down list
|
||||||
|
function loadAiOptions(options){
|
||||||
|
try {
|
||||||
|
var menu = document.getElementById('ai_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 loadAiOptions function");
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//function to load transcription options to the drop down list
|
||||||
|
function loadTranscriptionOptions(options){
|
||||||
|
try {
|
||||||
|
var menu = document.getElementById('transkript_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 loadTranscriptionOptions");
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//function to load language options to the drop down list
|
||||||
|
function loadLanguageOptions(){
|
||||||
|
try {
|
||||||
|
var menu = document.getElementById('language_option');
|
||||||
|
var object_holdy;
|
||||||
|
var choice ;
|
||||||
|
object_holdy = Object.keys(languageOptions);
|
||||||
|
for(i = 0; i < object_holdy.length; i++){
|
||||||
|
choice = document.createElement('option');
|
||||||
|
choice.textContent = object_holdy[i];
|
||||||
|
choice.value = object_holdy[i];
|
||||||
|
menu.appendChild(choice);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error in script.js loadLanguageOptions function");
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateSubmitBtn(hasFile){
|
||||||
|
try {
|
||||||
|
submitButton.disabled = !hasFile;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deaktivateProgressbar(){
|
||||||
|
try {
|
||||||
|
document.getElementById("progressbar").style.visibility = "hidden";
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Video thumbnail generation
|
||||||
|
function generateThumbnail(path){
|
||||||
|
const videoElement = document.getElementById("previewThumbnail");
|
||||||
|
while (videoElement.firstChild) videoElement.removeChild(videoElement.firstChild);
|
||||||
|
videoElement.src = path;
|
||||||
|
videoElement.type = "video/mov";
|
||||||
|
videoElement.load();
|
||||||
|
videoElement.style.maxWidth = 40;
|
||||||
|
videoElement.style.maxHeight = 40;
|
||||||
|
videoElement.autoplay = false;
|
||||||
|
}
|
||||||
|
|||||||
+53
-12
@@ -5,7 +5,7 @@ body {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background-color: #555;
|
background-color: #f2f3f4;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
@@ -17,9 +17,10 @@ body {
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 400px;
|
width: 350px;
|
||||||
|
height: 200px;
|
||||||
transition: border 0.3s, background-color 0.3s;
|
transition: border 0.3s, background-color 0.3s;
|
||||||
border: 2px dashed #ccc;
|
border: 2px dashed #7378c9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -40,11 +41,35 @@ body {
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #333;
|
color: #333;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#thumbnailContainer {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#thumbnailImage {
|
||||||
|
width: 200px;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0px 4px 10px rgba(0,0,0,0.1);
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
#previewThumbnail {
|
||||||
|
width: 150px;
|
||||||
|
height: 100px;
|
||||||
|
/*border: 1px dashed black;*/
|
||||||
|
}
|
||||||
|
|
||||||
.custom-btn {
|
.custom-btn {
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
|
margin-top: 10px;
|
||||||
background-color: #007BFF;
|
background-color: #007BFF;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -84,6 +109,8 @@ gap: 5px;
|
|||||||
|
|
||||||
.submit-btn {
|
.submit-btn {
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
background-color: #007BFF;
|
background-color: #007BFF;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -92,17 +119,26 @@ gap: 5px;
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.submit-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.mitte {
|
.mitte {
|
||||||
background-color: #f2f3f4;
|
background-color: #FDFCFA;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
width: 700px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 5% 50px;
|
padding: 5% 50px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
border: 1px;
|
border: 0px;
|
||||||
border-color: black;
|
border-color: black;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0px 4px 10px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@@ -116,6 +152,7 @@ h1 {
|
|||||||
background: rgb(42, 46, 78);
|
background: rgb(42, 46, 78);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress_fill{
|
.progress_fill{
|
||||||
@@ -133,15 +170,19 @@ h1 {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flagsBtns {
|
|
||||||
|
.dropdownMenus {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
margin-top: 1px;
|
||||||
|
gap: 150px;
|
||||||
|
padding: 2px 10px 2px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.de_Btn, .eng_Btn, .in_Btn {
|
#ai_type, #transkript_type, #language_option {
|
||||||
padding: 8px 16px;
|
padding: 3px;
|
||||||
color: white;
|
}
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
.labelDiv {
|
||||||
cursor: pointer;
|
gap: 200px;
|
||||||
}
|
}
|
||||||
@@ -35,6 +35,8 @@ console.log("-------------------------------------------------------------------
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// --------------------------------------------------------- CLI COMMANDS --------------------------------------------------------- //
|
// --------------------------------------------------------- CLI COMMANDS --------------------------------------------------------- //
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
@@ -60,7 +62,7 @@ let mainWindow;
|
|||||||
function createWindow() {
|
function createWindow() {
|
||||||
mainWindow = new electron.BrowserWindow({
|
mainWindow = new electron.BrowserWindow({
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 800,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
@@ -74,6 +76,180 @@ function createWindow() {
|
|||||||
electron.app.whenReady().then(createWindow);
|
electron.app.whenReady().then(createWindow);
|
||||||
|
|
||||||
|
|
||||||
electron.ipcMain.on("extract", (event, args) => {
|
// electron.ipcMain.on("extract", (event, args) => {
|
||||||
mapFunctions.get("extraction-video-to-audio").function(args)
|
// mapFunctions.get("extraction-video-to-audio").function(args)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// setTimeout(() => {
|
||||||
|
// mainWindow.webContents.send("fuck", "worked uwu")
|
||||||
|
// }, 5000);
|
||||||
|
|
||||||
|
electron.ipcMain.handle('get-module-names', async () => {
|
||||||
|
let module_array = {
|
||||||
|
"ai_modules":[],
|
||||||
|
"transcription_modules":[]
|
||||||
|
}
|
||||||
|
mapFunctions.forEach(e => {
|
||||||
|
switch(e.type){
|
||||||
|
case "llm":
|
||||||
|
module_array.ai_modules.push({"name": e.name, "displayname": e.displayname})
|
||||||
|
break;
|
||||||
|
case "transcription":
|
||||||
|
module_array.transcription_modules.push({"name": e.name, "displayname": e.displayname})
|
||||||
|
break;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
// console.log(module_array);
|
||||||
|
return module_array
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// electron.ipcMain.on("get_modules", async (event, args) => {
|
||||||
|
// let module_array = {
|
||||||
|
// "ai_modules":[],
|
||||||
|
// "transcription_modules":[]
|
||||||
|
// }
|
||||||
|
// mapFunctions.forEach(e => {
|
||||||
|
// switch(e.type){
|
||||||
|
// case "llm":
|
||||||
|
// module_array.ai_modules.push({"name": e.name, "displayname": e.displayname})
|
||||||
|
// break;
|
||||||
|
// case "transcription":
|
||||||
|
// module_array.transcription_modules.push({"name": e.name, "displayname": e.displayname})
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// console.log(module_array);
|
||||||
|
|
||||||
|
// mainWindow.webContents.send("modules", module_array)
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
electron.ipcMain.on("file_submit", async (event, args) => {
|
||||||
|
try {
|
||||||
|
let curstep = 0
|
||||||
|
let totalsteps = 3 + args.document.styles.length
|
||||||
|
|
||||||
|
if(args.document.styles.length == 0)
|
||||||
|
throw new Error("At least one Document Style needed");
|
||||||
|
|
||||||
|
|
||||||
|
console.log(args);
|
||||||
|
let audiopath = ""
|
||||||
|
let transcriptpath = ""
|
||||||
|
|
||||||
|
console.log("\n\n Running the Video to Audio Extractor");
|
||||||
|
// This code handles the Video to Audio extraction module call
|
||||||
|
await mapFunctions.get("module-handler").function(args.video.module, {inputVideoPath: args.video.inputVideoPath, outputType: mapFunctions.get(args.transcription.module).audioformat}).then(resp => {
|
||||||
|
console.log(resp);
|
||||||
|
audiopath = resp
|
||||||
|
curstep++
|
||||||
|
mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
|
||||||
|
}).catch(err => {
|
||||||
|
mainWindow.webContents.send("error", err)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
console.log("\n\n Running the Audio to Transcription module");
|
||||||
|
// TODO implement transcription module
|
||||||
|
// This code handles the Audio to Text transcription module call
|
||||||
|
await mapFunctions.get("module-handler").function(args.transcription.module, audiopath).then(resp => {
|
||||||
|
console.log(resp);
|
||||||
|
transcriptpath = resp
|
||||||
|
curstep++
|
||||||
|
mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
|
||||||
|
}).catch(err => {
|
||||||
|
mainWindow.webContents.send("error", err)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
console.log("\n\n Running the Transcription Summarizer module");
|
||||||
|
// This code summarises the transcript, so that it can be used by an llm
|
||||||
|
// await mapFunctions.get("summarize-transcription").function('A:\\programing\\@projects\\video2document\\storage\\transcripts\\IMG_2978.json').then(resp => {
|
||||||
|
await mapFunctions.get("summarize-transcription2").function(transcriptpath).then(resp => {
|
||||||
|
console.log(resp);
|
||||||
|
transcriptpath = resp
|
||||||
|
curstep++
|
||||||
|
mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
|
||||||
|
}).catch(err => {
|
||||||
|
mainWindow.webContents.send("error", err)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log("\n\n Running the LLM module");
|
||||||
|
// TODO implement documentation module
|
||||||
|
// This code handles the Text to Document processing module call
|
||||||
|
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/standard_meeting_report.txt", language: "en"}).then(resp => {
|
||||||
|
console.log(resp);
|
||||||
|
transcriptpath = resp
|
||||||
|
curstep++
|
||||||
|
mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
|
||||||
|
}).catch(err => {
|
||||||
|
mainWindow.webContents.send("error", err)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO actually implement this functionality
|
||||||
|
// Module to get the first few lines for each speaker to send to the frontend
|
||||||
|
// await mapFunctions.get("speaker-getter-idfk").function(transcriptpath).then(resp => {
|
||||||
|
// console.log(resp);
|
||||||
|
// transcriptpath = resp
|
||||||
|
// curstep++
|
||||||
|
// mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
|
||||||
|
|
||||||
|
// // {
|
||||||
|
// // speakerA: {source: "Pfad zur Audio File"},
|
||||||
|
// // speakerB:.....
|
||||||
|
// // }
|
||||||
|
mainWindow.webContents.send("speakers", {speakerA:"pfad1", speakerB:"pfad2"})
|
||||||
|
// }).catch(err => {
|
||||||
|
// mainWindow.webContents.send("error", err)
|
||||||
|
// return
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let q =
|
||||||
|
{
|
||||||
|
video: {
|
||||||
|
module: "String", // The name of the module, idk if we ever implement other extraction modules, the default one is extraction-video-to-audio
|
||||||
|
inputVideoPath: "String", // See script.js on line 27 for an example of what this should look like
|
||||||
|
outputType: "String" // The file format to be used for the audio output file, such as wav, mp3, flac and so on
|
||||||
|
},
|
||||||
|
transcription:{
|
||||||
|
module: "String" // The module name of the transcription model you want to use
|
||||||
|
},
|
||||||
|
document:{
|
||||||
|
module: "String", // The module name of the AI model you want to use to create the document
|
||||||
|
styles: [ // An array of all the document styles/prompts you want to have the document be processed with
|
||||||
|
{
|
||||||
|
prompt: "String",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let q1 = {
|
||||||
|
"ai_modules": [
|
||||||
|
{name:"abc", displayname:"ABC"},
|
||||||
|
{name:"qeg", displayname:"aqghegahu"}
|
||||||
|
],
|
||||||
|
"transcription_modules": [
|
||||||
|
{name:"abc", displayname:"ABC"},
|
||||||
|
{name:"qeg", displayname:"aqghegahu"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
Generated
+2714
-36
File diff suppressed because it is too large
Load Diff
+9
-3
@@ -1,10 +1,16 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cli-progress": "^3.12.0",
|
"@google/genai": "^1.30.0",
|
||||||
|
"@types/axios": "^0.9.36",
|
||||||
|
"axios": "^1.13.2",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
"electron": "^39.1.1",
|
"electron": "^39.1.1",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"ffmpeg-static": "^5.2.0",
|
"ffmpeg-static": "^5.2.0",
|
||||||
"fluent-ffmpeg": "^2.1.3"
|
"fluent-ffmpeg": "^2.1.3",
|
||||||
|
"html-to-docx": "^1.8.0",
|
||||||
|
"mocha": "^11.7.5",
|
||||||
|
"puppeteer": "^24.33.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cli-progress": "^3.11.6",
|
"@types/cli-progress": "^3.11.6",
|
||||||
@@ -23,7 +29,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron main.js",
|
"start": "electron main.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "mocha ./test/unit/test.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
fastapi
|
|
||||||
uvicorn
|
|
||||||
pytest
|
|
||||||
+8
-1
@@ -6,13 +6,20 @@ platform = process.platform
|
|||||||
mainDir = __dirname
|
mainDir = __dirname
|
||||||
fs = require("fs")
|
fs = require("fs")
|
||||||
readline = require("readline")
|
readline = require("readline")
|
||||||
|
|
||||||
|
puppeteer = require("puppeteer")
|
||||||
|
htmltodocx = require("html-to-docx")
|
||||||
config = require("./config/config")
|
config = require("./config/config")
|
||||||
|
|
||||||
ffmpegPath = require('ffmpeg-static');
|
ffmpegPath = require('ffmpeg-static');
|
||||||
ffmpeg = require('fluent-ffmpeg');
|
ffmpeg = require('fluent-ffmpeg');
|
||||||
path = require('path');
|
path = require('path');
|
||||||
cliProgress = require('cli-progress');
|
// cliProgress = require('cli-progress');
|
||||||
|
|
||||||
// { app, BrowserWindow, ipcMain, dialog } = require('electron');
|
// { app, BrowserWindow, ipcMain, dialog } = require('electron');
|
||||||
|
|
||||||
electron = require('electron');
|
electron = require('electron');
|
||||||
|
|
||||||
|
axios = require("axios")
|
||||||
|
|
||||||
|
console.log(require('dotenv').config({path: __dirname + '/.env'}));
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
npx ts-node ./extract.ts /Users/mikehughes/Downloads/Testvideo/Kurzgesagt.mov
|
|
||||||
npx ts-node ./transcribe.ts ../storage/audio/Kurzgesagt.wav
|
|
||||||
|
|
||||||
npx ts-node ./extract.ts /Users/mikehughes/Downloads/Testvideo/GitLabMeeting.mov
|
|
||||||
npx ts-node ./transcribe.ts ../storage/audio/GitLabMeeting.wav
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#!/usr/bin/env ts-node
|
|
||||||
|
|
||||||
import { extractAudioFromVideo } from "../services/modules/extraction/ffmpegExtractor.ts";
|
|
||||||
|
|
||||||
const videoPath = process.argv[2];
|
|
||||||
|
|
||||||
if (!videoPath) {
|
|
||||||
console.error("Usage: ts-node extractAudio.ts <videoPath>");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
console.log(`Extracting audio from: ${videoPath}`);
|
|
||||||
|
|
||||||
await extractAudioFromVideo(videoPath); // Call the extraction function (ffmpegExtractor.ts in services/modules/extraction)
|
|
||||||
|
|
||||||
console.log("Audio extraction completed successfully.");
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Audio extraction failed:", err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
//node show-models.js, remember to set SAIA_API_KEY in your environment before running the script
|
||||||
|
|
||||||
|
const SAIA_API_KEY = process.env.SAIA_API_KEY;
|
||||||
|
const SAIA_MODELS_URL = "https://chat-ai.academiccloud.de/v1/models";
|
||||||
|
|
||||||
|
// Script to list available models
|
||||||
|
(async () => {
|
||||||
|
if (!SAIA_API_KEY) {
|
||||||
|
console.error("ERROR: SAIA_API_KEY environment variable is not set!");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Fetching available models from SAIA...\n");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(SAIA_MODELS_URL, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Bearer ${SAIA_API_KEY}`,
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const text = await response.text();
|
||||||
|
throw new Error(`SAIA API error (${response.status}): ${text}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
console.log("Available models:");
|
||||||
|
console.log(JSON.stringify(data, null, 2));
|
||||||
|
|
||||||
|
if (data.data && Array.isArray(data.data)) {
|
||||||
|
console.log("\n\nModel IDs:");
|
||||||
|
data.data.forEach(model => {
|
||||||
|
console.log(`- ${model.id}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching models:", error);
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { whisperLocal } from "../services/modules/transcription/local/whisperLocal.ts";
|
|
||||||
|
|
||||||
const audioPath = process.argv[2];
|
|
||||||
if (!audioPath) {
|
|
||||||
console.error("Please provide an audio file path as argument.");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const whisper = new whisperLocal();
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
const text = await whisper.transcribe(audioPath);
|
|
||||||
console.log(text);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Transcription failed:", err);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const puppeteer = require('puppeteer');
|
||||||
|
const htmlToDocx = require('html-to-docx');
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
|
const outputDir = path.join(__dirname, "../../../storage/documents");
|
||||||
|
|
||||||
|
if (!fs.existsSync(outputDir)) {
|
||||||
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showSaveDialog(defaultName, format) {
|
||||||
|
const platform = os.platform();
|
||||||
|
|
||||||
|
if (platform === 'darwin') {
|
||||||
|
// macOS
|
||||||
|
const applescript = `
|
||||||
|
set defaultName to "${defaultName}.${format}"
|
||||||
|
set theFile to choose file name with prompt "Dokument speichern als:" default name defaultName
|
||||||
|
POSIX path of theFile
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = execSync(`osascript -e '${applescript}'`, { encoding: 'utf8' });
|
||||||
|
return result.trim();
|
||||||
|
} catch (err) {
|
||||||
|
if (err.status === 1) return null; // User canceled
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
} else if (platform === 'win32') {
|
||||||
|
// Windows
|
||||||
|
const powershell = `
|
||||||
|
Add-Type -AssemblyName System.Windows.Forms
|
||||||
|
$dialog = New-Object System.Windows.Forms.SaveFileDialog
|
||||||
|
$dialog.FileName = "${defaultName}.${format}"
|
||||||
|
$dialog.Filter = "${format.toUpperCase()} Dateien (*.${format})|*.${format}|Alle Dateien (*.*)|*.*"
|
||||||
|
$dialog.Title = "Dokument speichern als"
|
||||||
|
$result = $dialog.ShowDialog()
|
||||||
|
if ($result -eq 'OK') { $dialog.FileName }
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = execSync(`powershell -Command "${powershell.replace(/\n/g, '; ')}"`, {
|
||||||
|
encoding: 'utf8'
|
||||||
|
});
|
||||||
|
return result.trim() || null;
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Linux - zenity oder kdialog
|
||||||
|
try {
|
||||||
|
const result = execSync(
|
||||||
|
`zenity --file-selection --save --confirm-overwrite --filename="${defaultName}.${format}"`,
|
||||||
|
{ encoding: 'utf8' }
|
||||||
|
);
|
||||||
|
return result.trim();
|
||||||
|
} catch (err) {
|
||||||
|
try {
|
||||||
|
const result = execSync(
|
||||||
|
`kdialog --getsavefilename . "${defaultName}.${format}"`,
|
||||||
|
{ encoding: 'utf8' }
|
||||||
|
);
|
||||||
|
return result.trim();
|
||||||
|
} catch (err2) {
|
||||||
|
// Fallback
|
||||||
|
return path.join(os.homedir(), 'Downloads', `${defaultName}.${format}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const module_exports = {
|
||||||
|
name: "htmlDocumentConverter",
|
||||||
|
type: "converter",
|
||||||
|
displayname: "HTML Document Converter",
|
||||||
|
description: "Converts LLM-generated HTML to PDF, DOCX, TXT, or HTML",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main conversion function
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {string} options.inputPath - Path to the HTML input
|
||||||
|
* @param {string} options.format - 'pdf' | 'docx' | 'html' | 'txt'
|
||||||
|
* @param {string} [options.outputName] - Optional output filename (without extension)
|
||||||
|
* @param {boolean} [options.showDialog] - Show save dialog (default: false in module mode, true in CLI mode)
|
||||||
|
*/
|
||||||
|
async convert({ inputPath, format = 'pdf', outputName, showDialog = false }) {
|
||||||
|
if (!fs.existsSync(inputPath)) {
|
||||||
|
throw new Error(`Input file not found: ${inputPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ext = path.extname(inputPath).toLowerCase();
|
||||||
|
const baseName = outputName || path.basename(inputPath, ext);
|
||||||
|
|
||||||
|
let outputFile;
|
||||||
|
|
||||||
|
if (showDialog) {
|
||||||
|
// Zeige nativen Dialog
|
||||||
|
outputFile = await showSaveDialog(baseName, format);
|
||||||
|
if (!outputFile) {
|
||||||
|
console.log('Speichervorgang abgebrochen.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Nutze Standard-Ausgabeverzeichnis
|
||||||
|
outputFile = path.join(outputDir, `${baseName}.${format.toLowerCase()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let htmlContent = fs.readFileSync(inputPath, 'utf8');
|
||||||
|
|
||||||
|
// Remove <think> tags if present
|
||||||
|
htmlContent = htmlContent.replace(/<think>[\s\S]*?<\/think>/gi, '');
|
||||||
|
|
||||||
|
switch (format.toLowerCase()) {
|
||||||
|
case 'html':
|
||||||
|
fs.writeFileSync(outputFile, htmlContent, 'utf8');
|
||||||
|
break;
|
||||||
|
case 'pdf':
|
||||||
|
await this.htmlToPDF(htmlContent, outputFile);
|
||||||
|
break;
|
||||||
|
case 'docx':
|
||||||
|
await this.htmlToDOCX(htmlContent, outputFile);
|
||||||
|
break;
|
||||||
|
case 'txt':
|
||||||
|
fs.writeFileSync(outputFile, this.htmlToTXT(htmlContent), 'utf8');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported format: ${format}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Erfolgreich gespeichert: ${outputFile}`);
|
||||||
|
return outputFile;
|
||||||
|
},
|
||||||
|
|
||||||
|
// HTML → PDF
|
||||||
|
async htmlToPDF(html, outputPath) {
|
||||||
|
const browser = await puppeteer.launch({
|
||||||
|
headless: true,
|
||||||
|
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||||||
|
});
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await page.setContent(html, { waitUntil: 'networkidle0' });
|
||||||
|
await page.pdf({
|
||||||
|
path: outputPath,
|
||||||
|
format: 'A4',
|
||||||
|
printBackground: true,
|
||||||
|
margin: { top: '20mm', right: '20mm', bottom: '20mm', left: '20mm' }
|
||||||
|
});
|
||||||
|
await browser.close();
|
||||||
|
},
|
||||||
|
|
||||||
|
// HTML → DOCX
|
||||||
|
async htmlToDOCX(html, outputPath) {
|
||||||
|
const buffer = await htmlToDocx(html);
|
||||||
|
fs.writeFileSync(outputPath, buffer);
|
||||||
|
},
|
||||||
|
|
||||||
|
// HTML → TXT (rudimentär)
|
||||||
|
htmlToTXT(html) {
|
||||||
|
return html.replace(/<[^>]*>/g, '').replace(/\s+\n/g, '\n').trim();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = module_exports;
|
||||||
|
|
||||||
|
// CLI usage mit Dialog
|
||||||
|
if (require.main === module) {
|
||||||
|
(async () => {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
if (args.length < 1) {
|
||||||
|
console.log('Usage: node htmlDocumentConverter.js <input.html> [format]');
|
||||||
|
console.log('Formats: pdf (default), docx, html, txt');
|
||||||
|
console.log('');
|
||||||
|
console.log('Ein nativer "Speichern unter" Dialog wird automatisch geöffnet.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputPath = args[0];
|
||||||
|
const format = args[1] || 'pdf';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await module_exports.convert({
|
||||||
|
inputPath,
|
||||||
|
format,
|
||||||
|
showDialog: true
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Konvertierung fehlgeschlagen:', err.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
name:"chatgpt", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
|
|
||||||
type:"document", // value used to differentiate each module to order them in the UI
|
|
||||||
displayname:"ChatGPT", // The displayname used within the UI
|
|
||||||
async function(parameter){
|
|
||||||
// TODO add code to actually send the transcript to ChatGPT and get a response back
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
// Ensure ffmpeg binary is available
|
// Ensure ffmpeg binary is available
|
||||||
if (!ffmpegPath) {
|
if (!ffmpegPath) {
|
||||||
throw new Error('FFmpeg binary not found!');
|
throw new Error('FFmpeg binary not found!');
|
||||||
@@ -25,18 +24,12 @@ module.exports = {
|
|||||||
outputType: String // Audio file output format
|
outputType: String // Audio file output format
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let progressBar = new cliProgress.SingleBar({
|
|
||||||
format: 'Processing |{bar}| {percentage}% | {timemark}',
|
|
||||||
barCompleteChar: '\u2588',
|
|
||||||
barIncompleteChar: '\u2591',
|
|
||||||
hideCursor: true
|
|
||||||
});
|
|
||||||
try {
|
try {
|
||||||
// if (meta.url === `file://${process.argv[1]}`) {
|
return new Promise((resolve, reject) => {
|
||||||
this.extractAudioFromVideo(parameter.inputVideoPath, progressBar, parameter.outputType)
|
this.extractAudioFromVideo(parameter.inputVideoPath, parameter.outputType)
|
||||||
.then(() => console.log('Audio extraction successful.'))
|
.then((resp) => resolve(resp))
|
||||||
.catch((err) => console.error(err));
|
.catch((err) => {reject(err)});
|
||||||
// }
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(parameter.outputType);
|
console.log(parameter.outputType);
|
||||||
|
|
||||||
@@ -53,7 +46,7 @@ module.exports = {
|
|||||||
* - Shows CLI progress bar
|
* - Shows CLI progress bar
|
||||||
* - Handles errors gracefully (without errors)
|
* - Handles errors gracefully (without errors)
|
||||||
*/
|
*/
|
||||||
extractAudioFromVideo: async function (videoFilePath, progressBar, outputType){
|
extractAudioFromVideo: async function (videoFilePath, outputType){
|
||||||
let inputVideoName = path.basename(videoFilePath, path.extname(videoFilePath));
|
let inputVideoName = path.basename(videoFilePath, path.extname(videoFilePath));
|
||||||
let outputAudioPath = path.join(outputDir, `${inputVideoName}.${outputType}`);
|
let outputAudioPath = path.join(outputDir, `${inputVideoName}.${outputType}`);
|
||||||
|
|
||||||
@@ -64,30 +57,18 @@ module.exports = {
|
|||||||
// .audioCodec('pcm_s16le')
|
// .audioCodec('pcm_s16le')
|
||||||
.audioChannels(1)
|
.audioChannels(1)
|
||||||
.audioFrequency(16000)
|
.audioFrequency(16000)
|
||||||
// .setFfmpegPath("./ffmpeg.exe")
|
|
||||||
.on('progress', (progress) => {
|
|
||||||
if (!progressBar.isActive) progressBar.start(100, 0, { timemark: '00:00:00' });
|
|
||||||
if (progress.percent) {
|
|
||||||
progressBar.update(progress.percent, { timemark: progress.timemark });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on('end', () => {
|
.on('end', () => {
|
||||||
progressBar.update(100, { timemark: 'done' });
|
resolve(outputAudioPath);
|
||||||
progressBar.stop();
|
|
||||||
console.log(`Extraction completed: ${outputAudioPath}`);
|
|
||||||
resolve();
|
|
||||||
})
|
})
|
||||||
.on('error', (err) => {
|
.on('error', (err) => {
|
||||||
progressBar.stop();
|
// console.error(`failed_audio_extraction on type ${outputType}: ${err.message}`);
|
||||||
console.error(`failed_audio_extraction on type ${outputType}: ${err.message}`);
|
|
||||||
reject(err);
|
reject(err);
|
||||||
})
|
})
|
||||||
.save(outputAudioPath);
|
.save(outputAudioPath);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log();
|
// console.log(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
|
||||||
|
// Prepare output directory (always storage/transcriptionSummaries under project root)
|
||||||
|
const outputDir = `${__dirname}/../../../storage/transcriptionSummaries`;
|
||||||
|
if (!fs.existsSync(outputDir)) {
|
||||||
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
//Speaker, ALL-Sentences, Start, End
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: "summarize-transcription", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
|
||||||
|
type: "summarizer", // value used to differentiate each module to order them in the UI
|
||||||
|
displayname: "Summarizer", // The displayname used within the UI
|
||||||
|
async function(args) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
let inputJson = args.json;
|
||||||
|
|
||||||
|
//JSON Path
|
||||||
|
if (args.jsonPath) {
|
||||||
|
try {
|
||||||
|
const raw = fs.readFileSync(args.jsonPath, "utf-8");
|
||||||
|
inputJson = JSON.parse(raw);
|
||||||
|
} catch (e) {
|
||||||
|
// console.error("Failed to load JSON from file:", e);
|
||||||
|
reject(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// JSON parsen
|
||||||
|
if (typeof args === "string") {
|
||||||
|
try {
|
||||||
|
await new Promise((res, rej) => {
|
||||||
|
fs.readFile(args, 'utf8', function (err, data) {
|
||||||
|
if (err){
|
||||||
|
rej(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
inputJson = JSON.parse(data);
|
||||||
|
res()
|
||||||
|
});
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
// console.log("Invalid JSON in summarize-transcription");
|
||||||
|
// console.log(e)
|
||||||
|
reject(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const words = inputJson.words;
|
||||||
|
if (!Array.isArray(words)) {
|
||||||
|
reject("No words Array found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const ENDINGS = [".", "!", "?"]; // '...' auch als Satzende ?
|
||||||
|
const ABBREVIATIONS = new Set(["z.B.", "bzw.", "u.a.", "Dr.", "Mr.", "Mrs.", "Prof.", "etc."]); //TODO weitere Ergaenzen
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
let currentSentence = "";
|
||||||
|
let currentSpeaker = null;
|
||||||
|
let startTime = null;
|
||||||
|
let endTime = null;
|
||||||
|
|
||||||
|
for (const w of words) {
|
||||||
|
if (!currentSpeaker) currentSpeaker = w.speaker;
|
||||||
|
if (startTime === null) startTime = w.start;
|
||||||
|
endTime = w.end;
|
||||||
|
|
||||||
|
//speaker changing
|
||||||
|
if (currentSpeaker !== w.speaker && currentSentence) {
|
||||||
|
const lastEntry = result[result.length - 1];
|
||||||
|
if (lastEntry && lastEntry.speaker === currentSpeaker) {
|
||||||
|
lastEntry.sentence += " " + currentSentence;
|
||||||
|
lastEntry.end = endTime;
|
||||||
|
} else {
|
||||||
|
result.push({
|
||||||
|
speaker: currentSpeaker,
|
||||||
|
sentence: currentSentence,
|
||||||
|
start: startTime,
|
||||||
|
end: endTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
currentSentence = "";
|
||||||
|
startTime = w.start;
|
||||||
|
}
|
||||||
|
currentSpeaker = w.speaker;
|
||||||
|
currentSentence += (currentSentence ? " " : "") + w.text; //sentence beginning or not
|
||||||
|
const lastWord = w.text.trim();
|
||||||
|
const lastChar = lastWord.slice(-1);
|
||||||
|
const isAbbreviation = ABBREVIATIONS.has(lastWord);
|
||||||
|
|
||||||
|
//sentence ending
|
||||||
|
if (ENDINGS.includes(lastChar) && !isAbbreviation) {
|
||||||
|
const lastEntry = result[result.length - 1];
|
||||||
|
if (lastEntry && lastEntry.speaker === currentSpeaker) {
|
||||||
|
lastEntry.sentence += " " + currentSentence;
|
||||||
|
lastEntry.end = endTime;
|
||||||
|
} else {
|
||||||
|
result.push({
|
||||||
|
speaker: currentSpeaker,
|
||||||
|
sentence: currentSentence,
|
||||||
|
start: startTime,
|
||||||
|
end: endTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
currentSentence = "";
|
||||||
|
startTime = null;
|
||||||
|
endTime = null;
|
||||||
|
currentSpeaker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// safe last sentence
|
||||||
|
if (currentSentence) {
|
||||||
|
const lastEntry = result[result.length - 1];
|
||||||
|
if (lastEntry && lastEntry.speaker === currentSpeaker) {
|
||||||
|
lastEntry.sentence += " " + currentSentence;
|
||||||
|
lastEntry.end = endTime;
|
||||||
|
} else {
|
||||||
|
result.push({
|
||||||
|
speaker: currentSpeaker,
|
||||||
|
sentence: currentSentence,
|
||||||
|
start: startTime,
|
||||||
|
end: endTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output as Text
|
||||||
|
const output = result.map(r =>
|
||||||
|
`Sprecher ${r.speaker} [${r.start.toFixed(2)} - ${r.end.toFixed(2)}]: ${r.sentence}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Output on cosole
|
||||||
|
//console.log("\n------------\nMerged Transcription Result:\n", output, "\n------------\n");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const jsonPath = path.join(outputDir, "transcription_result.json");
|
||||||
|
fs.writeFileSync(jsonPath, JSON.stringify(result, null, 2), "utf-8");
|
||||||
|
|
||||||
|
const txtPath = path.join(outputDir, "transcription_result.txt");
|
||||||
|
fs.writeFileSync(txtPath, output.join("\n"), "utf-8");
|
||||||
|
|
||||||
|
// console.log(`Summary successfully saved:\n- ${jsonPath}\n- ${txtPath}`);
|
||||||
|
|
||||||
|
resolve(jsonPath);
|
||||||
|
} catch (err) {
|
||||||
|
// console.error("Error saving Summary:", err);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
|
||||||
|
// Prepare output directory (always storage/transcriptionSummaries under project root)
|
||||||
|
const outputDir = `${__dirname}/../../../storage/transcriptionSummaries`;
|
||||||
|
if (!fs.existsSync(outputDir)) {
|
||||||
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSessionId(inputPath) {
|
||||||
|
try {
|
||||||
|
const parsed = new URL(inputPath);
|
||||||
|
const base = path.basename(parsed.pathname);
|
||||||
|
return base.replace(/\.[^.]+$/, '');
|
||||||
|
} catch {
|
||||||
|
return path.basename(inputPath, path.extname(inputPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Speaker, Sentence, Start, End
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: "summarize-transcription2", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
|
||||||
|
type: "summarizer", // value used to differentiate each module to order them in the UI
|
||||||
|
displayname: "Summarizer", // The displayname used within the UI
|
||||||
|
async function(args) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
let inputJson = args.json;
|
||||||
|
|
||||||
|
//JSON Path
|
||||||
|
if (args.jsonPath) {
|
||||||
|
try {
|
||||||
|
const raw = fs.readFileSync(args.jsonPath, "utf-8");
|
||||||
|
inputJson = JSON.parse(raw);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to load JSON from file:", e);
|
||||||
|
reject("Could not read JSON from file path.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// JSON parsen
|
||||||
|
if (typeof args === "string") {
|
||||||
|
try {
|
||||||
|
await new Promise((res, rej) => {
|
||||||
|
fs.readFile(args, 'utf8', function (err, data) {
|
||||||
|
if (err){
|
||||||
|
rej(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
inputJson = JSON.parse(data);
|
||||||
|
res()
|
||||||
|
});
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
// console.log("Invalid JSON in summarize-transcription");
|
||||||
|
// console.log(e)
|
||||||
|
reject(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const words = inputJson.words;
|
||||||
|
if (!Array.isArray(words)) {
|
||||||
|
reject("No words Array found")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ENDINGS = [".", "!", "?"]; // '...' auch als Satzende ?
|
||||||
|
const ABBREVIATIONS = new Set(["z.B.", "bzw.", "u.a.", "Dr.", "Mr.", "Mrs.", "Prof.", "etc."]); //TODO weitere Ergaenzen
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
let currentSentence = "";
|
||||||
|
let currentSpeaker = null;
|
||||||
|
let startTime = null;
|
||||||
|
let endTime = null;
|
||||||
|
|
||||||
|
for (const w of words) {
|
||||||
|
if (!currentSpeaker) currentSpeaker = w.speaker;
|
||||||
|
if (startTime === null) startTime = w.start;
|
||||||
|
endTime = w.end;
|
||||||
|
|
||||||
|
//speaker changing
|
||||||
|
if (currentSpeaker !== w.speaker && currentSentence) {
|
||||||
|
result.push({
|
||||||
|
speaker: currentSpeaker,
|
||||||
|
sentence: currentSentence,
|
||||||
|
start: startTime,
|
||||||
|
end: endTime
|
||||||
|
});
|
||||||
|
currentSentence = "";
|
||||||
|
startTime = w.start;
|
||||||
|
}
|
||||||
|
currentSpeaker = w.speaker;
|
||||||
|
currentSentence += (currentSentence ? " " : "") + w.text; //sentence beginning or not
|
||||||
|
const lastWord = w.text.trim();
|
||||||
|
const lastChar = lastWord.slice(-1);
|
||||||
|
const isAbbreviation = ABBREVIATIONS.has(lastWord);
|
||||||
|
|
||||||
|
//sentence ending
|
||||||
|
if (ENDINGS.includes(lastChar) && !isAbbreviation) {
|
||||||
|
result.push({
|
||||||
|
speaker: currentSpeaker,
|
||||||
|
sentence: currentSentence,
|
||||||
|
start: startTime,
|
||||||
|
end: endTime
|
||||||
|
});
|
||||||
|
currentSentence = "";
|
||||||
|
startTime = null;
|
||||||
|
endTime = null;
|
||||||
|
currentSpeaker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// safe last sentence
|
||||||
|
if (currentSentence) {
|
||||||
|
result.push({
|
||||||
|
speaker: currentSpeaker,
|
||||||
|
sentence: currentSentence,
|
||||||
|
start: startTime,
|
||||||
|
end: endTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Output as Text
|
||||||
|
const output = result.map(r =>
|
||||||
|
`Sprecher ${r.speaker} [${r.start.toFixed(2)} - ${r.end.toFixed(2)}]: ${r.sentence}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Output on cosole
|
||||||
|
//console.log("\n------------\nMerged Transcription Result:\n", output, "\n------------\n");
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
let filename = getSessionId(args);
|
||||||
|
|
||||||
|
const jsonPath = path.join(outputDir, `${filename}-${new Date().getTime()}.json`);
|
||||||
|
fs.writeFileSync(jsonPath, JSON.stringify(result, null, 2), "utf-8");
|
||||||
|
|
||||||
|
const txtPath = path.join(outputDir, `${filename}-${new Date().getTime()}.txt`);
|
||||||
|
fs.writeFileSync(txtPath, output.join("\n"), "utf-8");
|
||||||
|
|
||||||
|
// console.log(`Summary successfully saved:\n- ${jsonPath}\n- ${txtPath}`);
|
||||||
|
resolve(jsonPath);
|
||||||
|
} catch (err) {
|
||||||
|
// console.error("Error saving Summary:", err);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 module_exports = {
|
||||||
|
name: "llm-saia_openai_gpt",
|
||||||
|
type: "llm",
|
||||||
|
displayname: "GPT 120B",
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
// --- 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
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = module_exports;
|
||||||
|
|
||||||
|
// CLI Mode: Allow direct execution
|
||||||
|
if (require.main === module) {
|
||||||
|
(async () => {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (args.length < 2) {
|
||||||
|
console.error("Usage: node llm-openai-gpt.js <transcriptPath> <documentTypePath> [language]");
|
||||||
|
console.error("Example: node llm-openai-gpt.js ./transcript.json ./docType.txt de");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [transcriptPath, documentTypePath, language] = args;
|
||||||
|
|
||||||
|
// Check if API key is set
|
||||||
|
if (!SAIA_API_KEY) {
|
||||||
|
console.error("ERROR: SAIA_API_KEY environment variable is not set!");
|
||||||
|
console.error("Please set it with: export SAIA_API_KEY='your_api_key_here'");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if files exist
|
||||||
|
if (!fs.existsSync(transcriptPath)) {
|
||||||
|
console.error(`ERROR: Transcript file not found: ${transcriptPath}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(documentTypePath)) {
|
||||||
|
console.error(`ERROR: Document type file not found: ${documentTypePath}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Starting document generation...");
|
||||||
|
console.log(`Transcript: ${transcriptPath}`);
|
||||||
|
console.log(`Document Type: ${documentTypePath}`);
|
||||||
|
console.log(`Language: ${language || 'en (default)'}`);
|
||||||
|
|
||||||
|
await module_exports.createDocumentFromTranscript(
|
||||||
|
transcriptPath,
|
||||||
|
documentTypePath,
|
||||||
|
language || 'en'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("Done!");
|
||||||
|
})();
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure Google API key is set in environment variables: export GOOGLE_API_KEY="your_api_key_here"
|
||||||
|
const GEMINI_API_KEY = process.env.GOOGLE_API_KEY; // Ensure Google API key is set in environment variables: export GOOGLE_API_KEY="your_api_key_here"
|
||||||
|
const GEMINI_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent"; // URL for the REST call, used model and action
|
||||||
|
|
||||||
|
const module_exports = {
|
||||||
|
name: "llm-gemini",
|
||||||
|
type: "llm",
|
||||||
|
displayname: "Gemini LLM",
|
||||||
|
description: "Generates documents using Google Gemini LLM",
|
||||||
|
|
||||||
|
async function(parameter) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
// console.log("Gemini LLM module invoked with parameters:", parameter);
|
||||||
|
|
||||||
|
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 Gemini LLM module:", error);
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
createDocumentFromTranscript: async function(transcriptPath, documentTypePath, language = "en") { // default language is English
|
||||||
|
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(`${GEMINI_URL}?key=${GEMINI_API_KEY}`, { //safe model response in variable
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
parts: [
|
||||||
|
{ text: promptText } // Input prompt for content generation
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) { //ok is true when a responce was successfull
|
||||||
|
const text = await response.text();
|
||||||
|
throw new Error(`Gemini API error (${response.status}): ${text}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Get generated text from response or default to empty string (if null)
|
||||||
|
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
|
||||||
|
fs.writeFileSync(outPath, output, "utf8"); // Write output to file
|
||||||
|
|
||||||
|
// console.log("Generated document written to:", outPath);
|
||||||
|
resolve(outPath)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// console.error("Error generating Gemini content:", error);
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = module_exports;
|
||||||
|
|
||||||
|
// CLI Mode: Allow direct execution
|
||||||
|
if (require.main === module) {
|
||||||
|
(async () => {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (args.length < 2) {
|
||||||
|
console.error("Usage: node llm-gemini.js <transcriptPath> <documentTypePath> [language]");
|
||||||
|
console.error("Example: node llm-gemini.js ./transcript.json ./docType.txt de");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [transcriptPath, documentTypePath, language] = args;
|
||||||
|
|
||||||
|
// Check if API key is set
|
||||||
|
if (!GEMINI_API_KEY) {
|
||||||
|
console.error("ERROR: GOOGLE_API_KEY environment variable is not set!");
|
||||||
|
console.error("Please set it with: export GOOGLE_API_KEY='your_api_key_here'");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if files exist
|
||||||
|
if (!fs.existsSync(transcriptPath)) {
|
||||||
|
console.error(`ERROR: Transcript file not found: ${transcriptPath}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(documentTypePath)) {
|
||||||
|
console.error(`ERROR: Document type file not found: ${documentTypePath}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Starting document generation...");
|
||||||
|
console.log(`Transcript: ${transcriptPath}`);
|
||||||
|
console.log(`Document Type: ${documentTypePath}`);
|
||||||
|
console.log(`Language: ${language || 'en (default)'}`);
|
||||||
|
|
||||||
|
await module_exports.createDocumentFromTranscript(
|
||||||
|
transcriptPath,
|
||||||
|
documentTypePath,
|
||||||
|
language || 'en'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("Done!");
|
||||||
|
})();
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
const SAIA_URL = "https://chat-ai.academiccloud.de/v1/chat/completions"; // URL for the REST call, used model and action
|
||||||
|
|
||||||
|
const module_exports = {
|
||||||
|
name: "qwen3-235b-a22b",
|
||||||
|
type: "llm",
|
||||||
|
displayname: "QWEN 3 235B",
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
// --- 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
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = module_exports;
|
||||||
|
|
||||||
|
// CLI Mode: Allow direct execution
|
||||||
|
if (require.main === module) {
|
||||||
|
(async () => {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (args.length < 2) {
|
||||||
|
console.error("Usage: node qwen3.js <transcriptPath> <documentTypePath> [language]");
|
||||||
|
console.error("Example: node qwen3.js ./transcript.json ./docType.txt de");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [transcriptPath, documentTypePath, language] = args;
|
||||||
|
|
||||||
|
// Check if API key is set
|
||||||
|
if (!SAIA_API_KEY) {
|
||||||
|
console.error("ERROR: SAIA_API_KEY environment variable is not set!");
|
||||||
|
console.error("Please set it with: export SAIA_API_KEY='your_api_key_here'");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if files exist
|
||||||
|
if (!fs.existsSync(transcriptPath)) {
|
||||||
|
console.error(`ERROR: Transcript file not found: ${transcriptPath}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(documentTypePath)) {
|
||||||
|
console.error(`ERROR: Document type file not found: ${documentTypePath}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Starting document generation...");
|
||||||
|
console.log(`Transcript: ${transcriptPath}`);
|
||||||
|
console.log(`Document Type: ${documentTypePath}`);
|
||||||
|
console.log(`Language: ${language || 'en (default)'}`);
|
||||||
|
|
||||||
|
await module_exports.createDocumentFromTranscript(
|
||||||
|
transcriptPath,
|
||||||
|
documentTypePath,
|
||||||
|
language || 'en'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("Done!");
|
||||||
|
})();
|
||||||
|
}
|
||||||
+1
-2
@@ -5,7 +5,7 @@ import { fileURLToPath } from "url"; // To handle __dirname in ES modules
|
|||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url); // Get current file path
|
const __filename = fileURLToPath(import.meta.url); // Get current file path
|
||||||
const __dirname = path.dirname(__filename); // Get current directory path
|
const __dirname = path.dirname(__filename); // Get current directory path
|
||||||
const transcriptsDir = path.resolve(__dirname, "../../storage/transcriptions");
|
const transcriptsDir = path.resolve(__dirname, "../../../storage/transcriptions");
|
||||||
|
|
||||||
|
|
||||||
export class whisperLocal { // is called by transcribe.ts
|
export class whisperLocal { // is called by transcribe.ts
|
||||||
@@ -26,7 +26,6 @@ export class whisperLocal { // is called by transcribe.ts
|
|||||||
|
|
||||||
async transcribe(audioPath: string): Promise<string> { //asyncronous function to transcribe audio
|
async transcribe(audioPath: string): Promise<string> { //asyncronous function to transcribe audio
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const transcriptsDir = path.resolve(__dirname, "../../../../storage/transcripts"); //storage directory for transcripts
|
|
||||||
|
|
||||||
if (!fs.existsSync(transcriptsDir)) { //if transcripts directory does not exist, create it
|
if (!fs.existsSync(transcriptsDir)) { //if transcripts directory does not exist, create it
|
||||||
fs.mkdirSync(transcriptsDir, { recursive: true });
|
fs.mkdirSync(transcriptsDir, { recursive: true });
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
const API_KEY = process.env.ASSEMBLYAI_API_KEY;
|
||||||
|
const BASE_URL = 'https://api.assemblyai.com/v2';
|
||||||
|
|
||||||
|
//---------------------------------------------------Upload audio---------------------------------------------------
|
||||||
|
|
||||||
|
async function uploadAudio(audioPath) {
|
||||||
|
const audioData = fs.readFileSync(audioPath);
|
||||||
|
|
||||||
|
const response = await axios.post(`${BASE_URL}/upload`, audioData, {
|
||||||
|
headers: {
|
||||||
|
authorization: API_KEY,
|
||||||
|
'content-type': 'application/octet-stream'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data.upload_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
////---------------------------------------------------Extract session id---------------------------------------------------
|
||||||
|
|
||||||
|
function getSessionId(inputPath) {
|
||||||
|
try {
|
||||||
|
const parsed = new URL(inputPath);
|
||||||
|
const base = path.basename(parsed.pathname);
|
||||||
|
return base.replace(/\.[^.]+$/, '');
|
||||||
|
} catch {
|
||||||
|
return path.basename(inputPath, path.extname(inputPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------Create transcript---------------------------------------------------
|
||||||
|
|
||||||
|
async function createTranscript(audioUrl) {
|
||||||
|
const response = await axios.post(
|
||||||
|
`${BASE_URL}/transcript`,
|
||||||
|
{
|
||||||
|
audio_url: audioUrl,
|
||||||
|
speaker_labels: true,
|
||||||
|
language_detection: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
authorization: API_KEY,
|
||||||
|
'content-type': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------Poll transcript---------------------------------------------------
|
||||||
|
|
||||||
|
async function pollTranscript(transcriptId) {
|
||||||
|
while (true) {
|
||||||
|
const response = await axios.get(`${BASE_URL}/transcript/${transcriptId}`, {
|
||||||
|
headers: { authorization: API_KEY }
|
||||||
|
});
|
||||||
|
|
||||||
|
const status = response.data.status;
|
||||||
|
|
||||||
|
if (status === 'completed') return response.data;
|
||||||
|
if (status === 'error') throw new Error(`Transcription failed: ${response.data.error}`);
|
||||||
|
|
||||||
|
await new Promise(res => setTimeout(res, 3000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------Save transcript---------------------------------------------------
|
||||||
|
|
||||||
|
function saveTranscript(transcript, sessionId) {
|
||||||
|
const outputDir = path.join(__dirname, '../../../storage/transcripts');
|
||||||
|
|
||||||
|
if (!fs.existsSync(outputDir)) {
|
||||||
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputPath = path.join(outputDir, `${sessionId}.json`);
|
||||||
|
fs.writeFileSync(outputPath, JSON.stringify(transcript, null, 2));
|
||||||
|
|
||||||
|
// console.log(`Transcript saved: ${outputPath}`);
|
||||||
|
|
||||||
|
return outputPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------Modul---------------------------------------------------
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'assembly',
|
||||||
|
type: 'transcription',
|
||||||
|
displayname: 'AssemblyAI',
|
||||||
|
audioformat: "mp3",
|
||||||
|
|
||||||
|
async function(audioFileName) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
// audioFileName ist nur "datei.mp3"
|
||||||
|
const audioPath = audioFileName;
|
||||||
|
|
||||||
|
let audioUrl;
|
||||||
|
|
||||||
|
if (/^https?:\/\//i.test(audioFileName)) {
|
||||||
|
audioUrl = audioFileName;
|
||||||
|
} else {
|
||||||
|
if (!fs.existsSync(audioPath)) {
|
||||||
|
throw new Error(`Audio file not found: ${audioPath}`);
|
||||||
|
}
|
||||||
|
audioUrl = await uploadAudio(audioPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const transcriptId = await createTranscript(audioUrl);
|
||||||
|
const transcript = await pollTranscript(transcriptId);
|
||||||
|
|
||||||
|
const sessionId = getSessionId(audioFileName);
|
||||||
|
|
||||||
|
resolve(saveTranscript(transcript, sessionId));
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// console.error('Transcription error:', error.message);
|
||||||
|
reject(error);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
name:"assembly", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
|
|
||||||
type:"transcription", // value used to differentiate each module to order them in the UI
|
|
||||||
displayname:"Assembly", // The displayname used within the UI
|
|
||||||
async function(parameter){
|
|
||||||
// TODO add code to actually process the audio file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,319 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "transcription",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"dependencies": {
|
|
||||||
"axios": "^1.13.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/axios": "^0.9.36",
|
|
||||||
"@types/node": "^24.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/axios": {
|
|
||||||
"version": "0.9.36",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.9.36.tgz",
|
|
||||||
"integrity": "sha512-NLOpedx9o+rxo/X5ChbdiX6mS1atE4WHmEEIcR9NLenRVa5HoVjAvjafwU3FPTqnZEstpoqCaW7fagqSoTDNeg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/node": {
|
|
||||||
"version": "24.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz",
|
|
||||||
"integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"undici-types": "~7.16.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/asynckit": {
|
|
||||||
"version": "0.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
|
||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/axios": {
|
|
||||||
"version": "1.13.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
|
||||||
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"follow-redirects": "^1.15.6",
|
|
||||||
"form-data": "^4.0.4",
|
|
||||||
"proxy-from-env": "^1.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/call-bind-apply-helpers": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"function-bind": "^1.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/combined-stream": {
|
|
||||||
"version": "1.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
|
||||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"delayed-stream": "~1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/delayed-stream": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/dunder-proto": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"call-bind-apply-helpers": "^1.0.1",
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"gopd": "^1.2.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/es-define-property": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/es-errors": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/es-object-atoms": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"es-errors": "^1.3.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/es-set-tostringtag": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"get-intrinsic": "^1.2.6",
|
|
||||||
"has-tostringtag": "^1.0.2",
|
|
||||||
"hasown": "^2.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/follow-redirects": {
|
|
||||||
"version": "1.15.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
|
||||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "individual",
|
|
||||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"debug": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/form-data": {
|
|
||||||
"version": "4.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
|
||||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"asynckit": "^0.4.0",
|
|
||||||
"combined-stream": "^1.0.8",
|
|
||||||
"es-set-tostringtag": "^2.1.0",
|
|
||||||
"hasown": "^2.0.2",
|
|
||||||
"mime-types": "^2.1.12"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/function-bind": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/get-intrinsic": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"call-bind-apply-helpers": "^1.0.2",
|
|
||||||
"es-define-property": "^1.0.1",
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"es-object-atoms": "^1.1.1",
|
|
||||||
"function-bind": "^1.1.2",
|
|
||||||
"get-proto": "^1.0.1",
|
|
||||||
"gopd": "^1.2.0",
|
|
||||||
"has-symbols": "^1.1.0",
|
|
||||||
"hasown": "^2.0.2",
|
|
||||||
"math-intrinsics": "^1.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/get-proto": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"dunder-proto": "^1.0.1",
|
|
||||||
"es-object-atoms": "^1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/gopd": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/has-symbols": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/has-tostringtag": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"has-symbols": "^1.0.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/hasown": {
|
|
||||||
"version": "2.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"function-bind": "^1.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/math-intrinsics": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mime-db": {
|
|
||||||
"version": "1.52.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
|
||||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mime-types": {
|
|
||||||
"version": "2.1.35",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
|
||||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"mime-db": "1.52.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/proxy-from-env": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/undici-types": {
|
|
||||||
"version": "7.16.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
|
||||||
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"axios": "^1.13.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/axios": "^0.9.36",
|
|
||||||
"@types/node": "^24.10.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name:"Startup_function",
|
name:"Startup_function",
|
||||||
async function(){
|
async function(){
|
||||||
@@ -6,8 +7,18 @@ module.exports = {
|
|||||||
// We are now calling the example function from the example folder
|
// We are now calling the example function from the example folder
|
||||||
mapFunctions.get("example").function("Startup")
|
mapFunctions.get("example").function("Startup")
|
||||||
|
|
||||||
|
// let transcript = await mapFunctions.get("assembly").function('../../storage/audio/IMG_2978.wav');
|
||||||
|
|
||||||
|
// let summary = await mapFunctions.get("summarize-transcription").function({jsonPath:'/Users/santa/Proj25/video2document/storage/transcripts/IMG_2978.json'});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./a.mp4", outputType:"wav"})
|
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./a.mp4", outputType:"wav"})
|
||||||
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./b.mp4", outputType:"wav"})
|
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./b.mp4", outputType:"wav"})
|
||||||
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./b.mp4", outputType:"flac"})
|
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./b.mp4", outputType:"flac"})
|
||||||
|
|
||||||
|
// mapFunctions.get("ipc-handler").function("extraction-video-to-audio", {inputVideoPath:"./a.mp4", outputType:"flac"})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
module.exports = {
|
||||||
|
name:"module-handler", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
|
||||||
|
async function(module,parameter){
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
if(mapFunctions.get(module) == undefined){
|
||||||
|
reject("requested modules not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mapFunctions.get(module).function(parameter).then((result) => {
|
||||||
|
resolve(result)
|
||||||
|
}).catch((err) => {
|
||||||
|
reject(err)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// services/pipeline/jobs/transcribeLatest.ts
|
// services/pipeline/jobs/transcribeLatest.ts
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
// @ts-ignore: module has no type declarations or cannot be resolved in current TS config
|
||||||
import assembly from '../../modules/transcription/assembly';
|
import assembly from '../../modules/transcription/assembly';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,217 @@
|
|||||||
|
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: Unclear:<reason>.
|
||||||
|
|
||||||
|
==================== STRUCTURE & RULES ====================
|
||||||
|
|
||||||
|
{
|
||||||
|
"FORMAT": "HTML",
|
||||||
|
|
||||||
|
"STRUCTURE": {
|
||||||
|
"titlepage": [
|
||||||
|
"title",
|
||||||
|
"date",
|
||||||
|
"start",
|
||||||
|
"end",
|
||||||
|
"duration",
|
||||||
|
"location",
|
||||||
|
"host",
|
||||||
|
"participants"
|
||||||
|
],
|
||||||
|
|
||||||
|
"toc": "[section](#anchor) — HH:MM:SS",
|
||||||
|
|
||||||
|
"section": {
|
||||||
|
"h2": "<topic> — 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": "Unclear:<reason>"
|
||||||
|
},
|
||||||
|
|
||||||
|
"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:<reason>.
|
||||||
|
|
||||||
|
==================== STRUCTURE & RULES ====================
|
||||||
|
|
||||||
|
{
|
||||||
|
"FORMAT": "HTML",
|
||||||
|
|
||||||
|
"STRUCTURE": {
|
||||||
|
"titlepage": [
|
||||||
|
"title",
|
||||||
|
"date",
|
||||||
|
"start",
|
||||||
|
"end",
|
||||||
|
"duration",
|
||||||
|
"location",
|
||||||
|
"host",
|
||||||
|
"participants"
|
||||||
|
],
|
||||||
|
|
||||||
|
"toc": "[section](#anchor) — HH:MM:SS",
|
||||||
|
|
||||||
|
"section": {
|
||||||
|
"h2": "<topic> — 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:<reason>"
|
||||||
|
},
|
||||||
|
|
||||||
|
"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.
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
import assemblyModule from './assembly.ts';
|
import assemblyModule from '../../services/modules/transcription-remote/assembly.ts';
|
||||||
|
|
||||||
// Test: URL passed as argument OR local file ./storage/audio/test.wav
|
// Test: URL passed as argument OR local file ./storage/audio/test.wav
|
||||||
const audioPath = process.argv[2] || './storage/audio/test.wav';
|
const audioPath = process.argv[2] || './storage/audio/test.wav';
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
require('dotenv').config();
|
||||||
|
const path = require('path');
|
||||||
|
const assemblyModule = require('../../services/modules/transcription-remote/assembly.js');
|
||||||
|
|
||||||
|
// Audio-Datei oder URL aus Kommandozeile, Standard: test.wav
|
||||||
|
const audioPath = process.argv[2] || './storage/audio/IMG_2978.wav';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const transcript = await assemblyModule.run(audioPath);
|
||||||
|
|
||||||
|
console.log('Transcription succesful');
|
||||||
|
console.log('Transcript ID:', transcript?.id);
|
||||||
|
console.log('Speaker labels:', transcript?.utterances?.length || 0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in Transcription:', error?.message || error);
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
**Goal:** Generate a structured meeting report (Markdown). **Output ONLY:** final .md. No meta.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"FORMAT": "markdown",
|
||||||
|
|
||||||
|
"STRUCTURE": {
|
||||||
|
"titlepage": ["title","date","start","end","duration","location","host","participants"],
|
||||||
|
"toc": "[section](#anchor) — HH:MM:SS",
|
||||||
|
"section": {
|
||||||
|
"h2": "<topic> — 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:<reason>"
|
||||||
|
},
|
||||||
|
|
||||||
|
"JSON_OUTPUT_OPTIONAL": true,
|
||||||
|
|
||||||
|
"PROMPT_SNIPPET": "Generate meeting report in markdown using STRUCTURE and STYLE. Output only the report."
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
const gemini = require("../../../services/modules/llm-gemini/gemini.js");
|
||||||
|
|
||||||
|
gemini.function({
|
||||||
|
inputTranscriptPath: "./transcript.json",
|
||||||
|
documentTypePath: "./documentType.json",
|
||||||
|
language: "de"
|
||||||
|
});
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"speaker": "A",
|
||||||
|
"sentence": "Smoke from hundreds of wildfires in Canada is triggering air quality alerts throughout the US Skylines from Maine to Maryland to Minnesota are gray and smoggy. And in some places, the air quality warnings include the warning to stay inside. We wanted to better understand what's happening here and why, so we called Peter DeCarlo, an associate professor in the Department of Environmental Health and Engineering at Johns Hopkins University. Good morning, Professor.",
|
||||||
|
"start": 240,
|
||||||
|
"end": 26560
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "B",
|
||||||
|
"sentence": "Good morning.",
|
||||||
|
"start": 28060,
|
||||||
|
"end": 28620
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "A",
|
||||||
|
"sentence": "So what is it about the conditions right now that have caused this round of wildfires to affect so many people so far away?",
|
||||||
|
"start": 29100,
|
||||||
|
"end": 37100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "B",
|
||||||
|
"sentence": "Well, there's a couple of things. The season has been pretty dry already, and then the fact that we're getting hit in the US is because there's a couple weather systems that are essentially channeling the smoke from those Canadian wildfires through Pennsylvania into the mid Atlantic and the Northeast and kind of just dropping the smoke there.",
|
||||||
|
"start": 39100,
|
||||||
|
"end": 55820
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "A",
|
||||||
|
"sentence": "So what is it in this haze that makes it harmful? And I'm assuming it is harmful.",
|
||||||
|
"start": 56590,
|
||||||
|
"end": 60670
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "B",
|
||||||
|
"sentence": "It is, it is. The levels outside right now in Baltimore are considered unhealthy. And most of that is due to what's called particulate matter, which are tiny particles, microscopic, smaller than the width of your hair, that can get into your lungs and impact your respiratory system, your cardiovascular system, and even your neurological, your brain.",
|
||||||
|
"start": 62350,
|
||||||
|
"end": 82590
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "A",
|
||||||
|
"sentence": "What makes this particularly harmful? Is it the volume of particulate? Is it something in particular? What is it exactly? Can you just drill down on that a little bit more?",
|
||||||
|
"start": 83630,
|
||||||
|
"end": 92190
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "B",
|
||||||
|
"sentence": "Yeah. So the concentration of particulate matter, I was looking at some of the monitors that we have was reaching levels of what are, in science speak, 150 micrograms per meter cubed, which is more than 10 times what the annual average should be in about four times higher than what you're supposed to have on a 24 hour average. And so the concentrations of these particles in the air are just much, much, much higher than we typically see. And exposure to those high levels can lead to a host of health problems.",
|
||||||
|
"start": 93550,
|
||||||
|
"end": 123350
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "A",
|
||||||
|
"sentence": "And who is most vulnerable? I noticed that in New York City, for example, they're canceling outdoor activities. And so here it is in the early days of summer and they have to keep all the kids inside. So who tends to be vulnerable in a situation like this?",
|
||||||
|
"start": 123430,
|
||||||
|
"end": 135990
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "B",
|
||||||
|
"sentence": "It's the youngest. So children, obviously, whose bodies are still developing, the elderly who are, you know, their bodies are more in decline and they're more susceptible to the health impacts of breathing, the poor air quality. And then people who have pre existing health conditions, people with respiratory conditions or heart conditions, can be triggered by high levels of air pollution.",
|
||||||
|
"start": 137610,
|
||||||
|
"end": 156650
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "A",
|
||||||
|
"sentence": "Could this get worse?",
|
||||||
|
"start": 157450,
|
||||||
|
"end": 158650
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "B",
|
||||||
|
"sentence": "That's a good question. I mean, I think if in some areas it's much worse than others and it just depends on kind of where the smoke is concentrated. I think New York has some of the higher concentrations right now, but that's going to change as that air moves away from the New York area. But over the course of the next few days, we will see different areas being hit at different times with the highest concentrations.",
|
||||||
|
"start": 162170,
|
||||||
|
"end": 183420
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "A",
|
||||||
|
"sentence": "I was going to ask you about.",
|
||||||
|
"start": 183740,
|
||||||
|
"end": 184660
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "B",
|
||||||
|
"sentence": "More fires start burning. I don't expect the concentrations to go up too much higher.",
|
||||||
|
"start": 184660,
|
||||||
|
"end": 189020
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "A",
|
||||||
|
"sentence": "I was going to ask you how and you started to answer this, but how much longer could this last? Forgive me if I'm asking you to speculate, but what do you think?",
|
||||||
|
"start": 189100,
|
||||||
|
"end": 196220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "B",
|
||||||
|
"sentence": "Well, I think the fires are going to burn for a little bit longer. But the key for us in the US Is the weather system changing. Right now it's the weather systems that are pulling that air into our Mid Atlantic and Northeast region. As those weather systems change and shift, we'll see that smoke going elsewhere and not impact us in this region as much. I think that's going to be the defining factor. I think the next couple days we're going to see a shift in that weather pattern and start to push the smoke away from where we are.",
|
||||||
|
"start": 198280,
|
||||||
|
"end": 227480
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "A",
|
||||||
|
"sentence": "And finally, with the impacts of climate change, we are seeing more wildfires. Will we be seeing more of these kinds of wide ranging air quality consequences or circumstances?",
|
||||||
|
"start": 227930,
|
||||||
|
"end": 239850
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "B",
|
||||||
|
"sentence": "I mean, that is one of the predictions for climate change. Looking into the future, the fire season is starting earlier and lasting longer and we're seeing more frequent fires. So yeah, this is probably something that we'll be seeing more, more frequently. This tends to be much more of an issue in the western U.S. so the eastern U.S. getting hit right now is a little bit new. But yeah, I think with climate change moving forward, this is something that is going to happen more frequently.",
|
||||||
|
"start": 241370,
|
||||||
|
"end": 267570
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "A",
|
||||||
|
"sentence": "That's Peter DeCarlo, associate professor in the Department of Environmental Health and Engineering at Johns Hopkins University. Professor DeCarlo, thanks so much for joining us and sharing this expertise with us.",
|
||||||
|
"start": 267970,
|
||||||
|
"end": 278210
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "B",
|
||||||
|
"sentence": "Thank you for having me.",
|
||||||
|
"start": 279410,
|
||||||
|
"end": 280530
|
||||||
|
}
|
||||||
|
]
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const transSummarizer = require("../../services/modules/jsonTools/transcriptionSummarizer.js");
|
||||||
|
|
||||||
|
// JSON-Datei laden
|
||||||
|
const inputJson = JSON.parse(fs.readFileSync("./testFile.json", "utf8"));
|
||||||
|
|
||||||
|
// Übergabe an den Summarizer
|
||||||
|
transSummarizer.function({
|
||||||
|
json: inputJson
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
from fastapi.testclient import TestClient
|
|
||||||
from app.main import app
|
|
||||||
|
|
||||||
client = TestClient(app)
|
|
||||||
|
|
||||||
def test_health():
|
|
||||||
response = client.get("/health")
|
|
||||||
assert response.status_code == 200
|
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
// DO NOT TOUCH THIS
|
||||||
|
require("../../requires.js")
|
||||||
|
|
||||||
|
mapFunctions = new Map()
|
||||||
|
// Loading the Function Map
|
||||||
|
var path = `${mainDir}/services/modules`
|
||||||
|
var folders = fs.readdirSync(path).filter(function (file) {
|
||||||
|
return fs.statSync(path+'/'+file).isDirectory();
|
||||||
|
});
|
||||||
|
folders.forEach(element => {
|
||||||
|
var commandFiles = fs.readdirSync(`${path}/${element}`).filter(file => file.endsWith('.js') && !file.startsWith("index"));
|
||||||
|
for (const file of commandFiles) {
|
||||||
|
delete require.cache[require.resolve(`${path}/${element}/${file}`)];
|
||||||
|
const command = require(`${path}/${element}/${file}`);
|
||||||
|
mapFunctions.set(command.name, command);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// You can touch beyond this point
|
||||||
|
|
||||||
|
let audiopath
|
||||||
|
let transcriptPath
|
||||||
|
let summarizePath
|
||||||
|
let llmpath
|
||||||
|
|
||||||
|
describe("Unit Tests", function() {
|
||||||
|
|
||||||
|
describe('Audio Extraction', function () {
|
||||||
|
this.slow(1000)
|
||||||
|
this.timeout(3000)
|
||||||
|
it('Extract .mp4 to .mp3', function (done) {
|
||||||
|
|
||||||
|
mapFunctions.get("extraction-video-to-audio").function({inputVideoPath: __dirname.replaceAll("\\","/")+"/testvideo.mp4", outputType: "mp3"}).then(resp => {
|
||||||
|
audiopath = resp
|
||||||
|
// console.log(resp);
|
||||||
|
done()
|
||||||
|
}).catch(err => {
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('Extract .mp4 to .flac', function (done) {
|
||||||
|
|
||||||
|
mapFunctions.get("extraction-video-to-audio").function({inputVideoPath: __dirname.replaceAll("\\","/")+"/testvideo.mp4", outputType: "flac"}).then(resp => {
|
||||||
|
// console.log(resp);
|
||||||
|
done()
|
||||||
|
}).catch(err => {
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('Extracting to a nonexistant format', function (done) {
|
||||||
|
|
||||||
|
mapFunctions.get("extraction-video-to-audio").function({inputVideoPath: __dirname.replaceAll("\\","/")+"/testvideo.mp4", outputType: "qqq"}).then(resp => {
|
||||||
|
// console.log(resp);
|
||||||
|
done("Didnt crash")
|
||||||
|
}).catch(err => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('Extracting from nonexistant file', function (done) {
|
||||||
|
|
||||||
|
mapFunctions.get("extraction-video-to-audio").function({inputVideoPath: "a", outputType: "mp3"}).then(resp => {
|
||||||
|
// console.log(resp);
|
||||||
|
done("Didnt crash")
|
||||||
|
}).catch(err => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('Extracting from nonexistant file to nonexistant format', function (done) {
|
||||||
|
|
||||||
|
mapFunctions.get("extraction-video-to-audio").function({inputVideoPath: "a", outputType: "qqq"}).then(resp => {
|
||||||
|
// console.log(resp);
|
||||||
|
done("Didnt crash")
|
||||||
|
}).catch(err => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe("Audio Transcription", function() {
|
||||||
|
this.slow(20000)
|
||||||
|
this.timeout(120000)
|
||||||
|
|
||||||
|
it('Assembly', function (done) {
|
||||||
|
mapFunctions.get("assembly").function(audiopath).then(resp => {
|
||||||
|
// console.log(resp);
|
||||||
|
transcriptPath = resp
|
||||||
|
done()
|
||||||
|
}).catch(err => {
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Assembly Wrong file', function (done) {
|
||||||
|
mapFunctions.get("assembly").function("a").then(resp => {
|
||||||
|
// console.log(resp);
|
||||||
|
// transcriptPath = resp
|
||||||
|
done("Didnt crash")
|
||||||
|
}).catch(err => {
|
||||||
|
// console.log(err);
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// TODO add more Transcription Tool tests here
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Transcript Summarizer", function() {
|
||||||
|
this.slow(100)
|
||||||
|
this.timeout(1000)
|
||||||
|
|
||||||
|
it("Summarizer 1", function (done){
|
||||||
|
mapFunctions.get("summarize-transcription").function(transcriptPath).then(resp => {
|
||||||
|
done()
|
||||||
|
}).catch(err => {
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Summarizer 1 Wrong File", function (done){
|
||||||
|
mapFunctions.get("summarize-transcription").function("a").then(resp => {
|
||||||
|
done("Didnt crash")
|
||||||
|
}).catch(err => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Summarizer 2 (Main)", function (done){
|
||||||
|
mapFunctions.get("summarize-transcription2").function(transcriptPath).then(resp => {
|
||||||
|
summarizePath = resp
|
||||||
|
done()
|
||||||
|
}).catch(err => {
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Summarizer 2 (Main) Wrong File", function (done){
|
||||||
|
mapFunctions.get("summarize-transcription2").function("a").then(resp => {
|
||||||
|
done("Didnt crash")
|
||||||
|
}).catch(err => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Large Language Model", function() {
|
||||||
|
this.slow(30000)
|
||||||
|
this.timeout(120000)
|
||||||
|
|
||||||
|
// it("ChatGPT", function (done){
|
||||||
|
// mapFunctions.get("chatgpt").function({inputTranscriptPath: summarizePath, documentTypePath: "./storage/documentType/meetingReport.json", language: "en"}).then(resp => {
|
||||||
|
// done()
|
||||||
|
// }).catch(err => {
|
||||||
|
// throw err
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
|
||||||
|
it("Gemini", function (done){
|
||||||
|
mapFunctions.get("llm-gemini").function({inputTranscriptPath: summarizePath, documentTypePath: "./storage/documentType/meetingReport.json", language: "en"}).then(resp => {
|
||||||
|
llmpath = resp
|
||||||
|
done()
|
||||||
|
}).catch(err => {
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
after(function() {
|
||||||
|
console.log(`\n\n\n${audiopath} \n${transcriptPath} \n${summarizePath} \n${llmpath}`);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
Reference in New Issue
Block a user