Merge branch 'develop' into 'feature/ui-test'

# Conflicts:
#   main.js
This commit is contained in:
Hughes, Mike
2025-12-16 15:54:00 +01:00
32 changed files with 2782 additions and 342 deletions
+15 -1
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -147,7 +147,7 @@
</div> </div>
<div class="step" id="step6" style="display:none;"> <div class="step" id="step6" style="display:none;">
<button class="download-btn" id="downloadButton" disabled>Download</button> <button class="download-btn" id="downloadButton" onclick="fileDownload()">Download</button>
</div> </div>
</div> </div>
+2 -2
View File
@@ -328,8 +328,8 @@ function sendSpeakerPackages(){
function fileDownload() { function fileDownload() {
try { try {
window.file_download.fileDownload(); window.download.file_download();
} catch (error) { } catch (error) {
console.error("Download failed:", error);
} }
} }
+1
View File
@@ -128,6 +128,7 @@ electron.ipcMain.handle('get-module-names', async () => {
electron.ipcMain.on("file_submit", async (event, args) => { electron.ipcMain.on("file_submit", async (event, args) => {
try { try {
globalArgs = args
let curstep = 0 let curstep = 0
let totalsteps = 4 let totalsteps = 4
+1862 -11
View File
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -3,12 +3,14 @@
"@google/genai": "^1.30.0", "@google/genai": "^1.30.0",
"@types/axios": "^0.9.36", "@types/axios": "^0.9.36",
"axios": "^1.13.2", "axios": "^1.13.2",
"cli-progress": "^3.12.0",
"dotenv": "^17.2.3", "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",
@@ -27,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",
-3
View File
@@ -1,3 +0,0 @@
fastapi
uvicorn
pytest
+4 -1
View File
@@ -6,12 +6,15 @@ 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');
+194
View File
@@ -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);
}
})();
}
+5 -22
View File
@@ -24,17 +24,11 @@ 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 {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.extractAudioFromVideo(parameter.inputVideoPath, progressBar, parameter.outputType) this.extractAudioFromVideo(parameter.inputVideoPath, parameter.outputType)
.then((resp) => resolve(resp)) .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);
@@ -52,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}`);
@@ -63,28 +57,17 @@ 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' });
progressBar.stop();
console.log(`Extraction completed: ${outputAudioPath}`);
resolve(outputAudioPath); resolve(outputAudioPath);
}) })
.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);
} }
}); });
} }
@@ -21,30 +21,36 @@ module.exports = {
const raw = fs.readFileSync(args.jsonPath, "utf-8"); const raw = fs.readFileSync(args.jsonPath, "utf-8");
inputJson = JSON.parse(raw); inputJson = JSON.parse(raw);
} catch (e) { } catch (e) {
console.error("Failed to load JSON from file:", e); // console.error("Failed to load JSON from file:", e);
return { error: "Could not read JSON from file path." }; reject(e)
return
} }
} }
// JSON parsen // JSON parsen
if (typeof args === "string") { if (typeof args === "string") {
try { try {
await new Promise((res) => { await new Promise((res, rej) => {
fs.readFile(args, 'utf8', function (err, data) { fs.readFile(args, 'utf8', function (err, data) {
if (err) throw err; if (err){
rej(err)
return
}
inputJson = JSON.parse(data); inputJson = JSON.parse(data);
res() res()
}); });
}) })
} catch (e) { } catch (e) {
console.log("Invalid JSON in summarize-transcription"); // console.log("Invalid JSON in summarize-transcription");
console.log(e) // console.log(e)
return { error: "Invalid JSON" }; reject(e)
return
} }
} }
const words = inputJson.words; const words = inputJson.words;
if (!Array.isArray(words)) { if (!Array.isArray(words)) {
return { error: "No words Array found" }; reject("No words Array found")
return
} }
const ENDINGS = [".", "!", "?"]; // '...' auch als Satzende ? const ENDINGS = [".", "!", "?"]; // '...' auch als Satzende ?
@@ -136,11 +142,11 @@ module.exports = {
const txtPath = path.join(outputDir, "transcription_result.txt"); const txtPath = path.join(outputDir, "transcription_result.txt");
fs.writeFileSync(txtPath, output.join("\n"), "utf-8"); fs.writeFileSync(txtPath, output.join("\n"), "utf-8");
console.log(`Summary successfully saved:\n- ${jsonPath}\n- ${txtPath}`); // console.log(`Summary successfully saved:\n- ${jsonPath}\n- ${txtPath}`);
resolve(jsonPath); resolve(jsonPath);
} catch (err) { } catch (err) {
console.error("Error saving Summary:", err); // console.error("Error saving Summary:", err);
reject(err); reject(err);
} }
}) })
@@ -32,29 +32,35 @@ module.exports = {
inputJson = JSON.parse(raw); inputJson = JSON.parse(raw);
} catch (e) { } catch (e) {
console.error("Failed to load JSON from file:", e); console.error("Failed to load JSON from file:", e);
return { error: "Could not read JSON from file path." }; reject("Could not read JSON from file path.")
return
} }
} }
// JSON parsen // JSON parsen
if (typeof args === "string") { if (typeof args === "string") {
try { try {
await new Promise((res) => { await new Promise((res, rej) => {
fs.readFile(args, 'utf8', function (err, data) { fs.readFile(args, 'utf8', function (err, data) {
if (err) throw err; if (err){
rej(err)
return
}
inputJson = JSON.parse(data); inputJson = JSON.parse(data);
res() res()
}); });
}) })
} catch (e) { } catch (e) {
console.log("Invalid JSON in summarize-transcription"); // console.log("Invalid JSON in summarize-transcription");
console.log(e) // console.log(e)
return { error: "Invalid JSON" }; reject(e)
return
} }
} }
const words = inputJson.words; const words = inputJson.words;
if (!Array.isArray(words)) { if (!Array.isArray(words)) {
return { error: "No words Array found" }; reject("No words Array found")
return;
} }
const ENDINGS = [".", "!", "?"]; // '...' auch als Satzende ? const ENDINGS = [".", "!", "?"]; // '...' auch als Satzende ?
@@ -132,10 +138,10 @@ module.exports = {
const txtPath = path.join(outputDir, `${filename}-${new Date().getTime()}.txt`); const txtPath = path.join(outputDir, `${filename}-${new Date().getTime()}.txt`);
fs.writeFileSync(txtPath, output.join("\n"), "utf-8"); fs.writeFileSync(txtPath, output.join("\n"), "utf-8");
console.log(`Summary successfully saved:\n- ${jsonPath}\n- ${txtPath}`); // console.log(`Summary successfully saved:\n- ${jsonPath}\n- ${txtPath}`);
resolve(jsonPath); resolve(jsonPath);
} catch (err) { } catch (err) {
console.error("Error saving Summary:", err); // console.error("Error saving Summary:", err);
reject(err); reject(err);
} }
}) })
+20 -15
View File
@@ -9,7 +9,6 @@ if (!fs.existsSync(outputDir)) {
// Ensure SAIA API key is set in environment variables: export SAIA_API_KEY="your_api_key_here" // 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_API_KEY = process.env.SAIA_API_KEY; // Ensure SAIA API key is set in environment variables
const SAIA_URL = "https://chat-ai.academiccloud.de/v1/chat/completions"; // URL for the REST call, used model and action const SAIA_URL = "https://chat-ai.academiccloud.de/v1/chat/completions"; // URL for the REST call, used model and action
const module_exports = { const module_exports = {
@@ -19,28 +18,33 @@ const module_exports = {
description: "Generates documents using OpenAI GPT OSS 120B via SAIA platform", description: "Generates documents using OpenAI GPT OSS 120B via SAIA platform",
async function(parameter) { async function(parameter) {
return new Promise(async (resolve, reject) => {
try { try {
console.log("SAIA OpenAI GPT module invoked with parameters:", parameter); // 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 resolve(await this.createDocumentFromTranscript( //Call the function to create document with transcript, document type and language
parameter.inputTranscriptPath, // Path to input transcript file 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.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 parameter.language // Language for the document which is chosen in the front end by the user
); ));
} catch (error) { } catch (error) {
console.error("Error in SAIA OpenAI GPT module:", error); // console.error("Error in SAIA OpenAI GPT module:", error);
reject(error)
} }
})
}, },
createDocumentFromTranscript: async function(transcriptPath, documentTypePath, language = "en") { // default language is English createDocumentFromTranscript: async function(transcriptPath, documentTypePath, language = "en") { // default language is English
return new Promise(async(resolve, reject) => {
try { try {
const transcript = await fs.promises.readFile(transcriptPath, "utf-8"); //read transcript file from Path 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 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 const promptText = `${documentType}, in language ${language}, transcript:\n\n${transcript}`; //combine doc type, language and transcript - Change prompt here if needed
// --- REST CALL --- // --- REST CALL ---
const response = await fetch(SAIA_URL, { const response = await fetch(SAIA_URL, { //safe model response in variable
method: "POST", method: "POST",
headers: { headers: {
"Authorization": `Bearer ${SAIA_API_KEY}`, "Authorization": `Bearer ${SAIA_API_KEY}`,
@@ -50,14 +54,14 @@ const module_exports = {
body: JSON.stringify({ body: JSON.stringify({
model: "openai-gpt-oss-120b", model: "openai-gpt-oss-120b",
messages: [ messages: [
{ role: "system", content: "You are a helpful assistant that generates documents from transcripts." }, { 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 } { role: "user", content: promptText }
], ],
temperature: 0 temperature: 0
}) })
}); });
if (!response.ok) { //ok is true when a response was successful if (!response.ok) { //ok is true when a responce was successfull
const text = await response.text(); const text = await response.text();
throw new Error(`SAIA API error (${response.status}): ${text}`); throw new Error(`SAIA API error (${response.status}): ${text}`);
} }
@@ -67,18 +71,19 @@ const module_exports = {
// Get generated text from response or default to empty string (if null) // Get generated text from response or default to empty string (if null)
// SAIA uses OpenAI-compatible structure: data.choices[x].message.content // SAIA uses OpenAI-compatible structure: data.choices[x].message.content
const output = data.choices?.[0]?.message?.content || ""; const output = data.choices?.[0]?.message?.content || "";
let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath)); // Name for the output file let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath)); // Name for the output file
console.log(inputTranscriptName); // console.log(inputTranscriptName);
const outPath = path.join(outputDir, `${inputTranscriptName}.html`); // Output file path & name to make naming dynamic. Pulled from input transcript name
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 fs.writeFileSync(outPath, output, "utf8"); // Write output to file
console.log("Generated document written to:", outPath); // console.log("Generated document written to:", outPath);
resolve(outPath)
} catch (error) { } catch (error) {
console.error("Error generating SAIA content:", error); // console.error("Error generating SAIA content:", error);
reject(error)
} }
})
} }
}; };
@@ -91,7 +96,7 @@ if (require.main === module) {
if (args.length < 2) { if (args.length < 2) {
console.error("Usage: node llm-openai-gpt.js <transcriptPath> <documentTypePath> [language]"); console.error("Usage: node llm-openai-gpt.js <transcriptPath> <documentTypePath> [language]");
console.error("Example: node llm-openai-gpt.js ./transcript.json ./docType.json de"); console.error("Example: node llm-openai-gpt.js ./transcript.json ./docType.txt de");
process.exit(1); process.exit(1);
} }
+68 -10
View File
@@ -1,3 +1,6 @@
const fs = require('fs');
const path = require('path');
const outputDir = path.join(__dirname, "../../../storage/documents"); // path for output directory const outputDir = path.join(__dirname, "../../../storage/documents"); // path for output directory
if (!fs.existsSync(outputDir)) { if (!fs.existsSync(outputDir)) {
@@ -5,32 +8,36 @@ if (!fs.existsSync(outputDir)) {
} }
// Ensure Google API key is set in environment variables: export GOOGLE_API_KEY="your_api_key_here" // 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_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 GEMINI_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent"; // URL for the REST call, used model and action
module.exports = { const module_exports = {
name: "llm-gemini", name: "llm-gemini",
type: "llm", type: "llm",
displayname: "Gemini LLM", displayname: "Gemini LLM",
description: "Generates documents using Google Gemini LLM", description: "Generates documents using Google Gemini LLM",
async function(parameter) { async function(parameter) {
return new Promise(async (resolve, reject) => {
try { try {
console.log("Gemini LLM module invoked with parameters:", parameter); // console.log("Gemini LLM module invoked with parameters:", parameter);
await this.createDocumentFromTranscript( //Call the function to create document with transcript, document type and language resolve(await this.createDocumentFromTranscript( //Call the function to create document with transcript, document type and language
parameter.inputTranscriptPath, // Path to input transcript file 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.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 parameter.language // Language for the document which is chosen in the front end by the user
); ));
} catch (error) { } catch (error) {
console.error("Error in Gemini LLM module:", error); // console.error("Error in Gemini LLM module:", error);
reject(error)
} }
})
}, },
createDocumentFromTranscript: async function(transcriptPath, documentTypePath, language = "en") { // default language is English createDocumentFromTranscript: async function(transcriptPath, documentTypePath, language = "en") { // default language is English
return new Promise(async(resolve, reject) => {
try { try {
const transcript = await fs.promises.readFile(transcriptPath, "utf-8"); //read transcript file from Path 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 documentType = await fs.promises.readFile(documentTypePath, "utf-8"); //read document type from Path
@@ -63,14 +70,65 @@ module.exports = {
// Get generated text from response or default to empty string (if null) // Get generated text from response or default to empty string (if null)
const output = data?.candidates?.[0]?.content?.parts?.[0]?.text || ""; const output = data?.candidates?.[0]?.content?.parts?.[0]?.text || "";
let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath)); // Name for the output file let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath)); // Name for the output file
console.log(inputTranscriptName); // console.log(inputTranscriptName);
const outPath = path.join(outputDir, `${inputTranscriptName}.md`); // Output file path & name to make naming dynamic. Pulled from input transcript name const outPath = path.join(outputDir, `${inputTranscriptName}.html`); // Output file path & name to make naming dynamic. Pulled from input transcript name
fs.writeFileSync(outPath, output, "utf8"); // Write output to file fs.writeFileSync(outPath, output, "utf8"); // Write output to file
console.log("Generated document written to:", outPath); // console.log("Generated document written to:", outPath);
resolve(outPath)
} catch (error) { } catch (error) {
console.error("Error generating Gemini content:", 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!");
})();
}
-123
View File
@@ -1,123 +0,0 @@
const fs = require('fs');
const path = require('path');
const outputDir = path.join(__dirname, "../../../storage/documents");
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
const SAIA_API_KEY = process.env.SAIA_API_KEY;
const SAIA_URL = "https://chat-ai.academiccloud.de/v1/chat/completions";
const module_exports = {
name: "llm-saia_llama_3.3",
type: "llm",
displayname: "LLAMA",
description: "Generates documents using Llama 3.3 70B Instruct via SAIA platform",
async function(parameter) {
try {
console.log("SAIA Llama 3.3 70B module invoked with parameters:", parameter);
await this.createDocumentFromTranscript(
parameter.inputTranscriptPath,
parameter.documentTypePath,
parameter.language
);
} catch (error) {
console.error("Error in SAIA Llama 3.3 70B module:", error);
}
},
createDocumentFromTranscript: async function(transcriptPath, documentTypePath, language = "en") {
try {
const transcript = await fs.promises.readFile(transcriptPath, "utf-8");
const documentType = await fs.promises.readFile(documentTypePath, "utf-8");
const promptText = `${documentType}, in language ${language}, transcript:\n\n${transcript}`;
// --- 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: "llama-3.3-70b-instruct", // Korrekter Modellname!
messages: [
{ role: "system", content: "You are a helpful assistant that generates documents from transcripts." },
{ role: "user", content: promptText }
],
temperature: 0
})
});
if (!response.ok) {
const text = await response.text();
throw new Error(`SAIA API error (${response.status}): ${text}`);
}
const data = await response.json();
const output = data.choices?.[0]?.message?.content || "";
let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath));
console.log(inputTranscriptName);
const outPath = path.join(outputDir, `${inputTranscriptName}.md`);
fs.writeFileSync(outPath, output, "utf8");
console.log("Generated document written to:", outPath);
} catch (error) {
console.error("Error generating SAIA content:", error);
}
}
};
module.exports = module_exports;
if (require.main === module) {
(async () => {
const args = process.argv.slice(2);
if (args.length < 2) {
console.error("Usage: node llm-llama-3.3.js <transcriptPath> <documentTypePath> [language]");
console.error("Example: node llm-llama-3.3.js ./transcript.json ./docType.json de");
process.exit(1);
}
const [transcriptPath, documentTypePath, language] = args;
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);
}
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!");
})();
}
+136
View File
@@ -0,0 +1,136 @@
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) {
return new Promise(async (resolve, reject) => {
try {
// console.log("SAIA QWEN 3 235B 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 SAIA QWEN 3 235B 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(SAIA_URL, { //safe model response in variable
method: "POST",
headers: {
"Authorization": `Bearer ${SAIA_API_KEY}`,
"Accept": "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "qwen3-235b-a22b",
messages: [
{ role: "system", content: "You are a helpful assistant that generates HTML documents from transcripts. Output only valid HTML content without any preamble, explanations, or markdown formatting." },
{ role: "user", content: promptText }
],
temperature: 0
})
});
if (!response.ok) { //ok is true when a responce was successfull
const text = await response.text();
throw new Error(`SAIA API error (${response.status}): ${text}`);
}
const data = await response.json();
// Get generated text from response or default to empty string (if null)
// SAIA uses OpenAI-compatible structure: data.choices[x].message.content
const output = data.choices?.[0]?.message?.content || "";
let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath)); // Name for the output file
// console.log(inputTranscriptName);
const outPath = path.join(outputDir, `${inputTranscriptName}.html`); // Output file path & name to make naming dynamic. Pulled from input transcript name
fs.writeFileSync(outPath, output, "utf8"); // Write output to file
// console.log("Generated document written to:", outPath);
resolve(outPath)
} catch (error) {
// console.error("Error generating SAIA content:", error);
reject(error)
}
})
}
};
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!");
})();
}
@@ -78,7 +78,7 @@ function saveTranscript(transcript, sessionId) {
const outputPath = path.join(outputDir, `${sessionId}.json`); const outputPath = path.join(outputDir, `${sessionId}.json`);
fs.writeFileSync(outputPath, JSON.stringify(transcript, null, 2)); fs.writeFileSync(outputPath, JSON.stringify(transcript, null, 2));
console.log(`Transcript saved: ${outputPath}`); // console.log(`Transcript saved: ${outputPath}`);
return outputPath; return outputPath;
} }
@@ -116,8 +116,9 @@ module.exports = {
resolve(saveTranscript(transcript, sessionId)); resolve(saveTranscript(transcript, sessionId));
} catch (error) { } catch (error) {
console.error('Transcription error:', error.message); // console.error('Transcription error:', error.message);
reject(error); reject(error);
return
} }
}) })
} }
+1
View File
@@ -1,3 +1,4 @@
module.exports = { module.exports = {
name:"Startup_function", name:"Startup_function",
async function(){ async function(){
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+25
View File
@@ -0,0 +1,25 @@
Du bist ein erfahrener Moderator und Projektmanager.
AUFGABE:
Erstelle eine sinnvolle Meeting-Agenda basierend auf dem folgenden Transkript.
ANFORDERUNGEN:
- Rekonstruiere die tatsächlichen Themenblöcke
- Ordne sie logisch und chronologisch
- Fasse ähnliche Diskussionen zusammen
- Keine irrelevanten Details aufnehmen
STRUKTUR:
- Titel der Agenda
- Ziel des Meetings (12 Sätze)
- Agenda-Punkte (nummeriert)
- Thema
- Kurzbeschreibung
- Ziel des Punktes (Information, Entscheidung, Diskussion)
STIL:
- Klar, kompakt
- Business-orientiert
- Keine Sprecher- oder Zeitangaben
TRANSKRIPT:
+21
View File
@@ -0,0 +1,21 @@
Du bist ein intelligenter Dokumenten-Generator.
AUFGABE:
Erstelle ein individuelles Dokument basierend auf:
1) dem Meeting-Transkript
2) der zusätzlichen Nutzeranweisung
WICHTIG:
- Priorisiere die Nutzeranweisung
- Nutze das Transkript als Wissensquelle
- Struktur, Tonalität und Detailgrad anpassen
- Inhalte logisch zusammenführen
FORMAT:
- Passe Struktur und Stil an den Nutzerwunsch an
- Klare Überschriften
- Keine Sprecher- oder Zeitangaben
TRANSKRIPT & NUTZERANWEISUNG:
+25
View File
@@ -0,0 +1,25 @@
Du bist ein professioneller Protokollführer.
AUFGABE:
Erstelle ein Ergebnisprotokoll basierend auf dem Meeting-Transkript.
FOKUS:
- Ergebnisse statt Diskussionen
- Entscheidungen, Beschlüsse, Vereinbarungen
- Klare, überprüfbare Aussagen
STRUKTUR:
1. Meeting-Informationen
2. Ergebnisse je Thema
- Thema
- Ergebnis / Beschluss
3. Entscheidungen
4. Aufgaben & Verantwortlichkeiten
5. Offene Punkte
REGELN:
- Keine Meinungen oder Spekulationen
- Keine Zeit- oder Sprecherangaben
- Sachlich, formal
TRANSKRIPT:
@@ -0,0 +1,34 @@
Du bist ein erfahrener Scrum Master.
AUFGABE:
Erstelle Sprint Planning Notes aus dem folgenden Meeting-Transkript.
FOKUS:
- Sprint-Ziele
- User Stories / Tasks
- Abhängigkeiten
- Risiken
- Commitments
STRUKTUR:
1. Sprint Overview
- Sprint-Ziel
- Zeitraum (falls erwähnt)
2. Geplante Arbeit
- User Story / Task
- Beschreibung
- Akzeptanzkriterien (falls ableitbar)
3. Abhängigkeiten & Blocker
4. Risiken & Annahmen
5. Vereinbarungen / Team-Commitments
STIL:
- Agile-konform
- Klar & umsetzungsorientiert
- Bullet Points bevorzugen
TRANSKRIPT:
@@ -0,0 +1,44 @@
Du bist ein professioneller Meeting-Analyst und Business Writer.
AUFGABE:
Erstelle einen strukturierten Follow-up Report basierend auf dem folgenden Meeting-Transkript.
ANFORDERUNGEN:
- Fasse Inhalte sinngemäß zusammen
- Entferne Redundanzen und Smalltalk
- Formuliere klar, präzise und professionell
- Verwende neutrale Business-Sprache
- Keine Zeitstempel oder Sprecher-Namen zitieren
- Leite Entscheidungen und Aufgaben logisch ab, wenn sie implizit sind
- Markiere offene Punkte klar
STRUKTUR DES DOKUMENTS:
1. Titel & Metadaten
- Meetingtitel (ableiten)
- Datum (falls im Transkript erwähnt, sonst „nicht angegeben“)
- Teilnehmer (zusammengefasst)
2. Executive Summary (max. 5 Bullet Points)
3. Besprochene Themen
- Thema
- Kernaussagen
- Relevante Erkenntnisse
4. Entscheidungen
- Entscheidung
- Kontext / Begründung
5. Action Items
- Aufgabe
- Verantwortlich (falls ableitbar)
- Ziel / Zweck
6. Offene Fragen & Risiken
STIL:
- Überschriften klar strukturiert
- Bullet Points bevorzugen
- Präzise, keine Umgangssprache
TRANSKRIPT:
-8
View File
@@ -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
+170
View File
@@ -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.