Compare commits

..

212 Commits

Author SHA1 Message Date
MikeHughes-BIN b57972bf1b Add error logging to file submission process 2025-12-22 14:21:50 +01:00
Hughes, Mike a96a3bebd1 Merge branch 'fix/export_function_windows' into 'develop'
Fix/export function windows

See merge request proj-wise2526-video2document/video2document!75
2025-12-22 14:06:44 +01:00
MikeHughes-BIN 8c0130337e Fix for windows power shell command 2025-12-19 15:31:08 +01:00
Hughes, Mike 4a7aeec869 Merge branch 'develop' into 'main'
Add start scripts for Windows and Unix environments that checks if node is...

See merge request proj-wise2526-video2document/video2document!74
2025-12-19 09:48:39 +01:00
Hughes, Mike 4f3b03f881 Merge branch 'feature/starting-scripts' into 'develop'
Add start scripts for Windows and Unix environments that checks if node is...

See merge request proj-wise2526-video2document/video2document!73
2025-12-19 09:47:31 +01:00
MikeHughes-BIN 4e4308ae51 Included starting scripts 2025-12-19 09:45:11 +01:00
MikeHughes-BIN 01913590b0 Edited the README 2025-12-19 09:36:49 +01:00
MikeHughes-BIN f750966952 Add start scripts for Windows and Unix environments that checks if node is installed, installs dependencys and starts the programm 2025-12-18 22:55:26 +01:00
Hughes, Mike 26ed7e3c58 Merge branch 'develop' into 'main'
Fixed the change speaker feature. every step till the point where its send to...

See merge request proj-wise2526-video2document/video2document!72
2025-12-17 16:58:15 +01:00
Hughes, Mike 9e05c744aa Merge branch 'feature/ui-test' into 'develop'
Feature/ui test

See merge request proj-wise2526-video2document/video2document!71
2025-12-17 16:51:18 +01:00
Verena Schulz df7ca369de Merge branch 'feature/ui-test' of gitlab.rlp.net:proj-wise2526-video2document/video2document into feature/ui-test 2025-12-17 16:42:11 +01:00
Verena Schulz b6bf178c48 First restructure changes 2025-12-17 16:42:02 +01:00
Hughes, Mike 43749fd073 Merge branch 'feature/ui-test' into 'develop'
Fixed the change speaker feature. every step till the point where its send to...

See merge request proj-wise2526-video2document/video2document!70
2025-12-17 16:15:37 +01:00
Hughes, Mike f616e06a7e Merge branch 'develop' into 'feature/ui-test'
# Conflicts:
#   main.js
2025-12-17 16:14:18 +01:00
eric.minning de43bdd190 Small fix regarding an old line which caused an error 2025-12-17 16:09:06 +01:00
eric.minning b742411dcc Fixed the display and resetting of the audio 2025-12-17 15:55:00 +01:00
eric.minning bbaab9cd21 Merge branch 'feature/ui-test' of https://gitlab.rlp.net/proj-wise2526-video2document/video2document into feature/ui-test 2025-12-17 15:29:18 +01:00
eric.minning fc8770e67f Fixed the change speaker feature. every step till the point where its send to the main functions now 2025-12-17 15:29:04 +01:00
Verena Schulz 958c9368d0 Added wrapper in step 2 2025-12-17 15:21:17 +01:00
Verena Schulz 145f91f091 centered custom_document.html 2025-12-17 14:49:32 +01:00
Verena Schulz fa429ec683 Linked Abbrechen button 2025-12-17 14:43:50 +01:00
Verena Schulz 6a8ce0a46d Linked custom_document.html with Custom docment button 2025-12-17 14:39:11 +01:00
Minning, Eric 2778626e4b Merge branch 'develop' into 'feature/ui-test'
Getting the ui-test up to date

See merge request proj-wise2526-video2document/video2document!69
2025-12-17 14:09:01 +01:00
Minning, Eric 0e746749e6 Getting the ui-test up to date 2025-12-17 14:09:00 +01:00
Hughes, Mike 853eb1a5eb Merge branch 'develop' into 'main'
New Stable Version 2.0

See merge request proj-wise2526-video2document/video2document!68
2025-12-16 19:20:19 +01:00
Hughes, Mike 3cba97a9d1 Merge branch 'fix/rename_document_type' into 'develop'
rename

See merge request proj-wise2526-video2document/video2document!67
2025-12-16 19:13:11 +01:00
MikeHughes-BIN 2900152945 rename 2025-12-16 19:11:11 +01:00
Minning, Eric 262cc2b4d6 Merge branch 'feature/ui-test' into 'develop'
Feature/ui test

See merge request proj-wise2526-video2document/video2document!66
2025-12-16 19:07:47 +01:00
eric.minning 292b893ed2 Merge branch 'feature/ui-test' of https://gitlab.rlp.net/proj-wise2526-video2document/video2document into feature/ui-test 2025-12-16 19:01:12 +01:00
eric.minning d9c76e8890 Bug fixes in the speaker selection 2025-12-16 19:01:03 +01:00
Verena Schulz 1016d2ca86 Merge branch 'feature/ui-test' of gitlab.rlp.net:proj-wise2526-video2document/video2document into feature/ui-test 2025-12-16 18:44:27 +01:00
Verena Schulz a014c62ce3 Centered download button better 2025-12-16 18:44:14 +01:00
Spanier, Pit 0a2a8e603f Merge branch 'feature/cleanup-and-tests' into 'develop'
added test for qwen3 model and added a console log for debugging purposes to...

See merge request proj-wise2526-video2document/video2document!65
2025-12-16 18:33:55 +01:00
emily 562debd883 added test for qwen3 model and added a console log for debugging purposes to the gemini test because that shit model keeps being dogshit and throwing errors about being overloaded because the dogshit company called google cant fucking manage to set up a model that doesnt shit itself the moment more than 3 people send a query at the same time, god i fucking hate google and LLMs, it is truly an insult that we have to write this dogshit software 2025-12-16 18:27:43 +01:00
emily 8927b62971 some more cleanup 2025-12-16 18:17:55 +01:00
emily b511b75db7 did some cli output cleanup, and fixed the test pipeline aswell as added a few tests 2025-12-16 18:15:40 +01:00
Minning, Eric 9b88f4719f Merge branch 'feature/ui-test' into 'develop'
Value missmatch fix

See merge request proj-wise2526-video2document/video2document!64
2025-12-16 17:35:17 +01:00
eric.minning e4770f484a Merge branch 'feature/ui-test' of https://gitlab.rlp.net/proj-wise2526-video2document/video2document into feature/ui-test 2025-12-16 17:25:54 +01:00
eric.minning 69deae9951 Fixed a value missmatch 2025-12-16 17:25:43 +01:00
Verena Schulz 6775509cf3 Merge branch 'feature/ui-test' of gitlab.rlp.net:proj-wise2526-video2document/video2document into feature/ui-test 2025-12-16 17:21:05 +01:00
Verena Schulz 3c3b27325e Centered download button 2025-12-16 17:20:55 +01:00
Hughes, Mike 0731f045ce Merge branch 'fix/rename_document_type' into 'develop'
corrected report naming again

See merge request proj-wise2526-video2document/video2document!63
2025-12-16 17:11:05 +01:00
MikeHughes-BIN b89e5ec587 corrected report naming again 2025-12-16 17:03:47 +01:00
Minning, Eric 598a8e5d34 Merge branch 'fix/rename_document_type' into 'develop'
Add requirement to retain speaker names in document templates and remove sprint planning notes

See merge request proj-wise2526-video2document/video2document!62
2025-12-16 17:00:45 +01:00
Minning, Eric 8f62b68184 Merge branch 'feature/ui-test' into 'develop'
One closing curly too much removed

See merge request proj-wise2526-video2document/video2document!61
2025-12-16 16:59:59 +01:00
MikeHughes-BIN fc041e1036 Add requirement to retain speaker names in document templates and remove sprint planning notes 2025-12-16 16:57:45 +01:00
MikeHughes-BIN 37382f7444 One closing curly too much removed 2025-12-16 16:49:46 +01:00
Hughes, Mike b05537fa70 Merge branch 'feature/ui-test' into 'develop'
Feature/ui test

See merge request proj-wise2526-video2document/video2document!59
2025-12-16 16:44:50 +01:00
Hughes, Mike 8e563187b0 Merge branch 'develop' into 'feature/ui-test'
# Conflicts:
#   main.js
2025-12-16 16:39:59 +01:00
MikeHughes-BIN 95ac7256d4 Fixed the Download button in main 2025-12-16 16:26:45 +01:00
Spanier, Pit d47cf21e9f Merge branch 'feature/38-sprecher-audio-snippets-s4-11' into 'develop'
extract speaker snippets mit main verknüpft.

See merge request proj-wise2526-video2document/video2document!60
2025-12-16 16:19:09 +01:00
Spanier, Pit 6cff6b9981 Merge branch 'develop' into 'feature/38-sprecher-audio-snippets-s4-11'
# Conflicts:
#   main.js
2025-12-16 16:18:17 +01:00
Hughes, Mike 11f9a02778 Merge branch 'develop' into 'feature/ui-test'
# Conflicts:
#   main.js
2025-12-16 15:54:00 +01:00
eric.minning 41cd8065ba Merge branch 'feature/ui-test' of https://gitlab.rlp.net/proj-wise2526-video2document/video2document into feature/ui-test 2025-12-16 15:51:18 +01:00
eric.minning c1e79b6603 Checkbox change from array to single value and value fix in html. 2025-12-16 15:50:28 +01:00
santa 0003d99041 extract speaker snippets mit main verknüpft. 2025-12-16 15:32:31 +01:00
Hughes, Mike 6a94f88e86 Merge branch 'feature/meeting_document_types' into 'develop'
Added Meeting Document Forms

See merge request proj-wise2526-video2document/video2document!55
2025-12-16 14:43:54 +01:00
MikeHughes-BIN 30f73f7bb7 Added Meeting Document Forms 2025-12-16 14:24:13 +01:00
Verena Schulz 59ac104d69 Hamburger components are clickable 2025-12-16 14:00:29 +01:00
Hughes, Mike 74439d680e Merge branch 'fix/download-button' into 'develop'
oopsies missed a </div>

See merge request proj-wise2526-video2document/video2document!53
2025-12-15 18:49:19 +01:00
MikeHughes-BIN 1b76b2e96d oopsies missed a </div> 2025-12-15 18:47:41 +01:00
Hughes, Mike 363ba2d1b5 Merge branch 'fix/download-button' into 'develop'
Enable download button functionality and improve error handling in file download

See merge request proj-wise2526-video2document/video2document!52
2025-12-15 18:43:50 +01:00
MikeHughes-BIN 6aa62ed534 Enable download button functionality and improve error handling in file download 2025-12-15 18:41:14 +01:00
Hughes, Mike cd474d7101 Merge branch 'feature/ui-test' into 'develop'
Removed a line which caused an error

See merge request proj-wise2526-video2document/video2document!51
2025-12-15 18:11:01 +01:00
eric.minning bd47a194c7 Removed a line which caused an error 2025-12-15 18:09:50 +01:00
Spanier, Pit ef080063a8 Merge branch 'feature/export-function-integration' into 'develop'
Changes to the LLMs to return a Promise (outp path) and main now calls the export process

See merge request proj-wise2526-video2document/video2document!50
2025-12-15 18:05:20 +01:00
Hughes, Mike c2c1aa1b17 Merge branch 'fix/gui-width' into 'develop'
Increased window witdth from 800px to 1200px

See merge request proj-wise2526-video2document/video2document!47
2025-12-15 17:59:12 +01:00
Hughes, Mike ee3bcdcd05 Merge branch 'feature/ui-test' into 'develop'
Implemented the function for the download button.

See merge request proj-wise2526-video2document/video2document!49
2025-12-15 17:58:33 +01:00
MikeHughes-BIN 04b2457ca3 Changes to the LLMs to return a Promise (outp path) and main now calls the export process 2025-12-15 17:58:03 +01:00
eric.minning bac6e2b7f0 Implemented the function for the download button. 2025-12-15 17:21:02 +01:00
Spanier, Pit 9760704883 Merge branch 'feature/ui-test' into 'develop'
Feature/ui test

See merge request proj-wise2526-video2document/video2document!48
2025-12-15 17:09:06 +01:00
eric.minning aee1428cb6 Changed location of the output type value in the response package 2025-12-15 17:07:07 +01:00
eric.minning 7494e13c8c Fixed an a wrong call of the speakerAudios function. 2025-12-15 17:01:04 +01:00
MikeHughes-BIN 2998799826 Window width for the GUI to fit the Arrows 2025-12-15 16:42:44 +01:00
Hughes, Mike 6146e7091b Merge branch 'feature/ui-test' into 'develop'
Added to Step 2 and 3. Added a first new version of the progressbar +...

See merge request proj-wise2526-video2document/video2document!46
2025-12-15 16:26:10 +01:00
Hughes, Mike 3a5540d865 Merge branch 'fix/use-new-meeting-report' into 'develop'
the old report type was still used

See merge request proj-wise2526-video2document/video2document!45
2025-12-15 16:10:37 +01:00
MikeHughes-BIN 948ddb4310 the old report type was still used 2025-12-15 16:06:18 +01:00
Hughes, Mike 889d455fbe Merge branch 'fix/add_puppeteer' into 'develop'
The requirements needed to include puppeteer and html-to-docs

See merge request proj-wise2526-video2document/video2document!44
2025-12-15 15:46:42 +01:00
MikeHughes-BIN 58741f0057 The requirements needed to include puppeteer and html-to-docs 2025-12-15 15:43:03 +01:00
eric.minning 950cc17878 Merge branch 'feature/ui-test' of https://gitlab.rlp.net/proj-wise2526-video2document/video2document into feature/ui-test 2025-12-15 15:22:08 +01:00
eric.minning aba8552d11 Fixes to the loading of the speaker options 2025-12-15 15:19:47 +01:00
Verena Schulz 500e62daf0 Merge branch 'feature/ui-test' of gitlab.rlp.net:proj-wise2526-video2document/video2document into feature/ui-test 2025-12-15 15:05:18 +01:00
Verena Schulz ad6d20b134 Added download button (without function) 2025-12-15 15:04:05 +01:00
Hughes, Mike a8c284edd2 Merge branch 'feature/30-backend-export-funktion-des-dokuments-in-verschiedene-datei-formate-s4-05' into 'develop'
Feature/30 backend export funktion des dokuments in verschiedene datei formate s4 05

See merge request proj-wise2526-video2document/video2document!43
2025-12-15 15:03:36 +01:00
eric.minning 6e0ea41ea6 Implemented a first version for all functions for step5 speaker (untested) 2025-12-15 14:49:43 +01:00
MikeHughes-BIN 3a5808d97c Merge branch 'develop' into feature/30-backend-export-funktion-des-dokuments-in-verschiedene-datei-formate-s4-05 2025-12-15 14:41:28 +01:00
Hughes, Mike 4083d5a5cb Merge branch 'feature/35-backend-llm-chat-gpt-integration-s4-10' into 'develop'
Feature/35 backend llm chat gpt integration s4 10

See merge request proj-wise2526-video2document/video2document!42
2025-12-15 14:33:31 +01:00
MikeHughes-BIN d655925031 merge conflict resolve 2025-12-15 14:30:14 +01:00
MikeHughes-BIN 405a32098c Remove success and error icons from console log messages for consistency 2025-12-15 14:15:40 +01:00
Hughes, Mike 1683502aa1 Merge branch 'develop' into 'feature/35-backend-llm-chat-gpt-integration-s4-10'
# Conflicts:
#   services/modules/llm-gemini/gemini.js
2025-12-15 14:07:32 +01:00
MikeHughes-BIN 789ecd3a31 Update document processing to use prompts from styles and add structured meeting report template 2025-12-15 14:01:51 +01:00
MikeHughes-BIN e72d03efbe Added Pre's Document in the correct Folder 2025-12-15 13:55:40 +01:00
eric.minning d95b6d5178 Merge branch 'feature/ui-test' of https://gitlab.rlp.net/proj-wise2526-video2document/video2document into feature/ui-test 2025-12-15 13:54:40 +01:00
eric.minning 20b4ae6a3b Checkbox fix, old progressbar removal and visibility fix in the new one 2025-12-15 13:54:31 +01:00
MikeHughes-BIN ec57411992 Refactor document generation to output HTML format and update system instructions for clarity 2025-12-15 13:53:14 +01:00
Verena Schulz accb65252c Improved stability 2025-12-15 13:48:27 +01:00
MikeHughes-BIN 271fe78b7b Changed the module to use puppeteer and html-to-docx 2025-12-14 18:14:16 +01:00
MikeHughes-BIN 1e38cc79f4 Extended the Role description 2025-12-14 16:53:35 +01:00
MikeHughes-BIN 746fec05d4 Changed model from llama to quen3 because llama was timing out due to token limit 2025-12-14 16:28:48 +01:00
Spanier, Pit d647f53790 Merge branch 'feature/implementation-of-ci-s4-09' into 'develop'
fixed the pipeline

See merge request proj-wise2526-video2document/video2document!38
2025-12-14 16:13:28 +01:00
emily 791dbab739 edited the timeouts for the transcription and LLM tests 2025-12-13 23:04:31 +01:00
emily c497fa9532 fixed a mistake i made in the pipeline 2025-12-13 23:02:29 +01:00
Verena Schulz bb589e6aad New Checkboxes 2025-12-13 21:21:17 +01:00
emily 3ce632bf8d Optimised the pipeline
Replaced the test video with a lower size version in an attempt at keeping this shitty gitlab.rlp provided action running from crashing constantly
2025-12-13 18:46:02 +01:00
Hughes, Mike 5ea8ec6a1a Merge branch 'feature/implementation-of-ci-s4-09' into 'develop'
implemented first CI pipeline version with a test test that only checks if 1...

See merge request proj-wise2526-video2document/video2document!34
2025-12-13 14:13:06 +01:00
eric.minning 4747c44c52 Merge remote-tracking branch 'origin/develop' into feature/ui-test 2025-12-11 16:23:00 +01:00
eric.minning a0abedc921 Removed a error in the speakerAudioViewer 2025-12-11 16:04:16 +01:00
MikeHughes-BIN 7cd334645f Added a module that safes the file in a requested format. 2025-12-11 14:22:04 +01:00
Spanier, Pit 15e2e35bda Merge branch 'feature/35-backend-llm-chat-gpt-integration-s4-10' into 'develop'
Feature/35 backend llm chat gpt integration s4 10

See merge request proj-wise2526-video2document/video2document!35
2025-12-11 13:30:14 +01:00
MikeHughes-BIN 53508b175a changed Display name 2025-12-11 13:27:00 +01:00
MikeHughes-BIN 3dd8485140 audit 2025-12-11 13:23:22 +01:00
MikeHughes-BIN 68c1f0ed9f removed google from requires 2025-12-11 13:20:54 +01:00
MikeHughes-BIN 2b597add6c fixed a unicode bug 2025-12-11 13:03:04 +01:00
MikeHughes-BIN 3af038d195 Multiple AI models implemented - chatgpt, llama 2025-12-11 12:41:11 +01:00
MikeHughes-BIN b546c96238 Refactor code structure for improved readability and maintainability 2025-12-11 12:08:07 +01:00
emily 6512b50d83 removal of an unneeded file 2025-12-10 22:46:25 +01:00
Verena Schulz b26ef96cfc Prototype hamburger menu for language and help buttons 2025-12-10 16:59:14 +01:00
emily faee605f12 Initial version of the working CI pipeline
Added mocha based unit tests for each module
Did a bit of cleanup in the modules to remove debug console.log calls
Removed the Progress bar in the extractor and the library requirement
Promisified the gemini module to make sure it returns the path as a promise instead of just on the cli
Fixed gitignore so that it now only ignores the content int the storage directories, and not the whole directories
Added neetingReport.json for the LLMs to use
2025-12-09 22:07:43 +01:00
emily 037d29e656 added gitkeep to the storage folders, and edited the gitignore to be more precise for our use case 2025-12-09 20:53:16 +01:00
emily 8c201db539 implemented first CI pipeline version with a test test that only checks if 1 is 1, needs implementation of actual tests 2025-12-09 17:56:59 +01:00
eric.minning 6eef82bb64 Implemented the color change of the progressbar 2025-12-09 17:03:02 +01:00
eric.minning b182e0a3c7 fixed the submitbutton 2025-12-09 15:23:49 +01:00
eric.minning 34a8b186a8 removed an event listener which was doubeld and a submitbutton call 2025-12-09 15:12:32 +01:00
eric.minning 70d3bfa201 fixed error in file lookup 2025-12-09 15:01:16 +01:00
eric.minning 5661f7c13f speaker fix 2025-12-09 14:50:19 +01:00
eric.minning 208879b497 Merge remote-tracking branch 'origin/develop' into feature/ui-test 2025-12-09 14:40:44 +01:00
eric.minning f817d4c29e Changed checkboxvalues and added more error catching 2025-12-09 14:38:26 +01:00
Spanier, Pit 882b49a489 Merge branch 'feature/audio-format-on-transcription-tool-selection-s4-08' into 'develop'
removed the need to send the transcription module defined audio output format...

See merge request proj-wise2526-video2document/video2document!33
2025-12-09 14:31:25 +01:00
eric.minning 5e6b700c36 Merge remote-tracking branch 'origin/develop' into feature/ui-test 2025-12-09 14:21:14 +01:00
Spanier, Pit 25d11d74a3 Merge branch 'fix/main-path-to-document-for-ai' into 'develop'
Currently we only have the standard meeting document and the config right now...

See merge request proj-wise2526-video2document/video2document!32
2025-12-09 14:12:14 +01:00
Verena Schulz 0efb7585ba Fixed height of middle container and several other css bugs 2025-12-07 16:09:12 +01:00
MikeHughes-BIN 0d3535128d Currently we only have the standard meeting document and the config right now prevents the programm from running. Can be adapted later 2025-12-06 16:43:32 +01:00
emily e661aefbe8 added json to send to frontend for the speaker naming functionality 2025-12-05 17:09:45 +01:00
eric.minning 59b820d917 Added to Step 2 and 3. Added a first new version of the progressbar + functions in Step 4. Implemented the basics and functions for Step 5 2025-12-05 17:02:20 +01:00
emily ed94928953 removed the need to send the transcription module defined audio output format to the frontend, and handle it on the backend instead 2025-12-05 16:45:39 +01:00
emily 8adf470f18 removed a debug console output 2025-12-05 15:58:45 +01:00
emily f858f2edbb added functionality for transcription tool to tell the frontend what audio format to use 2025-12-05 15:58:17 +01:00
MikeHughes-BIN 425e24853e Similar to the Gemini LLM we now have a ChatGPT REST call. The API Key is still missing 2025-12-04 10:58:50 +01:00
Verena Schulz 1c5d399c58 Added 5th and 6th step, headline above steps 2025-12-03 17:55:55 +01:00
Verena Schulz 33093733ef Added arrows for navigating through steps 2025-12-02 17:13:36 +01:00
Verena Schulz 4f0d2bcc4a Fixed mockup sequence 2025-11-27 18:57:17 +01:00
Spanier, Pit 05c04aaef2 Merge branch 'feature/gemini-ai' into 'develop'
REST API & Auto Naming

See merge request proj-wise2526-video2document/video2document!31
2025-11-26 21:57:51 +01:00
MikeHughes-BIN 84cc94aa34 REST API & Auto Naming 2025-11-26 21:55:19 +01:00
Verena Schulz ef379a5b7b First prototype of new mockup 2025-11-25 16:31:26 +01:00
Verena Schulz 0bbbb2f9b7 Fixed submit button bug and progessbar not showing when uploading video with button 2025-11-25 15:11:18 +01:00
Spanier, Pit 16f319f89f Merge branch 'feature/implementing-everything' into 'develop'
working on implementing everything

See merge request proj-wise2526-video2document/video2document!27
2025-11-24 17:25:37 +01:00
santa 9ab98825a2 made it so that the transcript summary file name is not hardcoded anymore 2025-11-24 17:24:08 +01:00
emily 9a0a349813 Switched to transcription summarizer2 due to a better output format 2025-11-24 17:01:49 +01:00
emily 9dfc05e987 Fixed Transcription Summarizer, and now the whole pipeline works (except for LLM, because we need the keys for that) 2025-11-24 16:51:48 +01:00
santa a5a60635fc worked on fixing the code 2025-11-24 16:40:12 +01:00
emily af13907fdc added console outputs for debugging 2025-11-24 16:14:26 +01:00
emily 812bca8cfb working on implementing everything
Currently getting stuck on the assembly module as assembly ai for some fucking reason blocks me from making any requests
This includes making a new user account
2025-11-24 15:35:32 +01:00
emily 34d644d7d7 Merge commit '2f5efee9c7f81cadc22f033f9ba20bb5cbc2d66e' into develop 2025-11-24 14:41:10 +01:00
Spanier, Pit e73a2a8203 Merge branch 'feature/modular-ipc-system-implementation' into 'develop'
Implemented first version of the json object that should be send back to the...

See merge request proj-wise2526-video2document/video2document!26
2025-11-24 14:31:32 +01:00
santa 2f5efee9c7 Summarizer und weiters angepasst 2025-11-24 14:27:49 +01:00
santa 465fe8bd41 Summarizer angepasst 2025-11-24 14:25:29 +01:00
emily 78e5cf0503 Merge commit '718664523cb982791ec596e62b6536baca692e80' into feature/modular-ipc-system-implementation 2025-11-24 14:20:34 +01:00
eric.minning 718664523c Fix, so the transcript module names get loaded in the right drop down menu 2025-11-24 14:19:57 +01:00
emily eb42a1f377 fixed small error 2025-11-24 14:16:10 +01:00
emily 0e7b42cb4a Merge commit '90825436520562cc7638f885b70286f765d9f20a' into feature/modular-ipc-system-implementation 2025-11-24 14:15:23 +01:00
emily 31ddddab6a replaced get modules ipc event handler with a two way handler 2025-11-24 14:14:16 +01:00
eric.minning 9082543652 version 2 of trying to get the module names 2025-11-24 14:13:09 +01:00
emily b9657e1c95 fixed another typo in the file submission object 2025-11-24 13:10:07 +01:00
emily baeb9efd2b fixed file submit code and incorrect property naming in file submission object 2025-11-24 13:04:41 +01:00
emily 8c4d213ed0 Merge commit '6531f5f51c125b9cef761b457ab09f494c77a7e7' into feature/modular-ipc-system-implementation 2025-11-24 12:56:21 +01:00
emily 474e587ff2 Added functionality to group modules for frontend 2025-11-24 12:53:05 +01:00
eric.minning 6531f5f51c Implemented the interaction between the gui and main so that the ai/transcript names get loaded inside the dropdowns 2025-11-24 12:52:49 +01:00
emily 05449ad8f2 removed an empty line 2025-11-24 12:24:33 +01:00
emily 8046fe53d1 Merge commit 'b4f2ed561dbf2ae87d7816e8aa5b708ed70fb596' into feature/modular-ipc-system-implementation 2025-11-24 12:20:06 +01:00
emily c2f22b3525 merged Mikes code for the google gemini module
included the requires libraries in the package.json
implemented the library into requires.js
cleaned up ffmpeg from the events
2025-11-24 12:12:46 +01:00
emily 6257ad05a8 Merge commit '4ade9575f33fd353522a07abe2f60e2d1be6feb4' into feature/modular-ipc-system-implementation 2025-11-24 12:08:35 +01:00
emily bf7438b753 cleaned up ffmpegextractor.js 2025-11-24 12:07:54 +01:00
emily 868945fb92 Merge commit '925eb33eab45c084e9386ada66d9cb14b966e312' into feature/modular-ipc-system-implementation 2025-11-24 12:06:58 +01:00
Hughes, Mike 0f689e2846 Merge branch 'feature/26-llm-schnittstelle-implementieren-s3-07' into 'develop'
Feature/26 llm schnittstelle implementieren s3 07

See merge request proj-wise2526-video2document/video2document!24
2025-11-22 14:38:07 +01:00
Verena Schulz b4f2ed561d Grey background removed, bigger middle part 2025-11-22 13:48:01 +01:00
MikeHughes-BIN 4ade9575f3 Changes to the test 2025-11-20 16:30:47 +01:00
Azeufack Noupeu Willy 925eb33eab chore: add .env.example template for AssemblyAI API key 2025-11-20 15:42:03 +01:00
Azeufack Noupeu Willy 911cba14fd chore(typescript): add @ts-ignore for assembly module import 2025-11-20 14:34:07 +01:00
Azeufack Noupeu Willy 6813659443 feat(main): add pipeline orchestrator for auto-transcription 2025-11-20 14:31:26 +01:00
Azeufack Noupeu Willy a0ed2ab7bd feat(extraction): add audio_ready event emission
- Add EventEmitter to emit audio_ready when extraction completes
- Pass sessionId and audioPath in event data
- Export audioEvents for Main process orchestrator

Refs: S3-06 AC1,AC3,AC7
2025-11-20 14:05:52 +01:00
santa b87bfd444d Test erstellt 2025-11-20 13:27:15 +01:00
santa 97b571b7f9 Einbindung des Summarizer 2025-11-20 11:51:34 +01:00
santa 455147a41b Summarizer Tool erstellt zur zusammenfassung der vom TranskriptionTool kommenden json 2025-11-20 10:15:11 +01:00
MikeHughes-BIN a178ccf30f Added comments to my code to make it more understandable 2025-11-18 19:53:19 +01:00
MikeHughes-BIN 75a454ad60 Moved test files into integration folder as they use multiple different components 2025-11-18 19:38:33 +01:00
MikeHughes-BIN a1d804f463 Test created and changes to gemini.js file 2025-11-18 19:16:51 +01:00
eric.minning fde4d584ab If you change the language, the corresponding flag will now show above the language selection 2025-11-18 16:25:10 +01:00
eric.minning abd72c9b54 Implemented first version of the json object that should be send back to the main. as well as fixed some bugs 2025-11-18 12:59:08 +01:00
MikeHughes-BIN 8e7e0b5043 Implement Gemini LLM module for document generation (first non tested prototype) 2025-11-17 21:16:50 +01:00
emily 4dc53b9d5f implemented first version of the modular IPC system 2025-11-17 18:00:04 +01:00
eric.minning 5f14d633e3 Added labels and expanded the language function 2025-11-17 17:34:27 +01:00
Verena Schulz 7b01c4f022 Video icon improvement, changed dropdown alignment 2025-11-17 16:00:53 +01:00
eric.minning 4f57ec25bf Changed the handleFiles function so it also contains the call for the generateThumbnail function 2025-11-17 15:18:18 +01:00
eric.minning f8fb71e146 Added Verenas function to have a preview image of the selected video file 2025-11-17 15:02:22 +01:00
eric.minning ada231cd28 Implemented a deaktivateProgressbar function 2025-11-17 13:15:37 +01:00
eric.minning 1e3d830ae2 Fixed a merg mistake and added try/catch lines 2025-11-17 11:27:17 +01:00
Verena Schulz ab0e737f33 Submit button disabled without video, bigger middle part and dropzone, button in dropzone 2025-11-16 15:06:47 +01:00
Hughes, Mike 4b72568ad3 Merge branch 'feature/ui-test' into 'develop'
Implemented a dropdown field for the languages as well as functions and a file...

See merge request proj-wise2526-video2document/video2document!23
2025-11-16 14:52:29 +01:00
santa 9441699561 if Pruefung fuer Datei Endung gefixt 2025-11-16 14:46:19 +01:00
MikeHughes-BIN 76c18fa713 Merge branch 'develop' into feature/ui-test 2025-11-15 15:46:26 +01:00
Hughes, Mike 283b4ed6af Merge branch 'develop' into 'main'
Implemented the general modular framework.

See merge request proj-wise2526-video2document/video2document!22
2025-11-15 15:14:24 +01:00
Spanier, Pit 2edc7f8351 Merge branch 'fix/transcription-module-fix' into 'develop'
New Folder structure

See merge request proj-wise2526-video2document/video2document!21
2025-11-15 15:11:35 +01:00
MikeHughes-BIN 6083773f88 New Folder structure 2025-11-15 14:45:13 +01:00
eric.minning 4a91f03289 Implemented a dropdown field for the languages as well as functions and a file to easily implement other languages. 2025-11-14 18:32:41 +01:00
Spanier, Pit 444d408480 Merge branch 'feature/fixing-the-program' into 'develop'
fixed the program by moving the example module back to where the program can ACTUALLY load it

See merge request proj-wise2526-video2document/video2document!20
2025-11-14 14:30:16 +01:00
emily d9eacafc3a fixed the program by moving the example module back to where the program can ACTUALLY load it 2025-11-14 14:28:11 +01:00
Hughes, Mike ab737f0dc9 Merge branch 'feature/12-externe-transkription-s2-02b' into 'develop'
feat(S2-02b): Implement AssemblyAI external transcription with speaker diarization

See merge request proj-wise2526-video2document/video2document!18
2025-11-13 17:38:39 +01:00
MikeHughes-BIN 79e0c48755 Reduced Number of test paths to avoid redundancy 2025-11-13 17:35:40 +01:00
MikeHughes-BIN 9254ddc57f Changed the Folder Structure for better maintainability 2025-11-13 17:34:22 +01:00
Azeufack Noupeu Willy c021272ca4 merge: Resolve conflicts with develop 2025-11-13 15:22:33 +01:00
Azeufack Noupeu Willy e7e97a7f60 feat(S2-02b): Implement AssemblyAI external transcription with speaker diarization
- Add assembly.ts module for REST API transcription via AssemblyAI
- Implement 5-step pipeline: upload → create job → poll status → download → save
- Enable speaker_labels for diarization (Speaker A, B, C...)
- Add millisecond-precision timestamps for each utterance
- Store JSON transcripts in storage/transcripts/{session_id}.json
- Add axios, dotenv dependencies
- Add transcribeLatest.ts helper for quick testing

User Story: S2-02b - Externe Transkription per REST API
2025-11-13 13:07:18 +01:00
eric.minning 1a681eb2b8 Improved error log and implemented a variable and function for file path storage 2025-11-12 20:14:56 +01:00
62 changed files with 18682 additions and 418 deletions
BIN
View File
Binary file not shown.
+16 -2
View File
@@ -963,9 +963,23 @@ app.*.symbols
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock
#Storage files
storage/
# Storage files
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
*.webm
*.mp3
*.mov
*.wav
*.flac
!testvideo.mp4
+20 -25
View File
@@ -1,33 +1,28 @@
workflow:
rules:
# Run the pipeline for merge requests or when committing to a branch
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH
image: python:3.14.0
image: node:latest
stages:
- setup
# - install
- test
setup_environment:
stage: setup
script:
- pip install --upgrade pip
- pip install -r requirements.txt
- echo "Dependencies installed successfully."
# job-install:
# stage: install
# script:
# - npm install
# artifacts:
# 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
script:
- echo "Running V2D Framework basic test..."
- python -m unittest discover || echo "No tests found."
only:
- main
- feature/ci-pipeline-s1-09a-1
- npm install
- echo "ASSEMBLYAI_API_KEY=$apikey_assembly" > .env
- echo "GOOGLE_API_KEY=$apikey_gemini" >> .env
- echo "SAIA_API_KEY=$apikey_saia" >> .env
- npm test
+44 -81
View File
@@ -1,93 +1,56 @@
# 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
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
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.
Follow the steps below to install Video2Document (v2d):
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
### 1. Install Node.js
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
Download and install Node.js from [https://nodejs.org/en](https://nodejs.org/en)
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
### 2. Clone the Repository
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
Show your appreciation to those who have contributed to the project.
Alternatively, you can download the ZIP file and extract it manually.
## License
For open source projects, say how it is licensed.
### 3. Start the Program
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
The start scripts automatically install dependencies and launch the program. Choose the appropriate method for your operating system:
**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.
+161
View File
@@ -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>
+142 -26
View File
@@ -8,45 +8,161 @@
</head>
<body>
<div class="mitte" id="mitte">
<div class="flagsBtns" id="flagsBtns">
<button class="de_Btn" id="de_Btn" onclick="changeLanguage('de')"><img src="flags/germany-flag-png-large.jpg" width="25px" height="20px"/></button>
<button class="eng_Btn" id="eng_Btn" onclick="changeLanguage('en')"><img src="flags/united-kingdom-flag-png-large.jpg" width="25px" height="20px"/></button>
<button class="in_Btn" id="in_Btn" onclick="changeLanguage('in')"><img src="flags/india-flag-png-large.png" width="25px" height="20px"/></button>
</div>
<div id="h1-wrapper">
<section class="p-menu1">
<nav id="navbar" class="navigation" role="navigation">
<input id="toggle1" type="checkbox" />
<label class="hamburger1" for="toggle1">
<div class="top"></div>
<div class="meat"></div>
<div class="bottom"></div>
</label>
<nav class="menu1">
<a href="custom_document.html" class="li1">Custom document</a>
<a href="" class="li1">Help</a>
</nav>
</nav>
</section>
<h1 id="h1">Video to document</h1>
</div>
<div class="step-nav">
<div class="step-item active" data-step="1">1. Step</div>
<div class="step-item" data-step="2">2. Step</div>
<div class="step-item" data-step="3">3. Step</div>
<div class="step-item" data-step="4">4. Step</div>
<div class="step-item" data-step="5">5. Step</div>
<div class="step-item" data-step="6">6. Step</div>
</div>
<div id="middleContainerWrapper" class="middle-container-wrapper">
<button id="prevBtn" class="navBtn" disabled>&larr;</button>
<div class="mitte" id="mitte">
<div class="step" id="step1">
<div class="upload-container" id="uploadContainer">
<p id="p1">Drag and drop video file</p>
<video id="previewThumbnail" autoplay="false">
</video>
<div class="file-name" id="fileName">No video chosen</div>
<div id="thumbnailContainer">
<img id="thumbnailImage" style="display:none;">
</div>
<button class="custom-btn" id="manualUploadBtn">Search video</button>
<input type="file" id="videoUpload" accept="video/*">
<div 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">
<label id="label_format" for="docFormat">Meeting report</label>
</div>
</div>
<div class="checkbox-container">
<input type="checkbox" name="docFormat" id="docFormatSummary">
<label id="label_summary" for="docFormatSummary">Summary with timestamps</label>
<div class="step" id="step2" style="display:none;">
<div class="KI-wrapper">
<label id="labelKI">Select ki:</label>
<select name="ai_type" id="ai_type"></select>
</div>
<div class="transcript-wrap">
<label id="labelTranscription">Select transcription:</label>
<select name="transkript_type" id="transkript_type"></select>
</div>
<div class="type-wrapper">
<label id="labelType">Select type:</label>
<select name="output_type" id="output_type">
<option value="pdf">.pdf</option>
<option value="word">.word</option>
<option value="txt">.txt</option>
</select>
</div>
<div class="language-wrapper">
<label id="labelLanguage">Select language:</label>
<img id="labelLanguageFlag" src="flags/united-kingdom-flag-png-large.jpg" width="20" height="10" >
<select name="language_option" id="language_option">
</select>
</div>
</div>
<div class="step" id="step3" style="display:none;">
<div class="checkbox-group">
<label id="checkbox-label" for="checkbox-group">Choose prefered document style:</label>
<div class="checkbox-container">
<input type="checkbox" name ="docFormat" id="docFormat" value="followup-report">
<label id="label_format" for="docFormat">Follow-up Report</label>
</div>
<div class="checkbox-container">
<input type="checkbox" name="docFormat" id="docFormatSummary1" value="agenda">
<label id="label_summary" for="docFormatSummary">Agenda</label>
</div>
<div class="checkbox-container">
<input type="checkbox" name="docFormat" id="docFormatSummary2" value="result-protocol">
<label id="label_summary" for="docFormatSummary">Resultprotocol</label>
</div>
<div class="checkbox-container">
<input type="checkbox" name="docFormat" id="docFormatSummary3" value="sprint-planning">
<label id="label_summary" for="docFormatSummary">Sprint Planning Note</label>
</div>
<div class="checkbox-container">
<input type="checkbox" name="docFormat" id="docFormatCustom" value="custom">
<select name="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">&rarr;</button>
</div>
<button class="submit-btn" id="submitButton" onclick="checkBoxes()">Submit</button>
<div class="progressbar">
<div class="progress_fill"></div>
<span class="progress_text">0%</span>
</div>
</div>
<script src="script.js"></script>
<script src="./renderer.js"></script>
<script src="languages.js"></script>
<script src="script.js"></script>
<script src="./renderer.js"></script>
</body>
</html>
+48
View File
@@ -0,0 +1,48 @@
var languageOptions = {
"eng":{
"flagPath": "flags/united-kingdom-flag-png-large.jpg",
"labelKI": "Select ki:",
"labelTranscription": "Select transcription:",
"labelLanguage": "Select language:",
"title": "Video to document",
"h1": "Video to document",
"p1": "Drag and drop video file",
"fileName": "No video chosen",
"manualUploadBtn": "Search video",
"checkbox_group": "Choose prefered document style:",
"label_format": "Meeting report",
"label_summary": "Summary with timestamps",
"submitButton": "Submit"
},
"de":{
"flagPath": "flags/germany-flag-png-large.jpg",
"labelKI": "Waehle KI:",
"labelTranscription": "Waehle Transkription:",
"labelLanguage": "Waehle Sprache:",
"title": "Video zu Dokument",
"h1": "Video zu Dokument",
"p1": "Video per Drag & Drop ablegen",
"fileName": "Kein Video ausgewaehlt",
"manualUploadBtn": "Video suchen",
"checkbox_group": "Bevorzugte Dokumentvarianten:",
"label_format": "Meeting Bericht",
"label_summary": "Zusammenfassung mit Zeitstempeln",
"submitButton": "Absenden"
},
"in":{
"flagPath": "flags/india-flag-png-large.png",
"labelKI": "की का चयन करें:",
"labelTranscription": "प्रतिलेखन चुनें:",
"labelLanguage": "भाषा चुने:",
"title": "दस्तावेज़ के लिए वीडियो",
"h1": "दस्तावेज़ के लिए वीडियो",
"p1": "वीडियो फ़ाइल खींचें और छोड़ें",
"fileName": "कोई वीडियो नहीं चुना गया",
"manualUploadBtn": "वीडियो खोजें",
"checkbox_group": "पसंदीदा दस्तावेज़ शैली चुनें:",
"label_format": "बैठक रिपोर्ट",
"label_summary": "टाइमस्टैम्प के साथ सारांश",
"submitButton": "जमा करना"
}
};
+24 -5
View File
@@ -1,18 +1,37 @@
const { contextBridge, ipcRenderer, webUtils } = require('electron')
try {
contextBridge.exposeInMainWorld("explorer", {
onFileDrop: (file) => webUtils.getPathForFile(file)
})
contextBridge.exposeInMainWorld("extractor", {
extract: (file) => ipcRenderer.send("extract", file)
contextBridge.exposeInMainWorld("submit", {
submit: (meeting_specifications) => {ipcRenderer.send("file_submit", meeting_specifications)}
})
contextBridge.exposeInMainWorld("electronAPI", {
getFilePath: (file) => {return webUtils.getPathForFile(file)}
})
contextBridge.exposeInMainWorld("onStartup", {
getModuleNames: () => ipcRenderer.invoke('get-module-names')
})
contextBridge.exposeInMainWorld('electron', {
progress: (callback) => ipcRenderer.on('progress', callback)
})
contextBridge.exposeInMainWorld('audios', {
speakerAudios: (callback) => ipcRenderer.on('speakerAudios', callback)
})
contextBridge.exposeInMainWorld("submitSpeaker", {
speaker_submit: (speaker_names) => {ipcRenderer.send("speaker_submit", speaker_names)}
})
contextBridge.exposeInMainWorld("download", {
file_download: () => {ipcRenderer.send("file_download")}
})
ipcRenderer.on("error", (event, err) => {alert(err)})
} catch (error) {
console.log("Error in preload.js");
}
+217 -5
View File
@@ -16,16 +16,228 @@ uploadContainer.addEventListener("drop", (e) => {
e.preventDefault()
const files = e.dataTransfer.files
const filePath = window.explorer.onFileDrop(files[0])
var holdy = filePath + "";
if(holdy.endsWith(".mp4") || holdy.endsWith(".mov") || holdy.endsWith(".avi") || holdy.endsWith( ".mkv")){
console.log(filePath)
const testEndings = [".mp4", ".mov", ".avi", ".mkv"];
var pathToLower = filePath.toLowerCase();
if(testEndings.some(e => pathToLower.endsWith(e))){
const files1 = e.dataTransfer.files;
handleFiles(files1);
}else{
alert('The given file is not compatible. These are the available types: [".mp4", ".mov", ".avi", ".mkv"].');
}
} catch (error) {
console.log("Error in renderer.js with the listerner for the drop function");
console.log(error);
}
})
window.addEventListener('load', async (e) => {
try {
loadLanguageOptions();
const value = await window.onStartup.getModuleNames();
loadAiOptions(value.ai_modules);
loadTranscriptionOptions(value.transcription_modules);
} catch (error) {
}
});
language_option.addEventListener('change', (e)=>{
try {
const select = document.getElementById('language_option');
changeLanguage(select.value);
} catch (error) {
}
});
//listener for the file explorer search when something got selected
videoUpload.addEventListener("change", () => {
try {
if (videoUpload.files.length > 0) {
const file = videoUpload.files;
handleFiles(file);
}
} catch (error) {
console.log(error);
}
});
//listener for the file explorer search
manualUploadBtn.addEventListener('click', () => {
try {
videoUpload.click();
} catch (error) {
console.log("Error in manualBtn EventListener click");
console.log(error);
}
});
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) {
}
};
})
+285 -60
View File
@@ -1,90 +1,102 @@
//listener for the file explorer search
manualUploadBtn.addEventListener('click', () => {
try {
videoUpload.click();
} catch (error) {
console.log("Error in manualBtn EventListener click");
}
});
let currentVideoPath = null;
//function to check if one checkbox is at least klicked
function checkBoxes() {
try {
const checkboxes = document.querySelectorAll('input[name="docFormat"]');
let isChecked = false;
var checkedCounter = 0;
checkboxes.forEach(function(checkbox){
if(checkbox.checked){
isChecked = true;
checkedCounter++;
}
});
if(isChecked){
if (isChecked) {
//Code to submit the video
var pathTest = window.electronAPI.getFilePath(videoUpload.files[0]);
if(pathTest.endsWith(".mp4") || holdy.endsWith(".mov") || holdy.endsWith(".avi") || holdy.endsWith( ".mkv")){
window.extractor.extract({inputVideoPath: pathTest, outputType:"wav"})
var selectedCheckboxes = {};
checkboxes.forEach(function(checkbox){
if(checkbox.checked){
selectedCheckboxes[checkbox.nextElementSibling.textContent] = "";
}
});
const testEndings = [".mp4", ".mov", ".avi", ".mkv"];
var pathTest = window.electronAPI.getFilePath(videoUpload.files[0]);
var pathToLower = pathTest.toLowerCase();
if(testEndings.some(e => pathToLower.endsWith(e))){
//assembly of the json for the main
var typeCheckbox;
if(document.getElementById("docFormat").checked) typeCheckbox = document.getElementById("docFormat").value;
if(document.getElementById("docFormatSummary1").checked) typeCheckbox = document.getElementById("docFormatSummary1").value;
if(document.getElementById("docFormatSummary2").checked) typeCheckbox = document.getElementById("docFormatSummary2").value;
if(document.getElementById("docFormatSummary3").checked) typeCheckbox = document.getElementById("docFormatSummary3").value;
if(document.getElementById("docFormatCustom").checked) typeCheckbox = document.getElementById("docFormatCustom").value;
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);
const outputType = document.getElementById("output_type");
const transcriptionType = document.getElementById("transkript_type");
const aiType = document.getElementById("ai_type");
const sendingPackage = {
"video": {
"module":"extraction-video-to-audio",
"inputVideoPath": pathTest
},
"transcription": {
"module": transcriptionType.value
},
"document": {
"module":aiType.value,
"type": typeCheckbox,
"outputType": outputType.value
}
};
window.submit.submit(sendingPackage)
}else{
alert('The given file is not compatible. These are the available types: [".mp4", ".mov", ".avi", ".mkv"].');
}
} else {
//language only english at the moment
alert('Please select at least one document type.');
}
} catch (error) {
console.log(error)
console.log("Error in script.js checkBoxes function");
console.log(error);
}
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./a.mp4", outputType:"wav"})
}
//language changing feature
function changeLanguage(language) {
if (language === 'en') {
document.getElementById('title').textContent = 'Video to document';
document.getElementById('h1').textContent = 'Video to document';
document.getElementById('p1').textContent = 'Drag and drop video file';
document.getElementById('fileName').textContent = 'No video chosen';
document.getElementById('manualUploadBtn').textContent = 'Search video';
document.getElementById('checkbox_group').textContent = 'Choose prefered document style:';
document.getElementById('label_format').textContent = 'Meeting report';
document.getElementById('label_summary').textContent = 'Summary with timestamps';
document.getElementById('submitButton').textContent = 'Submit';
} else if (language === 'de') {
document.getElementById('title').textContent = 'Video zu Dokument';
document.getElementById('h1').textContent = 'Video zu Dokument';
document.getElementById('p1').textContent = 'Video per Drag & Drop ablegen';
document.getElementById('fileName').textContent = 'Kein Video ausgewaehlt';
document.getElementById('manualUploadBtn').textContent = 'Video suchen';
document.getElementById('checkbox_group').textContent = 'Bevorzugte Dokumentvarianten:';
document.getElementById('label_format').textContent = 'Meeting Bericht';
document.getElementById('label_summary').textContent = 'Zusammenfassung mit Zeitstempeln';
document.getElementById('submitButton').textContent = 'Absenden';
} else if(language == "in") {
document.getElementById('title').textContent = 'दस्तावेज़ के लिए वीडियो';
document.getElementById('h1').textContent = 'दस्तावेज़ के लिए वीडियो';
document.getElementById('p1').textContent = 'वीडियो फ़ाइल खींचें और छोड़ें';
document.getElementById('fileName').textContent = 'कोई वीडियो नहीं चुना गया';
document.getElementById('manualUploadBtn').textContent = 'वीडियो खोजें';
document.getElementById('checkbox_group').textContent = 'पसंदीदा दस्तावेज़ शैली चुनें:';
document.getElementById('label_format').textContent = 'बैठक रिपोर्ट';
document.getElementById('label_summary').textContent = 'टाइमस्टैम्प के साथ सारांश';
document.getElementById('submitButton').textContent = 'जमा करना';
}
}
//listener for the file explorer search when something got selected
videoUpload.addEventListener('change', () => {
try {
handleFiles(videoUpload.files);
document.getElementById('labelLanguageFlag').src = languageOptions[language].flagPath;
document.getElementById('labelKI').textContent = languageOptions[language].labelKI;
document.getElementById('labelTranscription').textContent = languageOptions[language].labelTranscription;
document.getElementById('labelLanguage').textContent = languageOptions[language].labelLanguage;
document.getElementById('title').textContent = languageOptions[language].title;
document.getElementById('h1').textContent = languageOptions[language].h1;
document.getElementById('p1').textContent = languageOptions[language].p1;
document.getElementById('fileName').textContent = languageOptions[language].fileName;
document.getElementById('manualUploadBtn').textContent = languageOptions[language].manualUploadBtn;
document.getElementById('checkbox_group').textContent = languageOptions[language].checkbox_group;
document.getElementById('label_format').textContent = languageOptions[language].label_format;
document.getElementById('label_summary').textContent = languageOptions[language].label_summary;
document.getElementById('submitButton').textContent = languageOptions[language].submitButton;
} catch (error) {
console.log("Error in manualBtn EventListener change");
console.log("Error in script.js changeLanguage function");
console.log(error);
}
});
}
//function to display the file path in the drop down box
function handleFiles(files) {
@@ -92,24 +104,237 @@ function handleFiles(files) {
if (files.length > 0) {
const file = files[0];
if (file.type.startsWith('video/')) {
const filePath = window.explorer.onFileDrop(files[0])
videoUpload.files = files;
fileName.textContent = `Chosen video: ${file.name}`;
currentVideoPath = filePath;
generateThumbnail(filePath);
activateSubmitBtn(true);
}
}
} catch (error) {
console.log("Error in script.js handleFiles function");
console.log(error);
}
}
//function to regulate the progress on the progressbar
function updateProgressBar(bar, value){
function updateProgressBar(bar, value) {
try {
value = Math.round(value);
bar.querySelector(".progress_fill").style.width = `${value}%`;
bar.querySelector(".progress_text").textContent = `${value}%`;
} catch (error) {
console.log("Error in scripts.js updateProgressBar function");
console.log(error);
}
}
//function to load ai options to the drop down list
function loadAiOptions(options){
try {
var menu = document.getElementById('ai_type');
var object_holdy;
var choice ;
object_holdy = options
for(i = 0; i < options.length; i++){
choice = document.createElement('option');
choice.textContent = object_holdy[i].displayname;
choice.value = object_holdy[i].name;
menu.appendChild(choice);
}
} catch (error) {
console.log("Error in script.js loadAiOptions function");
console.log(error);
}
}
//function to load transcription options to the drop down list
function loadTranscriptionOptions(options){
try {
var menu = document.getElementById('transkript_type');
var object_holdy;
var choice ;
object_holdy = options
for(i = 0; i < options.length; i++){
choice = document.createElement('option');
choice.textContent = object_holdy[i].displayname;
choice.value = object_holdy[i].name;
menu.appendChild(choice);
}
} catch (error) {
console.log("Error in script.js function loadTranscriptionOptions");
console.log(error);
}
}
//function to load 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 loadLanguageOptions(){
try {
var menu = document.getElementById('language_option');
var object_holdy;
var choice ;
object_holdy = Object.keys(languageOptions);
for(i = 0; i < object_holdy.length; i++){
choice = document.createElement('option');
choice.textContent = object_holdy[i];
choice.value = object_holdy[i];
menu.appendChild(choice);
}
} catch (error) {
console.log("Error in script.js loadLanguageOptions function");
console.log(error);
}
}
//function to load speaker options to the drop down list
function loadSpeakerOptions(options){
try {
var menu = document.getElementById('cur_speaker');
var l = document.getElementById('cur_speaker').options.length -1;
for(i = l; i >= 0; i--){
menu.remove(i);
}
var object_holdy;
var choice;
object_holdy = Object.keys(options);
for(i = 0; i < object_holdy.length; i++){
choice = document.createElement('option');
choice.textContent = options[object_holdy[i]].name;
choice.value = object_holdy[i];
menu.appendChild(choice);
}
document.getElementById("speakerAudioViewer").src = options.speakerA.src;
} catch (error) {
console.log("Error in script.js loadSpeakerOptions function");
console.log(error);
}
}
//function to load speaker audio file options to the drop down list
function loadSpeakerAudio(option){
try {
var menu = document.getElementById('speakerAudioViewer');
var aud = document.createElement("source");
aud.src = options;
menu.appendChild(aud);
} catch (error) {
console.log("Error in script.js loadSpeakerAudio function");
console.log(error);
}
}
function activateSubmitBtn(hasFile){
try {
document.getElementById("submitButton").disabled = !hasFile;
} catch (error) {
console.log(error);
}
}
function deaktivateProgressbar(){
try {
document.getElementById("progressbar").style.visibility = "hidden";
} catch (error) {
console.log(error);
}
}
//Video thumbnail generation
function generateThumbnail(path){
const videoElement = document.getElementById("previewThumbnail");
while (videoElement.firstChild) videoElement.removeChild(videoElement.firstChild);
videoElement.src = path;
videoElement.type = "video/mov";
videoElement.load();
videoElement.style.maxWidth = 40;
videoElement.style.maxHeight = 40;
videoElement.autoplay = false;
}
//Step-navigation
const steps = document.querySelectorAll(".step");
const stepButtons = document.querySelectorAll(".step-item");
let currentStep = 1;
const totalSteps = steps.length;
function showStep(stepNumber) {
if (stepNumber < 1 || stepNumber > totalSteps){
console.error("StepNumber out of Bounds", stepNumber);
return;
}
steps.forEach(step => step.style.display = "none");
document.getElementById("step" + stepNumber).style.display = "flex";
stepButtons.forEach(btn => btn.classList.remove("active"));
document.querySelector(`.step-item[data-step="${stepNumber}"]`).classList.add("active");
const activeBtn = document.querySelector(`.step-item[data-step="${stepNumber}"]`);
if(activeBtn) activeBtn.classList.add("active");
prevBtn.disabled = stepNumber == 1;
nextBtn.disabled = stepNumber === totalSteps;
currentStep = stepNumber;
}
//Audio value setter
var speakerAudios = {};
var speakerEndValues = {};
function setSpeakerAudiosValue(valy){
try {
speakerAudios = valy;
speakerRewriten = valy;
document.getElementById("speakerAudioViewer").src = valy.speakerA.src;
} catch (error) {
}
}
function rewriteSpeakerName(){
try {
var tempy = document.getElementById("cur_speaker").value;
speakerAudios[tempy].name = document.getElementById("newSpeaker").value;
loadSpeakerOptions(speakerAudios);
} catch (error) {
console.log("\n\n\n" + error + "\n\n\n")
}
}
function sendSpeakerPackages(){
try {
window.submitSpeaker.speaker_submit(speakerAudios);
} catch (error) {
}
}
function fileDownload() {
try {
window.download.file_download();
} catch (error) {
console.error("Download failed:", error);
}
}
+399 -33
View File
@@ -5,11 +5,32 @@ body {
justify-content: center;
align-items: center;
height: 100vh;
background-color: #555;
gap: 15px;
background-color: #f2f3f4;
gap: 10px;
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 {
background: white;
@@ -17,9 +38,10 @@ body {
border-radius: 12px;
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
text-align: center;
width: 400px;
width: 350px;
height: 200px;
transition: border 0.3s, background-color 0.3s;
border: 2px dashed #ccc;
border: 2px dashed #7378c9;
}
@@ -40,11 +62,35 @@ body {
margin-top: 10px;
font-size: 14px;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#thumbnailContainer {
width: 100%;
display: flex;
justify-content: center;
margin-bottom: 15px;
}
#thumbnailImage {
width: 200px;
height: auto;
border-radius: 10px;
box-shadow: 0px 4px 10px rgba(0,0,0,0.1);
object-fit: cover;
}
#previewThumbnail {
width: 150px;
height: 100px;
/*border: 1px dashed black;*/
}
.custom-btn {
padding: 10px 20px;
margin-top: 10px;
background-color: #007BFF;
color: white;
border: none;
@@ -62,28 +108,85 @@ body {
background-color: #0056b3;
}
#step2 {
gap: 30px;
}
.KI-wrapper {
margin-top: 40px;
}
input[type="file"] {
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{
margin-top: 8px;
display: flex;
align-items: center;
gap: 5px;
margin-top: 8px;
display: flex;
justify-items: left;
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 {
display: flex;
align-items: center;
justify-content: center;
padding: 10px 20px;
margin: 130px auto 10px auto;
background-color: #007BFF;
color: white;
border: none;
@@ -92,30 +195,36 @@ gap: 5px;
font-size: 14px;
}
.mitte {
background-color: #f2f3f4;
display: flex;
flex-direction: column;
align-items: center;
padding: 5% 50px;
margin-top: 20px;
gap: 10px;
border: 1px;
border-color: black;
border-style: solid;
.submit-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
h1 {
align-content: center;
.mitte {
background-color: #FFF;
display: flex;
width: 700px;
flex-direction: column;
align-items: center;
padding: 40px;
gap: 10px;
border: 0px;
border-color: black;
border-style: solid;
border-radius: 6px;
box-shadow: 0px 4px 10px rgba(0,0,0,0.1);
}
.progressbar{
position: relative;
width: 210px;
height: 30px;
margin: 50px 20px 5px 20px;
background: rgb(42, 46, 78);
border-radius: 5px;
overflow: hidden;
visibility: hidden;
}
.progress_fill{
@@ -133,15 +242,272 @@ h1 {
color: white;
}
.flagsBtns {
.dropdownMenus {
display: flex;
justify-content: flex-end;
justify-content: center;
margin-top: 1px;
margin-bottom: 30px;
gap: 170px;
padding: 2px 10px 2px 10px;
}
.de_Btn, .eng_Btn, .in_Btn {
padding: 8px 16px;
#ai_type, #transkript_type, #language_option {
padding: 3px;
}
.labelDiv {
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;
}
+190 -4
View File
@@ -35,6 +35,8 @@ console.log("-------------------------------------------------------------------
// --------------------------------------------------------- CLI COMMANDS --------------------------------------------------------- //
const rl = readline.createInterface({
@@ -59,8 +61,8 @@ let mainWindow;
function createWindow() {
mainWindow = new electron.BrowserWindow({
width: 800,
height: 600,
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
@@ -74,6 +76,190 @@ function createWindow() {
electron.app.whenReady().then(createWindow);
electron.ipcMain.on("extract", (event, args) => {
mapFunctions.get("extraction-video-to-audio").function(args)
// electron.ipcMain.on("extract", (event, args) => {
// mapFunctions.get("extraction-video-to-audio").function(args)
// })
// setTimeout(() => {
// mainWindow.webContents.send("fuck", "worked uwu")
// }, 5000);
electron.ipcMain.handle('get-module-names', async () => {
let module_array = {
"ai_modules":[],
"transcription_modules":[]
}
mapFunctions.forEach(e => {
switch(e.type){
case "llm":
module_array.ai_modules.push({"name": e.name, "displayname": e.displayname})
break;
case "transcription":
module_array.transcription_modules.push({"name": e.name, "displayname": e.displayname})
break;
}
})
// console.log(module_array);
return module_array
});
// electron.ipcMain.on("get_modules", async (event, args) => {
// let module_array = {
// "ai_modules":[],
// "transcription_modules":[]
// }
// mapFunctions.forEach(e => {
// switch(e.type){
// case "llm":
// module_array.ai_modules.push({"name": e.name, "displayname": e.displayname})
// break;
// case "transcription":
// module_array.transcription_modules.push({"name": e.name, "displayname": e.displayname})
// break;
// }
// })
// console.log(module_array);
// mainWindow.webContents.send("modules", module_array)
// })
var globalArgs = {}
var globalFinalHtmlPath = ""
electron.ipcMain.on("file_submit", async (event, args) => {
try {
globalArgs = args
let curstep = 0
let totalsteps = 4
const TEMPLATE_MAP = {
"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);
let audiopath = ""
let transcriptpath = ""
console.log("\n\n Running the Video to Audio Extractor");
// This code handles the Video to Audio extraction module call
await mapFunctions.get("module-handler").function(args.video.module, {inputVideoPath: args.video.inputVideoPath, outputType: mapFunctions.get(args.transcription.module).audioformat}).then(resp => {
console.log(resp);
audiopath = resp
curstep++
mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
}).catch(err => {
mainWindow.webContents.send("error", err)
console.log(err);
return
})
console.log("\n\n Running the Audio to Transcription module");
// TODO implement transcription module
// This code handles the Audio to Text transcription module call
await mapFunctions.get("module-handler").function(args.transcription.module, audiopath).then(resp => {
console.log(resp);
transcriptpath = resp
curstep++
mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
}).catch(err => {
mainWindow.webContents.send("error", err)
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
// This code handles the Text to Document processing module call
console.log(`\n\n Running the LLM for Document Style ${args.document.type}`);
await mapFunctions.get("module-handler").function(args.document.module, { inputTranscriptPath: transcriptpath, documentTypePath: "./storage/documentType/" + templateFile, language: "en" }).then(resp => {
console.log(resp);
globalFinalHtmlPath = resp
curstep++
mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
}).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) {
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 =
{
video: {
module: "String", // The name of the module, idk if we ever implement other extraction modules, the default one is extraction-video-to-audio
inputVideoPath: "String", // See script.js on line 27 for an example of what this should look like
outputType: "String" // The file format to be used for the audio output file, such as wav, mp3, flac and so on
},
transcription:{
module: "String" // The module name of the transcription model you want to use
},
document:{
module: "String", // The module name of the AI model you want to use to create the document
styles: [ // An array of all the document styles/prompts you want to have the document be processed with
{
prompt: "String",
}
]
}
}
let q1 = {
"ai_modules": [
{name:"abc", displayname:"ABC"},
{name:"qeg", displayname:"aqghegahu"}
],
"transcription_modules": [
{name:"abc", displayname:"ABC"},
{name:"qeg", displayname:"aqghegahu"}
]
}
+2716 -40
View File
File diff suppressed because it is too large Load Diff
+10 -4
View File
@@ -1,10 +1,16 @@
{
"dependencies": {
"cli-progress": "^3.12.0",
"@google/genai": "^1.30.0",
"@types/axios": "^0.9.36",
"axios": "^1.13.2",
"dotenv": "^17.2.3",
"electron": "^39.1.1",
"express": "^5.1.0",
"ffmpeg-static": "^5.2.0",
"fluent-ffmpeg": "^2.1.3"
"ffmpeg-static": "^5.3.0",
"fluent-ffmpeg": "^2.1.3",
"html-to-docx": "^1.8.0",
"mocha": "^11.7.5",
"puppeteer": "^24.33.0"
},
"devDependencies": {
"@types/cli-progress": "^3.11.6",
@@ -23,7 +29,7 @@
},
"scripts": {
"start": "electron main.js",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "mocha ./test/unit/test.js"
},
"repository": {
"type": "git",
-3
View File
@@ -1,3 +0,0 @@
fastapi
uvicorn
pytest
+8 -1
View File
@@ -6,13 +6,20 @@ platform = process.platform
mainDir = __dirname
fs = require("fs")
readline = require("readline")
puppeteer = require("puppeteer")
htmltodocx = require("html-to-docx")
config = require("./config/config")
ffmpegPath = require('ffmpeg-static');
ffmpeg = require('fluent-ffmpeg');
path = require('path');
cliProgress = require('cli-progress');
// cliProgress = require('cli-progress');
// { app, BrowserWindow, ipcMain, dialog } = require('electron');
electron = require('electron');
axios = require("axios")
console.log(require('dotenv').config({path: __dirname + '/.env'}));
View File
-5
View File
@@ -1,5 +0,0 @@
npx ts-node ./extract.ts /Users/mikehughes/Downloads/Testvideo/Kurzgesagt.mov
npx ts-node ./transcribe.ts ../storage/audio/Kurzgesagt.wav
npx ts-node ./extract.ts /Users/mikehughes/Downloads/Testvideo/GitLabMeeting.mov
npx ts-node ./transcribe.ts ../storage/audio/GitLabMeeting.wav
-23
View File
@@ -1,23 +0,0 @@
#!/usr/bin/env ts-node
import { extractAudioFromVideo } from "../services/modules/extraction/ffmpegExtractor.ts";
const videoPath = process.argv[2];
if (!videoPath) {
console.error("Usage: ts-node extractAudio.ts <videoPath>");
process.exit(1);
}
(async () => {
try {
console.log(`Extracting audio from: ${videoPath}`);
await extractAudioFromVideo(videoPath); // Call the extraction function (ffmpegExtractor.ts in services/modules/extraction)
console.log("Audio extraction completed successfully.");
} catch (err) {
console.error("Audio extraction failed:", err);
process.exit(1);
}
})();
+47
View File
@@ -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);
}
})();
-18
View File
@@ -1,18 +0,0 @@
import { whisperLocal } from "../services/modules/transcription/local/whisperLocal.ts";
const audioPath = process.argv[2];
if (!audioPath) {
console.error("Please provide an audio file path as argument.");
process.exit(1);
}
const whisper = new whisperLocal();
(async () => {
try {
const text = await whisper.transcribe(audioPath);
console.log(text);
} catch (err) {
console.error("Transcription failed:", err);
}
})();
@@ -0,0 +1,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");
})
}
};
+197
View File
@@ -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);
}
})();
}
-8
View File
@@ -1,8 +0,0 @@
module.exports = {
name:"chatgpt", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
type:"document", // value used to differentiate each module to order them in the UI
displayname:"ChatGPT", // The displayname used within the UI
async function(parameter){
// TODO add code to actually send the transcript to ChatGPT and get a response back
}
}
+9 -28
View File
@@ -1,4 +1,3 @@
// Ensure ffmpeg binary is available
if (!ffmpegPath) {
throw new Error('FFmpeg binary not found!');
@@ -25,18 +24,12 @@ module.exports = {
outputType: String // Audio file output format
}
*/
let progressBar = new cliProgress.SingleBar({
format: 'Processing |{bar}| {percentage}% | {timemark}',
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
hideCursor: true
});
try {
// if (meta.url === `file://${process.argv[1]}`) {
this.extractAudioFromVideo(parameter.inputVideoPath, progressBar, parameter.outputType)
.then(() => console.log('Audio extraction successful.'))
.catch((err) => console.error(err));
// }
return new Promise((resolve, reject) => {
this.extractAudioFromVideo(parameter.inputVideoPath, parameter.outputType)
.then((resp) => resolve(resp))
.catch((err) => {reject(err)});
})
} catch (error) {
console.log(parameter.outputType);
@@ -53,7 +46,7 @@ module.exports = {
* - Shows CLI progress bar
* - 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 outputAudioPath = path.join(outputDir, `${inputVideoName}.${outputType}`);
@@ -64,30 +57,18 @@ module.exports = {
// .audioCodec('pcm_s16le')
.audioChannels(1)
.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', () => {
progressBar.update(100, { timemark: 'done' });
progressBar.stop();
console.log(`Extraction completed: ${outputAudioPath}`);
resolve();
resolve(outputAudioPath);
})
.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);
})
.save(outputAudioPath);
} 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);
}
})
}
}
+137
View File
@@ -0,0 +1,137 @@
// const fs = require('fs');
// const path = require('path');
const outputDir = path.join(__dirname, "../../../storage/documents"); // path for output directory
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true }); // Create output directory if it doesn't exist
}
// Ensure SAIA API key is set in environment variables: export SAIA_API_KEY="your_api_key_here"
const SAIA_API_KEY = process.env.SAIA_API_KEY; // Ensure SAIA API key is set in environment variables
const SAIA_URL = "https://chat-ai.academiccloud.de/v1/chat/completions"; // URL for the REST call, used model and action
const module_exports = {
name: "llm-saia_openai_gpt",
type: "llm",
displayname: "GPT 120B",
description: "Generates documents using OpenAI GPT OSS 120B via SAIA platform",
async function(parameter) {
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!");
})();
}
+134
View File
@@ -0,0 +1,134 @@
const fs = require('fs');
const path = require('path');
const outputDir = path.join(__dirname, "../../../storage/documents"); // path for output directory
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true }); // Create output directory if it doesn't exist
}
// Ensure Google API key is set in environment variables: export GOOGLE_API_KEY="your_api_key_here"
const GEMINI_API_KEY = process.env.GOOGLE_API_KEY; // Ensure Google API key is set in environment variables: export GOOGLE_API_KEY="your_api_key_here"
const GEMINI_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent"; // URL for the REST call, used model and action
const module_exports = {
name: "llm-gemini",
type: "llm",
displayname: "Gemini LLM",
description: "Generates documents using Google Gemini LLM",
async function(parameter) {
return new Promise(async (resolve, reject) => {
try {
// console.log("Gemini LLM module invoked with parameters:", parameter);
resolve(await this.createDocumentFromTranscript( //Call the function to create document with transcript, document type and language
parameter.inputTranscriptPath, // Path to input transcript file
parameter.documentTypePath, // Path to document type file which is chosen in the front end by the user
parameter.language // Language for the document which is chosen in the front end by the user
));
} catch (error) {
// console.error("Error in Gemini LLM module:", error);
reject(error)
}
})
},
createDocumentFromTranscript: async function(transcriptPath, documentTypePath, language = "en") { // default language is English
return new Promise(async(resolve, reject) => {
try {
const transcript = await fs.promises.readFile(transcriptPath, "utf-8"); //read transcript file from Path
const documentType = await fs.promises.readFile(documentTypePath, "utf-8"); //read document type from Path
const promptText = `${documentType}, in language ${language}, transcript:\n\n${transcript}`; //combine doc type, language and transcript - Change prompt here if needed
// --- REST CALL ---
const response = await fetch(`${GEMINI_URL}?key=${GEMINI_API_KEY}`, { //safe model response in variable
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
contents: [
{
parts: [
{ text: promptText } // Input prompt for content generation
]
}
]
})
});
if (!response.ok) { //ok is true when a responce was successfull
const text = await response.text();
throw new Error(`Gemini API error (${response.status}): ${text}`);
}
const data = await response.json();
// Get generated text from response or default to empty string (if null)
const output = data?.candidates?.[0]?.content?.parts?.[0]?.text || "";
let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath)); // Name for the output file
// console.log(inputTranscriptName);
const outPath = path.join(outputDir, `${inputTranscriptName}.html`); // Output file path & name to make naming dynamic. Pulled from input transcript name
fs.writeFileSync(outPath, output, "utf8"); // Write output to file
// console.log("Generated document written to:", outPath);
resolve(outPath)
} catch (error) {
// console.error("Error generating Gemini content:", error);
reject(error)
}
})
}
};
module.exports = module_exports;
// CLI Mode: Allow direct execution
if (require.main === module) {
(async () => {
const args = process.argv.slice(2);
if (args.length < 2) {
console.error("Usage: node llm-gemini.js <transcriptPath> <documentTypePath> [language]");
console.error("Example: node llm-gemini.js ./transcript.json ./docType.txt de");
process.exit(1);
}
const [transcriptPath, documentTypePath, language] = args;
// Check if API key is set
if (!GEMINI_API_KEY) {
console.error("ERROR: GOOGLE_API_KEY environment variable is not set!");
console.error("Please set it with: export GOOGLE_API_KEY='your_api_key_here'");
process.exit(1);
}
// Check if files exist
if (!fs.existsSync(transcriptPath)) {
console.error(`ERROR: Transcript file not found: ${transcriptPath}`);
process.exit(1);
}
if (!fs.existsSync(documentTypePath)) {
console.error(`ERROR: Document type file not found: ${documentTypePath}`);
process.exit(1);
}
console.log("Starting document generation...");
console.log(`Transcript: ${transcriptPath}`);
console.log(`Document Type: ${documentTypePath}`);
console.log(`Language: ${language || 'en (default)'}`);
await module_exports.createDocumentFromTranscript(
transcriptPath,
documentTypePath,
language || 'en'
);
console.log("Done!");
})();
}
+136
View File
@@ -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!");
})();
}
@@ -5,7 +5,7 @@ import { fileURLToPath } from "url"; // To handle __dirname in ES modules
const __filename = fileURLToPath(import.meta.url); // Get current file path
const __dirname = path.dirname(__filename); // Get current directory path
const transcriptsDir = path.resolve(__dirname, "../../storage/transcriptions");
const transcriptsDir = path.resolve(__dirname, "../../../storage/transcriptions");
export class whisperLocal { // is called by transcribe.ts
@@ -26,7 +26,6 @@ export class whisperLocal { // is called by transcribe.ts
async transcribe(audioPath: string): Promise<string> { //asyncronous function to transcribe audio
return new Promise((resolve, reject) => {
const transcriptsDir = path.resolve(__dirname, "../../../../storage/transcripts"); //storage directory for transcripts
if (!fs.existsSync(transcriptsDir)) { //if transcripts directory does not exist, create it
fs.mkdirSync(transcriptsDir, { recursive: true });
@@ -0,0 +1,125 @@
const API_KEY = process.env.ASSEMBLYAI_API_KEY;
const BASE_URL = 'https://api.assemblyai.com/v2';
//---------------------------------------------------Upload audio---------------------------------------------------
async function uploadAudio(audioPath) {
const audioData = fs.readFileSync(audioPath);
const response = await axios.post(`${BASE_URL}/upload`, audioData, {
headers: {
authorization: API_KEY,
'content-type': 'application/octet-stream'
}
});
return response.data.upload_url;
}
////---------------------------------------------------Extract session id---------------------------------------------------
function getSessionId(inputPath) {
try {
const parsed = new URL(inputPath);
const base = path.basename(parsed.pathname);
return base.replace(/\.[^.]+$/, '');
} catch {
return path.basename(inputPath, path.extname(inputPath));
}
}
//---------------------------------------------------Create transcript---------------------------------------------------
async function createTranscript(audioUrl) {
const response = await axios.post(
`${BASE_URL}/transcript`,
{
audio_url: audioUrl,
speaker_labels: true,
language_detection: true
},
{
headers: {
authorization: API_KEY,
'content-type': 'application/json'
}
}
);
return response.data.id;
}
//---------------------------------------------------Poll transcript---------------------------------------------------
async function pollTranscript(transcriptId) {
while (true) {
const response = await axios.get(`${BASE_URL}/transcript/${transcriptId}`, {
headers: { authorization: API_KEY }
});
const status = response.data.status;
if (status === 'completed') return response.data;
if (status === 'error') throw new Error(`Transcription failed: ${response.data.error}`);
await new Promise(res => setTimeout(res, 3000));
}
}
//---------------------------------------------------Save transcript---------------------------------------------------
function saveTranscript(transcript, sessionId) {
const outputDir = path.join(__dirname, '../../../storage/transcripts');
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
const outputPath = path.join(outputDir, `${sessionId}.json`);
fs.writeFileSync(outputPath, JSON.stringify(transcript, null, 2));
// console.log(`Transcript saved: ${outputPath}`);
return outputPath;
}
//---------------------------------------------------Modul---------------------------------------------------
module.exports = {
name: 'assembly',
type: 'transcription',
displayname: 'AssemblyAI',
audioformat: "mp3",
async function(audioFileName) {
return new Promise(async (resolve, reject) => {
try {
// audioFileName ist nur "datei.mp3"
const audioPath = audioFileName;
let audioUrl;
if (/^https?:\/\//i.test(audioFileName)) {
audioUrl = audioFileName;
} else {
if (!fs.existsSync(audioPath)) {
throw new Error(`Audio file not found: ${audioPath}`);
}
audioUrl = await uploadAudio(audioPath);
}
const transcriptId = await createTranscript(audioUrl);
const transcript = await pollTranscript(transcriptId);
const sessionId = getSessionId(audioFileName);
resolve(saveTranscript(transcript, sessionId));
} catch (error) {
// console.error('Transcription error:', error.message);
reject(error);
return
}
})
}
};
@@ -0,0 +1,133 @@
import 'dotenv/config';
import axios from 'axios';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const API_KEY = process.env.ASSEMBLYAI_API_KEY;
const BASE_URL = 'https://api.assemblyai.com/v2';
/**
* Uploads audio file to AssemblyAI
*/
async function uploadAudio(audioPath: string): Promise<string> {
const audioData = fs.readFileSync(audioPath);
const response = await axios.post<{ upload_url: string }>(`${BASE_URL}/upload`, audioData, {
headers: {
'authorization': API_KEY,
'content-type': 'application/octet-stream'
}
});
return response.data.upload_url;
}
/**
* Extract a session id (basename without extension) from a local path or a URL
*/
function getSessionId(inputPath: string): string {
try {
const parsed = new URL(inputPath);
const base = path.basename(parsed.pathname);
return base.replace(/\.[^.]+$/, '');
} catch (err) {
// not a URL, treat as local path
return path.basename(inputPath, path.extname(inputPath));
}
}
/**
* Creates transcription job with speaker diarization
*/
async function createTranscript(audioUrl: string): Promise<string> {
const response = await axios.post<{ id: string }>(`${BASE_URL}/transcript`, {
audio_url: audioUrl,
speaker_labels: true,
language_detection: true
}, {
headers: {
'authorization': API_KEY,
'content-type': 'application/json'
}
});
return response.data.id;
}
/**
* Polls transcript status until completed
*/
async function pollTranscript(transcriptId: string): Promise<any> {
while (true) {
const response = await axios.get<any>(`${BASE_URL}/transcript/${transcriptId}`, {
headers: { 'authorization': API_KEY }
});
const status = response.data.status;
if (status === 'completed') {
return response.data;
} else if (status === 'error') {
throw new Error(`Transcription failed: ${response.data.error}`);
}
// Wait 3 seconds before next poll
await new Promise(resolve => setTimeout(resolve, 3000));
}
}
/**
* Saves transcript to storage
*/
function saveTranscript(transcript: any, sessionId: string): void {
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}`);
}
export default {
name: "assembly",
type: "transcription",
displayname: "AssemblyAI",
run: async (audioPath: string) => {
try {
// Determine if audioPath is an external URL or a local file
let audioUrl: string;
if (/^https?:\/\//i.test(audioPath)) {
console.log('🔗 Using external audio URL...');
audioUrl = audioPath;
} else {
console.log('🔄 Uploading local audio...');
if (!fs.existsSync(audioPath)) {
throw new Error(`Audio file not found: ${audioPath}`);
}
audioUrl = await uploadAudio(audioPath);
}
console.log('🔄 Creating transcript job...');
const transcriptId = await createTranscript(audioUrl);
console.log('⏳ Waiting for transcription...');
const transcript = await pollTranscript(transcriptId);
const sessionId = getSessionId(audioPath);
saveTranscript(transcript, sessionId);
return transcript;
} catch (error: any) {
console.error('❌ Transcription error:', error.message);
throw error;
}
}
};
@@ -1,8 +0,0 @@
module.exports = {
name:"assembly", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
type:"transcription", // value used to differentiate each module to order them in the UI
displayname:"Assembly", // The displayname used within the UI
async function(parameter){
// TODO add code to actually process the audio file
}
}
+12
View File
@@ -1,3 +1,4 @@
module.exports = {
name:"Startup_function",
async function(){
@@ -6,8 +7,19 @@ module.exports = {
// We are now calling the example function from the example folder
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:"./b.mp4", outputType:"wav"})
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./b.mp4", outputType:"flac"})
// mapFunctions.get("ipc-handler").function("extraction-video-to-audio", {inputVideoPath:"./a.mp4", outputType:"flac"})
}
}
@@ -0,0 +1,16 @@
module.exports = {
name:"module-handler", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
async function(module,parameter){
return new Promise(async (resolve, reject) => {
if(mapFunctions.get(module) == undefined){
reject("requested modules not found")
return
}
mapFunctions.get(module).function(parameter).then((result) => {
resolve(result)
}).catch((err) => {
reject(err)
});
})
}
}
@@ -0,0 +1,53 @@
// services/pipeline/jobs/transcribeLatest.ts
import path from 'path';
import fs from 'fs';
// @ts-ignore: module has no type declarations or cannot be resolved in current TS config
import assembly from '../../modules/transcription/assembly';
/**
* Finds the most recently modified .wav file in storage/audio/
*/
function getLatestWav(): string {
const audioDir = path.join(process.cwd(), 'storage', 'audio');
const files = fs.readdirSync(audioDir).filter(f => f.toLowerCase().endsWith('.wav'));
if (files.length === 0) throw new Error('⚠️ No .wav file found in storage/audio');
const newest = files
.map(f => ({ f, t: fs.statSync(path.join(audioDir, f)).mtimeMs }))
.sort((a, b) => b.t - a.t)[0].f;
return path.join(audioDir, newest);
}
/**
* Full transcription pipeline according to the defined workflow:
* 1. Audio Upload → AssemblyAI
* 2. Job Creation (transcript_id)
* 3. Polling Status (queued → processing → completed)
* 4. Download Transcript JSON
* 5. Storage: /transcripts/{session_id}.json
*/
async function main() {
const audioPath = getLatestWav();
console.log('1️⃣ Audio Upload → AssemblyAI');
console.log(' Source:', audioPath);
console.log('2️⃣ Job Creation (transcript_id)');
console.log('3️⃣ Polling Status (queued → processing → completed)');
console.log('4️⃣ Download Transcript JSON');
console.log('5️⃣ Storage: /transcripts/{session_id}.json');
// Execute the transcription process via the AssemblyAI module
const result = await assembly.run(audioPath);
console.log('✅ Transcription completed successfully');
console.log('🆔 Transcript ID:', result.id);
console.log('📁 Transcript file saved under: storage/transcripts/');
}
// Entry point
main().catch((err) => {
console.error('❌ Transcription pipeline failed:', err.message || err);
process.exit(1);
});
+23
View File
@@ -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
Executable
+15
View File
@@ -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
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+26
View File
@@ -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 (12 Sätze)
- Agenda-Punkte (nummeriert)
- Thema
- Kurzbeschreibung
- Ziel des Punktes (Information, Entscheidung, Diskussion)
STIL:
- Klar, kompakt
- Business-orientiert
- Keine Sprecher- oder Zeitangaben
- Namen aus dem Transkript speakerA, speakerB etc. sollen weiterhin bestehen bleiben wie sie sind und nicht im Dokument ersetzt werden
TRANSKRIPT:
+22
View File
@@ -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:
+45
View File
@@ -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:
+27
View File
@@ -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:
View File
+14
View File
@@ -0,0 +1,14 @@
import 'dotenv/config';
import assemblyModule from '../../services/modules/transcription-remote/assembly.ts';
// Test: URL passed as argument OR local file ./storage/audio/test.wav
const audioPath = process.argv[2] || './storage/audio/test.wav';
assemblyModule.run(audioPath)
.then(result => {
console.log('✅ Success!');
console.log('Transcript ID:', result.id);
})
.catch(error => {
console.error('❌ Error:', error?.message || error);
});
+18
View File
@@ -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);
}
})();
+39
View File
@@ -0,0 +1,39 @@
**Goal:** Generate a structured meeting report (Markdown). **Output ONLY:** final .md. No meta.
```json
{
"FORMAT": "markdown",
"STRUCTURE": {
"titlepage": ["title","date","start","end","duration","location","host","participants"],
"toc": "[section](#anchor) — HH:MM:SS",
"section": {
"h2": "<topic> — HH:MM:SS",
"summary": "1 sentence",
"key_points": "<=5 bullets, quotes optional",
"decisions": "list: text | owner | due",
"actions": "table: id | task | owner | due | status"
},
"exec_summary": "3 short sentences",
"consolidated": ["decisions", "actions"],
"appendix": "optional"
},
"STYLE": {
"tone": "neutral, concise",
"ts_format": "HH:MM:SS",
"no_meta": true
},
"PROCESS": {
"timestamps": "use if present; else estimate minimal",
"speakers": "use labels; else Speaker X",
"long_transcripts": "chunk → summarize → merge",
"unclear": "UNKLAR:<reason>"
},
"JSON_OUTPUT_OPTIONAL": true,
"PROMPT_SNIPPET": "Generate meeting report in markdown using STRUCTURE and STYLE. Output only the report."
}
+7
View File
@@ -0,0 +1,7 @@
const gemini = require("../../../services/modules/llm-gemini/gemini.js");
gemini.function({
inputTranscriptPath: "./transcript.json",
documentTypePath: "./documentType.json",
language: "de"
});
+122
View File
@@ -0,0 +1,122 @@
[
{
"speaker": "A",
"sentence": "Smoke from hundreds of wildfires in Canada is triggering air quality alerts throughout the US Skylines from Maine to Maryland to Minnesota are gray and smoggy. And in some places, the air quality warnings include the warning to stay inside. We wanted to better understand what's happening here and why, so we called Peter DeCarlo, an associate professor in the Department of Environmental Health and Engineering at Johns Hopkins University. Good morning, Professor.",
"start": 240,
"end": 26560
},
{
"speaker": "B",
"sentence": "Good morning.",
"start": 28060,
"end": 28620
},
{
"speaker": "A",
"sentence": "So what is it about the conditions right now that have caused this round of wildfires to affect so many people so far away?",
"start": 29100,
"end": 37100
},
{
"speaker": "B",
"sentence": "Well, there's a couple of things. The season has been pretty dry already, and then the fact that we're getting hit in the US is because there's a couple weather systems that are essentially channeling the smoke from those Canadian wildfires through Pennsylvania into the mid Atlantic and the Northeast and kind of just dropping the smoke there.",
"start": 39100,
"end": 55820
},
{
"speaker": "A",
"sentence": "So what is it in this haze that makes it harmful? And I'm assuming it is harmful.",
"start": 56590,
"end": 60670
},
{
"speaker": "B",
"sentence": "It is, it is. The levels outside right now in Baltimore are considered unhealthy. And most of that is due to what's called particulate matter, which are tiny particles, microscopic, smaller than the width of your hair, that can get into your lungs and impact your respiratory system, your cardiovascular system, and even your neurological, your brain.",
"start": 62350,
"end": 82590
},
{
"speaker": "A",
"sentence": "What makes this particularly harmful? Is it the volume of particulate? Is it something in particular? What is it exactly? Can you just drill down on that a little bit more?",
"start": 83630,
"end": 92190
},
{
"speaker": "B",
"sentence": "Yeah. So the concentration of particulate matter, I was looking at some of the monitors that we have was reaching levels of what are, in science speak, 150 micrograms per meter cubed, which is more than 10 times what the annual average should be in about four times higher than what you're supposed to have on a 24 hour average. And so the concentrations of these particles in the air are just much, much, much higher than we typically see. And exposure to those high levels can lead to a host of health problems.",
"start": 93550,
"end": 123350
},
{
"speaker": "A",
"sentence": "And who is most vulnerable? I noticed that in New York City, for example, they're canceling outdoor activities. And so here it is in the early days of summer and they have to keep all the kids inside. So who tends to be vulnerable in a situation like this?",
"start": 123430,
"end": 135990
},
{
"speaker": "B",
"sentence": "It's the youngest. So children, obviously, whose bodies are still developing, the elderly who are, you know, their bodies are more in decline and they're more susceptible to the health impacts of breathing, the poor air quality. And then people who have pre existing health conditions, people with respiratory conditions or heart conditions, can be triggered by high levels of air pollution.",
"start": 137610,
"end": 156650
},
{
"speaker": "A",
"sentence": "Could this get worse?",
"start": 157450,
"end": 158650
},
{
"speaker": "B",
"sentence": "That's a good question. I mean, I think if in some areas it's much worse than others and it just depends on kind of where the smoke is concentrated. I think New York has some of the higher concentrations right now, but that's going to change as that air moves away from the New York area. But over the course of the next few days, we will see different areas being hit at different times with the highest concentrations.",
"start": 162170,
"end": 183420
},
{
"speaker": "A",
"sentence": "I was going to ask you about.",
"start": 183740,
"end": 184660
},
{
"speaker": "B",
"sentence": "More fires start burning. I don't expect the concentrations to go up too much higher.",
"start": 184660,
"end": 189020
},
{
"speaker": "A",
"sentence": "I was going to ask you how and you started to answer this, but how much longer could this last? Forgive me if I'm asking you to speculate, but what do you think?",
"start": 189100,
"end": 196220
},
{
"speaker": "B",
"sentence": "Well, I think the fires are going to burn for a little bit longer. But the key for us in the US Is the weather system changing. Right now it's the weather systems that are pulling that air into our Mid Atlantic and Northeast region. As those weather systems change and shift, we'll see that smoke going elsewhere and not impact us in this region as much. I think that's going to be the defining factor. I think the next couple days we're going to see a shift in that weather pattern and start to push the smoke away from where we are.",
"start": 198280,
"end": 227480
},
{
"speaker": "A",
"sentence": "And finally, with the impacts of climate change, we are seeing more wildfires. Will we be seeing more of these kinds of wide ranging air quality consequences or circumstances?",
"start": 227930,
"end": 239850
},
{
"speaker": "B",
"sentence": "I mean, that is one of the predictions for climate change. Looking into the future, the fire season is starting earlier and lasting longer and we're seeing more frequent fires. So yeah, this is probably something that we'll be seeing more, more frequently. This tends to be much more of an issue in the western U.S. so the eastern U.S. getting hit right now is a little bit new. But yeah, I think with climate change moving forward, this is something that is going to happen more frequently.",
"start": 241370,
"end": 267570
},
{
"speaker": "A",
"sentence": "That's Peter DeCarlo, associate professor in the Department of Environmental Health and Engineering at Johns Hopkins University. Professor DeCarlo, thanks so much for joining us and sharing this expertise with us.",
"start": 267970,
"end": 278210
},
{
"speaker": "B",
"sentence": "Thank you for having me.",
"start": 279410,
"end": 280530
}
]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
const fs = require('fs');
const transSummarizer = require("../../services/modules/jsonTools/transcriptionSummarizer.js");
// JSON-Datei laden
const inputJson = JSON.parse(fs.readFileSync("./testFile.json", "utf8"));
// Übergabe an den Summarizer
transSummarizer.function({
json: inputJson
});
-8
View File
@@ -1,8 +0,0 @@
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_health():
response = client.get("/health")
assert response.status_code == 200
+243
View File
@@ -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.