AudioTransDiar / templates /index2.html
prthm11's picture
Update templates/index2.html
a7091bc verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Audio Transcription Studio</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href=".../icons8-speech recognition-external-smashingstocks-glyph-smashing-stocks-32.png?v=2">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:400,600,700&display=swap">
<style>
:root {
--bg: #18122b;
--bg-card: #231942;
--bg-card2: #251e3e;
--accent: #a259ec;
--accent2: #2563eb;
--text: #fff;
--text-muted: #bcbcbc;
--border: #312e4a;
--success: #22c55e;
--danger: #dc2626;
--cyan: #00fff7;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
font-family: 'Inter', Arial, sans-serif;
background: var(--bg);
color: var(--text);
}
.layout {
display: flex;
min-height: 100vh;
gap: 32px;
padding: 32px;
box-sizing: border-box;
}
.main-panel {
flex: 2;
display: flex;
flex-direction: column;
gap: 24px;
}
.card {
background: var(--bg-card);
border-radius: 18px;
box-shadow: 0 2px 16px #0003;
padding: 32px 32px 24px 32px;
margin-bottom: 0;
border: 1.5px solid var(--border);
}
.card h2,
.card h3 {
margin-top: 0;
color: var(--accent);
font-size: 1.5em;
font-weight: 700;
margin-bottom: 18px;
letter-spacing: 1px;
}
.sidebar {
flex: 1;
min-width: 320px;
background: var(--bg-card2);
border-radius: 18px;
box-shadow: 0 2px 16px #0003;
padding: 32px 28px 24px 28px;
display: flex;
flex-direction: column;
gap: 32px;
border: 1.5px solid var(--border);
height: fit-content;
}
.sidebar h3 {
color: var(--accent2);
font-size: 1.2em;
font-weight: 700;
margin-bottom: 18px;
letter-spacing: 1px;
display: flex;
align-items: center;
gap: 8px;
}
.sidebar label {
font-size: 1em;
color: var(--text-muted);
margin-top: 18px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.sidebar select,
.sidebar input[type="number"] {
width: 100%;
margin-top: 6px;
padding: 10px;
border-radius: 8px;
border: 1px solid var(--border);
background: #201c3a;
color: var(--text);
font-size: 1em;
margin-bottom: 10px;
outline: none;
transition: border 0.2s;
}
.sidebar select:focus,
.sidebar input[type="number"]:focus {
border: 1.5px solid var(--accent2);
}
.sidebar button {
width: 100%;
padding: 14px 0;
margin-top: 18px;
border: none;
border-radius: 8px;
background: var(--accent);
color: #fff;
font-size: 1.1em;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
box-shadow: 0 2px 8px #0002;
}
.sidebar button:disabled {
background: #a5b4fc;
cursor: not-allowed;
}
.sidebar .stop-btn {
background: var(--danger);
margin-top: 8px;
}
.toggle-row {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
}
.toggle-label {
flex: 1;
color: var(--text-muted);
font-size: 1em;
}
.toggle-switch {
width: 38px;
height: 22px;
background: #333;
border-radius: 12px;
position: relative;
cursor: pointer;
transition: background 0.2s;
}
.toggle-switch input {
display: none;
}
.toggle-slider {
position: absolute;
top: 2px;
left: 2px;
width: 18px;
height: 18px;
background: var(--accent2);
border-radius: 50%;
transition: left 0.2s;
}
.toggle-switch input:checked+.toggle-slider {
left: 18px;
background: var(--danger);
}
.status {
margin: 18px 0 0 0;
font-weight: bold;
color: var(--success);
font-size: 1.1em;
text-align: center;
}
.recorder-center {
display: flex;
flex-direction: column;
align-items: center;
gap: 18px;
margin-bottom: 18px;
}
.recorder-btn {
width: 90px;
height: 90px;
border-radius: 50%;
background: linear-gradient(135deg, #a259ec 60%, #2563eb 100%);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 32px #a259ec55;
cursor: pointer;
transition: box-shadow 0.2s, background 0.2s;
position: relative;
}
.recorder-btn.recording {
background: linear-gradient(135deg, #dc2626 60%, #a259ec 100%);
box-shadow: 0 0 32px #dc262655;
animation: pulse 1.2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 32px #dc262655;
}
50% {
box-shadow: 0 0 48px #dc2626aa;
}
100% {
box-shadow: 0 0 32px #dc262655;
}
}
.recorder-btn svg {
width: 38px;
height: 38px;
color: #fff;
}
.recorder-status {
color: var(--success);
font-size: 1.1em;
font-weight: 600;
margin-top: 8px;
}
.recorder-status.recording {
color: var(--danger);
}
.live {
margin-top: 0;
background: #201c3a;
border-radius: 12px;
padding: 18px 18px 10px 18px;
min-height: 90px;
border: 1px solid var(--border);
overflow: hidden;
/* hide outer overflow, inner #live will scroll */
display: flex;
flex-direction: column;
}
/* inner container which actually scrolls */
#live {
flex: 1 1 auto;
overflow-y: auto;
padding-right: 6px;
/* give room for scroll bar */
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
color: var(--text-muted);
}
.live h4 {
margin: 0 0 10px 0;
color: var(--cyan);
font-size: 1.08em;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.chunk {
background: linear-gradient(90deg, rgba(45, 37, 74, 0.2), rgba(38, 32, 63, 0.12));
margin-bottom: 8px;
padding: 10px 12px;
border-radius: 8px;
font-size: 0.98em;
color: var(--text);
box-shadow: 0 1px 2px #0002;
border: 1px solid rgba(255, 255, 255, 0.02);
}
/* Small speaker label */
.chunk b {
color: var(--cyan);
margin-right: 6px;
font-weight: 700;
}
/* THEMED SCROLLBAR - WebKit (Chrome, Edge, Safari) */
#live::-webkit-scrollbar {
width: 10px;
}
#live::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.02);
border-radius: 10px;
}
#live::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, var(--accent) 0%, var(--accent2) 100%);
border-radius: 10px;
border: 2px solid rgba(0, 0, 0, 0.15);
}
#live::-webkit-scrollbar-thumb:hover {
filter: brightness(0.95);
}
/* THEMED SCROLLBAR - Firefox */
#live {
scrollbar-width: thin;
scrollbar-color: var(--accent) rgba(255, 255, 255, 0.02);
}
/* responsive: reduce max-height on small screens */
@media (max-width: 700px) {
.live {
max-height: 200px;
}
}
.files h4 {
color: var(--accent2);
font-size: 1.08em;
margin: 0 0 10px 0;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.file {
background: #2d254a;
margin-bottom: 8px;
padding: 8px 12px;
border-radius: 5px;
font-size: 1em;
color: #e0e7ef;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 1px 2px #0001;
}
.file a {
color: var(--accent2);
text-decoration: none;
font-weight: 500;
}
.file a:hover {
text-decoration: underline;
}
#audio-player-container {
margin-bottom: 18px;
}
#waveform {
width: 100%;
height: 80px;
background: #2d254a;
border-radius: 6px;
}
#transcript-container {
background: #2d254a;
padding: 14px;
border-radius: 6px;
margin-top: 24px;
}
#transcript-content {
margin-top: 10px;
white-space: pre-wrap;
font-size: 1em;
color: #e0e7ef;
max-height: 300px;
overflow: auto;
background: #201c3a;
padding: 10px;
border-radius: 4px;
}
@media (max-width: 1100px) {
.layout {
flex-direction: column;
gap: 0;
padding: 12px;
}
.sidebar {
min-width: unset;
width: 100%;
margin-bottom: 18px;
}
.main-panel {
padding: 0;
}
}
@media (max-width: 700px) {
.card,
.sidebar {
padding: 16px 8px 12px 8px;
}
.main-panel {
gap: 12px;
}
}
</style>
</head>
<body>
<div class="layout">
<main class="main-panel">
<section class="card">
<h2 style="text-align:center;font-size:2.2em;color:#a259ec;margin-bottom:0;">Audio Transcription Studio</h2>
<div style="text-align:center;color:#bcbcbc;margin-bottom:24px;">
Record high-quality audio and get real-time AI-powered transcriptions with speaker detection.
</div>
<div class="recorder-center">
<div id="recorderBtn" class="recorder-btn" title="Start/Stop Recording">
<svg id="micIcon" xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 19v3" />
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
<rect x="9" y="2" width="6" height="13" rx="3" />
</svg>
<svg id="stopIcon" style="display:none;" xmlns="http://www.w3.org/2000/svg" fill="currentColor"
viewBox="0 0 24 24">
<rect x="6" y="6" width="12" height="12" rx="2" />
</svg>
</div>
<div id="recorderStatus" class="recorder-status">Ready to record</div>
</div>
</section>
<section class="card">
<h3><span style="color:var(--cyan);">💬</span> Live Transcription</h3>
<div class="live">
<div id="live" style="min-height:32px;color:#bcbcbc;">Start recording to see live transcription</div>
</div>
</section>
</main>
<aside class="sidebar">
<h3><span style="color:var(--accent2);">⚙️</span> Recording Settings</h3>
<label for="mic">Microphone Device</label>
<select id="mic" disabled>
<option value="1" selected>Default Microphone (#1)</option>
</select>
<label for="sys">System Audio (Optional)</label>
<select id="sys" disabled>
<option value="16" selected>System Loopback (#16)</option>
</select>
<label for="chunk_secs">Chunk Length (seconds)</label>
<input type="number" id="chunk_secs" value="5" min="1" max="60" readonly>
<label for="model">Transcription Model</label>
<select id="model" disabled>
<option value="small">Small (Fast)</option>
<option value="medium" selected>Medium (Balanced)</option>
<option value="large">Large (Accurate)</option>
</select>
<div class="toggle-row">
<span class="toggle-label">Disable Transcription</span>
<label class="toggle-switch">
<input type="checkbox" id="no_transcribe">
<span class="toggle-slider"></span>
</label>
</div>
<div class="status" id="status"></div>
</aside>
</div>
<script>
// --- Recording Button Logic ---
let isRecording = false;
let polling = null;
const recorderBtn = document.getElementById('recorderBtn');
const micIcon = document.getElementById('micIcon');
const stopIcon = document.getElementById('stopIcon');
const recorderStatus = document.getElementById('recorderStatus');
function setRecordingUI(recording) {
isRecording = recording;
if (recording) {
recorderBtn.classList.add('recording');
micIcon.style.display = 'none';
stopIcon.style.display = '';
recorderStatus.textContent = 'Recording...';
recorderStatus.classList.add('recording');
} else {
recorderBtn.classList.remove('recording');
micIcon.style.display = '';
stopIcon.style.display = 'none';
recorderStatus.textContent = 'Ready to record';
recorderStatus.classList.remove('recording');
}
}
recorderBtn.onclick = async function () {
if (!isRecording) {
await startRecording();
} else {
await stopRecording();
}
};
async function startRecording() {
const mic = 1;
const sys = 16;
const chunk_secs = 5;
const model = "medium";
const no_transcribe = document.getElementById('no_transcribe').checked;
const statusEl = document.getElementById('status');
statusEl.textContent = 'Starting...';
statusEl.style.color = 'var(--accent2)';
try {
const resp = await fetch('/api/start-recording', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mic, sys, chunk_secs, model, no_transcribe })
});
if (!resp.ok) {
let errMsg = `Failed to start recording (${resp.status})`;
try {
const json = await resp.json();
if (json && json.error) errMsg = json.error;
} catch { }
statusEl.textContent = errMsg;
statusEl.style.color = 'var(--danger)';
setRecordingUI(false);
return;
}
statusEl.textContent = 'Recording...';
statusEl.style.color = 'var(--danger)';
setRecordingUI(true);
pollStatus();
} catch (err) {
statusEl.textContent = 'Network error: could not start recording';
statusEl.style.color = 'var(--danger)';
setRecordingUI(false);
console.error("startRecording error:", err);
}
}
async function stopRecording() {
await fetch('/api/stop-recording', { method: 'POST' });
document.getElementById('status').textContent = 'Stopping...';
setRecordingUI(false);
if (polling) clearInterval(polling);
setTimeout(() => { loadFiles(); }, 2000);
}
function pollStatus() {
polling = setInterval(async () => {
const res = await fetch('/api/recording-status');
const data = await res.json();
setRecordingUI(data.recording);
const liveDiv = document.getElementById('live');
liveDiv.innerHTML = '';
if (data.live_segments && data.live_segments.length) {
data.live_segments.slice(-10).forEach(seg => {
const div = document.createElement('div');
div.className = 'chunk';
div.innerHTML = `<b>${seg.speaker || 'Speaker'}:</b> [${formatTime(seg.start)} - ${formatTime(seg.end)}] ${seg.text}`;
liveDiv.appendChild(div);
});
requestAnimationFrame(() => {
liveDiv.scrollTop = liveDiv.scrollHeight;
});
} else {
liveDiv.textContent = 'No Transcription Yet...';
}
if (!data.recording) {
clearInterval(polling);
setRecordingUI(false);
loadFiles();
}
}, 1000);
}
function formatTime(s) {
if (s == null) return "0:00";
const mm = Math.floor(s / 60);
const ss = Math.floor(s % 60).toString().padStart(2, "0");
return `${mm}:${ss}`;
}
// --- Load final files safely ---
async function loadFiles() {
const filesDiv = document.getElementById('files');
const audioPlayerDiv = document.getElementById('audio-player-container');
const transcriptDiv = document.getElementById('transcript-container');
if (!filesDiv || !audioPlayerDiv || !transcriptDiv) {
// Section not present → just skip
return;
}
filesDiv.innerHTML = '';
audioPlayerDiv.innerHTML = '';
transcriptDiv.innerHTML = '';
try {
const res = await fetch('/api/final-files');
const data = await res.json();
if (!data.files.length) {
filesDiv.textContent = 'No files yet.';
return;
}
// ... your existing file logic here ...
} catch (e) {
filesDiv.textContent = 'Error loading files.';
}
}
loadFiles();
if (!window.WaveSurfer) {
const script = document.createElement('script');
script.src = "https://unpkg.com/wavesurfer.js";
document.head.appendChild(script);
}
</script>
</body>
</html>