Viele Änderungen
This commit is contained in:
81
index.html
81
index.html
@@ -277,6 +277,36 @@ body {
|
||||
.btn-send:not(:disabled):hover { background: #d8d8d8; }
|
||||
.btn-send.success { background: var(--green); color: #000; }
|
||||
|
||||
.notice-stack {
|
||||
position: fixed;
|
||||
top: 18px;
|
||||
right: 18px;
|
||||
z-index: 300;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: min(420px, calc(100vw - 24px));
|
||||
}
|
||||
|
||||
.notice {
|
||||
padding: 14px 16px;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--border2);
|
||||
background: rgba(17,17,20,.96);
|
||||
box-shadow: var(--shadow-lg);
|
||||
color: var(--text);
|
||||
line-height: 1.5;
|
||||
animation: notice-in .18s ease;
|
||||
}
|
||||
|
||||
.notice.info { border-color: #3f3f46; }
|
||||
.notice.error { border-color: rgba(248,113,113,.55); }
|
||||
|
||||
@keyframes notice-in {
|
||||
from { opacity: 0; transform: translateY(-6px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
PROGRESS STEPS
|
||||
═══════════════════════════════════════════ */
|
||||
@@ -566,6 +596,8 @@ body {
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="notice-stack" id="noticeStack"></div>
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
UPLOAD SCREEN
|
||||
══════════════════════════════════════════ -->
|
||||
@@ -670,7 +702,7 @@ body {
|
||||
<div class="review-footer">
|
||||
<input type="text" class="title-input" id="titleInput" placeholder="Meeting-Titel…">
|
||||
<div class="email-chip-field" id="emailChipField">
|
||||
<input class="email-chip-input" id="emailChipInput" placeholder="E-Mail, Enter oder ","">
|
||||
<input class="email-chip-input" id="emailChipInput" placeholder="E-Mail, Enter oder ,">
|
||||
</div>
|
||||
<button class="btn btn-send" id="sendBtn">Senden</button>
|
||||
</div>
|
||||
@@ -795,6 +827,7 @@ const deleteModalSub = document.getElementById('deleteModalSub');
|
||||
const deleteReplSel = document.getElementById('deleteReplSel');
|
||||
const deleteCancelBtn = document.getElementById('deleteCancelBtn');
|
||||
const deleteConfirmBtn= document.getElementById('deleteConfirmBtn');
|
||||
const noticeStack = document.getElementById('noticeStack');
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// INIT
|
||||
@@ -964,8 +997,8 @@ function updateTimer() {
|
||||
// ═══════════════════════════════════════════
|
||||
// NEW / RESET
|
||||
// ═══════════════════════════════════════════
|
||||
function resetApp() {
|
||||
if (!confirm('Wirklich neu starten? Alle Daten gehen verloren.')) return;
|
||||
function resetApp(requireConfirmation = true) {
|
||||
if (requireConfirmation && !confirm('Wirklich neu starten? Alle Daten gehen verloren.')) return;
|
||||
stopRecording();
|
||||
clearAll();
|
||||
state = { screen:'upload', inputMode:null, uploadedFileName:'', uploadedFileSize:0,
|
||||
@@ -980,6 +1013,7 @@ function resetApp() {
|
||||
transcribeBtn.disabled = true;
|
||||
transcribeBtn.textContent = 'Transkribieren';
|
||||
sendBtn.textContent = 'Senden';
|
||||
sendBtn.classList.remove('success');
|
||||
sendBtn.style.background = ''; sendBtn.style.color = '';
|
||||
sendBtn.disabled = false;
|
||||
[1,2,3].forEach(n => setStep(n, null));
|
||||
@@ -997,6 +1031,14 @@ function setStep(n, s) {
|
||||
if (s) el.classList.add(s);
|
||||
}
|
||||
|
||||
function showNotice(message, kind = 'info', timeout = 6000) {
|
||||
const notice = document.createElement('div');
|
||||
notice.className = `notice ${kind}`;
|
||||
notice.textContent = message;
|
||||
noticeStack.appendChild(notice);
|
||||
window.setTimeout(() => notice.remove(), timeout);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// API
|
||||
// ═══════════════════════════════════════════
|
||||
@@ -1005,11 +1047,8 @@ async function api(url, init) {
|
||||
if (!r.ok) throw new Error(await r.text());
|
||||
return r.json();
|
||||
}
|
||||
const apiUpload = (blob, name) => { const fd=new FormData(); fd.append('file',blob,name); return api('/upload',{method:'POST',body:fd}); };
|
||||
const apiTranscribe = id => api('/transcribe',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({file_id:id})});
|
||||
const apiProcess = text => { const fd=new FormData(); fd.append('text',text); return api('/process',{method:'POST',body:fd}); };
|
||||
const apiSummarize = (transcript, title) => { const fd=new FormData(); fd.append('transcript',transcript); fd.append('title',title); return api('/summarize',{method:'POST',body:fd}); };
|
||||
const apiSend = (email,title,sum,tr) => { const fd=new FormData(); fd.append('email',email); fd.append('title',title); fd.append('summary',sum); fd.append('transcript',tr); return api('/send',{method:'POST',body:fd}); };
|
||||
const apiTranscribe = (blob, name) => { const fd=new FormData(); fd.append('file',blob,name); return api('/transcribe',{method:'POST',body:fd}); };
|
||||
const apiSend = (emails,title,tr) => { const fd=new FormData(); fd.append('emails',emails.join(',')); fd.append('title',title); fd.append('transcript',tr); return api('/send',{method:'POST',body:fd}); };
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// TRANSCRIBE FLOW
|
||||
@@ -1019,27 +1058,28 @@ transcribeBtn.addEventListener('click', async () => {
|
||||
transcribeBtn.textContent = 'Verarbeitung…';
|
||||
try {
|
||||
setStep(1,'active');
|
||||
const up = await apiUpload(audioBlob, state.uploadedFileName || 'audio.webm');
|
||||
const trPromise = apiTranscribe(audioBlob, state.uploadedFileName || 'audio.webm');
|
||||
setStep(1,'completed');
|
||||
|
||||
setStep(2,'active');
|
||||
const tr = await apiTranscribe(up.file_id);
|
||||
const tr = await trPromise;
|
||||
setStep(2,'completed');
|
||||
|
||||
setStep(3,'active');
|
||||
const pr = await apiProcess(tr.transcription);
|
||||
setStep(3,'completed');
|
||||
|
||||
state.segments = parseTranscript(pr.processed);
|
||||
state.segments = parseTranscript(tr.transcription);
|
||||
buildSpeakersFromSegments();
|
||||
state.screen = 'review';
|
||||
await saveState();
|
||||
showReview();
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
setStep(1,'error');
|
||||
if (document.getElementById('step2').classList.contains('active')) setStep(2,'error');
|
||||
else setStep(1,'error');
|
||||
transcribeBtn.disabled = false;
|
||||
transcribeBtn.textContent = 'Transkribieren';
|
||||
showNotice('Die Transkription konnte nicht gestartet oder abgeschlossen werden.', 'error');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1287,19 +1327,16 @@ sendBtn.addEventListener('click', async () => {
|
||||
const title = titleInput.value.trim() || 'Meeting';
|
||||
|
||||
try {
|
||||
sendBtn.textContent = 'Zusammenfassung…';
|
||||
const { summary } = await apiSummarize(transcript, title);
|
||||
state.summary = summary; await saveState();
|
||||
|
||||
sendBtn.textContent = 'Senden…';
|
||||
await Promise.all(emails.map(email => apiSend(email, title, summary, transcript)));
|
||||
|
||||
sendBtn.textContent = emails.length > 1 ? `Gesendet (${emails.length})` : 'Gesendet';
|
||||
sendBtn.classList.add('success');
|
||||
sendBtn.textContent = 'Wird gestartet…';
|
||||
await apiSend(emails, title, transcript);
|
||||
showNotice('Ihr Dokument wird jetzt bearbeitet und schon bald verschickt. Sie koennen direkt ein neues Meeting starten.');
|
||||
resetApp(false);
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
sendBtn.disabled = false;
|
||||
sendBtn.classList.remove('success');
|
||||
sendBtn.textContent = 'Fehler – erneut versuchen';
|
||||
showNotice('Der Versand konnte nicht gestartet werden. Bitte erneut versuchen.', 'error');
|
||||
setTimeout(() => { sendBtn.textContent='Senden'; }, 3000);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user