mirror of
https://gitlab.rlp.net/proj-wise2526-video2document/video2document.git
synced 2026-06-15 18:01:52 +02:00
Compare commits
169 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 56d17de4a5 | |||
| 3839feac19 | |||
| c62ee5aa41 | |||
| 9c156a7df3 | |||
| 797898fb8b | |||
| f81c9bc4e8 | |||
| 7b630646cc | |||
| b57972bf1b | |||
| a96a3bebd1 | |||
| 8c0130337e | |||
| 4a7aeec869 | |||
| 4f3b03f881 | |||
| 4e4308ae51 | |||
| 01913590b0 | |||
| f750966952 | |||
| 26ed7e3c58 | |||
| 9e05c744aa | |||
| df7ca369de | |||
| b6bf178c48 | |||
| 43749fd073 | |||
| f616e06a7e | |||
| de43bdd190 | |||
| b742411dcc | |||
| bbaab9cd21 | |||
| fc8770e67f | |||
| 958c9368d0 | |||
| 145f91f091 | |||
| fa429ec683 | |||
| 6a8ce0a46d | |||
| 2778626e4b | |||
| 0e746749e6 | |||
| 853eb1a5eb | |||
| 3cba97a9d1 | |||
| 2900152945 | |||
| 262cc2b4d6 | |||
| 292b893ed2 | |||
| d9c76e8890 | |||
| 1016d2ca86 | |||
| a014c62ce3 | |||
| 0a2a8e603f | |||
| 562debd883 | |||
| 8927b62971 | |||
| b511b75db7 | |||
| 9b88f4719f | |||
| e4770f484a | |||
| 69deae9951 | |||
| 6775509cf3 | |||
| 3c3b27325e | |||
| 0731f045ce | |||
| b89e5ec587 | |||
| 598a8e5d34 | |||
| 8f62b68184 | |||
| fc041e1036 | |||
| 37382f7444 | |||
| b05537fa70 | |||
| 8e563187b0 | |||
| 95ac7256d4 | |||
| d47cf21e9f | |||
| 6cff6b9981 | |||
| 11f9a02778 | |||
| 41cd8065ba | |||
| c1e79b6603 | |||
| 0003d99041 | |||
| 6a94f88e86 | |||
| 30f73f7bb7 | |||
| 59ac104d69 | |||
| 74439d680e | |||
| 1b76b2e96d | |||
| 363ba2d1b5 | |||
| 6aa62ed534 | |||
| cd474d7101 | |||
| bd47a194c7 | |||
| ef080063a8 | |||
| c2c1aa1b17 | |||
| ee3bcdcd05 | |||
| 04b2457ca3 | |||
| bac6e2b7f0 | |||
| 9760704883 | |||
| aee1428cb6 | |||
| 7494e13c8c | |||
| 2998799826 | |||
| 6146e7091b | |||
| 3a5540d865 | |||
| 948ddb4310 | |||
| 889d455fbe | |||
| 58741f0057 | |||
| 950cc17878 | |||
| aba8552d11 | |||
| 500e62daf0 | |||
| ad6d20b134 | |||
| a8c284edd2 | |||
| 6e0ea41ea6 | |||
| 3a5808d97c | |||
| 4083d5a5cb | |||
| d655925031 | |||
| 405a32098c | |||
| 1683502aa1 | |||
| 789ecd3a31 | |||
| e72d03efbe | |||
| d95b6d5178 | |||
| 20b4ae6a3b | |||
| ec57411992 | |||
| accb65252c | |||
| 271fe78b7b | |||
| 1e38cc79f4 | |||
| 746fec05d4 | |||
| d647f53790 | |||
| 791dbab739 | |||
| c497fa9532 | |||
| bb589e6aad | |||
| 3ce632bf8d | |||
| 5ea8ec6a1a | |||
| 4747c44c52 | |||
| a0abedc921 | |||
| 7cd334645f | |||
| 15e2e35bda | |||
| 53508b175a | |||
| 3dd8485140 | |||
| 68c1f0ed9f | |||
| 2b597add6c | |||
| 3af038d195 | |||
| b546c96238 | |||
| 6512b50d83 | |||
| b26ef96cfc | |||
| faee605f12 | |||
| 037d29e656 | |||
| 8c201db539 | |||
| 6eef82bb64 | |||
| b182e0a3c7 | |||
| 34a8b186a8 | |||
| 70d3bfa201 | |||
| 5661f7c13f | |||
| 208879b497 | |||
| f817d4c29e | |||
| 882b49a489 | |||
| 5e6b700c36 | |||
| 25d11d74a3 | |||
| 0efb7585ba | |||
| 0d3535128d | |||
| e661aefbe8 | |||
| 59b820d917 | |||
| ed94928953 | |||
| 8adf470f18 | |||
| f858f2edbb | |||
| 425e24853e | |||
| 1c5d399c58 | |||
| 33093733ef | |||
| 4f0d2bcc4a | |||
| 05c04aaef2 | |||
| 84cc94aa34 | |||
| ef379a5b7b | |||
| 0bbbb2f9b7 | |||
| 16f319f89f | |||
| 9ab98825a2 | |||
| 9a0a349813 | |||
| 9dfc05e987 | |||
| a5a60635fc | |||
| af13907fdc | |||
| 812bca8cfb | |||
| 34d644d7d7 | |||
| e73a2a8203 | |||
| 2f5efee9c7 | |||
| 465fe8bd41 | |||
| 0f689e2846 | |||
| b87bfd444d | |||
| 97b571b7f9 | |||
| 455147a41b | |||
| 9441699561 | |||
| 283b4ed6af |
Binary file not shown.
+16
-2
@@ -963,9 +963,23 @@ 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/
|
storage/documents/*
|
||||||
|
storage/transcriptionSummaries/*
|
||||||
|
storage/transcripts/*
|
||||||
|
storage/video/*
|
||||||
|
# The inverse for the .gitkeep files, to make sure the folders are there but not the local files
|
||||||
|
!storage/documents/.gitkeep
|
||||||
|
!storage/transcriptionSummaries/.gitkeep
|
||||||
|
!storage/transcripts/.gitkeep
|
||||||
|
!storage/video/.gitkeep
|
||||||
|
|
||||||
*.mp4
|
*.mp4
|
||||||
|
*.webm
|
||||||
|
*.mp3
|
||||||
|
*.mov
|
||||||
*.wav
|
*.wav
|
||||||
*.flac
|
*.flac
|
||||||
|
|
||||||
|
|
||||||
|
!testvideo.mp4
|
||||||
|
|||||||
+20
-25
@@ -1,33 +1,28 @@
|
|||||||
workflow:
|
image: node:latest
|
||||||
rules:
|
|
||||||
# Run the pipeline for merge requests or when committing to a branch
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
- if: $CI_COMMIT_BRANCH
|
|
||||||
|
|
||||||
image: python:3.14.0
|
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- setup
|
# - install
|
||||||
- test
|
- test
|
||||||
|
|
||||||
setup_environment:
|
# job-install:
|
||||||
stage: setup
|
# stage: install
|
||||||
script:
|
# script:
|
||||||
- pip install --upgrade pip
|
# - npm install
|
||||||
- pip install -r requirements.txt
|
# artifacts:
|
||||||
- echo "Dependencies installed successfully."
|
# untracked: false
|
||||||
|
# when: on_success
|
||||||
|
# access: all
|
||||||
|
# expire_in: "30 days"
|
||||||
|
# paths:
|
||||||
|
# - node_modules
|
||||||
|
|
||||||
only:
|
|
||||||
- main
|
|
||||||
- feature/ci-pipeline-s1-09a-1 # You can add more branches if needed
|
|
||||||
|
|
||||||
test_app:
|
|
||||||
|
job-test:
|
||||||
stage: test
|
stage: test
|
||||||
script:
|
script:
|
||||||
- echo "Running V2D Framework basic test..."
|
- npm install
|
||||||
- python -m unittest discover || echo "No tests found."
|
- echo "ASSEMBLYAI_API_KEY=$apikey_assembly" > .env
|
||||||
|
- echo "GOOGLE_API_KEY=$apikey_gemini" >> .env
|
||||||
only:
|
- echo "SAIA_API_KEY=$apikey_saia" >> .env
|
||||||
- main
|
- npm test
|
||||||
- feature/ci-pipeline-s1-09a-1
|
|
||||||
|
|
||||||
@@ -1,93 +1,56 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
## Usage
|
Follow the steps below to install Video2Document (v2d):
|
||||||
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.
|
|
||||||
|
|
||||||
## Support
|
### 1. Install Node.js
|
||||||
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.
|
|
||||||
|
|
||||||
## Roadmap
|
Download and install Node.js from [https://nodejs.org/en](https://nodejs.org/en)
|
||||||
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
|
||||||
|
|
||||||
## Contributing
|
### 2. Clone the Repository
|
||||||
State if you are open to contributions and what your requirements are for accepting them.
|
|
||||||
|
|
||||||
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.
|
Clone the repository using Git:
|
||||||
|
|
||||||
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.
|
```bash
|
||||||
|
git clone https://gitlab.rlp.net/proj-wise2526-video2document/video2document.git
|
||||||
|
```
|
||||||
|
|
||||||
## Authors and acknowledgment
|
Alternatively, you can download the ZIP file and extract it manually.
|
||||||
Show your appreciation to those who have contributed to the project.
|
|
||||||
|
|
||||||
## License
|
### 3. Start the Program
|
||||||
For open source projects, say how it is licensed.
|
|
||||||
|
|
||||||
## Project status
|
The start scripts automatically install dependencies and launch the program. Choose the appropriate method for your operating system:
|
||||||
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.
|
||||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,161 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Custom Document</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background: #f0f2f5;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
margin-top: 50px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 15px;
|
||||||
|
display: block;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"], textarea, select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
margin-top: 5px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
height: 120px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.2s;
|
||||||
|
background-color: #007BFF;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
.buttons {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.buttons button {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#result {
|
||||||
|
margin-top: 20px;
|
||||||
|
color: #333;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<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>
|
||||||
|
<select id="existingDocs">
|
||||||
|
<option value="">-- 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>
|
||||||
|
|
||||||
|
<label for="prompt">Dein Prompt:</label>
|
||||||
|
<textarea id="prompt" placeholder="Schreibe hier den Prompt für dein Dokument..."></textarea>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<a href="index.html">
|
||||||
|
<button id="goBackBtn">Abbrechen</button>
|
||||||
|
</a>
|
||||||
|
<button id="generateBtn">Dokument speichern</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="result"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const goBackBtn = document.getElementById("goBackBtn");
|
||||||
|
const generateBtn = document.getElementById("generateBtn");
|
||||||
|
const existingDocs = document.getElementById("existingDocs");
|
||||||
|
const docNameInput = document.getElementById("docName");
|
||||||
|
const promptInput = document.getElementById("prompt");
|
||||||
|
const resultDiv = document.getElementById("result");
|
||||||
|
|
||||||
|
// Zurück zur Haupt-GUI
|
||||||
|
goBackBtn.addEventListener("click", () => {
|
||||||
|
window.electronAPI.goBackToMain();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generiere Dokument
|
||||||
|
generateBtn.addEventListener("click", () => {
|
||||||
|
const prompt = promptInput.value.trim();
|
||||||
|
let docName = docNameInput.value.trim();
|
||||||
|
const selectedExisting = existingDocs.value;
|
||||||
|
|
||||||
|
if (!prompt) {
|
||||||
|
alert("Bitte gib einen Prompt ein!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn ein vorhandenes Dokument ausgewählt wurde, hängt der Prompt daran
|
||||||
|
if (selectedExisting) {
|
||||||
|
docName = selectedExisting; // prompt wird an vorhandenes Dokument angehängt
|
||||||
|
} else if (!docName) {
|
||||||
|
alert("Bitte gib einen Dokumentnamen ein, wenn du ein neues Dokument erstellen möchtest!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demo-Ausgabe im Result-Div
|
||||||
|
resultDiv.innerHTML = `<strong>Dokumentname:</strong> ${docName}<br><strong>Prompt:</strong> ${prompt}`;
|
||||||
|
|
||||||
|
// Hier kannst du den Prompt an dein LLM oder Module-Handler senden
|
||||||
|
// z.B. window.submit.submit({documentName: docName, prompt: prompt})
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+132
-33
@@ -8,27 +8,41 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="mitte" id="mitte">
|
<div id="h1-wrapper">
|
||||||
<div class="labelDiv" id="labelDiv">
|
<section class="p-menu1">
|
||||||
<label id="labelKI">Select ki:</label>
|
<nav id="navbar" class="navigation" role="navigation">
|
||||||
<label id="labelTranscription">Select transcription:</label>
|
<input id="toggle1" type="checkbox" />
|
||||||
<label id="labelLanguage">Select language:</label>
|
<label class="hamburger1" for="toggle1">
|
||||||
<img id="labelLanguageFlag" src="flags/united-kingdom-flag-png-large.jpg" width="20" height="10" >
|
<div class="top"></div>
|
||||||
</div>
|
<div class="meat"></div>
|
||||||
<div class="dropdownMenus" id="dropdownMenus">
|
<div class="bottom"></div>
|
||||||
<select name="ai_type" id="ai_type">
|
</label>
|
||||||
</select>
|
|
||||||
<select name="transkript_type" id="transkript_type">
|
<nav class="menu1">
|
||||||
</select>
|
<a href="custom_document.html" class="li1">Manage document types</a>
|
||||||
<select name="output_type" id="output_type">
|
<a href="" class="li1">Help</a>
|
||||||
<option value="mp4">mp4</option>
|
</nav>
|
||||||
</select>
|
</nav>
|
||||||
<select name="language_option" id="language_option">
|
</section>
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1 id="h1">Video to document</h1>
|
<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">
|
||||||
|
<button id="prevBtn" class="navBtn" disabled>←</button>
|
||||||
|
|
||||||
|
<div class="mitte" id="mitte">
|
||||||
|
|
||||||
|
<div class="step" id="step1">
|
||||||
<div class="upload-container" id="uploadContainer">
|
<div class="upload-container" id="uploadContainer">
|
||||||
<p id="p1">Drag and drop video file</p>
|
<p id="p1">Drag and drop video file</p>
|
||||||
<video id="previewThumbnail" autoplay="false">
|
<video id="previewThumbnail" autoplay="false">
|
||||||
@@ -40,26 +54,111 @@
|
|||||||
<button class="custom-btn" id="manualUploadBtn">Search video</button>
|
<button class="custom-btn" id="manualUploadBtn">Search video</button>
|
||||||
<input type="file" id="videoUpload" accept="video/*">
|
<input type="file" id="videoUpload" accept="video/*">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
<div class="checkbox-container">
|
<div class="step" id="step2" style="display:none;">
|
||||||
<input type="checkbox" name="docFormat" id="docFormatSummary" value="Summary with timestamps">
|
<div class="KI-wrapper">
|
||||||
<label id="label_summary" for="docFormatSummary">Summary with timestamps</label>
|
<label id="labelKI">Select ki:</label>
|
||||||
|
<select name="ai_type" id="ai_type"></select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="submit-btn" id="submitButton" onclick="checkBoxes()" disabled>Submit</button>
|
<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="ai_type" id="ai_type">
|
||||||
|
<option>nichts</option>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<button id ="nextBtn" class="navBtn">→</button>
|
||||||
|
|
||||||
<div class="progressbar" id="progressbar">
|
|
||||||
<div class="progress_fill"></div>
|
|
||||||
<span class="progress_text">0%</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="languages.js"></script>
|
<script src="languages.js"></script>
|
||||||
|
|||||||
+25
-11
@@ -3,25 +3,39 @@ const { contextBridge, ipcRenderer, webUtils } = require('electron')
|
|||||||
try {
|
try {
|
||||||
contextBridge.exposeInMainWorld("explorer", {
|
contextBridge.exposeInMainWorld("explorer", {
|
||||||
onFileDrop: (file) => webUtils.getPathForFile(file)
|
onFileDrop: (file) => webUtils.getPathForFile(file)
|
||||||
})
|
});
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld("submit", {
|
contextBridge.exposeInMainWorld("submit", {
|
||||||
submit: (meeting_specifications) => {ipcRenderer.send("file_submit", meeting_specifications)}
|
submit: (meeting_specifications) => ipcRenderer.send("file_submit", meeting_specifications)
|
||||||
})
|
});
|
||||||
|
|
||||||
|
// ALLE electronAPI Funktionen in EINEM Objekt
|
||||||
contextBridge.exposeInMainWorld("electronAPI", {
|
contextBridge.exposeInMainWorld("electronAPI", {
|
||||||
getFilePath: (file) => {return webUtils.getPathForFile(file)}
|
getFilePath: (file) => webUtils.getPathForFile(file),
|
||||||
})
|
saveSpeakerMapping: (data) => ipcRenderer.send("save-speaker-mapping", data)
|
||||||
|
});
|
||||||
|
|
||||||
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)
|
||||||
|
});
|
||||||
|
|
||||||
ipcRenderer.on("progress", (event, resp) => {
|
contextBridge.exposeInMainWorld("submitSpeaker", {
|
||||||
alert(`Finished step ${resp.curstep} of ${resp.totalsteps}`)
|
speaker_submit: (speaker_names) => ipcRenderer.send("speaker_submit", speaker_names)
|
||||||
})
|
});
|
||||||
|
|
||||||
ipcRenderer.on("error", (event, err) => {alert(err)})
|
contextBridge.exposeInMainWorld("download", {
|
||||||
|
file_download: () => ipcRenderer.send("file_download")
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on("error", (event, err) => { alert(err) });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error in preload.js");
|
console.log("Error in preload.js", error);
|
||||||
}
|
}
|
||||||
+171
-15
@@ -19,7 +19,6 @@ 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{
|
||||||
@@ -28,12 +27,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);
|
||||||
@@ -55,24 +54,16 @@ language_option.addEventListener('change', (e)=>{
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//listener for the file explorer search when something got selected
|
||||||
videoUpload.addEventListener("change", () => {
|
videoUpload.addEventListener("change", () => {
|
||||||
try {
|
try {
|
||||||
activateSubmitBtn(videoUpload.files.length > 0);
|
if (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
|
||||||
@@ -85,3 +76,168 @@ manualUploadBtn.addEventListener('click', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+173
-38
@@ -1,22 +1,22 @@
|
|||||||
|
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 {
|
||||||
const checkboxes = document.querySelectorAll('input[name="docFormat"]');
|
const checkboxes = document.querySelectorAll('input[name="docFormat"]');
|
||||||
let isChecked = false;
|
let isChecked = false;
|
||||||
var checkedCounter = 0;
|
var checkedCounter = 0;
|
||||||
checkboxes.forEach(function(checkbox){
|
checkboxes.forEach(function (checkbox) {
|
||||||
if(checkbox.checked){
|
if (checkbox.checked) {
|
||||||
isChecked = true;
|
isChecked = true;
|
||||||
checkedCounter++;
|
checkedCounter++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(isChecked){
|
if (isChecked) {
|
||||||
//Code to submit the video
|
//Code to submit the video
|
||||||
var selectedCheckboxes = {};
|
var selectedCheckboxes = {};
|
||||||
checkboxes.forEach(function(checkbox){
|
checkboxes.forEach(function (checkbox) {
|
||||||
if(checkbox.checked){
|
if (checkbox.checked) {
|
||||||
selectedCheckboxes[checkbox.nextElementSibling.textContent] = "";
|
selectedCheckboxes[checkbox.nextElementSibling.textContent] = "";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -24,41 +24,45 @@ function checkBoxes() {
|
|||||||
const testEndings = [".mp4", ".mov", ".avi", ".mkv"];
|
const testEndings = [".mp4", ".mov", ".avi", ".mkv"];
|
||||||
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
|
||||||
|
|
||||||
const selectedStyles = [checkedCounter];
|
var typeCheckbox;
|
||||||
var iter = 0;
|
if (document.getElementById("docFormat").checked) typeCheckbox = document.getElementById("docFormat").value;
|
||||||
checkboxes.forEach(function(checkbox){
|
if (document.getElementById("docFormatSummary1").checked) typeCheckbox = document.getElementById("docFormatSummary1").value;
|
||||||
if(checkbox.checked){
|
if (document.getElementById("docFormatSummary2").checked) typeCheckbox = document.getElementById("docFormatSummary2").value;
|
||||||
console.log(checkbox.value);
|
if (document.getElementById("docFormatSummary3").checked) typeCheckbox = document.getElementById("docFormatSummary3").value;
|
||||||
selectedStyles[iter] = {iter: checkbox.value};
|
if (document.getElementById("docFormatCustom").checked) typeCheckbox = document.getElementById("docFormatCustom").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");
|
||||||
const aiType = document.getElementById("ai_type");
|
const aiType = document.getElementById("ai_type");
|
||||||
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,
|
||||||
"styles": selectedStyles
|
"type": typeCheckbox,
|
||||||
|
"outputType": outputType.value
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.submit.submit(sendingPackage)
|
window.submit.submit(sendingPackage)
|
||||||
}else{
|
} else {
|
||||||
alert('The given file is not compatible. These are the available types: [".mp4", ".mov", ".avi", ".mkv"].');
|
alert('The given file is not compatible. These are the available types: [".mp4", ".mov", ".avi", ".mkv"].');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
//language only english at the moment
|
//language only english at the moment
|
||||||
alert('Please select at least one document type.');
|
alert('Please select at least one document type.');
|
||||||
@@ -103,6 +107,7 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -115,7 +120,7 @@ function handleFiles(files) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//function to regulate the progress on the progressbar
|
//function to regulate the progress on the progressbar
|
||||||
function updateProgressBar(bar, value){
|
function updateProgressBar(bar, value) {
|
||||||
try {
|
try {
|
||||||
value = Math.round(value);
|
value = Math.round(value);
|
||||||
bar.querySelector(".progress_fill").style.width = `${value}%`;
|
bar.querySelector(".progress_fill").style.width = `${value}%`;
|
||||||
@@ -128,13 +133,13 @@ function updateProgressBar(bar, value){
|
|||||||
}
|
}
|
||||||
|
|
||||||
//function to load ai options to the drop down list
|
//function to load ai options to the drop down list
|
||||||
function loadAiOptions(options){
|
function loadAiOptions(options) {
|
||||||
try {
|
try {
|
||||||
var menu = document.getElementById('ai_type');
|
var menu = document.getElementById('ai_type');
|
||||||
var object_holdy;
|
var object_holdy;
|
||||||
var choice ;
|
var choice;
|
||||||
object_holdy = options
|
object_holdy = options
|
||||||
for(i = 0; i < options.length; i++){
|
for (i = 0; i < options.length; i++) {
|
||||||
choice = document.createElement('option');
|
choice = document.createElement('option');
|
||||||
choice.textContent = object_holdy[i].displayname;
|
choice.textContent = object_holdy[i].displayname;
|
||||||
choice.value = object_holdy[i].name;
|
choice.value = object_holdy[i].name;
|
||||||
@@ -147,13 +152,13 @@ function loadAiOptions(options){
|
|||||||
}
|
}
|
||||||
|
|
||||||
//function to load transcription options to the drop down list
|
//function to load transcription options to the drop down list
|
||||||
function loadTranscriptionOptions(options){
|
function loadTranscriptionOptions(options) {
|
||||||
try {
|
try {
|
||||||
var menu = document.getElementById('transkript_type');
|
var menu = document.getElementById('transkript_type');
|
||||||
var object_holdy;
|
var object_holdy;
|
||||||
var choice ;
|
var choice;
|
||||||
object_holdy = options
|
object_holdy = options
|
||||||
for(i = 0; i < options.length; i++){
|
for (i = 0; i < options.length; i++) {
|
||||||
choice = document.createElement('option');
|
choice = document.createElement('option');
|
||||||
choice.textContent = object_holdy[i].displayname;
|
choice.textContent = object_holdy[i].displayname;
|
||||||
choice.value = object_holdy[i].name;
|
choice.value = object_holdy[i].name;
|
||||||
@@ -165,35 +170,91 @@ 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 {
|
||||||
var menu = document.getElementById('language_option');
|
var menu = document.getElementById('language_option');
|
||||||
var object_holdy;
|
var object_holdy;
|
||||||
var choice ;
|
var choice;
|
||||||
object_holdy = Object.keys(languageOptions);
|
object_holdy = Object.keys(languageOptions);
|
||||||
for(i = 0; i < object_holdy.length; i++){
|
for (i = 0; i < object_holdy.length; i++) {
|
||||||
choice = document.createElement('option');
|
choice = document.createElement('option');
|
||||||
choice.textContent = object_holdy[i];
|
choice.textContent = object_holdy[i];
|
||||||
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 activateSubmitBtn(hasFile){
|
//function to load speaker options to the drop down list
|
||||||
|
function loadSpeakerOptions(options) {
|
||||||
try {
|
try {
|
||||||
submitButton.disabled = !hasFile;
|
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) {
|
||||||
|
try {
|
||||||
|
document.getElementById("submitButton").disabled = !hasFile;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deaktivateProgressbar(){
|
function deaktivateProgressbar() {
|
||||||
try {
|
try {
|
||||||
document.getElementById("progressbar").style.visibility = "hidden";
|
document.getElementById("progressbar").style.visibility = "hidden";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -202,7 +263,7 @@ function deaktivateProgressbar(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Video thumbnail generation
|
//Video thumbnail generation
|
||||||
function generateThumbnail(path){
|
function generateThumbnail(path) {
|
||||||
const videoElement = document.getElementById("previewThumbnail");
|
const videoElement = document.getElementById("previewThumbnail");
|
||||||
while (videoElement.firstChild) videoElement.removeChild(videoElement.firstChild);
|
while (videoElement.firstChild) videoElement.removeChild(videoElement.firstChild);
|
||||||
videoElement.src = path;
|
videoElement.src = path;
|
||||||
@@ -212,3 +273,77 @@ 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 {
|
||||||
|
const oldKey = document.getElementById("cur_speaker").value;
|
||||||
|
const newName = document.getElementById("newSpeaker").value;
|
||||||
|
|
||||||
|
speakerAudios[oldKey].name = newName;
|
||||||
|
loadSpeakerOptions(speakerAudios);
|
||||||
|
|
||||||
|
// IPC-Aufruf an Electron main process
|
||||||
|
window.electronAPI.saveSpeakerMapping({
|
||||||
|
speakerId: oldKey,
|
||||||
|
speakerName: newName
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendSpeakerPackages() {
|
||||||
|
try {
|
||||||
|
window.submitSpeaker.speaker_submit(speakerAudios);
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileDownload() {
|
||||||
|
try {
|
||||||
|
window.download.file_download();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Download failed:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+350
-25
@@ -6,10 +6,31 @@ body {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background-color: #f2f3f4;
|
background-color: #f2f3f4;
|
||||||
gap: 15px;
|
gap: 10px;
|
||||||
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;
|
||||||
@@ -87,30 +108,85 @@ body {
|
|||||||
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;
|
||||||
align-items: center;
|
justify-items: left;
|
||||||
gap: 5px;
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-group {
|
|
||||||
margin-top: 15px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-btn {
|
.submit-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
margin-top: 10px;
|
margin: 130px auto 10px auto;
|
||||||
margin-bottom: 10px;
|
|
||||||
background-color: #007BFF;
|
background-color: #007BFF;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -126,13 +202,12 @@ gap: 5px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mitte {
|
.mitte {
|
||||||
background-color: #FDFCFA;
|
background-color: #FFF;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 700px;
|
width: 700px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 5% 50px;
|
padding: 40px;
|
||||||
margin-top: 20px;
|
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
border: 0px;
|
border: 0px;
|
||||||
border-color: black;
|
border-color: black;
|
||||||
@@ -141,14 +216,11 @@ gap: 5px;
|
|||||||
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;
|
||||||
@@ -173,9 +245,10 @@ h1 {
|
|||||||
|
|
||||||
.dropdownMenus {
|
.dropdownMenus {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: center;
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
gap: 150px;
|
margin-bottom: 30px;
|
||||||
|
gap: 170px;
|
||||||
padding: 2px 10px 2px 10px;
|
padding: 2px 10px 2px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,5 +257,257 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.labelDiv {
|
.labelDiv {
|
||||||
gap: 200px;
|
gap: 60px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
overflow-wrap:inherit;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
margin-top: 40px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Step bar*/
|
||||||
|
.step-nav {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
justify-content: center;
|
||||||
|
background: #fff;
|
||||||
|
padding: 10px 30px;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-item {
|
||||||
|
padding: 10px 25px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #eee;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-item.active {
|
||||||
|
background: #007BFF;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-item:hover {
|
||||||
|
background: #d9d9d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*panels*/
|
||||||
|
.step {
|
||||||
|
margin-top: 70px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
@@ -35,6 +35,8 @@ console.log("-------------------------------------------------------------------
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// --------------------------------------------------------- CLI COMMANDS --------------------------------------------------------- //
|
// --------------------------------------------------------- CLI COMMANDS --------------------------------------------------------- //
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
@@ -59,7 +61,7 @@ let mainWindow;
|
|||||||
|
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
mainWindow = new electron.BrowserWindow({
|
mainWindow = new electron.BrowserWindow({
|
||||||
width: 800,
|
width: 1200,
|
||||||
height: 800,
|
height: 800,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
@@ -101,7 +103,23 @@ electron.ipcMain.handle('get-module-names', async () => {
|
|||||||
return module_array
|
return module_array
|
||||||
});
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("save-speaker-mapping", (event, data) => {
|
||||||
|
|
||||||
|
const filePath = "/Users/mikehughes/PROJ/video2document/storage/speakerMapping/speakerMapping.json";
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
speakerId: data.speakerId,
|
||||||
|
speakerName: data.speakerName,
|
||||||
|
updated: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
|
||||||
|
console.log("Speaker mapping saved!");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to save speaker mapping", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
// electron.ipcMain.on("get_modules", async (event, args) => {
|
// electron.ipcMain.on("get_modules", async (event, args) => {
|
||||||
// let module_array = {
|
// let module_array = {
|
||||||
// "ai_modules":[],
|
// "ai_modules":[],
|
||||||
@@ -122,66 +140,113 @@ 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 = 2 + args.document.styles.length
|
let totalsteps = 4
|
||||||
|
|
||||||
if(args.document.styles.length == 0)
|
const TEMPLATE_MAP = {
|
||||||
throw new Error("At least one Document Style needed");
|
"followup-report": "followup_report.txt",
|
||||||
|
"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 = ""
|
||||||
let transcriptpath = ""
|
let transcriptpath = ""
|
||||||
|
|
||||||
|
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: args.video.outputType}).then(resp => {
|
await mapFunctions.get("module-handler").function(args.video.module, {inputVideoPath: args.video.inputVideoPath, outputType: mapFunctions.get(args.transcription.module).audioformat}).then(resp => {
|
||||||
// console.log(resp);
|
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
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
console.log("\n\n Running the Audio to Transcription module");
|
||||||
// TODO implement transcription module
|
// TODO implement transcription module
|
||||||
// // This code handles the Audio to Text transcription module call
|
// This code handles the Audio to Text transcription module call
|
||||||
// await mapFunctions.get("module-handler").function(args.transcription.module, audiopath).then(resp => {
|
await mapFunctions.get("module-handler").function(args.transcription.module, audiopath).then(resp => {
|
||||||
// console.log(resp);
|
console.log(resp);
|
||||||
// transcriptpath = resp
|
transcriptpath = 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)
|
||||||
// return
|
console.log(err);
|
||||||
// })
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
console.log("\n\n Running the Transcription Summarizer module");
|
||||||
|
// This code summarises the transcript, so that it can be used by an llm
|
||||||
|
// await mapFunctions.get("summarize-transcription").function('A:\\programing\\@projects\\video2document\\storage\\transcripts\\IMG_2978.json').then(resp => {
|
||||||
|
await mapFunctions.get("summarize-transcription2").function(transcriptpath).then(resp => {
|
||||||
|
console.log(resp);
|
||||||
|
transcriptpath = resp
|
||||||
|
curstep++
|
||||||
|
mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
|
||||||
|
}).catch(err => {
|
||||||
|
mainWindow.webContents.send("error", err)
|
||||||
|
console.log(err);
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
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++) {
|
|
||||||
// await mapFunctions.get("module-handler").function(args.document.module, {prompt: args.document.styles[i].prompt, transcript: transcriptpath}).then(resp => {
|
console.log(`\n\n Running the LLM for Document Style ${args.document.type}`);
|
||||||
// console.log(resp);
|
|
||||||
// transcriptpath = resp
|
await mapFunctions.get("module-handler").function(args.document.module, { inputTranscriptPath: transcriptpath, documentTypePath: "./storage/documentType/" + templateFile, language: "en" }).then(resp => {
|
||||||
// curstep++
|
console.log(resp);
|
||||||
// mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
|
globalFinalHtmlPath = resp
|
||||||
// }).catch(err => {
|
curstep++
|
||||||
// mainWindow.webContents.send("error", err)
|
mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
|
||||||
// return
|
}).catch(err => {
|
||||||
// })
|
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 =
|
||||||
{
|
{
|
||||||
|
|||||||
Generated
+2017
-48
File diff suppressed because it is too large
Load Diff
+7
-4
@@ -2,12 +2,15 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google/genai": "^1.30.0",
|
"@google/genai": "^1.30.0",
|
||||||
"@types/axios": "^0.9.36",
|
"@types/axios": "^0.9.36",
|
||||||
"cli-progress": "^3.12.0",
|
"axios": "^1.13.2",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"electron": "^39.1.1",
|
"electron": "^39.1.1",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"ffmpeg-static": "^5.2.0",
|
"ffmpeg-static": "^5.3.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",
|
||||||
@@ -26,7 +29,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron main.js",
|
"start": "electron main.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "mocha ./test/unit/test.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
fastapi
|
|
||||||
uvicorn
|
|
||||||
pytest
|
|
||||||
+8
-2
@@ -6,14 +6,20 @@ platform = process.platform
|
|||||||
mainDir = __dirname
|
mainDir = __dirname
|
||||||
fs = require("fs")
|
fs = require("fs")
|
||||||
readline = require("readline")
|
readline = require("readline")
|
||||||
|
|
||||||
|
puppeteer = require("puppeteer")
|
||||||
|
htmltodocx = require("html-to-docx")
|
||||||
config = require("./config/config")
|
config = require("./config/config")
|
||||||
|
|
||||||
ffmpegPath = require('ffmpeg-static');
|
ffmpegPath = require('ffmpeg-static');
|
||||||
ffmpeg = require('fluent-ffmpeg');
|
ffmpeg = require('fluent-ffmpeg');
|
||||||
path = require('path');
|
path = require('path');
|
||||||
cliProgress = require('cli-progress');
|
// cliProgress = require('cli-progress');
|
||||||
|
|
||||||
// { app, BrowserWindow, ipcMain, dialog } = require('electron');
|
// { app, BrowserWindow, ipcMain, dialog } = require('electron');
|
||||||
|
|
||||||
electron = require('electron');
|
electron = require('electron');
|
||||||
genai = require("@google/genai");
|
|
||||||
|
axios = require("axios")
|
||||||
|
|
||||||
|
console.log(require('dotenv').config({path: __dirname + '/.env'}));
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
//node show-models.js, remember to set SAIA_API_KEY in your environment before running the script
|
||||||
|
|
||||||
|
const SAIA_API_KEY = process.env.SAIA_API_KEY;
|
||||||
|
const SAIA_MODELS_URL = "https://chat-ai.academiccloud.de/v1/models";
|
||||||
|
|
||||||
|
// Script to list available models
|
||||||
|
(async () => {
|
||||||
|
if (!SAIA_API_KEY) {
|
||||||
|
console.error("ERROR: SAIA_API_KEY environment variable is not set!");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Fetching available models from SAIA...\n");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(SAIA_MODELS_URL, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Bearer ${SAIA_API_KEY}`,
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const text = await response.text();
|
||||||
|
throw new Error(`SAIA API error (${response.status}): ${text}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
console.log("Available models:");
|
||||||
|
console.log(JSON.stringify(data, null, 2));
|
||||||
|
|
||||||
|
if (data.data && Array.isArray(data.data)) {
|
||||||
|
console.log("\n\nModel IDs:");
|
||||||
|
data.data.forEach(model => {
|
||||||
|
console.log(`- ${model.id}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching models:", error);
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
|
||||||
|
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");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
@@ -24,17 +24,11 @@ module.exports = {
|
|||||||
outputType: String // Audio file output format
|
outputType: String // Audio file output format
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let progressBar = new cliProgress.SingleBar({
|
|
||||||
format: 'Processing |{bar}| {percentage}% | {timemark}',
|
|
||||||
barCompleteChar: '\u2588',
|
|
||||||
barIncompleteChar: '\u2591',
|
|
||||||
hideCursor: true
|
|
||||||
});
|
|
||||||
try {
|
try {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.extractAudioFromVideo(parameter.inputVideoPath, progressBar, parameter.outputType)
|
this.extractAudioFromVideo(parameter.inputVideoPath, parameter.outputType)
|
||||||
.then((resp) => resolve(resp))
|
.then((resp) => resolve(resp))
|
||||||
.catch((err) => console.error(err));
|
.catch((err) => {reject(err)});
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(parameter.outputType);
|
console.log(parameter.outputType);
|
||||||
@@ -52,7 +46,7 @@ module.exports = {
|
|||||||
* - Shows CLI progress bar
|
* - Shows CLI progress bar
|
||||||
* - Handles errors gracefully (without errors)
|
* - Handles errors gracefully (without errors)
|
||||||
*/
|
*/
|
||||||
extractAudioFromVideo: async function (videoFilePath, progressBar, outputType){
|
extractAudioFromVideo: async function (videoFilePath, outputType){
|
||||||
let inputVideoName = path.basename(videoFilePath, path.extname(videoFilePath));
|
let inputVideoName = path.basename(videoFilePath, path.extname(videoFilePath));
|
||||||
let outputAudioPath = path.join(outputDir, `${inputVideoName}.${outputType}`);
|
let outputAudioPath = path.join(outputDir, `${inputVideoName}.${outputType}`);
|
||||||
|
|
||||||
@@ -63,28 +57,17 @@ module.exports = {
|
|||||||
// .audioCodec('pcm_s16le')
|
// .audioCodec('pcm_s16le')
|
||||||
.audioChannels(1)
|
.audioChannels(1)
|
||||||
.audioFrequency(16000)
|
.audioFrequency(16000)
|
||||||
// .setFfmpegPath("./ffmpeg.exe")
|
|
||||||
.on('progress', (progress) => {
|
|
||||||
if (!progressBar.isActive) progressBar.start(100, 0, { timemark: '00:00:00' });
|
|
||||||
if (progress.percent) {
|
|
||||||
progressBar.update(progress.percent, { timemark: progress.timemark });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on('end', () => {
|
.on('end', () => {
|
||||||
progressBar.update(100, { timemark: 'done' });
|
|
||||||
progressBar.stop();
|
|
||||||
console.log(`Extraction completed: ${outputAudioPath}`);
|
|
||||||
resolve(outputAudioPath);
|
resolve(outputAudioPath);
|
||||||
})
|
})
|
||||||
.on('error', (err) => {
|
.on('error', (err) => {
|
||||||
progressBar.stop();
|
// console.error(`failed_audio_extraction on type ${outputType}: ${err.message}`);
|
||||||
console.error(`failed_audio_extraction on type ${outputType}: ${err.message}`);
|
|
||||||
reject(err);
|
reject(err);
|
||||||
})
|
})
|
||||||
.save(outputAudioPath);
|
.save(outputAudioPath);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log();
|
// console.log(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
|
||||||
|
// Prepare output directory (always storage/transcriptionSummaries under project root)
|
||||||
|
const outputDir = `${__dirname}/../../../storage/transcriptionSummaries`;
|
||||||
|
if (!fs.existsSync(outputDir)) {
|
||||||
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
//Speaker, ALL-Sentences, Start, End
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: "summarize-transcription", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
|
||||||
|
type: "summarizer", // value used to differentiate each module to order them in the UI
|
||||||
|
displayname: "Summarizer", // The displayname used within the UI
|
||||||
|
async function(args) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
let inputJson = args.json;
|
||||||
|
|
||||||
|
//JSON Path
|
||||||
|
if (args.jsonPath) {
|
||||||
|
try {
|
||||||
|
const raw = fs.readFileSync(args.jsonPath, "utf-8");
|
||||||
|
inputJson = JSON.parse(raw);
|
||||||
|
} catch (e) {
|
||||||
|
// console.error("Failed to load JSON from file:", e);
|
||||||
|
reject(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// JSON parsen
|
||||||
|
if (typeof args === "string") {
|
||||||
|
try {
|
||||||
|
await new Promise((res, rej) => {
|
||||||
|
fs.readFile(args, 'utf8', function (err, data) {
|
||||||
|
if (err){
|
||||||
|
rej(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
inputJson = JSON.parse(data);
|
||||||
|
res()
|
||||||
|
});
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
// console.log("Invalid JSON in summarize-transcription");
|
||||||
|
// console.log(e)
|
||||||
|
reject(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const words = inputJson.words;
|
||||||
|
if (!Array.isArray(words)) {
|
||||||
|
reject("No words Array found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const ENDINGS = [".", "!", "?"]; // '...' auch als Satzende ?
|
||||||
|
const ABBREVIATIONS = new Set(["z.B.", "bzw.", "u.a.", "Dr.", "Mr.", "Mrs.", "Prof.", "etc."]); //TODO weitere Ergaenzen
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
let currentSentence = "";
|
||||||
|
let currentSpeaker = null;
|
||||||
|
let startTime = null;
|
||||||
|
let endTime = null;
|
||||||
|
|
||||||
|
for (const w of words) {
|
||||||
|
if (!currentSpeaker) currentSpeaker = w.speaker;
|
||||||
|
if (startTime === null) startTime = w.start;
|
||||||
|
endTime = w.end;
|
||||||
|
|
||||||
|
//speaker changing
|
||||||
|
if (currentSpeaker !== w.speaker && currentSentence) {
|
||||||
|
const lastEntry = result[result.length - 1];
|
||||||
|
if (lastEntry && lastEntry.speaker === currentSpeaker) {
|
||||||
|
lastEntry.sentence += " " + currentSentence;
|
||||||
|
lastEntry.end = endTime;
|
||||||
|
} else {
|
||||||
|
result.push({
|
||||||
|
speaker: currentSpeaker,
|
||||||
|
sentence: currentSentence,
|
||||||
|
start: startTime,
|
||||||
|
end: endTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
currentSentence = "";
|
||||||
|
startTime = w.start;
|
||||||
|
}
|
||||||
|
currentSpeaker = w.speaker;
|
||||||
|
currentSentence += (currentSentence ? " " : "") + w.text; //sentence beginning or not
|
||||||
|
const lastWord = w.text.trim();
|
||||||
|
const lastChar = lastWord.slice(-1);
|
||||||
|
const isAbbreviation = ABBREVIATIONS.has(lastWord);
|
||||||
|
|
||||||
|
//sentence ending
|
||||||
|
if (ENDINGS.includes(lastChar) && !isAbbreviation) {
|
||||||
|
const lastEntry = result[result.length - 1];
|
||||||
|
if (lastEntry && lastEntry.speaker === currentSpeaker) {
|
||||||
|
lastEntry.sentence += " " + currentSentence;
|
||||||
|
lastEntry.end = endTime;
|
||||||
|
} else {
|
||||||
|
result.push({
|
||||||
|
speaker: currentSpeaker,
|
||||||
|
sentence: currentSentence,
|
||||||
|
start: startTime,
|
||||||
|
end: endTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
currentSentence = "";
|
||||||
|
startTime = null;
|
||||||
|
endTime = null;
|
||||||
|
currentSpeaker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// safe last sentence
|
||||||
|
if (currentSentence) {
|
||||||
|
const lastEntry = result[result.length - 1];
|
||||||
|
if (lastEntry && lastEntry.speaker === currentSpeaker) {
|
||||||
|
lastEntry.sentence += " " + currentSentence;
|
||||||
|
lastEntry.end = endTime;
|
||||||
|
} else {
|
||||||
|
result.push({
|
||||||
|
speaker: currentSpeaker,
|
||||||
|
sentence: currentSentence,
|
||||||
|
start: startTime,
|
||||||
|
end: endTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output as Text
|
||||||
|
const output = result.map(r =>
|
||||||
|
`Sprecher ${r.speaker} [${r.start.toFixed(2)} - ${r.end.toFixed(2)}]: ${r.sentence}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Output on cosole
|
||||||
|
//console.log("\n------------\nMerged Transcription Result:\n", output, "\n------------\n");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const jsonPath = path.join(outputDir, "transcription_result.json");
|
||||||
|
fs.writeFileSync(jsonPath, JSON.stringify(result, null, 2), "utf-8");
|
||||||
|
|
||||||
|
const txtPath = path.join(outputDir, "transcription_result.txt");
|
||||||
|
fs.writeFileSync(txtPath, output.join("\n"), "utf-8");
|
||||||
|
|
||||||
|
// console.log(`Summary successfully saved:\n- ${jsonPath}\n- ${txtPath}`);
|
||||||
|
|
||||||
|
resolve(jsonPath);
|
||||||
|
} catch (err) {
|
||||||
|
// console.error("Error saving Summary:", err);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
|
||||||
|
// Prepare output directory (always storage/transcriptionSummaries under project root)
|
||||||
|
const outputDir = `${__dirname}/../../../storage/transcriptionSummaries`;
|
||||||
|
if (!fs.existsSync(outputDir)) {
|
||||||
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSessionId(inputPath) {
|
||||||
|
try {
|
||||||
|
const parsed = new URL(inputPath);
|
||||||
|
const base = path.basename(parsed.pathname);
|
||||||
|
return base.replace(/\.[^.]+$/, '');
|
||||||
|
} catch {
|
||||||
|
return path.basename(inputPath, path.extname(inputPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Speaker, Sentence, Start, End
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: "summarize-transcription2", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
|
||||||
|
type: "summarizer", // value used to differentiate each module to order them in the UI
|
||||||
|
displayname: "Summarizer", // The displayname used within the UI
|
||||||
|
async function(args) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
let inputJson = args.json;
|
||||||
|
|
||||||
|
//JSON Path
|
||||||
|
if (args.jsonPath) {
|
||||||
|
try {
|
||||||
|
const raw = fs.readFileSync(args.jsonPath, "utf-8");
|
||||||
|
inputJson = JSON.parse(raw);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to load JSON from file:", e);
|
||||||
|
reject("Could not read JSON from file path.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// JSON parsen
|
||||||
|
if (typeof args === "string") {
|
||||||
|
try {
|
||||||
|
await new Promise((res, rej) => {
|
||||||
|
fs.readFile(args, 'utf8', function (err, data) {
|
||||||
|
if (err){
|
||||||
|
rej(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
inputJson = JSON.parse(data);
|
||||||
|
res()
|
||||||
|
});
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
// console.log("Invalid JSON in summarize-transcription");
|
||||||
|
// console.log(e)
|
||||||
|
reject(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const words = inputJson.words;
|
||||||
|
if (!Array.isArray(words)) {
|
||||||
|
reject("No words Array found")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ENDINGS = [".", "!", "?"]; // '...' auch als Satzende ?
|
||||||
|
const ABBREVIATIONS = new Set(["z.B.", "bzw.", "u.a.", "Dr.", "Mr.", "Mrs.", "Prof.", "etc."]); //TODO weitere Ergaenzen
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
let currentSentence = "";
|
||||||
|
let currentSpeaker = null;
|
||||||
|
let startTime = null;
|
||||||
|
let endTime = null;
|
||||||
|
|
||||||
|
for (const w of words) {
|
||||||
|
if (!currentSpeaker) currentSpeaker = w.speaker;
|
||||||
|
if (startTime === null) startTime = w.start;
|
||||||
|
endTime = w.end;
|
||||||
|
|
||||||
|
//speaker changing
|
||||||
|
if (currentSpeaker !== w.speaker && currentSentence) {
|
||||||
|
result.push({
|
||||||
|
speaker: currentSpeaker,
|
||||||
|
sentence: currentSentence,
|
||||||
|
start: startTime,
|
||||||
|
end: endTime
|
||||||
|
});
|
||||||
|
currentSentence = "";
|
||||||
|
startTime = w.start;
|
||||||
|
}
|
||||||
|
currentSpeaker = w.speaker;
|
||||||
|
currentSentence += (currentSentence ? " " : "") + w.text; //sentence beginning or not
|
||||||
|
const lastWord = w.text.trim();
|
||||||
|
const lastChar = lastWord.slice(-1);
|
||||||
|
const isAbbreviation = ABBREVIATIONS.has(lastWord);
|
||||||
|
|
||||||
|
//sentence ending
|
||||||
|
if (ENDINGS.includes(lastChar) && !isAbbreviation) {
|
||||||
|
result.push({
|
||||||
|
speaker: currentSpeaker,
|
||||||
|
sentence: currentSentence,
|
||||||
|
start: startTime,
|
||||||
|
end: endTime
|
||||||
|
});
|
||||||
|
currentSentence = "";
|
||||||
|
startTime = null;
|
||||||
|
endTime = null;
|
||||||
|
currentSpeaker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// safe last sentence
|
||||||
|
if (currentSentence) {
|
||||||
|
result.push({
|
||||||
|
speaker: currentSpeaker,
|
||||||
|
sentence: currentSentence,
|
||||||
|
start: startTime,
|
||||||
|
end: endTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Output as Text
|
||||||
|
const output = result.map(r =>
|
||||||
|
`Sprecher ${r.speaker} [${r.start.toFixed(2)} - ${r.end.toFixed(2)}]: ${r.sentence}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Output on cosole
|
||||||
|
//console.log("\n------------\nMerged Transcription Result:\n", output, "\n------------\n");
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
let filename = getSessionId(args);
|
||||||
|
|
||||||
|
const jsonPath = path.join(outputDir, `${filename}-${new Date().getTime()}.json`);
|
||||||
|
fs.writeFileSync(jsonPath, JSON.stringify(result, null, 2), "utf-8");
|
||||||
|
|
||||||
|
const txtPath = path.join(outputDir, `${filename}-${new Date().getTime()}.txt`);
|
||||||
|
fs.writeFileSync(txtPath, output.join("\n"), "utf-8");
|
||||||
|
|
||||||
|
// console.log(`Summary successfully saved:\n- ${jsonPath}\n- ${txtPath}`);
|
||||||
|
resolve(jsonPath);
|
||||||
|
} catch (err) {
|
||||||
|
// console.error("Error saving Summary:", err);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,137 @@
|
|||||||
module.exports = {
|
// const fs = require('fs');
|
||||||
name:"chatgpt", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
|
// const path = require('path');
|
||||||
type:"llm", // value used to differentiate each module to order them in the UI
|
|
||||||
displayname:"ChatGPT", // The displayname used within the UI
|
const outputDir = path.join(__dirname, "../../../storage/documents"); // path for output directory
|
||||||
async function(parameter){
|
|
||||||
// TODO add code to actually send the transcript to ChatGPT and get a response back
|
if (!fs.existsSync(outputDir)) {
|
||||||
}
|
fs.mkdirSync(outputDir, { recursive: true }); // Create output directory if it doesn't exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure SAIA API key is set in environment variables: export SAIA_API_KEY="your_api_key_here"
|
||||||
|
const SAIA_API_KEY = process.env.SAIA_API_KEY; // Ensure SAIA API key is set in environment variables
|
||||||
|
const SAIA_URL = "https://chat-ai.academiccloud.de/v1/chat/completions"; // URL for the REST call, used model and action
|
||||||
|
|
||||||
|
const module_exports = {
|
||||||
|
name: "llm-saia_openai_gpt",
|
||||||
|
type: "llm",
|
||||||
|
displayname: "GPT 120B",
|
||||||
|
description: "Generates documents using OpenAI GPT OSS 120B via SAIA platform",
|
||||||
|
|
||||||
|
async function(parameter) {
|
||||||
|
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!");
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
@@ -1,55 +1,134 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
const outputDir = path.join(__dirname, "../../../storage/documents"); // path for output directory
|
const outputDir = path.join(__dirname, "../../../storage/documents"); // path for output directory
|
||||||
|
|
||||||
if (!fs.existsSync(outputDir)) {
|
if (!fs.existsSync(outputDir)) {
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
const ai = new genai.GoogleGenAI({
|
// Ensure Google API key is set in environment variables: export GOOGLE_API_KEY="your_api_key_here"
|
||||||
apiKey: process.env.GOOGLE_API_KEY // Ensure Google API key is set in environment variables: export GOOGLE_API_KEY="your_api_key_here"
|
const GEMINI_API_KEY = process.env.GOOGLE_API_KEY; // Ensure Google API key is set in environment variables: export GOOGLE_API_KEY="your_api_key_here"
|
||||||
});
|
const GEMINI_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent"; // URL for the REST call, used model and action
|
||||||
|
|
||||||
module.exports = {
|
const module_exports = {
|
||||||
name: "llm-gemini",
|
name: "llm-gemini",
|
||||||
type: "llm",
|
type: "llm",
|
||||||
displayname: "Gemini LLM",
|
displayname: "Gemini LLM",
|
||||||
description: "Generates documents using Google Gemini LLM",
|
description: "Generates documents using Google Gemini LLM",
|
||||||
|
|
||||||
async function(parameter) {
|
async function(parameter) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
console.log("Gemini LLM module invoked with parameters:", parameter);
|
// console.log("Gemini LLM module invoked with parameters:", parameter);
|
||||||
|
|
||||||
await this.createDocumentFromTranscript( //Call the function to create document with transcript, document type and language
|
resolve(await this.createDocumentFromTranscript( //Call the function to create document with transcript, document type and language
|
||||||
parameter.inputTranscriptPath, // Path to input transcript file
|
parameter.inputTranscriptPath, // Path to input transcript file
|
||||||
parameter.documentTypePath, // Path to document type file which is chosen in the front end by the user
|
parameter.documentTypePath, // Path to document type file which is chosen in the front end by the user
|
||||||
parameter.language // Language for the document which is chosen in the front end by the user
|
parameter.language // Language for the document which is chosen in the front end by the user
|
||||||
);
|
));
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error in Gemini LLM module:", error);
|
// console.error("Error in Gemini LLM module:", error);
|
||||||
|
reject(error)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createDocumentFromTranscript: async function(transcriptPath, documentTypePath, language = "en") { // default language is English
|
createDocumentFromTranscript: async function(transcriptPath, documentTypePath, language = "en") { // default language is English
|
||||||
|
return new Promise(async(resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const transcript = await fs.promises.readFile(transcriptPath, "utf-8");
|
const transcript = await fs.promises.readFile(transcriptPath, "utf-8"); //read transcript file from Path
|
||||||
const documentType = await fs.promises.readFile(documentTypePath, "utf-8");
|
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}`;
|
// --- REST CALL ---
|
||||||
|
const response = await fetch(`${GEMINI_URL}?key=${GEMINI_API_KEY}`, { //safe model response in variable
|
||||||
const response = await ai.models.generateContent({
|
method: "POST",
|
||||||
model: "gemini-2.5-flash", // Specify the Gemini model to use
|
headers: {
|
||||||
contents: promptText // Input prompt for content generation
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
parts: [
|
||||||
|
{ text: promptText } // Input prompt for content generation
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const output = response.text || ""; // Get generated text from response or default to empty string (if null)
|
if (!response.ok) { //ok is true when a responce was successfull
|
||||||
|
const text = await response.text();
|
||||||
|
throw new Error(`Gemini API error (${response.status}): ${text}`);
|
||||||
|
}
|
||||||
|
|
||||||
const outPath = path.join(outputDir, "test.md"); // Output file path & name TO BE DONE to make dynamic out of input transcript name
|
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
|
fs.writeFileSync(outPath, output, "utf8"); // Write output to file
|
||||||
|
|
||||||
console.log("Generated document written to:", outPath);
|
// console.log("Generated document written to:", outPath);
|
||||||
|
resolve(outPath)
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error generating Gemini content:", error);
|
// console.error("Error generating Gemini content:", error);
|
||||||
|
reject(error)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports = module_exports;
|
||||||
|
|
||||||
|
// CLI Mode: Allow direct execution
|
||||||
|
if (require.main === module) {
|
||||||
|
(async () => {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (args.length < 2) {
|
||||||
|
console.error("Usage: node llm-gemini.js <transcriptPath> <documentTypePath> [language]");
|
||||||
|
console.error("Example: node llm-gemini.js ./transcript.json ./docType.txt de");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [transcriptPath, documentTypePath, language] = args;
|
||||||
|
|
||||||
|
// Check if API key is set
|
||||||
|
if (!GEMINI_API_KEY) {
|
||||||
|
console.error("ERROR: GOOGLE_API_KEY environment variable is not set!");
|
||||||
|
console.error("Please set it with: export GOOGLE_API_KEY='your_api_key_here'");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if files exist
|
||||||
|
if (!fs.existsSync(transcriptPath)) {
|
||||||
|
console.error(`ERROR: Transcript file not found: ${transcriptPath}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(documentTypePath)) {
|
||||||
|
console.error(`ERROR: Document type file not found: ${documentTypePath}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Starting document generation...");
|
||||||
|
console.log(`Transcript: ${transcriptPath}`);
|
||||||
|
console.log(`Document Type: ${documentTypePath}`);
|
||||||
|
console.log(`Language: ${language || 'en (default)'}`);
|
||||||
|
|
||||||
|
await module_exports.createDocumentFromTranscript(
|
||||||
|
transcriptPath,
|
||||||
|
documentTypePath,
|
||||||
|
language || 'en'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("Done!");
|
||||||
|
})();
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const outputDir = path.join(__dirname, "../../../storage/documents"); // path for output directory
|
||||||
|
|
||||||
|
if (!fs.existsSync(outputDir)) {
|
||||||
|
fs.mkdirSync(outputDir, { recursive: true }); // Create output directory if it doesn't exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure SAIA API key is set in environment variables: export SAIA_API_KEY="your_api_key_here"
|
||||||
|
const SAIA_API_KEY = process.env.SAIA_API_KEY;
|
||||||
|
const SAIA_URL = "https://chat-ai.academiccloud.de/v1/chat/completions"; // URL for the REST call, used model and action
|
||||||
|
|
||||||
|
const module_exports = {
|
||||||
|
name: "qwen3-235b-a22b",
|
||||||
|
type: "llm",
|
||||||
|
displayname: "QWEN 3 235B",
|
||||||
|
description: "Generates documents using QWEN 3 235B via SAIA platform",
|
||||||
|
|
||||||
|
async function(parameter) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
// console.log("SAIA QWEN 3 235B module invoked with parameters:", parameter);
|
||||||
|
|
||||||
|
resolve(await this.createDocumentFromTranscript( //Call the function to create document with transcript, document type and language
|
||||||
|
parameter.inputTranscriptPath, // Path to input transcript file
|
||||||
|
parameter.documentTypePath, // Path to document type file which is chosen in the front end by the user
|
||||||
|
parameter.language // Language for the document which is chosen in the front end by the user
|
||||||
|
));
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// console.error("Error in SAIA QWEN 3 235B module:", error);
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
createDocumentFromTranscript: async function(transcriptPath, documentTypePath, language = "en") { // default language is English
|
||||||
|
return new Promise(async(resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const transcript = await fs.promises.readFile(transcriptPath, "utf-8"); //read transcript file from Path
|
||||||
|
const documentType = await fs.promises.readFile(documentTypePath, "utf-8"); //read document type from Path
|
||||||
|
const promptText = `${documentType}, in language ${language}, transcript:\n\n${transcript}`; //combine doc type, language and transcript - Change prompt here if needed
|
||||||
|
|
||||||
|
// --- REST CALL ---
|
||||||
|
const response = await fetch(SAIA_URL, { //safe model response in variable
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Bearer ${SAIA_API_KEY}`,
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: "qwen3-235b-a22b",
|
||||||
|
messages: [
|
||||||
|
{ role: "system", content: "You are a helpful assistant that generates HTML documents from transcripts. Output only valid HTML content without any preamble, explanations, or markdown formatting." },
|
||||||
|
{ role: "user", content: promptText }
|
||||||
|
],
|
||||||
|
temperature: 0
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) { //ok is true when a responce was successfull
|
||||||
|
const text = await response.text();
|
||||||
|
throw new Error(`SAIA API error (${response.status}): ${text}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Get generated text from response or default to empty string (if null)
|
||||||
|
// SAIA uses OpenAI-compatible structure: data.choices[x].message.content
|
||||||
|
const output = data.choices?.[0]?.message?.content || "";
|
||||||
|
let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath)); // Name for the output file
|
||||||
|
// console.log(inputTranscriptName);
|
||||||
|
const outPath = path.join(outputDir, `${inputTranscriptName}.html`); // Output file path & name to make naming dynamic. Pulled from input transcript name
|
||||||
|
fs.writeFileSync(outPath, output, "utf8"); // Write output to file
|
||||||
|
|
||||||
|
// console.log("Generated document written to:", outPath);
|
||||||
|
resolve(outPath)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// console.error("Error generating SAIA content:", error);
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = module_exports;
|
||||||
|
|
||||||
|
// CLI Mode: Allow direct execution
|
||||||
|
if (require.main === module) {
|
||||||
|
(async () => {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (args.length < 2) {
|
||||||
|
console.error("Usage: node qwen3.js <transcriptPath> <documentTypePath> [language]");
|
||||||
|
console.error("Example: node qwen3.js ./transcript.json ./docType.txt de");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [transcriptPath, documentTypePath, language] = args;
|
||||||
|
|
||||||
|
// Check if API key is set
|
||||||
|
if (!SAIA_API_KEY) {
|
||||||
|
console.error("ERROR: SAIA_API_KEY environment variable is not set!");
|
||||||
|
console.error("Please set it with: export SAIA_API_KEY='your_api_key_here'");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if files exist
|
||||||
|
if (!fs.existsSync(transcriptPath)) {
|
||||||
|
console.error(`ERROR: Transcript file not found: ${transcriptPath}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(documentTypePath)) {
|
||||||
|
console.error(`ERROR: Document type file not found: ${documentTypePath}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Starting document generation...");
|
||||||
|
console.log(`Transcript: ${transcriptPath}`);
|
||||||
|
console.log(`Document Type: ${documentTypePath}`);
|
||||||
|
console.log(`Language: ${language || 'en (default)'}`);
|
||||||
|
|
||||||
|
await module_exports.createDocumentFromTranscript(
|
||||||
|
transcriptPath,
|
||||||
|
documentTypePath,
|
||||||
|
language || 'en'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("Done!");
|
||||||
|
})();
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const module_exports = {
|
||||||
|
name: "replace_speaker",
|
||||||
|
type: "processor",
|
||||||
|
displayname: "Speaker Name Replacer",
|
||||||
|
description: "Replaces speaker placeholder names with actual names based on a mapping in HTML files",
|
||||||
|
|
||||||
|
async function({ inputHtmlPath, speakerMappingPath }) {
|
||||||
|
return await this.replaceNames(inputHtmlPath, speakerMappingPath);
|
||||||
|
},
|
||||||
|
|
||||||
|
replaceNames: async function(inputHtmlPath, speakerMappingPath) {
|
||||||
|
try {
|
||||||
|
const htmlContent = await fs.promises.readFile(inputHtmlPath, "utf-8");
|
||||||
|
const mappingData = await fs.promises.readFile(speakerMappingPath, "utf-8");
|
||||||
|
|
||||||
|
let speakerMap = {};
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(mappingData);
|
||||||
|
if (parsed.speakerId && parsed.speakerName) {
|
||||||
|
speakerMap[parsed.speakerId] = parsed.speakerName;
|
||||||
|
} else {
|
||||||
|
Object.assign(speakerMap, parsed);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
const lines = mappingData.trim().split('\n');
|
||||||
|
lines.forEach(line => {
|
||||||
|
const [placeholder, realName] = line.split(',').map(s => s.trim());
|
||||||
|
if (placeholder && realName) speakerMap[placeholder] = realName;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let outputContent = htmlContent;
|
||||||
|
Object.entries(speakerMap).forEach(([placeholder, realName]) => {
|
||||||
|
const regex = new RegExp(`[\$begin:math:text$\\\\\[\]\?\$\{placeholder\}\[\\$end:math:text$\\]]?`, 'g');
|
||||||
|
outputContent = outputContent.replace(regex, realName);
|
||||||
|
});
|
||||||
|
|
||||||
|
await fs.promises.writeFile(inputHtmlPath, outputContent, "utf-8");
|
||||||
|
|
||||||
|
return inputHtmlPath;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error replacing speaker names:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = module_exports;
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
(async () => {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
if (args.length < 2) process.exit(1);
|
||||||
|
|
||||||
|
const [inputHtmlPath, speakerMappingPath] = args;
|
||||||
|
|
||||||
|
if (!fs.existsSync(inputHtmlPath)) process.exit(1);
|
||||||
|
if (!fs.existsSync(speakerMappingPath)) process.exit(1);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await module_exports.replaceNames(inputHtmlPath, speakerMappingPath);
|
||||||
|
} catch (err) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
+1
Submodule services/modules/transcription-local/whisper.cpp added at 999a7e0cbf
@@ -1,8 +1,125 @@
|
|||||||
module.exports = {
|
const API_KEY = process.env.ASSEMBLYAI_API_KEY;
|
||||||
name:"assembly", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
|
const BASE_URL = 'https://api.assemblyai.com/v2';
|
||||||
type:"transcription", // value used to differentiate each module to order them in the UI
|
|
||||||
displayname:"Assembly", // The displayname used within the UI
|
//---------------------------------------------------Upload audio---------------------------------------------------
|
||||||
async function(parameter){
|
|
||||||
// TODO add code to actually process the audio file
|
async function uploadAudio(audioPath) {
|
||||||
|
const audioData = fs.readFileSync(audioPath);
|
||||||
|
|
||||||
|
const response = await axios.post(`${BASE_URL}/upload`, audioData, {
|
||||||
|
headers: {
|
||||||
|
authorization: API_KEY,
|
||||||
|
'content-type': 'application/octet-stream'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data.upload_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
////---------------------------------------------------Extract session id---------------------------------------------------
|
||||||
|
|
||||||
|
function getSessionId(inputPath) {
|
||||||
|
try {
|
||||||
|
const parsed = new URL(inputPath);
|
||||||
|
const base = path.basename(parsed.pathname);
|
||||||
|
return base.replace(/\.[^.]+$/, '');
|
||||||
|
} catch {
|
||||||
|
return path.basename(inputPath, path.extname(inputPath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------Create transcript---------------------------------------------------
|
||||||
|
|
||||||
|
async function createTranscript(audioUrl) {
|
||||||
|
const response = await axios.post(
|
||||||
|
`${BASE_URL}/transcript`,
|
||||||
|
{
|
||||||
|
audio_url: audioUrl,
|
||||||
|
speaker_labels: true,
|
||||||
|
language_detection: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
authorization: API_KEY,
|
||||||
|
'content-type': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------Poll transcript---------------------------------------------------
|
||||||
|
|
||||||
|
async function pollTranscript(transcriptId) {
|
||||||
|
while (true) {
|
||||||
|
const response = await axios.get(`${BASE_URL}/transcript/${transcriptId}`, {
|
||||||
|
headers: { authorization: API_KEY }
|
||||||
|
});
|
||||||
|
|
||||||
|
const status = response.data.status;
|
||||||
|
|
||||||
|
if (status === 'completed') return response.data;
|
||||||
|
if (status === 'error') throw new Error(`Transcription failed: ${response.data.error}`);
|
||||||
|
|
||||||
|
await new Promise(res => setTimeout(res, 3000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------Save transcript---------------------------------------------------
|
||||||
|
|
||||||
|
function saveTranscript(transcript, sessionId) {
|
||||||
|
const outputDir = path.join(__dirname, '../../../storage/transcripts');
|
||||||
|
|
||||||
|
if (!fs.existsSync(outputDir)) {
|
||||||
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputPath = path.join(outputDir, `${sessionId}.json`);
|
||||||
|
fs.writeFileSync(outputPath, JSON.stringify(transcript, null, 2));
|
||||||
|
|
||||||
|
// console.log(`Transcript saved: ${outputPath}`);
|
||||||
|
|
||||||
|
return outputPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------Modul---------------------------------------------------
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'assembly',
|
||||||
|
type: 'transcription',
|
||||||
|
displayname: 'AssemblyAI',
|
||||||
|
audioformat: "mp3",
|
||||||
|
|
||||||
|
async function(audioFileName) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
// audioFileName ist nur "datei.mp3"
|
||||||
|
const audioPath = audioFileName;
|
||||||
|
|
||||||
|
let audioUrl;
|
||||||
|
|
||||||
|
if (/^https?:\/\//i.test(audioFileName)) {
|
||||||
|
audioUrl = audioFileName;
|
||||||
|
} else {
|
||||||
|
if (!fs.existsSync(audioPath)) {
|
||||||
|
throw new Error(`Audio file not found: ${audioPath}`);
|
||||||
|
}
|
||||||
|
audioUrl = await uploadAudio(audioPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const transcriptId = await createTranscript(audioUrl);
|
||||||
|
const transcript = await pollTranscript(transcriptId);
|
||||||
|
|
||||||
|
const sessionId = getSessionId(audioFileName);
|
||||||
|
|
||||||
|
resolve(saveTranscript(transcript, sessionId));
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// console.error('Transcription error:', error.message);
|
||||||
|
reject(error);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name:"Startup_function",
|
name:"Startup_function",
|
||||||
async function(){
|
async function(){
|
||||||
@@ -6,6 +7,14 @@ module.exports = {
|
|||||||
// We are now calling the example function from the example folder
|
// We are now calling the example function from the example folder
|
||||||
mapFunctions.get("example").function("Startup")
|
mapFunctions.get("example").function("Startup")
|
||||||
|
|
||||||
|
// let transcript = await mapFunctions.get("assembly").function('../../storage/audio/IMG_2978.wav');
|
||||||
|
|
||||||
|
// let summary = await mapFunctions.get("summarize-transcription").function({jsonPath:'/Users/santa/Proj25/video2document/storage/transcripts/IMG_2978.json'});
|
||||||
|
// 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' });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./a.mp4", outputType:"wav"})
|
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./a.mp4", outputType:"wav"})
|
||||||
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./b.mp4", outputType:"wav"})
|
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./b.mp4", outputType:"wav"})
|
||||||
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./b.mp4", outputType:"flac"})
|
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./b.mp4", outputType:"flac"})
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
@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
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#!/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
|
||||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,26 @@
|
|||||||
|
Du bist ein erfahrener Moderator und Projektmanager.
|
||||||
|
|
||||||
|
AUFGABE:
|
||||||
|
Erstelle eine sinnvolle Meeting-Agenda basierend auf dem folgenden Transkript.
|
||||||
|
|
||||||
|
ANFORDERUNGEN:
|
||||||
|
- Rekonstruiere die tatsächlichen Themenblöcke
|
||||||
|
- Ordne sie logisch und chronologisch
|
||||||
|
- Fasse ähnliche Diskussionen zusammen
|
||||||
|
- Keine irrelevanten Details aufnehmen
|
||||||
|
|
||||||
|
STRUKTUR:
|
||||||
|
- Titel der Agenda
|
||||||
|
- Ziel des Meetings (1–2 Sätze)
|
||||||
|
- Agenda-Punkte (nummeriert)
|
||||||
|
- Thema
|
||||||
|
- Kurzbeschreibung
|
||||||
|
- Ziel des Punktes (Information, Entscheidung, Diskussion)
|
||||||
|
|
||||||
|
STIL:
|
||||||
|
- Klar, kompakt
|
||||||
|
- Business-orientiert
|
||||||
|
- Keine Sprecher- oder Zeitangaben
|
||||||
|
- Namen aus dem Transkript speakerA, speakerB etc. sollen weiterhin bestehen bleiben wie sie sind und nicht im Dokument ersetzt werden
|
||||||
|
|
||||||
|
TRANSKRIPT:
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
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:
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
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:
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
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:
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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:
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"speakerId": "speakerB",
|
||||||
|
"speakerName": "Peter",
|
||||||
|
"updated": "2026-01-10T13:54:55.608Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
require('dotenv').config();
|
||||||
|
const path = require('path');
|
||||||
|
const assemblyModule = require('../../services/modules/transcription-remote/assembly.js');
|
||||||
|
|
||||||
|
// Audio-Datei oder URL aus Kommandozeile, Standard: test.wav
|
||||||
|
const audioPath = process.argv[2] || './storage/audio/IMG_2978.wav';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const transcript = await assemblyModule.run(audioPath);
|
||||||
|
|
||||||
|
console.log('Transcription succesful');
|
||||||
|
console.log('Transcript ID:', transcript?.id);
|
||||||
|
console.log('Speaker labels:', transcript?.utterances?.length || 0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in Transcription:', error?.message || error);
|
||||||
|
}
|
||||||
|
})();
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const transSummarizer = require("../../services/modules/jsonTools/transcriptionSummarizer.js");
|
||||||
|
|
||||||
|
// JSON-Datei laden
|
||||||
|
const inputJson = JSON.parse(fs.readFileSync("./testFile.json", "utf8"));
|
||||||
|
|
||||||
|
// Übergabe an den Summarizer
|
||||||
|
transSummarizer.function({
|
||||||
|
json: inputJson
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
from fastapi.testclient import TestClient
|
|
||||||
from app.main import app
|
|
||||||
|
|
||||||
client = TestClient(app)
|
|
||||||
|
|
||||||
def test_health():
|
|
||||||
response = client.get("/health")
|
|
||||||
assert response.status_code == 200
|
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
// 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.
Reference in New Issue
Block a user