Compare commits

..

1 Commits

Author SHA1 Message Date
santa 5bd5ae52c6 custom document erstellt 2025-12-15 15:35:19 +01:00
41 changed files with 453 additions and 4565 deletions
+2 -16
View File
@@ -963,23 +963,9 @@ app.*.symbols
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock !/dev/ci/**/Gemfile.lock
# Storage files #Storage files
storage/documents/* storage/
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
+25 -20
View File
@@ -1,28 +1,33 @@
image: node:latest workflow:
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:
# - install - setup
- test - test
# job-install: setup_environment:
# stage: install stage: setup
# script: script:
# - npm install - pip install --upgrade pip
# artifacts: - pip install -r requirements.txt
# untracked: false - echo "Dependencies installed successfully."
# 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:
- npm install - echo "Running V2D Framework basic test..."
- echo "ASSEMBLYAI_API_KEY=$apikey_assembly" > .env - python -m unittest discover || echo "No tests found."
- echo "GOOGLE_API_KEY=$apikey_gemini" >> .env
- echo "SAIA_API_KEY=$apikey_saia" >> .env only:
- npm test - main
- feature/ci-pipeline-s1-09a-1
+81 -366
View File
@@ -1,378 +1,93 @@
# Video2Document # Video2Document
## Getting started
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/topics/git/add_files/#add-files-to-a-git-repository) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin https://gitlab.rlp.net/proj-wise2526-video2document/video2document.git
git branch -M main
git push -uf origin main
```
## Integrate with your tools
- [ ] [Set up project integrations](https://gitlab.rlp.net/proj-wise2526-video2document/video2document/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Set auto-merge](https://docs.gitlab.com/user/project/merge_requests/auto_merge/)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation ## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
Follow the steps below to install Video2Document (v2d): ## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
### 1. Install Node.js ## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
Download and install Node.js from [https://nodejs.org/en](https://nodejs.org/en) ## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
### 2. Clone the Repository ## Contributing
State if you are open to contributions and what your requirements are for accepting them.
Clone the repository using Git: For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
```bash You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
git clone https://gitlab.rlp.net/proj-wise2526-video2document/video2document.git
```
Alternatively, you can download the ZIP file and extract it manually. ## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
### 3. Start the Program ## License
For open source projects, say how it is licensed.
The start scripts automatically install dependencies and launch the program. Choose the appropriate method for your operating system: ## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
**Windows:**
```bash
start.bat
```
**Linux:**
```bash
./start.sh
```
**macOS:**
```bash
./start.sh
```
#### Alternative: Manual Installation and Start
If the start scripts don't work, you can manually install dependencies and start the program:
```bash
cd video2document
npm install
npm start
```
### 5. Install FFmpeg
Make sure that **ffmpeg** is installed on your system, as it may be required for video processing.
---
**Installation complete!** You're now ready to use Video2Document.
###General Despription
## Project Overview
V2D (Video to Document Framework) is a student project developed to convert video and audio content into structured, readable documents using AI-based text processing. The tool is designed to help users automatically generate documents such as meeting reports, summaries or structured texts from recorded videos or audio files.
The main problem V2D addresses is the time-consuming manual effort required to listen to recordings and write documents afterwards. By automating transcription, structuring and document generation, the tool improves efficiency and usability for students, teams and small organizations.
The project was created as part of a university software engineering course and is developed using agile methods (Scrum). It focuses on modular design, usability and extensibility, allowing new document types and features to be added easily.
### Used Packages and Dependencies
This project uses several Node.js packages to handle video processing, AI-based document generation, backend communication, and the desktop user interface. Below is an overview of the most important dependencies and their purpose.
##Core Dependencies:
Electron:
Used to build the desktop application. It allows the project to run as a cross-platform GUI application using web technologies.
Express:
Acts as a lightweight backend server that handles internal API requests and communication between different parts of the application.
Axios:
Used for making HTTP requests, for example when communicating with external APIs such as AI services.
dotenv:
Used to securely load API keys and other sensitive configuration values from environment variables instead of hardcoding them in the source code.
@google/genai:
Provides access to Googles Generative AI models. This package is used to generate structured documents from transcribed text based on configurable prompts.
ffmpeg-static:
Provides a static FFmpeg binary so that video and audio processing works without requiring FFmpeg to be installed separately on the system.
fluent-ffmpeg:
Used together with FFmpeg to process video and audio files, for example extracting audio tracks from uploaded videos.
puppeteer:
Used to render and process HTML content programmatically. This is helpful for document previews and automated content generation.
html-to-docx:
Converts generated HTML documents into downloadable DOCX files, which are provided to the user as the final output.
mocha:
A testing framework used to run unit tests and ensure that core functionalities work as expected.
##Development Dependencies:
TypeScript:
Used to add static typing to the project, improving code quality, readability, and maintainability.
ts-node:
Allows running TypeScript files directly without manually compiling them first.
@types/node:
Provides TypeScript type definitions for Node.js APIs.
@types/fluent-ffmpeg:
Type definitions for fluent-ffmpeg to improve development experience and error checking.
@types/cli-progress:
Provides type support for progress bar functionality used during processing tasks.
###Why These Packages Are Needed
Together, these packages enable:
*Processing video and audio files
*Communicating with AI models for document generation
*Secure handling of API keys
*Generating structured documents (DOCX)
*Providing a user-friendly desktop interface
*Ensuring code quality through testing
###API Keys and Configuration
The V2D Video to Document tool uses external AI and media processing services to convert video and audio content into structured documents.
To access these services, several API keys are required. For security reasons, API keys are not stored in the repository and must be provided via environment variables.
##Supported API Keys
The project currently supports the following API keys. Depending on the configuration and selected provider, one or more of these keys may be used.
Google Gemini API
Environment variable: GOOGLE_API_KEY
Usage:
Used for AI-based document generation. The Large Language Model processes transcripts and creates structured documents such as meeting reports.
OpenAI (ChatGPT)
Environment variable: OPENAI_API_KEY
Usage:
Alternative AI provider for text processing and document generation.
AssemblyAI
Environment variable: ASSEMBLYAI_API_KEY
Usage:
Speech-to-text processing for audio and video files.
Saya
Environment variable: SAYA_API_KEY
Usage:
Additional or experimental AI provider that can be integrated into the document generation pipeline.
##How to Set API Keys
API keys must be configured as environment variables before starting the application.
Linux / macOS
export GOOGLE_API_KEY="your_api_key_here"
export OPENAI_API_KEY="your_api_key_here"
export ASSEMBLYAI_API_KEY="your_api_key_here"
export SAYA_API_KEY="your_api_key_here"
Windows (PowerShell)
setx GOOGLE_API_KEY "your_api_key_here"
setx OPENAI_API_KEY "your_api_key_here"
setx ASSEMBLYAI_API_KEY "your_api_key_here"
setx SAYA_API_KEY "your_api_key_here"
Alternatively, for local development, a .env file can be used:
GOOGLE_API_KEY=your_api_key_here
OPENAI_API_KEY=your_api_key_here
ASSEMBLYAI_API_KEY=your_api_key_here
SAYA_API_KEY=your_api_key_here
⚠️ Important:
The .env file must not be committed to the repository and should be listed in .gitignore.
Security Notes:
*API keys are injected at runtime
*No secrets are stored in the source code
*Prevents accidental exposure of sensitive data
*Supports secure collaboration in GitLab and CI/CD environments
*Follows best practices for secret management
###End-to-End User Guide (Video → Final Document)
This section describes how a user can create a structured document from a video using the V2D Video to Document tool.
Start the Application:
Ensure all required API keys are configured as environment variables.
Install dependencies:
npm install
Start the application:
npm start
The Electron-based GUI will open.
#Upload a Video File:
In the application interface, select Upload Video.
Choose a supported video file (e.g. .mp4, .mov).
The video is loaded into the system for processing.
#Audio Extraction:
The application automatically extracts audio from the uploaded video.
This is handled internally using FFmpeg.
No user interaction is required for this step.
#Speech-to-Text Transcription:
The extracted audio is sent to the speech-to-text service.
The transcription process converts spoken content into text.
The generated transcript is stored internally and used for further processing.
#Select Document Type:
The user selects a document type (e.g. Meeting Report).
Each document type is based on a predefined prompt template.
The selected template defines the structure and style of the final document.
#Document Generation:
The transcript and selected prompt are sent to the AI service.
The AI model processes the input and generates a structured document.
The output is formatted in Markdown.
#Document Preview:
The generated document is displayed in the application preview.
Users can review the content before exporting.
No manual editing is required, but validation is possible.
#Export the Final Document:
The user exports the document in the desired format.
#Supported formats include:
Markdown (.md)
Word (.docx)
The document is saved locally.
#Completion:
The final document is now ready for use.
The user can repeat the process with another video if needed.
###Resources
This section lists the main technologies, libraries, and external resources used in the V2D (Video to Document) project. These resources are required to understand, run, and further develop the application.
##Project Dependencies
The following packages and tools are used in this project (as defined in package.json):
Application & Backend
Node.js JavaScript runtime environment used for backend processing
Electron Framework for building the desktop application
Express Backend web framework for handling requests and internal APIs
AI & API Communication
@google/genai Used for AI-based document generation
Axios HTTP client for communicating with external APIs (LLMs)
Video & Audio Processing
ffmpeg-static Extracts audio from uploaded video files
fluent-ffmpeg Controls FFmpeg operations programmatically
Document Generation & Preview
html-to-docx Converts structured content into Word documents
Puppeteer Renders HTML content for document preview and processing
Configuration & Security
dotenv Loads API keys securely from environment variables
Testing & Development
Mocha Unit testing framework
TypeScript Improves code quality and type safety
These dependencies enable the complete end-to-end workflow from video input to structured document output.
##Relevant Repositories
V2D Main Repository:
https://gitlab.rlp.net/proj-wise2526-video2document/video2document
This repository contains the full source code, configuration files, and documentation for the V2D project.
Downloads & External Resources
The following tools and documentation may be required to run or understand the project:
Node.js: https://nodejs.org
Electron Documentation: https://www.electronjs.org/docs
FFmpeg: https://ffmpeg.org
Google Generative AI: https://ai.google.dev
Puppeteer: https://pptr.dev
+35 -124
View File
@@ -1,6 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -13,7 +12,7 @@
padding: 0; padding: 0;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: flex-start;
min-height: 100vh; min-height: 100vh;
} }
@@ -22,7 +21,7 @@
padding: 30px; padding: 30px;
margin-top: 50px; margin-top: 50px;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 20px rgba(0,0,0,0.1);
width: 90%; width: 90%;
max-width: 600px; max-width: 600px;
} }
@@ -39,9 +38,7 @@
color: #555; color: #555;
} }
input[type="text"], input[type="text"], textarea, select {
textarea,
select {
width: 100%; width: 100%;
padding: 10px; padding: 10px;
margin-top: 5px; margin-top: 5px;
@@ -61,10 +58,6 @@
margin-top: 25px; margin-top: 25px;
} }
.hidden {
visibility: hidden;
}
button { button {
padding: 10px 20px; padding: 10px 20px;
font-size: 14px; font-size: 14px;
@@ -84,7 +77,6 @@
.buttons { .buttons {
flex-direction: column; flex-direction: column;
} }
.buttons button { .buttons button {
width: 100%; width: 100%;
margin-top: 10px; margin-top: 10px;
@@ -98,151 +90,70 @@
} }
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>Manage document types</h1> <h1>Custom Document Generator</h1>
<label for="docName">Dokumentname:</label>
<input type="text" id="docName" placeholder="Gib hier den Dokumentnamen ein">
<label for="existingDocs">Vorhandene Dokumente auswählen (optional):</label> <label for="existingDocs">Vorhandene Dokumente auswählen (optional):</label>
<!--Drop Down--> <select id="existingDocs">
<select name="existingDocs" id="existingDocs"> <option value="">-- Neues Dokument erstellen --</option>
<option value="newDoc">-- Neues Dokument erstellen --</option> <option value="meeting_report_001">Meeting Report 001</option>
<option value="summary_01">Summary 01</option>
<option value="project_plan_A">Project Plan A</option>
</select> </select>
<div id="docNameWrapper">
<label for="docName">Dokumentname:</label>
<input type="text" id="docName" placeholder="Gib hier den Dokumentnamen ein">
</div>
<label for="prompt">Dein Prompt:</label> <label for="prompt">Dein Prompt:</label>
<textarea id="prompt" placeholder="Schreibe hier den Prompt für dein Dokument..."></textarea> <textarea id="prompt" placeholder="Schreibe hier den Prompt für dein Dokument..."></textarea>
<div class="buttons"> <div class="buttons">
<a href="index.html"> <button id="goBackBtn">Abbrechen</button>
<button id="goBackBtn">Abbrechen</button>
</a>
<button id="deleteBtn">Dokument löschen</button>
<button id="generateBtn">Dokument speichern</button> <button id="generateBtn">Dokument speichern</button>
</div> </div>
<div id="result"></div> <div id="result"></div>
</div> </div>
<script src="languages.js"></script>
<script> <script>
const goBackBtn = document.getElementById("goBackBtn"); const goBackBtn = document.getElementById("goBackBtn");
const generateBtn = document.getElementById("generateBtn"); const generateBtn = document.getElementById("generateBtn");
const deleteBtn = document.getElementById("deleteBtn");
const existingDocs = document.getElementById("existingDocs"); const existingDocs = document.getElementById("existingDocs");
const docNameInput = document.getElementById("docName"); const docNameInput = document.getElementById("docName");
const promptInput = document.getElementById("prompt"); const promptInput = document.getElementById("prompt");
const resultDiv = document.getElementById("result"); const resultDiv = document.getElementById("result");
const exampleText = "";
// dokumente speichern // Zurück zur Haupt-GUI
goBackBtn.addEventListener("click", () => {
window.electronAPI.goBackToMain();
});
// Generiere Dokument
generateBtn.addEventListener("click", () => { generateBtn.addEventListener("click", () => {
const name = docNameInput.value.trim(); const prompt = promptInput.value.trim();
const content = promptInput.value.trim(); let docName = docNameInput.value.trim();
if (!name || !content) { const selectedExisting = existingDocs.value;
resultDiv.textContent = "Bitte Dokumentname und Prompt ausfüllen.";
setTimeout(() => {
resultDiv.textContent = "";
}, 3000);
return;
}
window.api.saveTxtFile(name, content).then();
resultDiv.textContent = "Dokument erfolgreich gespeichert!";
setTimeout(() => {
resultDiv.textContent = "";
}, 3000);
reloadDocuments();
}); if (!prompt) {
alert("Bitte gib einen Prompt ein!");
// dokumente löschen
deleteBtn.addEventListener("click", () => {
const name = docNameInput.value.trim();
if (!name) {
resultDiv.textContent = "Bitte Dokumentname angeben.";
setTimeout(() => {
resultDiv.textContent = "";
}, 3000);
return; return;
} }
const confirmDelete = confirm( // Wenn ein vorhandenes Dokument ausgewählt wurde, hängt der Prompt daran
`Möchtest du das Dokument "${name}" wirklich löschen?` if (selectedExisting) {
); docName = selectedExisting; // prompt wird an vorhandenes Dokument angehängt
} else if (!docName) {
if (!confirmDelete) return; alert("Bitte gib einen Dokumentnamen ein, wenn du ein neues Dokument erstellen möchtest!");
window.api.deleteTxtFile(name).then((success) => {
if (success) {
resultDiv.textContent = "Dokument erfolgreich gelöscht!";
reloadDocuments();
existingDocs.value = "newDoc";
existingDocs.dispatchEvent(new Event("change"));
} else {
resultDiv.textContent = "Dokument konnte nicht gelöscht werden.";
}
setTimeout(() => {
resultDiv.textContent = "";
}, 3000);
});
});
//function to load existingDoc options to the drop down list
const select = document.getElementById('existingDocs');
window.api.getTxtFiles().then(files => {
reloadDocuments();
});
//content anzeigen
const docNameWrapper = document.getElementById("docNameWrapper");
existingDocs.addEventListener("change", async () => {
const selected = existingDocs.value;
if (selected === "newDoc") {
docNameWrapper.classList.remove("hidden");
docNameInput.value = "";
promptInput.value = exampleText;
return; return;
} }
docNameWrapper.classList.add("hidden");
const content = await window.api.readTxtFile(selected); // Demo-Ausgabe im Result-Div
promptInput.value = content; resultDiv.innerHTML = `<strong>Dokumentname:</strong> ${docName}<br><strong>Prompt:</strong> ${prompt}`;
docNameInput.value = selected.replace(".txt", "");
// Hier kannst du den Prompt an dein LLM oder Module-Handler senden
// z.B. window.submit.submit({documentName: docName, prompt: prompt})
}); });
//reload drop down
function reloadDocuments() {
[...existingDocs.querySelectorAll('option:not([value="newDoc"])')]
.forEach(o => o.remove());
window.api.getTxtFiles().then(files => {
files.forEach(file => {
const option = document.createElement('option');
option.value = file;
option.textContent = file
.replace('.txt', '') // Endung entfernen
.replace(/_/g, ' ') // Leerzeichen ersetzen
.replace(/\b\w/g, c => c.toUpperCase()) // ersten Buchstaben groß
existingDocs.appendChild(option);
//customDocumentTypes.appendChild(option);
});
});
}
</script> </script>
<script src="./renderer.js"></script>
</body> </body>
</html>
</html>
+51 -147
View File
@@ -7,159 +7,63 @@
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
</head> </head>
<body> <body>
<div id="h1-wrapper">
<section class="p-menu1">
<nav id="navbar" class="navigation" role="navigation">
<input id="toggle1" type="checkbox" />
<label class="hamburger1" for="toggle1">
<div class="top"></div>
<div class="meat"></div>
<div class="bottom"></div>
</label>
<nav class="menu1">
<a href="custom_document.html" class="li1">Manage document types</a>
<a href="help_page.html" class="li1">Help</a>
</nav>
</nav>
</section>
<h1 id="h1">Video to document</h1>
</div>
<div class="step-nav">
<div class="step-item active" data-step="1">1. Step</div>
<div class="step-item" data-step="2">2. Step</div>
<div class="step-item" data-step="3">3. Step</div>
<div class="step-item" data-step="4">4. Step</div>
<div class="step-item" data-step="5">5. Step</div>
<div class="step-item" data-step="6">6. Step</div>
</div>
<div id="middleContainerWrapper" class="middle-container-wrapper"> <div class="mitte" id="mitte">
<button id="prevBtn" class="navBtn" disabled>&larr;</button> <div class="labelDiv" id="labelDiv">
<label id="labelKI">Select ki:</label>
<div class="mitte" id="mitte"> <label id="labelTranscription">Select transcription:</label>
<label id="labelLanguage">Select language:</label>
<div class="step" id="step1"> <img id="labelLanguageFlag" src="flags/united-kingdom-flag-png-large.jpg" width="20" height="10" >
<div class="upload-container" id="uploadContainer"> </div>
<p id="p1">Drag and drop video file</p> <div class="dropdownMenus" id="dropdownMenus">
<video id="previewThumbnail" autoplay="false"> <select name="ai_type" id="ai_type">
</video> </select>
<div class="file-name" id="fileName">No video chosen</div> <select name="transkript_type" id="transkript_type">
<div id="thumbnailContainer"> </select>
<img id="thumbnailImage" style="display:none;"> <select name="output_type" id="output_type">
</div> <option value="flac">flac</option>
<button class="custom-btn" id="manualUploadBtn">Search video</button> <option value="mp3">mp3</option>
<input type="file" id="videoUpload" accept="video/*"> <option value="wav">wav</option>
</div> </select>
</div> <select name="language_option" id="language_option">
</select>
<div class="step" id="step2" style="display:none;">
<div class="KI-wrapper">
<label id="labelKI">Select ki:</label>
<select name="ai_type" id="ai_type"></select>
</div>
<div class="transcript-wrap">
<label id="labelTranscription">Select transcription:</label>
<select name="transkript_type" id="transkript_type"></select>
</div>
<div class="type-wrapper">
<label id="labelType">Select type:</label>
<select name="output_type" id="output_type">
<option value="pdf">.pdf</option>
<option value="word">.word</option>
<option value="txt">.txt</option>
</select>
</div>
<div class="language-wrapper">
<label id="labelLanguage">Select language:</label>
<img id="labelLanguageFlag" src="flags/united-kingdom-flag-png-large.jpg" width="20" height="10" >
<select name="language_option" id="language_option">
</select>
</div>
</div>
<div class="step" id="step3" style="display:none;">
<div class="checkbox-group">
<label id="checkbox-label" for="checkbox-group">Choose prefered document style:</label>
<div class="checkbox-container">
<input type="checkbox" name ="docFormat" id="docFormat" value="followup-report">
<label id="label_format" for="docFormat">Follow-up Report</label>
</div>
<div class="checkbox-container">
<input type="checkbox" name="docFormat" id="docFormatSummary1" value="agenda">
<label id="label_summary" for="docFormatSummary">Agenda</label>
</div>
<div class="checkbox-container">
<input type="checkbox" name="docFormat" id="docFormatSummary2" value="result-protocol">
<label id="label_summary" for="docFormatSummary">Resultprotocol</label>
</div>
<div class="checkbox-container">
<input type="checkbox" name="docFormat" id="docFormatSummary3" value="sprint-planning">
<label id="label_summary" for="docFormatSummary">Sprint Planning Note</label>
</div>
<div class="checkbox-container">
<input type="checkbox" name="docFormat" id="docFormatCustom" value="custom">
<select name="customDocumentTypes" id="customDocumentTypes">
</select>
</div>
</div>
</div>
<div class="step" id="step4" style="display:none;">
<button class="submit-btn" id="submitButton" onclick="checkBoxes()" disabled>Submit</button>
<div class="testy" id="testy">
<div class="box2" id="box1">
</div>
<p>---Starting---</p>
<div class="box2" id="box2">
</div>
<p>---Transkribing---</p>
<div class="box2" id="box3">
</div>
<p>---Document creation---</p>
<div class="box2" id="box4">
</div>
</div>
</div>
<div class="step" id="step5" style="display:none;">
<div class="speakerView" id="speakerView">
<label id="labelSpeaker">Select Speaker:</label>
<select name="cur_speaker" id="cur_speaker">
</select>
</div>
<div class="speakerAudio" id="speakerAutio">
<label id="labelSpeakerAudio">Selected Speaker</label>
<audio controls id="speakerAudioViewer">
Currently there is no audio file here.
</audio>
</div>
<div class="speakerWrite" id="speakerWrite">
<label id="labelSpeakerWriter">Write name:</label>
<input type="text" id="newSpeaker">
</div>
<div class="speakerButton-group">
<button id="speakerLocker" onclick="rewriteSpeakerName()">Rename Speaker</button>
<button id="speakerResender" onclick="sendSpeakerPackages()">Rewrite document</button>
</div>
</div>
<div class="step" id="step6" style="display:none;">
<button class="download-btn" id="downloadButton" onclick="fileDownload()">Download</button>
</div>
</div> </div>
<button id ="nextBtn" class="navBtn">&rarr;</button> <h1 id="h1">Video to document</h1>
<div class="upload-container" id="uploadContainer">
<p id="p1">Drag and drop video file</p>
<video id="previewThumbnail" autoplay="false">
</video>
<div class="file-name" id="fileName">No video chosen</div>
<div id="thumbnailContainer">
<img id="thumbnailImage" style="display:none;">
</div>
<button class="custom-btn" id="manualUploadBtn">Search video</button>
<input type="file" id="videoUpload" accept="video/*">
</div>
<div class="checkbox-group">
<label id="checkbox_group" for="checkbox-group">Choose prefered document style:</label>
<div class="checkbox-container">
<input type="checkbox" name ="docFormat" id="docFormat" value="Meeting report">
<label id="label_format" for="docFormat">Meeting report</label>
</div>
<div class="checkbox-container">
<input type="checkbox" name="docFormat" id="docFormatSummary" value="Summary with timestamps">
<label id="label_summary" for="docFormatSummary">Summary with timestamps</label>
</div>
</div> </div>
<button class="submit-btn" id="submitButton" onclick="checkBoxes()" disabled>Submit</button>
<div class="progressbar" id="progressbar">
<div class="progress_fill"></div>
<span class="progress_text">0%</span>
</div>
</div>
<script src="languages.js"></script> <script src="languages.js"></script>
<script src="script.js"></script> <script src="script.js"></script>
<script src="./renderer.js"></script> <script src="./renderer.js"></script>
+7 -28
View File
@@ -5,44 +5,23 @@ try {
onFileDrop: (file) => webUtils.getPathForFile(file) onFileDrop: (file) => webUtils.getPathForFile(file)
}) })
contextBridge.exposeInMainWorld("submit", { contextBridge.exposeInMainWorld("submit", {
submit: (meeting_specifications) => { ipcRenderer.send("file_submit", meeting_specifications) } submit: (meeting_specifications) => {ipcRenderer.send("file_submit", meeting_specifications)}
}) })
contextBridge.exposeInMainWorld("electronAPI", { contextBridge.exposeInMainWorld("electronAPI", {
getFilePath: (file) => { return webUtils.getPathForFile(file) } getFilePath: (file) => {return webUtils.getPathForFile(file)}
}) })
contextBridge.exposeInMainWorld("onStartup", { contextBridge.exposeInMainWorld("onStartup", {
getModuleNames: () => ipcRenderer.invoke('get-module-names') getModuleNames: () => ipcRenderer.invoke('get-module-names')
}) })
contextBridge.exposeInMainWorld('electron', {
progress: (callback) => ipcRenderer.on('progress', callback)
})
contextBridge.exposeInMainWorld('audios', {
speakerAudios: (callback) => ipcRenderer.on('speakerAudios', callback)
})
contextBridge.exposeInMainWorld("submitSpeaker", {
speaker_submit: (speaker_names) => { ipcRenderer.send("speaker_submit", speaker_names) }
})
contextBridge.exposeInMainWorld("download", {
file_download: () => { ipcRenderer.send("file_download") }
})
//documenttypes
contextBridge.exposeInMainWorld('api', {
getTxtFiles: () => ipcRenderer.invoke('get-txt-files'),
saveTxtFile: (name, content) =>
ipcRenderer.invoke('save-txt-file', name, content),
readTxtFile: (fileName) =>
ipcRenderer.invoke('read-txt-file', fileName),
deleteTxtFile: (fileName) =>
ipcRenderer.invoke('delete-txt-file', fileName)
});
ipcRenderer.on("error", (event, err) => { alert(err) }) ipcRenderer.on("progress", (event, resp) => {
alert(`Finished step ${resp.curstep} of ${resp.totalsteps}`)
})
ipcRenderer.on("error", (event, err) => {alert(err)})
} catch (error) { } catch (error) {
console.log("Error in preload.js"); console.log("Error in preload.js");
} }
+16 -172
View File
@@ -19,6 +19,7 @@ uploadContainer.addEventListener("drop", (e) => {
const testEndings = [".mp4", ".mov", ".avi", ".mkv"]; const testEndings = [".mp4", ".mov", ".avi", ".mkv"];
var pathToLower = filePath.toLowerCase(); var pathToLower = filePath.toLowerCase();
if(testEndings.some(e => pathToLower.endsWith(e))){ if(testEndings.some(e => pathToLower.endsWith(e))){
document.getElementById("progressbar").style.visibility = "visible";
const files1 = e.dataTransfer.files; const files1 = e.dataTransfer.files;
handleFiles(files1); handleFiles(files1);
}else{ }else{
@@ -27,12 +28,12 @@ uploadContainer.addEventListener("drop", (e) => {
} catch (error) { } catch (error) {
console.log("Error in renderer.js with the listerner for the drop function"); console.log("Error in renderer.js with the listerner for the drop function");
console.log(error);
} }
}) })
window.addEventListener('load', async (e) => { window.addEventListener('load', async (e) => {
try { try {
console.log("test");
loadLanguageOptions(); loadLanguageOptions();
const value = await window.onStartup.getModuleNames(); const value = await window.onStartup.getModuleNames();
loadAiOptions(value.ai_modules); loadAiOptions(value.ai_modules);
@@ -54,16 +55,24 @@ language_option.addEventListener('change', (e)=>{
}); });
//listener for the file explorer search when something got selected
videoUpload.addEventListener("change", () => { videoUpload.addEventListener("change", () => {
try { try {
if (videoUpload.files.length > 0) { activateSubmitBtn(videoUpload.files.length > 0);
const file = videoUpload.files;
handleFiles(file);
}
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
});
//listener for the file explorer search when something got selected
videoUpload.addEventListener('change', () => {
try {
handleFiles(videoUpload.files);
} catch (error) {
console.log("Error in manualBtn EventListener change");
console.log(error);
}
}); });
//listener for the file explorer search //listener for the file explorer search
@@ -75,169 +84,4 @@ manualUploadBtn.addEventListener('click', () => {
console.log(error); console.log(error);
} }
}); });
stepButtons.forEach(btn => {
btn.addEventListener("click", () => {
try {
const step = parseInt(btn.dataset.step);
showStep(step);
} catch (error) {
}
});
});
prevBtn.addEventListener("click", () => {
try {
if (currentStep > 1) showStep(currentStep - 1);
} catch (error) {
}
});
nextBtn.addEventListener("click", () => {
try {
if(currentStep < totalSteps) showStep(currentStep + 1);
} catch (error) {
}
});
//Checkboxlistener so that only one can be selected at a time
docFormat.addEventListener("change", (e) =>{
try {
if(docFormat.checked){
docFormatSummary1.checked = false;
docFormatSummary2.checked = false;
docFormatSummary3.checked = false;
docFormatCustom.checked = false;
}
} catch (error) {
}
});
docFormatSummary1.addEventListener("change", (e) =>{
try {
if(docFormatSummary1.checked){
docFormat.checked = false;
docFormatSummary2.checked = false;
docFormatSummary3.checked = false;
docFormatCustom.checked = false;
}
} catch (error) {
}
});
docFormatSummary2.addEventListener("change", (e) =>{
try {
if(docFormatSummary2.checked){
docFormatSummary1.checked = false;
docFormat.checked = false;
docFormatSummary3.checked = false;
docFormatCustom.checked = false;
}
} catch (error) {
}
});
docFormatSummary3.addEventListener("change", (e) =>{
try {
if(docFormatSummary3.checked){
docFormatSummary1.checked = false;
docFormatSummary2.checked = false;
docFormat.checked = false;
docFormatCustom.checked = false;
}
} catch (error) {
}
});
docFormatCustom.addEventListener("change", (e) =>{
try {
if(docFormatCustom.checked){
docFormatSummary1.checked = false;
docFormatSummary2.checked = false;
docFormatSummary3.checked = false;
docFormat.checked = false;
}
} catch (error) {
}
});
//Speaker change listener
cur_speaker.addEventListener("change", (e) =>{
try {
document.getElementById("speakerAudioViewer").src = speakerAudios[document.getElementById("cur_speaker").value].src;
} catch (error) {
}
});
window.audios.speakerAudios((event, arg) => {
loadSpeakerOptions(arg);
setSpeakerAudiosValue(arg);
});
window.electron.progress((event, arg) => {
if(arg.curstep == 1){
setCircleOne();
}else if(arg.curstep == 2){
setCircleZwo();
} else if(arg.curstep == 3){
setCircleThree();
}else if(arg.curstep == 4){
setCircleFour();
}
});
function setCircleOne(){
try {
if(document.getElementById("box1").style.backgroundColor == "green"){
document.getElementById("box1").style.backgroundColor = "red";
}else{
document.getElementById("box1").style.backgroundColor = "green";
}
} catch (error) {
}
};
function setCircleZwo(){
try {
if(document.getElementById("box2").style.backgroundColor == "green"){
document.getElementById("box2").style.backgroundColor = "red";
}else{
document.getElementById("box2").style.backgroundColor = "green";
}
} catch (error) {
}
};
function setCircleThree(){
try {
if(document.getElementById("box3").style.backgroundColor == "green"){
document.getElementById("box3").style.backgroundColor = "red";
}else{
document.getElementById("box3").style.backgroundColor = "green";
}
} catch (error) {
}
};
function setCircleFour(){
try {
if(document.getElementById("box4").style.backgroundColor == "green"){
document.getElementById("box4").style.backgroundColor = "red";
}else{
document.getElementById("box4").style.backgroundColor = "green";
}
} catch (error) {
}
};
+16 -140
View File
@@ -1,4 +1,4 @@
let currentVideoPath = null;
//function to check if one checkbox is at least klicked //function to check if one checkbox is at least klicked
function checkBoxes() { function checkBoxes() {
try { try {
@@ -25,20 +25,18 @@ function checkBoxes() {
var pathTest = window.electronAPI.getFilePath(videoUpload.files[0]); var pathTest = window.electronAPI.getFilePath(videoUpload.files[0]);
var pathToLower = pathTest.toLowerCase(); var pathToLower = pathTest.toLowerCase();
if(testEndings.some(e => pathToLower.endsWith(e))){ if(testEndings.some(e => pathToLower.endsWith(e))){
document.getElementById("progressbar").style.visibility = "visible";
//assembly of the json for the main //assembly of the json for the main
var typeCheckbox; const selectedStyles = [checkedCounter];
if(document.getElementById("docFormat").checked) typeCheckbox = document.getElementById("docFormat").value; var iter = 0;
if(document.getElementById("docFormatSummary1").checked) typeCheckbox = document.getElementById("docFormatSummary1").value; checkboxes.forEach(function(checkbox){
if(document.getElementById("docFormatSummary2").checked) typeCheckbox = document.getElementById("docFormatSummary2").value; if(checkbox.checked){
if(document.getElementById("docFormatSummary3").checked) typeCheckbox = document.getElementById("docFormatSummary3").value; console.log(checkbox.value);
if(document.getElementById("docFormatCustom").checked) typeCheckbox = document.getElementById("docFormatCustom").value; selectedStyles[iter] = {iter: checkbox.value};
iter++;
document.getElementById("testy").style.visibility = "visible" }
document.getElementById("box1").style.backgroundColor = "red"; });
document.getElementById("box2").style.backgroundColor = "red";
document.getElementById("box3").style.backgroundColor = "red";
document.getElementById("box4").style.backgroundColor = "red";
console.log(selectedCheckboxes); console.log(selectedCheckboxes);
const outputType = document.getElementById("output_type"); const outputType = document.getElementById("output_type");
const transcriptionType = document.getElementById("transkript_type"); const transcriptionType = document.getElementById("transkript_type");
@@ -46,15 +44,15 @@ function checkBoxes() {
const sendingPackage = { const sendingPackage = {
"video": { "video": {
"module":"extraction-video-to-audio", "module":"extraction-video-to-audio",
"inputVideoPath": pathTest "inputVideoPath": pathTest,
"outputType": outputType.value
}, },
"transcription": { "transcription": {
"module": transcriptionType.value "module": transcriptionType.value
}, },
"document": { "document": {
"module":aiType.value, "module":aiType.value,
"type": typeCheckbox, "styles": selectedStyles
"outputType": outputType.value
} }
}; };
window.submit.submit(sendingPackage) window.submit.submit(sendingPackage)
@@ -107,7 +105,6 @@ function handleFiles(files) {
const filePath = window.explorer.onFileDrop(files[0]) const filePath = window.explorer.onFileDrop(files[0])
videoUpload.files = files; videoUpload.files = files;
fileName.textContent = `Chosen video: ${file.name}`; fileName.textContent = `Chosen video: ${file.name}`;
currentVideoPath = filePath;
generateThumbnail(filePath); generateThumbnail(filePath);
activateSubmitBtn(true); activateSubmitBtn(true);
} }
@@ -170,25 +167,6 @@ function loadTranscriptionOptions(options){
} }
} }
//function to load data type options to the drop down list
function loadDataTypeOptions(options){
try {
var menu = document.getElementById('output_type');
var object_holdy;
var choice ;
object_holdy = options
for(i = 0; i < options.length; i++){
choice = document.createElement('option');
choice.textContent = object_holdy[i].displayname;
choice.value = object_holdy[i].name;
menu.appendChild(choice);
}
} catch (error) {
console.log("Error in script.js function loadDataTypeOptions");
console.log(error);
}
}
//function to load language options to the drop down list //function to load language options to the drop down list
function loadLanguageOptions(){ function loadLanguageOptions(){
try { try {
@@ -202,53 +180,16 @@ function loadLanguageOptions(){
choice.value = object_holdy[i]; choice.value = object_holdy[i];
menu.appendChild(choice); menu.appendChild(choice);
} }
} catch (error) { } catch (error) {
console.log("Error in script.js loadLanguageOptions function"); console.log("Error in script.js loadLanguageOptions function");
console.log(error); console.log(error);
} }
} }
//function to load speaker options to the drop down list
function loadSpeakerOptions(options){
try {
var menu = document.getElementById('cur_speaker');
var l = document.getElementById('cur_speaker').options.length -1;
for(i = l; i >= 0; i--){
menu.remove(i);
}
var object_holdy;
var choice;
object_holdy = Object.keys(options);
for(i = 0; i < object_holdy.length; i++){
choice = document.createElement('option');
choice.textContent = options[object_holdy[i]].name;
choice.value = object_holdy[i];
menu.appendChild(choice);
}
document.getElementById("speakerAudioViewer").src = options.speakerA.src;
} catch (error) {
console.log("Error in script.js loadSpeakerOptions function");
console.log(error);
}
}
//function to load speaker audio file options to the drop down list
function loadSpeakerAudio(option){
try {
var menu = document.getElementById('speakerAudioViewer');
var aud = document.createElement("source");
aud.src = options;
menu.appendChild(aud);
} catch (error) {
console.log("Error in script.js loadSpeakerAudio function");
console.log(error);
}
}
function activateSubmitBtn(hasFile){ function activateSubmitBtn(hasFile){
try { try {
document.getElementById("submitButton").disabled = !hasFile; submitButton.disabled = !hasFile;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
@@ -273,68 +214,3 @@ function generateThumbnail(path){
videoElement.style.maxHeight = 40; videoElement.style.maxHeight = 40;
videoElement.autoplay = false; videoElement.autoplay = false;
} }
//Step-navigation
const steps = document.querySelectorAll(".step");
const stepButtons = document.querySelectorAll(".step-item");
let currentStep = 1;
const totalSteps = steps.length;
function showStep(stepNumber) {
if (stepNumber < 1 || stepNumber > totalSteps){
console.error("StepNumber out of Bounds", stepNumber);
return;
}
steps.forEach(step => step.style.display = "none");
document.getElementById("step" + stepNumber).style.display = "flex";
stepButtons.forEach(btn => btn.classList.remove("active"));
document.querySelector(`.step-item[data-step="${stepNumber}"]`).classList.add("active");
const activeBtn = document.querySelector(`.step-item[data-step="${stepNumber}"]`);
if(activeBtn) activeBtn.classList.add("active");
prevBtn.disabled = stepNumber == 1;
nextBtn.disabled = stepNumber === totalSteps;
currentStep = stepNumber;
}
//Audio value setter
var speakerAudios = {};
var speakerEndValues = {};
function setSpeakerAudiosValue(valy){
try {
speakerAudios = valy;
speakerRewriten = valy;
document.getElementById("speakerAudioViewer").src = valy.speakerA.src;
} catch (error) {
}
}
function rewriteSpeakerName(){
try {
var tempy = document.getElementById("cur_speaker").value;
speakerAudios[tempy].name = document.getElementById("newSpeaker").value;
loadSpeakerOptions(speakerAudios);
} catch (error) {
console.log("\n\n\n" + error + "\n\n\n")
}
}
function sendSpeakerPackages(){
try {
window.submitSpeaker.speaker_submit(speakerAudios);
} catch (error) {
}
}
function fileDownload() {
try {
window.download.file_download();
} catch (error) {
console.error("Download failed:", error);
}
}
+28 -353
View File
@@ -6,32 +6,11 @@ body {
align-items: center; align-items: center;
height: 100vh; height: 100vh;
background-color: #f2f3f4; background-color: #f2f3f4;
gap: 10px; gap: 15px;
margin: 0; margin: 0;
} }
#h1 {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
margin: 0;
z-index: 20;
}
#h1-wrapper {
position: relative;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
width: 770px;
height: 60px;
background-color: #FFF;
box-shadow: 0px 4px 10px rgba(0,0,0,0.1);
border-radius: 5px;
margin-bottom: 10px;
display: flex;
align-items: center;
}
.upload-container { .upload-container {
background: white; background: white;
padding: 40px; padding: 40px;
@@ -107,86 +86,31 @@ body {
.submit-btn:hover { .submit-btn:hover {
background-color: #0056b3; background-color: #0056b3;
} }
#step2 {
gap: 30px;
}
.KI-wrapper {
margin-top: 40px;
}
input[type="file"] { input[type="file"] {
display: none; display: none;
} }
.checkbox-group {
--borderColor: #007bfff5;
--borderWidth: .125em;
}
.checkbox-group input[type=checkbox] {
-webkit-appearance: none;
appearance: none;
vertical-align: middle;
background: #fff;
font-size: 1.8em;
border-radius: 0.125em;
display: inline-block;
border: var(--borderWidth) solid var(--borderColor);
width: 1em;
height: 1em;
position: relative;
}
.checkbox-group input[type=checkbox]:before,
.checkbox-group input[type=checkbox]:after {
content: "";
position: absolute;
background: var(--borderColor);
width: calc(var(--borderWidth) * 3);
height: var(--borderWidth);
top: 50%;
left: 10%;
transform-origin: left center;
}
.checkbox-group input[type=checkbox]:before {
transform: rotate(45deg) translate(calc(var(--borderWidth) / -2), calc(var(--borderWidth) / -2)) scaleX(0);
transition: transform 200ms ease-in 200ms;
}
.checkbox-group input[type=checkbox]:after {
width: calc(var(--borderWidth) * 5);
transform: rotate(-45deg) translateY(calc(var(--borderWidth) * 2)) scaleX(0);
transform-origin: left center;
transition: transform 200ms ease-in;
}
.checkbox-group input[type=checkbox]:checked:before {
transform: rotate(45deg) translate(calc(var(--borderWidth) / -2), calc(var(--borderWidth) / -2)) scaleX(1);
transition: transform 200ms ease-in;
}
.checkbox-group input[type=checkbox]:checked:after {
width: calc(var(--borderWidth) * 5);
transform: rotate(-45deg) translateY(calc(var(--borderWidth) * 2)) scaleX(1);
transition: transform 200ms ease-out 200ms;
}
.checkbox-group input[type=checkbox]:focus {
outline: calc(var(--borderWidth) / 2) dotted rgba(0, 0, 0, 0.25);
}
.checkbox-container{ .checkbox-container{
margin-top: 8px; margin-top: 8px;
display: flex; display: flex;
justify-items: left; align-items: center;
align-items: center; gap: 5px;
gap: 5px;
} }
.checkbox-group {
.submit-btn { margin-top: 15px;
margin-bottom: 15px;
display: flex; display: flex;
align-items: center; flex-direction: column;
justify-content: center; gap: 10px;
align-items: flex-start;
}
.submit-btn {
padding: 10px 20px; padding: 10px 20px;
margin: 130px auto 10px auto; margin-top: 10px;
margin-bottom: 10px;
background-color: #007BFF; background-color: #007BFF;
color: white; color: white;
border: none; border: none;
@@ -202,12 +126,13 @@ input[type="file"] {
} }
.mitte { .mitte {
background-color: #FFF; background-color: #FDFCFA;
display: flex; display: flex;
width: 700px; width: 700px;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 40px; padding: 5% 50px;
margin-top: 20px;
gap: 10px; gap: 10px;
border: 0px; border: 0px;
border-color: black; border-color: black;
@@ -216,11 +141,14 @@ input[type="file"] {
box-shadow: 0px 4px 10px rgba(0,0,0,0.1); box-shadow: 0px 4px 10px rgba(0,0,0,0.1);
} }
h1 {
align-content: center;
}
.progressbar{ .progressbar{
position: relative; position: relative;
width: 210px; width: 210px;
height: 30px; height: 30px;
margin: 50px 20px 5px 20px;
background: rgb(42, 46, 78); background: rgb(42, 46, 78);
border-radius: 5px; border-radius: 5px;
overflow: hidden; overflow: hidden;
@@ -245,10 +173,9 @@ input[type="file"] {
.dropdownMenus { .dropdownMenus {
display: flex; display: flex;
justify-content: center; justify-content: flex-end;
margin-top: 1px; margin-top: 1px;
margin-bottom: 30px; gap: 150px;
gap: 170px;
padding: 2px 10px 2px 10px; padding: 2px 10px 2px 10px;
} }
@@ -257,257 +184,5 @@ input[type="file"] {
} }
.labelDiv { .labelDiv {
gap: 60px; gap: 200px;
display: flex;
justify-content: center;
overflow-wrap:inherit;
padding-bottom: 20px;
margin-top: 40px;
margin-bottom: 10px;
}
/*Step bar*/
.step-nav {
display: flex;
gap: 20px;
justify-content: center;
background: #fff;
padding: 10px 30px;
border-radius: 6px;
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
}
.step-item {
padding: 10px 25px;
border-radius: 8px;
background: #eee;
cursor: pointer;
font-weight: bold;
transition: 0.2s;
}
.step-item.active {
background: #007BFF;
color: white;
}
.step-item:hover {
background: #d9d9d9;
}
/*panels*/
.step {
margin-top: 70px;
display: flex;
flex-direction: column;
min-height: 400px;
}
/*Navigation arrows*/
.step-nav-arrows {
display: flex;
justify-content: space-between;
align-items: center;
}
.middle-container-wrapper {
display: flex;
align-items: center;
justify-content: center;
gap: 30px;
width: max-content;
height: auto;
}
.navBtn {
display: flex;
justify-content: center;
padding: 10px 25px;
background-color: #007BFF;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
}
.navBtn:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.testy{
background-color: #FFF;
display: flex;
width: auto;
flex-direction: row;
align-items: center;
margin-top: 20px;
gap: 10px;
border: 0px;
border-color: black;
border-style: solid;
border-radius: 6px;
visibility: hidden;
}
.box2 {
background-color: red;
width: 40px;
height: 40px;
padding: 5px;
border: 1px solid black;
margin: 5px;
border-radius: 100px;
}
li {
color: #FFF;
}
.p-menu1 {
margin-left: 20px;
z-index: 10;
}
.hamburger1 {
height: 45px;
margin: 0;
padding-top: 8px;
display: grid;
grid-template-rows: repeat(3, 1fr);
justify-items: center;
z-index: 120;
}
.hamburger1 div {
background-color: rgb(61, 61, 61);
position: relative;
width: 40px;
height: 5px;
margin-top: 0;
-webkit-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
}
#toggle1 {
display: none;
}
#toggle1:checked + .hamburger1 .top {
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
margin-top: 22.5px;
}
#toggle1:checked + .hamburger1 .meat {
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
margin-top: -5px;
}
#toggle1:checked + .hamburger1 .bottom {
-webkit-transform: scale(0);
transform: scale(0);
}
#toggle1:checked ~ .menu1 {
height: 150px;
width: 300px;
}
/* Menu */
.menu1 {
position: absolute;
top: 55px;
left: 20px;
width: 240px;
border-radius: 5px;
background-color: #1C3B69;
margin: 0;
display: grid;
grid-template-rows: 1fr repeat(4, 0.5fr);
grid-row-gap: 25px;
padding: 0;
list-style: none;
clear: both;
width: auto;
text-align: center;
height: 0px;
overflow: hidden;
transition: height 0.3s ease, width 0.3s ease;
z-index: 9999;
-webkit-transition: all 0.3s ease;
}
.menu1 a:first-child {
margin-top: 30px;
}
.menu1 a:last-child {
margin-bottom: 30px;
}
.li1 {
color: #fff;
width: 100%;
margin: 0;
padding: 10px 0px;
font: 700 20px 'Oswald', sans-serif;
text-decoration: none;
}
.li1:hover {
background-color: #FFF;
color: rgb(61, 61, 61);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
-webkit-transition: all 0.3s ease;
transition: all 0.3s ease;
}
#step2, #step3, #step5 {
font-size: larger;
}
#step4 {
align-items: center;
}
#step5 {
align-items: flex-start;
}
.button-group {
display: flex;
gap: 12px;
justify-content: center;
margin-top: 10px;
}
.download-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 10px 20px;
margin-left: auto;
margin-right: 0px;
margin-top: 130px;
margin-bottom: 10px;
background-color: #007BFF;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
}
#speakerLocker, #speakerResender{
padding: 10px 20px;
margin: 20px auto;
background-color: #007BFF;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
} }
+17 -83
View File
@@ -61,7 +61,7 @@ let mainWindow;
function createWindow() { function createWindow() {
mainWindow = new electron.BrowserWindow({ mainWindow = new electron.BrowserWindow({
width: 1200, width: 800,
height: 800, height: 800,
webPreferences: { webPreferences: {
nodeIntegration: false, nodeIntegration: false,
@@ -124,28 +124,16 @@ electron.ipcMain.handle('get-module-names', async () => {
// mainWindow.webContents.send("modules", module_array) // mainWindow.webContents.send("modules", module_array)
// }) // })
var globalArgs = {}
var globalFinalHtmlPath = ""
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 = 3 + args.document.styles.length
const TEMPLATE_MAP = { if(args.document.styles.length == 0)
"followup-report": "followup_report.txt", throw new Error("At least one Document Style needed");
"agenda": "agenda.txt",
"result-protocol": "result_protocol.txt",
"sprint-planning": "sprint_planning_note.txt",
"custom": "custom_document.txt"
};
const templateFile = TEMPLATE_MAP[args.document.type];
if (!templateFile) {
throw new Error("Unknown document type: " + args.document.type);
}
console.log(args); console.log(args);
let audiopath = "" let audiopath = ""
@@ -153,14 +141,13 @@ electron.ipcMain.on("file_submit", async (event, args) => {
console.log("\n\n Running the Video to Audio Extractor"); console.log("\n\n Running the Video to Audio Extractor");
// This code handles the Video to Audio extraction module call // This code handles the Video to Audio extraction module call
await mapFunctions.get("module-handler").function(args.video.module, {inputVideoPath: args.video.inputVideoPath, outputType: mapFunctions.get(args.transcription.module).audioformat}).then(resp => { await mapFunctions.get("module-handler").function(args.video.module, {inputVideoPath: args.video.inputVideoPath, outputType: args.video.outputType}).then(resp => {
console.log(resp); console.log(resp);
audiopath = resp audiopath = resp
curstep++ curstep++
mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps}) mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
}).catch(err => { }).catch(err => {
mainWindow.webContents.send("error", err) mainWindow.webContents.send("error", err)
console.log(err);
return return
}) })
@@ -175,7 +162,6 @@ electron.ipcMain.on("file_submit", async (event, args) => {
mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps}) mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
}).catch(err => { }).catch(err => {
mainWindow.webContents.send("error", err) mainWindow.webContents.send("error", err)
console.log(err);
return return
}) })
@@ -190,47 +176,31 @@ electron.ipcMain.on("file_submit", async (event, args) => {
mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps}) mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
}).catch(err => { }).catch(err => {
mainWindow.webContents.send("error", err) mainWindow.webContents.send("error", err)
console.log(err);
return return
}) })
console.log("\n\n Running the LLM module"); console.log("\n\n Running the LLM module");
// TODO implement documentation module // TODO implement documentation module
// This code handles the Text to Document processing module call // This code handles the Text to Document processing module call
for (let i = 0; i < args.document.styles.length; i++) {
console.log(`\n\n Running the LLM for Document Style ${args.document.type}`); await mapFunctions.get("module-handler").function(args.document.module, {prompt: args.document.styles[i].prompt, transcript: transcriptpath}).then(resp => {
console.log(resp);
await mapFunctions.get("module-handler").function(args.document.module, { inputTranscriptPath: transcriptpath, documentTypePath: "./storage/documentType/" + templateFile, language: "en" }).then(resp => { transcriptpath = resp
console.log(resp); curstep++
globalFinalHtmlPath = resp mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
curstep++ }).catch(err => {
mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps}) mainWindow.webContents.send("error", err)
}).catch(err => { return
mainWindow.webContents.send("error", err) })
console.log(err); }
return
})
await mapFunctions.get("extract-speaker-snippets").function({audioPath: audiopath, jsonPath: transcriptpath }).then(resp => {
mainWindow.webContents.send("speakerAudios", resp)
}).catch(err => {
mainWindow.webContents.send("error", err)
console.log(err);
return
})
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
}) })
electron.ipcMain.on("file_download", async() => {
await mapFunctions.get("htmlDocumentConverter").convert({inputPath:globalFinalHtmlPath, format: globalArgs.document.outputType, showDialog: true});
})
electron.ipcMain.on("speaker_submit", async() => {
console.log("\n\n\nJa also hier kam was an \n\n\n");
})
let q = let q =
{ {
@@ -263,39 +233,3 @@ let q1 = {
{name:"qeg", displayname:"aqghegahu"} {name:"qeg", displayname:"aqghegahu"}
] ]
} }
//gibt Documentfiles an preload zurück
electron.ipcMain.handle('get-txt-files', () => {
const storagePath = `${mainDir}/storage/documentType`
return fs.readdirSync(storagePath)
.filter(f => f.endsWith('.txt'))
});
//speichern neuer document types
electron.ipcMain.handle('save-txt-file', (event, fileName, content) => {
const filePath = `${mainDir}/storage/documentType/${fileName}.txt`;
fs.writeFileSync(filePath, content, 'utf8');
return true;
});
//read file content
electron.ipcMain.handle('read-txt-file', (event, fileName) => {
const filePath = `${mainDir}/storage/documentType/${fileName}`;
return fs.readFileSync(filePath, 'utf8');
});
//delete documentfiles
electron.ipcMain.handle('delete-txt-file', (event, fileName) => {
const filePath = `${mainDir}/storage/documentType/${fileName}.txt`;
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
return true;
} else {
return false;
}
});
+45 -1888
View File
File diff suppressed because it is too large Load Diff
+4 -6
View File
@@ -3,14 +3,12 @@
"@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.3.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",
@@ -29,7 +27,7 @@
}, },
"scripts": { "scripts": {
"start": "electron main.js", "start": "electron main.js",
"test": "mocha ./test/unit/test.js" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
+3
View File
@@ -0,0 +1,3 @@
fastapi
uvicorn
pytest
+2 -4
View File
@@ -6,19 +6,17 @@ platform = process.platform
mainDir = __dirname mainDir = __dirname
fs = require("fs") fs = require("fs")
readline = require("readline") readline = require("readline")
puppeteer = require("puppeteer")
htmltodocx = require("html-to-docx")
config = require("./config/config") config = require("./config/config")
ffmpegPath = require('ffmpeg-static'); ffmpegPath = require('ffmpeg-static');
ffmpeg = require('fluent-ffmpeg'); ffmpeg = require('fluent-ffmpeg');
path = require('path'); path = require('path');
// cliProgress = require('cli-progress'); cliProgress = require('cli-progress');
// { app, BrowserWindow, ipcMain, dialog } = require('electron'); // { app, BrowserWindow, ipcMain, dialog } = require('electron');
electron = require('electron'); electron = require('electron');
genai = require("@google/genai");
axios = require("axios") axios = require("axios")
-47
View File
@@ -1,47 +0,0 @@
const fs = require('fs');
const path = require('path');
//node show-models.js, remember to set SAIA_API_KEY in your environment before running the script
const SAIA_API_KEY = process.env.SAIA_API_KEY;
const SAIA_MODELS_URL = "https://chat-ai.academiccloud.de/v1/models";
// Script to list available models
(async () => {
if (!SAIA_API_KEY) {
console.error("ERROR: SAIA_API_KEY environment variable is not set!");
process.exit(1);
}
console.log("Fetching available models from SAIA...\n");
try {
const response = await fetch(SAIA_MODELS_URL, {
method: "GET",
headers: {
"Authorization": `Bearer ${SAIA_API_KEY}`,
"Accept": "application/json"
}
});
if (!response.ok) {
const text = await response.text();
throw new Error(`SAIA API error (${response.status}): ${text}`);
}
const data = await response.json();
console.log("Available models:");
console.log(JSON.stringify(data, null, 2));
if (data.data && Array.isArray(data.data)) {
console.log("\n\nModel IDs:");
data.data.forEach(model => {
console.log(`- ${model.id}`);
});
}
} catch (error) {
console.error("Error fetching models:", error);
}
})();
@@ -1,102 +0,0 @@
const ffmpeg = require("fluent-ffmpeg");
const ffmpegPath = require("ffmpeg-static");
ffmpeg.setFfmpegPath(ffmpegPath);
module.exports = {
name: "extract-speaker-snippets",
type: "audio",
displayname: "Extract Speaker Snippets",
async function(parameter) {
return new Promise(async (resolve, reject) => {
let output = {}
// console.log("Extract Speaker Snippets\n");
// Pfade
const AUDIO_PATH = parameter.audioPath; // Gesamt-Audio
const JSON_PATH = parameter.jsonPath; // json summary
const OUTPUT_DIR = path.join(__dirname, "/../../../storage/audio/speakerSnippets");
if (!AUDIO_PATH || !JSON_PATH) {
// console.error("no audioPath or jsonPath available");
reject(new Error("no audioPath or jsonPath available"));
return;
}
// Output-Ordner
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
// JSON laden
let entries;
try {
entries = JSON.parse(fs.readFileSync(JSON_PATH, "utf8"));
} catch (err) {
// console.error("JSON reading failed", err);
reject(new Error(err));
return;
}
if (!Array.isArray(entries)) {
// console.error("JSON is not an Array");
reject(new Error("JSON is not an Array"));
return;
}
// Pro Speaker genau EINEN Satz merken
const speakerMap = {};
for (const item of entries) {
if (!speakerMap[item.speaker]) {
speakerMap[item.speaker] = item;
}
}
// FFmpeg pro Speaker ausführen (sequenziell)
for (const speaker of Object.keys(speakerMap)) {
const data = speakerMap[speaker];
// ms → Sekunden
const startSec = data.start / 1000;
const durationSec = (data.end - data.start) / 1000;
if (durationSec <= 0) {
// console.log(`invalid times for Speaker ${speaker}`);
continue;
}
const outFile = path.join(OUTPUT_DIR, `speaker_${speaker}.wav`);
try {
await new Promise((res, rej) => {
ffmpeg(AUDIO_PATH)
.setStartTime(startSec)
.setDuration(durationSec)
.output(outFile)
.on("end", () => {
output[`speaker${speaker}`] = {src: outFile, name: `speaker${speaker}`}
// console.log(`Snippet erstellt: speaker_${speaker}.wav`);
res();
})
.on("error", (err) => {
// console.error(`FFmpeg Fehler (${speaker})`, err.message);
rej(err);
return
})
.run();
});
} catch (error) {
reject(error)
return
}
}
resolve(output)
// console.log("\nAlle Speaker-Snippets erstellt\n");
})
}
};
-197
View File
@@ -1,197 +0,0 @@
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') {
const safeName = decodeURIComponent(defaultName);
const powershell = `
Add-Type -AssemblyName System.Windows.Forms;
$dialog = New-Object System.Windows.Forms.SaveFileDialog;
$dialog.FileName = '${safeName}.${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 -NoProfile -Command "${powershell.replace(/\r?\n/g, ' ')}"`,
{ encoding: 'utf8' }
);
return result.trim() || null;
} catch (err) {
if (err.status === 1) return null; // User cancelled
throw new Error("Save dialog failed: " + err.message);
}
} 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);
}
})();
}
+22 -5
View File
@@ -24,11 +24,17 @@ 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, parameter.outputType) this.extractAudioFromVideo(parameter.inputVideoPath, progressBar, parameter.outputType)
.then((resp) => resolve(resp)) .then((resp) => resolve(resp))
.catch((err) => {reject(err)}); .catch((err) => console.error(err));
}) })
} catch (error) { } catch (error) {
console.log(parameter.outputType); console.log(parameter.outputType);
@@ -46,7 +52,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, outputType){ extractAudioFromVideo: async function (videoFilePath, progressBar, 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}`);
@@ -57,17 +63,28 @@ 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) => {
// console.error(`failed_audio_extraction on type ${outputType}: ${err.message}`); progressBar.stop();
console.error(`failed_audio_extraction on type ${outputType}: ${err.message}`);
reject(err); reject(err);
}) })
.save(outputAudioPath); .save(outputAudioPath);
} catch (error) { } catch (error) {
// console.log(error); console.log();
} }
}); });
} }
@@ -21,36 +21,30 @@ 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);
reject(e) return { error: "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, rej) => { await new Promise((res) => {
fs.readFile(args, 'utf8', function (err, data) { fs.readFile(args, 'utf8', function (err, data) {
if (err){ if (err) throw 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)
reject(e) return { error: "Invalid JSON" };
return
} }
} }
const words = inputJson.words; const words = inputJson.words;
if (!Array.isArray(words)) { if (!Array.isArray(words)) {
reject("No words Array found") return { error: "No words Array found" };
return
} }
const ENDINGS = [".", "!", "?"]; // '...' auch als Satzende ? const ENDINGS = [".", "!", "?"]; // '...' auch als Satzende ?
@@ -142,11 +136,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,35 +32,29 @@ 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);
reject("Could not read JSON from file path.") return { error: "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, rej) => { await new Promise((res) => {
fs.readFile(args, 'utf8', function (err, data) { fs.readFile(args, 'utf8', function (err, data) {
if (err){ if (err) throw err;
rej(err) inputJson = JSON.parse(data);
return res()
} });
inputJson = JSON.parse(data); })
res() } catch (e) {
}); console.log("Invalid JSON in summarize-transcription");
}) console.log(e)
} catch (e) { return { error: "Invalid JSON" };
// console.log("Invalid JSON in summarize-transcription"); }
// console.log(e)
reject(e)
return
} }
}
const words = inputJson.words; const words = inputJson.words;
if (!Array.isArray(words)) { if (!Array.isArray(words)) {
reject("No words Array found") return { error: "No words Array found" };
return;
} }
const ENDINGS = [".", "!", "?"]; // '...' auch als Satzende ? const ENDINGS = [".", "!", "?"]; // '...' auch als Satzende ?
@@ -76,7 +70,7 @@ module.exports = {
if (!currentSpeaker) currentSpeaker = w.speaker; if (!currentSpeaker) currentSpeaker = w.speaker;
if (startTime === null) startTime = w.start; if (startTime === null) startTime = w.start;
endTime = w.end; endTime = w.end;
//speaker changing //speaker changing
if (currentSpeaker !== w.speaker && currentSentence) { if (currentSpeaker !== w.speaker && currentSentence) {
result.push({ result.push({
@@ -138,10 +132,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);
} }
}) })
+6 -135
View File
@@ -1,137 +1,8 @@
// const fs = require('fs'); module.exports = {
// const path = require('path'); name:"chatgpt", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
type:"llm", // value used to differentiate each module to order them in the UI
const outputDir = path.join(__dirname, "../../../storage/documents"); // path for output directory displayname:"ChatGPT", // The displayname used within the UI
async function(parameter){
if (!fs.existsSync(outputDir)) { // TODO add code to actually send the transcript to ChatGPT and get a response back
fs.mkdirSync(outputDir, { recursive: true }); // Create output directory if it doesn't exist
}
// Ensure SAIA API key is set in environment variables: export SAIA_API_KEY="your_api_key_here"
const SAIA_API_KEY = process.env.SAIA_API_KEY; // Ensure SAIA API key is set in environment variables
const SAIA_URL = "https://chat-ai.academiccloud.de/v1/chat/completions"; // URL for the REST call, used model and action
const module_exports = {
name: "llm-saia_openai_gpt",
type: "llm",
displayname: "GPT 120B",
description: "Generates documents using OpenAI GPT OSS 120B via SAIA platform",
async function(parameter) {
return new Promise(async (resolve, reject) => {
try {
// console.log("SAIA OpenAI GPT 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 OpenAI GPT 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
// return
// --- REST CALL ---
const response = await fetch(SAIA_URL, { //safe model response in variable
method: "POST",
headers: {
"Authorization": `Bearer ${SAIA_API_KEY}`,
"Accept": "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "openai-gpt-oss-120b",
messages: [
{ role: "system", content: "You are a helpful assistant that generates HTML documents from transcripts. Output only valid HTML content without any preamble, explanations, or markdown formatting." },
{ role: "user", content: promptText }
],
temperature: 0
})
});
if (!response.ok) { //ok is true when a 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 llm-openai-gpt.js <transcriptPath> <documentTypePath> [language]");
console.error("Example: node llm-openai-gpt.js ./transcript.json ./docType.txt de");
process.exit(1);
}
const [transcriptPath, documentTypePath, language] = args;
// Check if API key is set
if (!SAIA_API_KEY) {
console.error("ERROR: SAIA_API_KEY environment variable is not set!");
console.error("Please set it with: export SAIA_API_KEY='your_api_key_here'");
process.exit(1);
}
// Check if files exist
if (!fs.existsSync(transcriptPath)) {
console.error(`ERROR: Transcript file not found: ${transcriptPath}`);
process.exit(1);
}
if (!fs.existsSync(documentTypePath)) {
console.error(`ERROR: Document type file not found: ${documentTypePath}`);
process.exit(1);
}
console.log("Starting document generation...");
console.log(`Transcript: ${transcriptPath}`);
console.log(`Document Type: ${documentTypePath}`);
console.log(`Language: ${language || 'en (default)'}`);
await module_exports.createDocumentFromTranscript(
transcriptPath,
documentTypePath,
language || 'en'
);
console.log("Done!");
})();
} }
+54 -112
View File
@@ -1,134 +1,76 @@
const fs = require('fs'); const outputDir = path.join(__dirname, "../../../storage/documents"); // path for output directory
const path = require('path');
const outputDir = path.join(__dirname, "../../../storage/documents"); // path for output directory
if (!fs.existsSync(outputDir)) { if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true }); // Create output directory if it doesn't exist fs.mkdirSync(outputDir, { recursive: true }); // Create output directory if it doesn't exist
} }
// Ensure Google API key is set in environment variables: export GOOGLE_API_KEY="your_api_key_here" // Ensure Google API key is set in environment variables: export GOOGLE_API_KEY="your_api_key_here"
const GEMINI_API_KEY = process.env.GOOGLE_API_KEY; // Ensure Google API key is set in environment variables: export GOOGLE_API_KEY="your_api_key_here"
const GEMINI_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent"; // URL for the REST call, used model and action
const module_exports = { 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
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);
resolve(await this.createDocumentFromTranscript( //Call the function to create document with transcript, document type and language 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) {
// console.error("Error in Gemini LLM module:", error);
reject(error)
}
})
} catch (error) {
console.error("Error in Gemini LLM module:", 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(`${GEMINI_URL}?key=${GEMINI_API_KEY}`, { //safe model response in variable const response = await fetch(`${GEMINI_URL}?key=${GEMINI_API_KEY}`, { //safe model response in variable
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json"
}, },
body: JSON.stringify({ body: JSON.stringify({
contents: [ contents: [
{ {
parts: [ parts: [
{ text: promptText } // Input prompt for content generation { text: promptText } // Input prompt for content generation
] ]
} }
] ]
}) })
}); });
if (!response.ok) { //ok is true when a responce was successfull if (!response.ok) { //ok is true when a responce was successfull
const text = await response.text(); const text = await response.text();
throw new Error(`Gemini API error (${response.status}): ${text}`); throw new Error(`Gemini API error (${response.status}): ${text}`);
}
const data = await response.json();
// Get generated text from response or default to empty string (if null)
const output = data?.candidates?.[0]?.content?.parts?.[0]?.text || "";
let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath)); // Name for the output file
// console.log(inputTranscriptName);
const outPath = path.join(outputDir, `${inputTranscriptName}.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 Gemini content:", error);
reject(error)
} }
})
const data = await response.json();
// Get generated text from response or default to empty string (if null)
const output = data?.candidates?.[0]?.content?.parts?.[0]?.text || "";
let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath)); // Name for the output file
console.log(inputTranscriptName);
const outPath = path.join(outputDir, `${inputTranscriptName}.md`); // Output file path & name to make naming dynamic. Pulled from input transcript name
fs.writeFileSync(outPath, output, "utf8"); // Write output to file
console.log("Generated document written to:", outPath);
} catch (error) {
console.error("Error generating Gemini content:", error);
}
} }
}; };
module.exports = module_exports;
// CLI Mode: Allow direct execution
if (require.main === module) {
(async () => {
const args = process.argv.slice(2);
if (args.length < 2) {
console.error("Usage: node llm-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!");
})();
}
-136
View File
@@ -1,136 +0,0 @@
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!");
})();
}
Submodule services/modules/transcription-local/whisper.cpp deleted from 999a7e0cbf
@@ -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;
} }
@@ -89,7 +89,6 @@ module.exports = {
name: 'assembly', name: 'assembly',
type: 'transcription', type: 'transcription',
displayname: 'AssemblyAI', displayname: 'AssemblyAI',
audioformat: "mp3",
async function(audioFileName) { async function(audioFileName) {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
@@ -116,9 +115,8 @@ 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
} }
}) })
} }
-2
View File
@@ -1,4 +1,3 @@
module.exports = { module.exports = {
name:"Startup_function", name:"Startup_function",
async function(){ async function(){
@@ -10,7 +9,6 @@ module.exports = {
// let transcript = await mapFunctions.get("assembly").function('../../storage/audio/IMG_2978.wav'); // let transcript = await mapFunctions.get("assembly").function('../../storage/audio/IMG_2978.wav');
// let summary = await mapFunctions.get("summarize-transcription").function({jsonPath:'/Users/santa/Proj25/video2document/storage/transcripts/IMG_2978.json'}); // let summary = await mapFunctions.get("summarize-transcription").function({jsonPath:'/Users/santa/Proj25/video2document/storage/transcripts/IMG_2978.json'});
// let snippets = await mapFunctions.get("extract-speaker-snippets").function({audioPath:'/Users/santa/Proj25/video2document/storage/audio/KittyKat.wav', jsonPath1: '/Users/santa/Proj25/video2document/storage/transcriptionSummaries/KittyKat-1765806474958.json' });
-23
View File
@@ -1,23 +0,0 @@
@echo off
cd /d %~dp0
:: Prüfen ob Node.js installiert ist
where node >nul 2>nul
if %errorlevel% neq 0 (
echo Node.js ist nicht installiert.
pause
exit /b 1
)
echo Node.js gefunden:
node -v
npm install
if %errorlevel% neq 0 (
echo npm install fehlgeschlagen.
pause
exit /b 1
)
npm start
pause
-15
View File
@@ -1,15 +0,0 @@
#!/bin/bash
# Prüfen ob Node.js installiert ist
if ! command -v node >/dev/null 2>&1; then
echo "Node.js ist nicht installiert."
exit 1
fi
echo "Node.js gefunden: $(node -v)"
# Abhängigkeiten installieren
npm install || exit 1
# Projekt starten
npm start
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
-26
View File
@@ -1,26 +0,0 @@
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
- Namen aus dem Transkript speakerA, speakerB etc. sollen weiterhin bestehen bleiben wie sie sind und nicht im Dokument ersetzt werden
TRANSKRIPT:
-22
View File
@@ -1,22 +0,0 @@
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
- Namen aus dem Transkript speakerA, speakerB etc. sollen weiterhin bestehen bleiben wie sie sind und nicht im Dokument ersetzt werden
FORMAT:
- Passe Struktur und Stil an den Nutzerwunsch an
- Klare Überschriften
- Keine Sprecher- oder Zeitangaben
TRANSKRIPT & NUTZERANWEISUNG:
-45
View File
@@ -1,45 +0,0 @@
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
- Namen aus dem Transkript speakerA, speakerB etc. sollen weiterhin bestehen bleiben wie sie sind und nicht im Dokument ersetzt werden
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:
-27
View File
@@ -1,27 +0,0 @@
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
- Namen aus dem Transkript speakerA, speakerB etc. sollen weiterhin bestehen bleiben wie sie sind und nicht im Dokument ersetzt werden
TRANSKRIPT:
@@ -1,35 +0,0 @@
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
- Namen aus dem Transkript speakerA, speakerB etc. sollen weiterhin bestehen bleiben wie sie sind und nicht im Dokument ersetzt werden
TRANSKRIPT:
+8
View File
@@ -0,0 +1,8 @@
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
-243
View File
@@ -1,243 +0,0 @@
// 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
let speakers
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 => {
done(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 => {
done(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 => {
done(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 => {
done(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 => {
done(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("llm-saia_openai_gpt").function({inputTranscriptPath: summarizePath, documentTypePath: "./storage/documentType/followup_report.txt", language: "en"}).then(resp => {
llmpath = resp
done()
}).catch(err => {
done(err)
})
})
it("Gemini", function (done){
mapFunctions.get("llm-gemini").function({inputTranscriptPath: summarizePath, documentTypePath: "./storage/documentType/followup_report.txt", language: "en"}).then(resp => {
done()
}).catch(err => {
if(err.includes("(503)")){done()} // Error 503 is gemini overload, so an Error that they can at any time throw at us which would crash the pipeline, so we just ignore it and we just imagine that the test passed
else{
console.log(err);
done(err)
}
})
})
it("Qwen3", function (done){
mapFunctions.get("qwen3-235b-a22b").function({inputTranscriptPath: summarizePath, documentTypePath: "./storage/documentType/followup_report.txt", language: "en"}).then(resp => {
done()
}).catch(err => {
done(err)
})
})
it("ChatGPT (Nonexistant Type File)", function (done){
mapFunctions.get("llm-saia_openai_gpt").function({inputTranscriptPath: summarizePath, documentTypePath: "a", language: "en"}).then(resp => {
done("Didnt crash")
}).catch(err => {
done()
})
})
it("Gemini (Nonexistant Type File)", function (done){
mapFunctions.get("llm-gemini").function({inputTranscriptPath: summarizePath, documentTypePath: "a", language: "en"}).then(resp => {
done("Didnt crash")
}).catch(err => {
done()
})
})
it("Qwen3 (Nonexistant Type File)", function (done){
mapFunctions.get("qwen3-235b-a22b").function({inputTranscriptPath: summarizePath, documentTypePath: "a", language: "en"}).then(resp => {
done("Didnt crash")
}).catch(err => {
done()
})
})
})
describe("Audio Snippet", function() {
this.slow(1000)
this.timeout(5000)
// transcriptPath = "A:\\programing\\@projects\\video2document\\storage\\transcriptionSummaries\\testvideo-1765900665001.json"
// audiopath = "A:\\programing\\@projects\\video2document\\storage\\audio\\testvideo.mp3"
it("Audio Snipper Generator", function (done){
mapFunctions.get("extract-speaker-snippets").function({audioPath: audiopath, jsonPath: summarizePath }).then(resp => {
speakers = resp
done()
}).catch(err => {
done(err)
})
})
it("Audio Snipper Generator (Nonexistant Transcript File)", function (done){
mapFunctions.get("extract-speaker-snippets").function({audioPath: audiopath, jsonPath: "a" }).then(resp => {
speakers = resp
done("Didnt crash")
}).catch(err => {
done()
})
})
it("Audio Snipper Generator (Nonexistant Audio File)", function (done){
mapFunctions.get("extract-speaker-snippets").function({audioPath: "a", jsonPath: summarizePath }).then(resp => {
speakers = resp
done("Didnt crash")
}).catch(err => {
done()
})
})
})
after(function() {
console.log(`\n\n\n${audiopath} \n${transcriptPath} \n${summarizePath} \n${llmpath}`);
})
})
Binary file not shown.