mirror of
https://gitlab.rlp.net/proj-wise2526-video2document/video2document.git
synced 2026-06-15 18:01:52 +02:00
Merge branch 'develop' into 'feature/ui-test'
# Conflicts: # main.js
This commit is contained in:
+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
|
|
||||||
|
|
||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Generated
+1862
-11
File diff suppressed because it is too large
Load Diff
+5
-3
@@ -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",
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
fastapi
|
|
||||||
uvicorn
|
|
||||||
pytest
|
|
||||||
+4
-1
@@ -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');
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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!");
|
||||||
|
})();
|
||||||
|
}
|
||||||
@@ -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!");
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
@@ -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,3 +1,4 @@
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name:"Startup_function",
|
name:"Startup_function",
|
||||||
async function(){
|
async function(){
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,25 @@
|
|||||||
|
Du bist ein erfahrener Moderator und Projektmanager.
|
||||||
|
|
||||||
|
AUFGABE:
|
||||||
|
Erstelle eine sinnvolle Meeting-Agenda basierend auf dem folgenden Transkript.
|
||||||
|
|
||||||
|
ANFORDERUNGEN:
|
||||||
|
- Rekonstruiere die tatsächlichen Themenblöcke
|
||||||
|
- Ordne sie logisch und chronologisch
|
||||||
|
- Fasse ähnliche Diskussionen zusammen
|
||||||
|
- Keine irrelevanten Details aufnehmen
|
||||||
|
|
||||||
|
STRUKTUR:
|
||||||
|
- Titel der Agenda
|
||||||
|
- Ziel des Meetings (1–2 Sätze)
|
||||||
|
- Agenda-Punkte (nummeriert)
|
||||||
|
- Thema
|
||||||
|
- Kurzbeschreibung
|
||||||
|
- Ziel des Punktes (Information, Entscheidung, Diskussion)
|
||||||
|
|
||||||
|
STIL:
|
||||||
|
- Klar, kompakt
|
||||||
|
- Business-orientiert
|
||||||
|
- Keine Sprecher- oder Zeitangaben
|
||||||
|
|
||||||
|
TRANSKRIPT:
|
||||||
@@ -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:
|
||||||
|
|
||||||
|
|
||||||
@@ -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:
|
||||||
@@ -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