diff --git a/electron/main/index.html b/electron/main/index.html index 2e68d1d..0d9587f 100644 --- a/electron/main/index.html +++ b/electron/main/index.html @@ -97,31 +97,33 @@ @@ -194,30 +196,49 @@ diff --git a/electron/main/languages.js b/electron/main/languages.js index fc0b1da..38a31d7 100644 --- a/electron/main/languages.js +++ b/electron/main/languages.js @@ -1,7 +1,7 @@ var languageOptions = { "eng":{ "flagPath": "flags/united-kingdom-flag-png-large.jpg", - "labelKI": "Select ki:", + "labelKI": "Select AI:", "labelTranscription": "Select transcription:", "labelLanguage": "Select language:", "title": "Video to document", @@ -9,7 +9,7 @@ var languageOptions = { "p1": "Drag and drop video file", "fileName": "No video chosen", "manualUploadBtn": "Search video", - "checkbox_group": "Choose prefered document style:", + "checkbox_group": "Choose preferred document style:", "label_format": "Meeting report", "label_summary": "Summary with timestamps", "submitButton": "Submit", @@ -27,7 +27,7 @@ var languageOptions = { "speakerResender": "Rewrite document", "downloadButton": "Download", "box1_p1": "---Starting---", - "box2_p2": "---Transkribing---", + "box2_p2": "---Transcribing---", "box3_p3": "---Document creation---", "labelType": "Select document type:", @@ -46,13 +46,13 @@ var languageOptions = { }, "de":{ "flagPath": "flags/germany-flag-png-large.jpg", - "labelKI": "Waehle KI:", - "labelTranscription": "Waehle Transkription:", - "labelLanguage": "Waehle Sprache:", + "labelKI": "Wähle KI:", + "labelTranscription": "Wähle Transkription:", + "labelLanguage": "Wähle Sprache:", "title": "Video zu Dokument", "h1": "Video zu Dokument", "p1": "Video per Drag & Drop ablegen", - "fileName": "Kein Video ausgewaehlt", + "fileName": "Kein Video ausgewählt", "manualUploadBtn": "Video suchen", "checkbox_group": "Bevorzugte Dokumentvarianten:", "label_format": "Meeting Bericht", @@ -64,7 +64,7 @@ var languageOptions = { "step_nav4": "Schritt 4", "step_nav5": "Schritt 5", "step_nav6": "Schritt 6", - "h2": "Uploade dein Video hier:", + "h2": "Lade dein Video hier hoch:", "labelSpeaker": "Wähle Sprecher:", "labelSpeakerAudio": "Ausgewählter Sprecher:", "labelSpeakerWriter": "Schreib Namen:", @@ -72,27 +72,27 @@ var languageOptions = { "speakerResender": "Überschreibe Dokument", "downloadButton": "Download", "box1_p1": "---Startet---", - "box2_p2": "---Transkribing---", - "box3_p3": "---Dokument kreieren---", - "labelType": "Wähle Dokumenttype:", + "box2_p2": "---Transkribierung---", + "box3_p3": "---Dokument erstellen---", + "labelType": "Wähle Dokumenttyp:", "customDocBtn": "Dokumenttypen verwalten", "cd_h1": "Dokumenttypen verwalten", "cd_existingDocs": "Vorhandene Dokumente auswählen (optional):", - "cd_docName": "Dokument Name", + "cd_docName": "Dokumentname", "docName": "Geben Sie hier den Dokumentnamen ein", "cd_promt": "Ihr Prompt:", "prompt": "Geben Sie hier die Eingabeaufforderung für Ihr Dokument ein...", "goBackBtn": "Zurück", "deleteBtn": "Lösche Dokument", - "generateBtn": "Speicher Dokument", + "generateBtn": "Speichere Dokument", "newDoc": "-- Neues Dokument erstellen --" }, "in":{ "flagPath": "flags/india-flag-png-large.png", - "labelKI": "की का चयन करें:", + "labelKI": "KI का चयन करें:", "labelTranscription": "प्रतिलेखन चुनें:", - "labelLanguage": "भाषा चुने:", + "labelLanguage": "भाषा चुनें:", "title": "दस्तावेज़ के लिए वीडियो", "h1": "दस्तावेज़ के लिए वीडियो", "p1": "वीडियो फ़ाइल खींचें और छोड़ें", @@ -101,7 +101,7 @@ var languageOptions = { "checkbox_group": "पसंदीदा दस्तावेज़ शैली चुनें:", "label_format": "बैठक रिपोर्ट", "label_summary": "टाइमस्टैम्प के साथ सारांश", - "submitButton": "जमा करना", + "submitButton": "जमा करें", "step_nav1": "स्टेप 1", "step_nav2": "स्टेप 2", "step_nav3": "स्टेप 3", @@ -110,11 +110,11 @@ var languageOptions = { "step_nav6": "स्टेप 6", "h2": "अपना वीडियो यहां अपलोड करें:", "labelSpeaker": "स्पीकर चुनें:", - "labelSpeakerAudio": "चयनित वक्ता:", + "labelSpeakerAudio": "चयनित स्पीकर:", "labelSpeakerWriter": "नाम लिखें:", "speakerLocker": "स्पीकर का नाम बदलें", - "speakerResender": "दस्तावेज़ पुनः लिखें", - "downloadButton": "डाउनलोड करना", + "speakerResender": "दस्तावेज़ फिर से लिखें", + "downloadButton": "डाउनलोड करें", "box1_p1": "---प्रारंभ---", "box2_p2": "---प्रतिलेखन---", "box3_p3": "---दस्तावेज़ निर्माण---", @@ -127,10 +127,10 @@ var languageOptions = { "docName": "यहां दस्तावेज़ का नाम दर्ज करें", "cd_promt": "आपका संकेत:", "prompt": "अपने दस्तावेज़ के लिए प्रॉम्प्ट यहां टाइप करें...", - "goBackBtn": "वापस करना", - "deleteBtn": "दस्तावेज़ हटाएँ", + "goBackBtn": "वापस जाएं", + "deleteBtn": "दस्तावेज़ हटाएं", "generateBtn": "दस्तावेज़ सहेजें", - "newDoc": "-- नया दस्तावेज़ बनाएँ --" + "newDoc": "-- नया दस्तावेज़ बनाएं --" } diff --git a/electron/main/style.css b/electron/main/style.css index 89750a1..74e9002 100644 --- a/electron/main/style.css +++ b/electron/main/style.css @@ -11,12 +11,12 @@ body { } #h1 { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); + position: static; + transform: none; margin: 0; z-index: 20; + flex: 1; + text-align: center; } #h1-wrapper { @@ -30,6 +30,26 @@ body { margin-bottom: 10px; display: flex; align-items: center; + justify-content: space-between; + padding: 0 20px; + box-sizing: border-box; +} + +.gui-language { + position: absolute; + right: 20px; + top: 50%; + transform: translateY(-50%); + z-index: 100; + pointer-events: auto; +} + +#language_option { + padding: 8px 12px; + border-radius: 4px; + border: 1px solid #ccc; + font-size: 14px; + cursor: pointer; } .upload-container { @@ -85,7 +105,6 @@ body { #previewThumbnail { width: 150px; height: 100px; - /*border: 1px dashed black;*/ } .custom-btn { @@ -108,8 +127,9 @@ body { background-color: #0056b3; } -#step2 { - gap: 25px; +.step h2 { + width: 100%; + text-align: center; } .KI-wrapper { @@ -356,7 +376,10 @@ input[type="file"] { #ai_type, #transkript_type, #language_option { - padding: 3px; + padding: 8px 12px; + border-radius: 4px; + border: 1px solid #ccc; + font-size: 14px; } .labelDiv { @@ -404,7 +427,6 @@ input[type="file"] { .step { margin-top: 40px; margin-bottom: 40px; - ; display: flex; flex-direction: column; min-height: 425px; @@ -579,7 +601,27 @@ li { transition: all 0.3s ease; } -#step2, + + +#step2 { + font-size: larger; + align-items: center; +} + +.step2-form { + width: 100%; + max-width: 420px; + display: flex; + flex-direction: column; + gap: 24px; /* DAS ist dein Spacing */ +} + +.step2-row { + display: flex; + flex-direction: column; + gap: 6px; +} + #step3, #step5 { font-size: larger; @@ -590,7 +632,7 @@ li { } #step5 { - align-items: flex-start; + align-items: center; } .button-group { @@ -614,27 +656,84 @@ li { font-size: 14px; } +.h2 { + font-size: 25px; +} + + +.speaker-container { + width: 100%; + max-width: 700px; + margin-top: 30px; +} + +.speaker-table { + width: 100%; + border-collapse: collapse; + background: white; +} + +.speaker-table tbody tr { + display: flex; + align-items: center; + gap: 20px; + margin-bottom: 25px; + padding: 10px 0; +} + +.label-cell { + flex: 0 0 150px; + text-align: left; +} + +.label-cell label { + font-weight: 400; + display: block; +} + +.input-cell { + flex: 1; +} + +#cur_speaker, +#newSpeaker { + width: 100%; + padding: 10px; + border-radius: 6px; + border: 1px solid #ccc; + font-size: 14px; + box-sizing: border-box; +} + +#speakerAudioViewer { + width: 100%; + height: 35px; + border-radius: 6px; +} + +.speaker-button-group { + display: flex; + gap: 15px; + justify-content: center; + margin-top: 30px; +} + #speakerLocker, #speakerResender { - padding: 10px 20px; - margin: 20px auto; + padding: 12px 25px; background-color: #007BFF; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; + font-weight: 500; + transition: background-color 0.2s; } -.h2 { - font-size: 25px; -} - -.speakerView, -.speakerAudio, -.speakerWrite { - margin-top: auto; - margin-bottom: auto; +#speakerLocker:hover, +#speakerResender:hover { + background-color: #0056b3; } .container { @@ -702,4 +801,10 @@ button:hover { margin-top: 20px; color: #333; word-break: break-word; +} + +.container input, +.container textarea, +.container select { + width: 100%; } \ No newline at end of file diff --git a/main.js b/main.js index be47e74..cebd6b1 100644 --- a/main.js +++ b/main.js @@ -168,9 +168,39 @@ electron.ipcMain.on("file_submit", async (event, args) => { throw new Error("Unknown document type: " + args.document.type); } - console.log(args); - let audiopath = ""; - let transcriptpath = ""; +electron.ipcMain.on("file_download", async (event) => { + try { + if (!globalFinalHtmlPath) { + throw new Error("No document generated yet"); + } + + const format = String(globalArgs?.document?.outputType || "") + .replace('.', '') + .toLowerCase(); + + if (!format) { + throw new Error("No output format selected"); + } + + const outputPath = await mapFunctions + .get("htmlDocumentConverter") + .convert({ + inputPath: globalFinalHtmlPath, + format, + showDialog: true + }); + + event.sender.send("download_success", { + path: outputPath, + format + }); + + } catch (err) { + console.error("file_download failed:", err); + + event.sender.send("error", err.message || String(err)); + } +}); console.log("\n\n Running the Video to Audio Extractor"); // This code handles the Video to Audio extraction module call @@ -365,4 +395,4 @@ electron.ipcMain.handle('delete-txt-file', (event, fileName) => { } else { return false; } -}); +}); \ No newline at end of file diff --git a/services/modules/convert/convert.js b/services/modules/convert/convert.js index 54b30ef..40045eb 100644 --- a/services/modules/convert/convert.js +++ b/services/modules/convert/convert.js @@ -1,35 +1,37 @@ -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 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 }); + fs.mkdirSync(outputDir, { recursive: true }); } async function showSaveDialog(defaultName, format) { - const platform = os.platform(); - - if (platform === 'darwin') { - // macOS - const applescript = ` + 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') { + + 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 = ` @@ -43,155 +45,192 @@ async function showSaveDialog(defaultName, format) { `; try { - const result = execSync( - `powershell -NoProfile -Command "${powershell.replace(/\r?\n/g, ' ')}"`, - { encoding: 'utf8' } - ); - return result.trim() || null; + 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); + 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}`); - } - } + } 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", + 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}`); - } + /** + * 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 }) { + format = format.toLowerCase().replace(".", ""); // <-- FIX - 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 tags if present - htmlContent = htmlContent.replace(/[\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(); + if (!["pdf", "docx", "html", "txt"].includes(format)) { + throw new Error(`Unsupported format: ${format}`); } + 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 tags if present + htmlContent = htmlContent.replace(/[\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) { + let browser; + try { + 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", + }, + }); + } finally { + if (browser) { + await browser.close(); + } + } + }, + + // HTML → DOCX + async htmlToDOCX(html, outputPath) { + try { + // html‑to‑docx library converts HTML string into a Word .docx buffer + // Usage from html‑to‑docx docs: + // await HTMLtoDOCX(htmlString, headerHTMLString, documentOptions, footerHTMLString) [oai_citation:0‡GitHub](https://github.com/privateOmega/html-to-docx?utm_source=chatgpt.com) + const buffer = await htmlToDocx(html, null, { + table: { row: { cantSplit: true } }, + }); + fs.writeFileSync(outputPath, buffer); + } catch (err) { + throw new Error(`DOCX conversion failed: ${err.message}`); + } + }, + + // HTML → TXT + htmlToTXT(html) { + // A decent plain text conversion: strip tags and collapse whitespace + // If you want more advanced extraction consider using a library like `html-to-text` or `strip-html` [oai_citation:1‡GitHub](https://github.com/html-to-text/node-html-to-text?utm_source=chatgpt.com) + return ( + html + // Remove all tags + .replace(/<[^>]+>/g, "") + // Convert multiple whitespace into single spaces + .replace(/\s+/g, " ") + .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 [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); - } + (async () => { + const args = process.argv.slice(2); + if (args.length < 1) { + console.log("Usage: node htmlDocumentConverter.js [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'; + 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); - } - })(); -} \ No newline at end of file + try { + await module_exports.convert({ + inputPath, + format, + showDialog: true, + }); + } catch (err) { + console.error("Konvertierung fehlgeschlagen:", err.message); + process.exit(1); + } + })(); +}