AudioTransDiar / templates /index2_upload.html
prthm11's picture
Upload 12 files
4207399 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;
display: flex;
flex-direction: column;
}
/* inner container which actually scrolls */
#live {
flex: 1 1 auto;
overflow-y: auto;
padding-right: 6px;
-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);
}
.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);
}
#live {
scrollbar-width: thin;
scrollbar-color: var(--accent) rgba(255, 255, 255, 0.02);
}
@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;
}
}
/* UPLOAD area styles */
.upload {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 18px 22px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.03);
cursor: default;
width: 100%;
max-width: 420px;
margin: 0 auto;
}
.upload-btn {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
cursor: pointer;
outline: none;
user-select: none;
}
.upload-btn:focus-visible {
box-shadow: 0 0 0 8px rgba(37, 99, 235, 0.08);
border-radius: 12px;
}
.upload-img {
width: 120px;
height: 96px;
object-fit: contain;
display: block;
user-select: none;
pointer-events: none;
}
.upload-text {
color: #bcbcbc;
font-weight: 700;
font-size: 14px;
text-align: center;
max-width: 220px;
word-break: break-word;
}
/* preview area inside upload container */
.upload-preview {
width: 100%;
display: flex;
flex-direction: column;
gap: 10px;
align-items: center;
margin-top: 6px;
}
.upload-preview .filename {
color: var(--text);
font-weight: 600;
font-size: 0.95em;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
.upload-preview .controls {
display: flex;
gap: 8px;
align-items: center;
}
.btn-small {
padding: 6px 10px;
border-radius: 6px;
background: #2d254a;
color: var(--text);
border: 1px solid rgba(255, 255, 255, 0.03);
cursor: pointer;
font-weight: 600;
}
</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="upload">
<label for="uploadFile" class="upload-btn" tabindex="0" role="button"
aria-label="Upload audio file">
<img class="upload-img" src="/static/icon_upload.png" alt="Upload icon" />
<small class="upload-text">Upload .mp3, .wav file</small>
</label>
<input id="uploadFile" type="file" accept=".mp3,.wav,audio/*" style="display:none" />
<div id="uploadPreview" class="upload-preview" aria-live="polite"></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>
(function () {
const uploadEl = document.getElementById('uploadFile');
const preview = document.getElementById('uploadPreview');
const live = document.getElementById('live');
const statusEl = document.getElementById('status');
let audioEl = null;
let es = null; // EventSource
let playing = false;
let currentFile = null;
async function uploadFile(file) {
const fd = new FormData();
fd.append('file', file);
const resp = await fetch('/api/upload', { method: 'POST', body: fd });
return resp.json();
}
function createAudioPlayer(url, filename) {
preview.innerHTML = '';
const container = document.createElement('div');
container.style.width = '100%';
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.style.alignItems = 'center';
const audio = document.createElement('audio');
audio.controls = true;
audio.src = url;
audio.id = 'uploadedAudio';
audio.style.width = '100%';
audio.dataset.filename = filename;
const info = document.createElement('div');
info.className = 'filename';
info.textContent = filename;
container.appendChild(info);
container.appendChild(audio);
preview.appendChild(container);
// listeners
audio.addEventListener('play', onPlay);
audio.addEventListener('pause', onPause);
audioEl = audio;
}
async function onPlay() {
if (!audioEl || playing) return;
playing = true;
currentFile = audioEl.dataset.filename;
// update UI
statusEl.textContent = "▶️ Transcribing...";
statusEl.style.color = "var(--success)";
try {
await fetch('/api/start-transcribe-file', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filename: currentFile })
});
} catch (e) {
console.error('start failed', e);
}
// open SSE for transcription
if (es) es.close();
es = new EventSource('/events');
es.onmessage = function (ev) {
const line = ev.data;
if (!line) return;
appendLine(line);
};
es.onerror = (e) => {
console.warn('SSE error', e);
};
}
async function onPause() {
if (!audioEl || !playing) return;
playing = false;
statusEl.textContent = "⏸️ Stopped";
statusEl.style.color = "var(--danger)";
try {
await fetch('/stop', { method: 'POST' });
} catch (e) {
console.error('stop failed', e);
}
if (es) {
es.close();
es = null;
}
}
// function appendLine(s) {
// const chunk = document.createElement('div');
// chunk.className = 'chunk';
// chunk.textContent = s;
// live.appendChild(chunk);
// live.scrollTop = live.scrollHeight;
// }
function appendLine(s) {
const chunk = document.createElement('div');
chunk.className = 'chunk';
// Try to parse format: [file.wav] 0.00-3.00 Speaker A: Hello world
const m = s.match(/^\[(.*?)\]\s+([\d.]+)-([\d.]+)\s+Speaker\s+(\S+):\s+(.*)$/);
if (m) {
const [, file, start, end, speaker, text] = m;
chunk.innerHTML = `<b>${speaker}</b> [${start}-${end}s]: ${text}`;
} else {
chunk.textContent = s;
}
live.appendChild(chunk);
live.scrollTop = live.scrollHeight;
}
// Poll /status every few seconds (optional, keeps sidebar updated)
async function pollStatus() {
try {
const r = await fetch('/status');
const js = await r.json();
if (js.running) {
statusEl.textContent = "▶️ Running";
statusEl.style.color = "var(--success)";
} else if (!playing) {
statusEl.textContent = "⏸️ Idle";
statusEl.style.color = "var(--text-muted)";
}
} catch (e) { }
setTimeout(pollStatus, 3000);
}
pollStatus();
uploadEl.addEventListener('change', async (ev) => {
const file = ev.target.files && ev.target.files[0];
if (!file) return;
const res = await uploadFile(file);
if (res && res.success) {
createAudioPlayer(res.url, res.filename);
live.innerHTML = '<div style="color:#bcbcbc;">Ready. Play audio to start live transcription.</div>';
} else {
alert('Upload failed: ' + (res && res.error ? res.error : 'unknown'));
}
});
})();
</script>
</body>
</html>