| | <!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>OllamaStream - Local AI Media Tools</title> |
| | <link rel="preconnect" href="https://fonts.googleapis.com"> |
| | <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| | <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet"> |
| | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| | <style> |
| | :root { |
| | --bg-dark: #0f0f13; |
| | --bg-panel: #18181f; |
| | --bg-panel-hover: #22222b; |
| | --primary: #6366f1; |
| | --primary-glow: rgba(99, 102, 241, 0.4); |
| | --accent: #10b981; |
| | --text-main: #ffffff; |
| | --text-muted: #9ca3af; |
| | --border: #2d2d3a; |
| | --glass: rgba(24, 24, 31, 0.7); |
| | --glass-border: rgba(255, 255, 255, 0.08); |
| | --danger: #ef4444; |
| | } |
| | |
| | * { |
| | box-sizing: border-box; |
| | margin: 0; |
| | padding: 0; |
| | outline: none; |
| | } |
| | |
| | body { |
| | font-family: 'Inter', sans-serif; |
| | background-color: var(--bg-dark); |
| | color: var(--text-main); |
| | min-height: 100vh; |
| | display: flex; |
| | flex-direction: column; |
| | overflow-x: hidden; |
| | } |
| | |
| | |
| | header { |
| | height: 70px; |
| | border-bottom: 1px solid var(--border); |
| | display: flex; |
| | align-items: center; |
| | justify-content: space-between; |
| | padding: 0 2rem; |
| | background: var(--glass); |
| | backdrop-filter: blur(12px); |
| | position: sticky; |
| | top: 0; |
| | z-index: 100; |
| | } |
| | |
| | .logo { |
| | display: flex; |
| | align-items: center; |
| | gap: 12px; |
| | font-weight: 700; |
| | font-size: 1.2rem; |
| | background: linear-gradient(135deg, #fff 0%, #a5b4fc 100%); |
| | -webkit-background-clip: text; |
| | -webkit-text-fill-color: transparent; |
| | } |
| | |
| | .logo i { |
| | background: var(--primary); |
| | -webkit-background-clip: text; |
| | -webkit-text-fill-color: transparent; |
| | font-size: 1.5rem; |
| | } |
| | |
| | .status-indicator { |
| | display: flex; |
| | align-items: center; |
| | gap: 8px; |
| | font-size: 0.85rem; |
| | color: var(--text-muted); |
| | background: rgba(255,255,255,0.05); |
| | padding: 6px 12px; |
| | border-radius: 20px; |
| | } |
| | |
| | .status-dot { |
| | width: 8px; |
| | height: 8px; |
| | border-radius: 50%; |
| | background-color: var(--accent); |
| | box-shadow: 0 0 8px var(--accent); |
| | animation: pulse 2s infinite; |
| | } |
| | |
| | .built-with { |
| | font-size: 0.85rem; |
| | color: var(--text-muted); |
| | text-decoration: none; |
| | border: 1px solid var(--border); |
| | padding: 6px 12px; |
| | border-radius: 6px; |
| | transition: all 0.3s ease; |
| | } |
| | |
| | .built-with:hover { |
| | border-color: var(--primary); |
| | color: var(--primary); |
| | } |
| | |
| | |
| | .app-container { |
| | display: flex; |
| | flex: 1; |
| | height: calc(100vh - 70px); |
| | } |
| | |
| | |
| | aside { |
| | width: 260px; |
| | background: var(--bg-panel); |
| | border-right: 1px solid var(--border); |
| | padding: 2rem 1rem; |
| | display: flex; |
| | flex-direction: column; |
| | gap: 0.5rem; |
| | } |
| | |
| | .nav-item { |
| | display: flex; |
| | align-items: center; |
| | gap: 12px; |
| | padding: 12px 16px; |
| | border-radius: 10px; |
| | color: var(--text-muted); |
| | cursor: pointer; |
| | transition: all 0.2s ease; |
| | font-weight: 500; |
| | } |
| | |
| | .nav-item:hover { |
| | background: var(--bg-panel-hover); |
| | color: var(--text-main); |
| | } |
| | |
| | .nav-item.active { |
| | background: rgba(99, 102, 241, 0.1); |
| | color: var(--primary); |
| | border: 1px solid rgba(99, 102, 241, 0.2); |
| | } |
| | |
| | .nav-item i { |
| | width: 20px; |
| | text-align: center; |
| | } |
| | |
| | |
| | main { |
| | flex: 1; |
| | padding: 2rem; |
| | overflow-y: auto; |
| | position: relative; |
| | } |
| | |
| | .panel-header { |
| | margin-bottom: 2rem; |
| | } |
| | |
| | .panel-header h1 { |
| | font-size: 2rem; |
| | margin-bottom: 0.5rem; |
| | } |
| | |
| | .panel-header p { |
| | color: var(--text-muted); |
| | } |
| | |
| | |
| | .upload-zone { |
| | border: 2px dashed var(--border); |
| | border-radius: 16px; |
| | padding: 4rem 2rem; |
| | text-align: center; |
| | background: rgba(255,255,255,0.02); |
| | transition: all 0.3s ease; |
| | cursor: pointer; |
| | position: relative; |
| | overflow: hidden; |
| | } |
| | |
| | .upload-zone:hover, .upload-zone.drag-over { |
| | border-color: var(--primary); |
| | background: rgba(99, 102, 241, 0.05); |
| | } |
| | |
| | .upload-zone i { |
| | font-size: 3rem; |
| | color: var(--primary); |
| | margin-bottom: 1rem; |
| | } |
| | |
| | .upload-zone h3 { |
| | margin-bottom: 0.5rem; |
| | } |
| | |
| | .upload-zone p { |
| | color: var(--text-muted); |
| | font-size: 0.9rem; |
| | } |
| | |
| | .file-info { |
| | margin-top: 1rem; |
| | display: none; |
| | background: var(--bg-panel); |
| | padding: 1rem; |
| | border-radius: 8px; |
| | border: 1px solid var(--border); |
| | align-items: center; |
| | justify-content: space-between; |
| | } |
| | |
| | .file-details { |
| | display: flex; |
| | align-items: center; |
| | gap: 10px; |
| | } |
| | |
| | |
| | .controls-grid { |
| | display: grid; |
| | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
| | gap: 1.5rem; |
| | margin-top: 2rem; |
| | } |
| | |
| | .input-group label { |
| | display: block; |
| | margin-bottom: 0.5rem; |
| | font-size: 0.9rem; |
| | color: var(--text-muted); |
| | } |
| | |
| | .form-select, .form-input { |
| | width: 100%; |
| | background: var(--bg-panel); |
| | border: 1px solid var(--border); |
| | color: var(--text-main); |
| | padding: 12px; |
| | border-radius: 8px; |
| | font-family: inherit; |
| | transition: border-color 0.2s; |
| | } |
| | |
| | .form-select:focus, .form-input:focus { |
| | border-color: var(--primary); |
| | } |
| | |
| | |
| | .action-btn { |
| | margin-top: 2rem; |
| | width: 100%; |
| | padding: 16px; |
| | background: linear-gradient(90deg, var(--primary) 0%, #4f46e5 100%); |
| | color: white; |
| | border: none; |
| | border-radius: 12px; |
| | font-size: 1rem; |
| | font-weight: 600; |
| | cursor: pointer; |
| | transition: all 0.3s ease; |
| | display: flex; |
| | align-items: center; |
| | justify-content: center; |
| | gap: 10px; |
| | box-shadow: 0 4px 20px var(--primary-glow); |
| | } |
| | |
| | .action-btn:hover { |
| | transform: translateY(-2px); |
| | box-shadow: 0 8px 30px var(--primary-glow); |
| | } |
| | |
| | .action-btn:disabled { |
| | opacity: 0.5; |
| | cursor: not-allowed; |
| | transform: none; |
| | } |
| | |
| | |
| | .results-area { |
| | margin-top: 3rem; |
| | display: none; |
| | animation: fadeIn 0.5s ease; |
| | } |
| | |
| | .result-card { |
| | background: var(--bg-panel); |
| | border: 1px solid var(--border); |
| | border-radius: 12px; |
| | overflow: hidden; |
| | } |
| | |
| | .result-header { |
| | padding: 1rem 1.5rem; |
| | border-bottom: 1px solid var(--border); |
| | display: flex; |
| | justify-content: space-between; |
| | align-items: center; |
| | } |
| | |
| | .result-content { |
| | padding: 1.5rem; |
| | min-height: 200px; |
| | max-height: 400px; |
| | overflow-y: auto; |
| | font-family: 'JetBrains Mono', monospace; |
| | font-size: 0.9rem; |
| | line-height: 1.6; |
| | color: var(--text-muted); |
| | } |
| | |
| | |
| | .audio-player-wrapper { |
| | padding: 2rem; |
| | display: flex; |
| | flex-direction: column; |
| | align-items: center; |
| | gap: 1rem; |
| | } |
| | |
| | .waveform { |
| | width: 100%; |
| | height: 60px; |
| | background: rgba(255,255,255,0.03); |
| | border-radius: 8px; |
| | position: relative; |
| | overflow: hidden; |
| | display: flex; |
| | align-items: center; |
| | justify-content: center; |
| | gap: 3px; |
| | } |
| | |
| | .bar { |
| | width: 4px; |
| | background: var(--primary); |
| | border-radius: 2px; |
| | animation: wave 1s ease-in-out infinite; |
| | } |
| | |
| | |
| | .progress-container { |
| | width: 100%; |
| | background-color: rgba(255,255,255,0.1); |
| | border-radius: 10px; |
| | margin-top: 1rem; |
| | height: 6px; |
| | overflow: hidden; |
| | display: none; |
| | } |
| | |
| | .progress-bar { |
| | width: 0%; |
| | height: 100%; |
| | background: var(--primary); |
| | border-radius: 10px; |
| | transition: width 0.3s linear; |
| | } |
| | |
| | .status-text { |
| | margin-top: 10px; |
| | font-size: 0.9rem; |
| | color: var(--text-muted); |
| | text-align: center; |
| | min-height: 1.5em; |
| | } |
| | |
| | |
| | @keyframes pulse { |
| | 0% { opacity: 1; } |
| | 50% { opacity: 0.5; } |
| | 100% { opacity: 1; } |
| | } |
| | |
| | @keyframes wave { |
| | 0%, 100% { height: 10%; } |
| | 50% { height: 100%; } |
| | } |
| | |
| | @keyframes fadeIn { |
| | from { opacity: 0; transform: translateY(10px); } |
| | to { opacity: 1; transform: translateY(0); } |
| | } |
| | |
| | |
| | @media (max-width: 768px) { |
| | .app-container { |
| | flex-direction: column; |
| | height: auto; |
| | } |
| | |
| | aside { |
| | width: 100%; |
| | flex-direction: row; |
| | overflow-x: auto; |
| | padding: 1rem; |
| | border-bottom: 1px solid var(--border); |
| | border-right: none; |
| | } |
| | |
| | .nav-item { |
| | white-space: nowrap; |
| | } |
| | |
| | header { |
| | padding: 0 1rem; |
| | } |
| | |
| | .built-with { |
| | display: none; |
| | } |
| | } |
| | </style> |
| | </head> |
| | <body> |
| |
|
| | <header> |
| | <div class="logo"> |
| | <i class="fa-solid fa-microphone-lines"></i> |
| | OllamaStream |
| | </div> |
| | <div class="status-indicator"> |
| | <div class="status-dot"></div> |
| | <span>Ollama Server Connected (Local)</span> |
| | </div> |
| | <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">Built with anycoder</a> |
| | </header> |
| |
|
| | <div class="app-container"> |
| | <aside> |
| | <div class="nav-item active" onclick="switchTab('transcribe')"> |
| | <i class="fa-solid fa-file-audio"></i> |
| | Transcribe |
| | </div> |
| | <div class="nav-item" onclick="switchTab('translate')"> |
| | <i class="fa-solid fa-language"></i> |
| | Translate |
| | </div> |
| | <div class="nav-item" onclick="switchTab('dub')"> |
| | <i class="fa-solid fa-wand-magic-sparkles"></i> |
| | Dub Video |
| | </div> |
| | </aside> |
| |
|
| | <main id="main-content"> |
| | |
| | </main> |
| | </div> |
| |
|
| | <script> |
| | // Application State |
| | const state = { |
| | activeTab: 'transcribe', |
| | uploadedFile: null, |
| | isProcessing: false, |
| | progress: 0 |
| | }; |
| | |
| | // Available Models (Simulated) |
| | const models = { |
| | transcribe: ['whisper', 'whisper-large', 'faster-whisper'], |
| | llm: ['llama3', 'mistral', 'gemma'] |
| | }; |
| | |
| | // DOM Elements |
| | const mainContent = document.getElementById('main-content'); |
| | |
| | // Initial Render |
| | renderInterface(); |
| | |
| | // --- Functions --- |
| | |
| | function switchTab(tabName) { |
| | state.activeTab = tabName; |
| | state.uploadedFile = null; // Reset file on tab switch for simplicity |
| | state.progress = 0; |
| | |
| | // Update UI tabs |
| | document.querySelectorAll('.nav-item').forEach(el => el.classList.remove('active')); |
| | event.currentTarget.classList.add('active'); |
| | |
| | renderInterface(); |
| | } |
| | |
| | function renderInterface() { |
| | let html = ''; |
| | |
| | if (state.activeTab === 'transcribe') { |
| | html = getTranscribeTemplate(); |
| | } else if (state.activeTab === 'translate') { |
| | html = getTranslateTemplate(); |
| | } else if (state.activeTab === 'dub') { |
| | html = getDubTemplate(); |
| | } |
| | |
| | mainContent.innerHTML = html; |
| | |
| | // Re-attach listeners after render |
| | attachListeners(); |
| | } |
| | |
| | // --- Templates --- |
| | |
| | function getTranscribeTemplate() { |
| | return ` |
| | <div class="panel-header"> |
| | <h1>Audio Transcription</h1> |
| | <p>Convert audio to text using local Whisper models via Ollama.</p> |
| | </div> |
| | |
| | <div class="upload-zone" id="drop-zone"> |
| | <i class="fa-solid fa-cloud-arrow-up"></i> |
| | <h3>Drag & Drop Audio File</h3> |
| | <p>MP3, WAV, M4A, FLAC supported</p> |
| | <input type="file" id="file-input" hidden accept="audio/*"> |
| | </div> |
| | |
| | <div class="file-info" id="file-info"> |
| | <div class="file-details"> |
| | <i class="fa-solid fa-file-audio" style="color: var(--primary)"></i> |
| | <div> |
| | <div id="filename" style="font-weight:500">filename.mp3</div> |
| | <div id="filesize" style="font-size:0.8rem; color:var(--text-muted)">2.4 MB</div> |
| | </div> |
| | </div> |
| | <i class="fa-solid fa-xmark" style="cursor:pointer; color:var(--text-muted)" onclick="removeFile()"></i> |
| | </div> |
| | |
| | <div class="controls-grid"> |
| | <div class="input-group"> |
| | <label>Model</label> |
| | <select class="form-select" id="model-select"> |
| | ${models.transcribe.map(m => `<option value="${m}">${m}</option>`).join('')} |
| | </select> |
| | </div> |
| | <div class="input-group"> |
| | <label>Language (Optional)</label> |
| | <select class="form-select"> |
| | <option value="">Auto Detect</option> |
| | <option value="en">English</option> |
| | <option value="es">Spanish</option> |
| | <option value="fr">French</option> |
| | <option value="de">German</option> |
| | </select> |
| | </div> |
| | </div> |
| | |
| | <button class="action-btn" id="process-btn" onclick="processTask('transcribe')"> |
| | <i class="fa-solid fa-play"></i> Start Transcription |
| | </button> |
| | |
| | ${getProgressTemplate()} |
| | ${getResultsTemplate('transcription')} |
| | `; |
| | } |
| | |
| | function getTranslateTemplate() { |
| | return ` |
| | <div class="panel-header"> |
| | <h1>Smart Translate</h1> |
| | <p>Translate content using Llama 3 for context-aware accuracy.</p> |
| | </div> |
| | |
| | <div class="upload-zone" id="drop-zone"> |
| | <i class="fa-solid fa-file-import"></i> |
| | <h3>Drag Audio or Text File</h3> |
| | <p>Supports SRT, VTT, TXT, and Audio files</p> |
| | <input type="file" id="file-input" hidden> |
| | </div> |
| | |
| | <div class="file-info" id="file-info"> |
| | <div class="file-details"> |
| | <i class="fa-solid fa-file" style="color: var(--accent)"></i> |
| | <div> |
| | <div id="filename" style="font-weight:500">interview.srt</div> |
| | </div> |
| | </div> |
| | <i class="fa-solid fa-xmark" style="cursor:pointer; color:var(--text-muted)" onclick="removeFile()"></i> |
| | </div> |
| | |
| | <div class="controls-grid"> |
| | <div class="input-group"> |
| | <label>Source Language</label> |
| | <select class="form-select"> |
| | <option value="auto">Auto Detect</option> |
| | <option value="en">English</option> |
| | <option value="es">Spanish</option> |
| | </select> |
| | </div> |
| | <div class="input-group"> |
| | <label>Target Language</label> |
| | <select class="form-select"> |
| | <option value="es">Spanish</option> |
| | <option value="fr">French</option> |
| | <option value="de">German</option> |
| | <option value="jp">Japanese</option> |
| | </select> |
| | </div> |
| | <div class="input-group"> |
| | <label>AI Model</label> |
| | <select class="form-select"> |
| | ${models.llm.map(m => `<option value="${m}">${m}</option>`).join('')} |
| | </select> |
| | </div> |
| | </div> |
| | |
| | <button class="action-btn" id="process-btn" onclick="processTask('translate')"> |
| | <i class="fa-solid fa-language"></i> Translate Content |
| | </button> |
| | |
| | ${getProgressTemplate()} |
| | ${getResultsTemplate('translation')} |
| | `; |
| | } |
| | |
| | function getDubTemplate() { |
| | return ` |
| | <div class="panel-header"> |
| | <h1>AI Dubbing</h1> |
| | <p>Replace original voice track with a generated AI voice clone.</p> |
| | </div> |
| | |
| | <div class="upload-zone" id="drop-zone"> |
| | <i class="fa-solid fa-video"></i> |
| | <h3>Drag Video File</h3> |
| | <p>MP4, MOV, WEBM supported</p> |
| | <input type="file" id="file-input" hidden accept="video/*"> |
| | </div> |
| | |
| | <div class="file-info" id="file-info"> |
| | <div class="file-details"> |
| | <i class="fa-solid fa-film" style="color: var(--danger)"></i> |
| | <div> |
| | <div id="filename" style="font-weight:500">presentation.mp4</div> |
| | <div id="filesize" style="font-size:0.8rem; color:var(--text-muted)">15 MB</div> |
| | </div> |
| | </div> |
| | <i class="fa-solid fa-xmark" style="cursor:pointer; color:var(--text-muted)" onclick="removeFile()"></i> |
| | </div> |
| | |
| | <div class="controls-grid"> |
| | <div class="input-group"> |
| | <label>Target Language</label> |
| | <select class="form-select"> |
| | <option value="en-US">English (US)</option> |
| | <option value="en-UK">English (UK)</option> |
| | <option value="es-ES">Spanish (Spain)</option> |
| | <option value="jp-JP">Japanese</option> |
| | </select> |
| | </div> |
| | <div class="input-group"> |
| | <label>Voice Model</label> |
| | <select class="form-select"> |
| | <option value="alloy">Alloy (Neutral)</option> |
| | <option value="echo">Echo (British)</option> |
| | <option value="shimmer">Shimmer (Female)</option> |
| | </select> |
| | </div> |
| | </div> |
| | |
| | <button class="action-btn" id="process-btn" onclick="processTask('dub')"> |
| | <i class="fa-solid fa-wand-magic-sparkles"></i> Generate Dub |
| | </button> |
| | |
| | ${getProgressTemplate()} |
| | ${getResultsTemplate('dub')} |
| | `; |
| | } |
| | |
| | function getProgressTemplate() { |
| | return ` |
| | <div class="progress-container" id="progress-container"> |
| | <div class="progress-bar" id="progress-bar"></div> |
| | </div> |
| | <div class="status-text" id="status-text"></div> |
| | `; |
| | } |
| | |
| | function getResultsTemplate(type) { |
| | let content = ''; |
| | if (type === 'transcription') { |
| | content = `<p style="color:var(--text-muted); text-align:center; padding:2rem;">No transcription generated yet.</p>`; |
| | } else if (type === 'translation') { |
| | content = `<p style="color:var(--text-muted); text-align:center; padding:2rem;">No translation generated yet.</p>`; |
| | } else if (type === 'dub') { |
| | content = ` |
| | <div class="audio-player-wrapper"> |
| | <div class="waveform" id="waveform"> |
| | |
| | </div> |
| | <audio controls style="width: 100%; margin-top:1rem;"> |
| | <source src="" type="audio/mpeg"> |
| | Your browser does not support the audio element. |
| | </audio> |
| | <div style="display:flex; gap:10px; margin-top:10px;"> |
| | <button class="form-select" style="width:auto; cursor:pointer;">Download MP3</button> |
| | <button class="form-select" style="width:auto; cursor:pointer;">Download Video</button> |
| | </div> |
| | </div> |
| | `; |
| | } |
| | |
| | return ` |
| | <div class="results-area" id="results-area"> |
| | <div class="result-card"> |
| | <div class="result-header"> |
| | <span style="font-weight:600">Output Result</span> |
| | <button class="form-select" style="width:auto; padding:4px 8px; cursor:pointer; font-size:0.8rem;">Copy / Save</button> |
| | </div> |
| | <div class="result-content" id="result-content"> |
| | ${content} |
| | </div> |
| | </div> |
| | </div> |
| | `; |
| | } |
| | |
| | // --- Logic --- |
| | |
| | function attachListeners() { |
| | const dropZone = document.getElementById('drop-zone'); |
| | const fileInput = document.getElementById('file-input'); |
| | |
| | if(dropZone) { |
| | dropZone.addEventListener('click', () => fileInput.click()); |
| | |
| | dropZone.addEventListener('dragover', (e) => { |
| | e.preventDefault(); |
| | dropZone.classList.add('drag-over'); |
| | }); |
| | |
| | dropZone.addEventListener('dragleave', () => { |
| | dropZone.classList.remove('drag-over'); |
| | }); |
| | |
| | dropZone.addEventListener('drop', (e) => { |
| | e.preventDefault(); |
| | dropZone.classList.remove('drag-over'); |
| | if (e.dataTransfer.files.length) { |
| | handleFile(e.dataTransfer.files[0]); |
| | } |
| | }); |
| | } |
| | |
| | if(fileInput) { |
| | fileInput.addEventListener('change', (e) => { |
| | if (e.target.files.length) { |
| | handleFile(e.target.files[0]); |
| | } |
| | }); |
| | } |
| | } |
| | |
| | function handleFile(file) { |
| | state.uploadedFile = file; |
| | |
| | // Update UI |
| | document.getElementById('filename').innerText = file.name; |
| | document.getElementById('filesize') ? document.getElementById('filesize').innerText = (file.size / 1024 / 1024).toFixed(2) + ' MB' : null; |
| | |
| | document.querySelector('.upload-zone').style.display = 'none'; |
| | document.getElementById('file-info').style.display = 'flex'; |
| | } |
| | |
| | function removeFile() { |
| | state.uploadedFile = null; |
| | document.querySelector('.upload-zone').style.display = 'block'; |
| | document.getElementById('file-info').style.display = 'none'; |
| | document.getElementById('file-input').value = ''; |
| | } |
| | |
| | function processTask(task) { |
| | if (!state.uploadedFile) { |
| | alert('Please upload a file first.'); |
| | return; |
| | } |
| | |
| | const btn = document.getElementById('process-btn'); |
| | const progressContainer = document.getElementById('progress-container'); |
| | const progressBar = document.getElementById('progress-bar'); |
| | const statusText = document.getElementById('status-text'); |
| | const resultsArea = document.getElementById('results-area'); |
| | |
| | // Reset UI |
| | btn.disabled = true; |
| | btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> Processing...'; |
| | progressContainer.style.display = 'block'; |
| | resultsArea.style.display = 'none'; |
| | state.progress = 0; |
| | |
| | // Simulation Loop |
| | const interval = setInterval(() => { |
| | state.progress += Math.random() * 10; |
| | |
| | if (state.progress > 100) state.progress = 100; |
| | |
| | progressBar.style.width = state.progress + '%'; |
| | |
| | if (state.progress < 30) { |
| | statusText.innerText = `Loading model context...`; |
| | } else if (state.progress < 70) { |
| | statusText.innerText = `Analyzing ${state.uploadedFile.name}...`; |
| | } else if (state.progress < 95) { |
| | statusText.innerText = task === 'dub' ? 'Synthesizing audio track...' : 'Generating text...'; |
| | } else { |
| | statusText.innerText = 'Finalizing...'; |
| | } |
| | |
| | if (state.progress >= 100) { |
| | clearInterval(interval); |
| | finishTask(task); |
| | } |
| | }, 300); |
| | } |
| | |
| | function finishTask(task) { |
| | const btn = document.getElementById('process-btn'); |
| | const statusText = document.getElementById('status-text'); |
| | const resultsArea = document.getElementById('results-area'); |
| | const resultContent = document.getElementById('result-content'); |
| | |
| | btn.disabled = false; |
| | btn.innerHTML = `<i class="fa-solid fa-check"></i> Done`; |
| | statusText.innerText = 'Processing Complete'; |
| | |
| | resultsArea.style.display = 'block'; |
| | |
| | // Simulate results based on task |
| | if (task === 'transcribe') { |
| | resultContent.innerHTML = ` |
| | <div style="margin-bottom:10px; border-bottom:1px solid #333; padding-bottom:10px;"> |
| | <span style="color:var(--primary); font-size:0.8rem;">00:00:05</span> |
| | <p>Welcome to the demonstration of the new AI system.</p> |
| | </div> |
| | <div style="margin-bottom:10px; border-bottom:1px solid #333; padding-bottom:10px;"> |
| | <span style="color:var(--primary); font-size:0.8rem;">00:00:12</span> |
| | <p>Today we are going to explore how local models can process media.</p> |
| | </div> |
| | <div> |
| | <span style="color:var(--primary); font-size:0.8rem;">00:00:20</span> |
| | <p>This is a simulated transcription for the UI demo.</p> |
| | </div> |
| | `; |
| | } else if (task === 'translate') { |
| | resultContent.innerHTML = ` |
| | <p><strong>Original:</strong> This is a simulated transcript.</p> |
| | <br> |
| | <p><strong>Translated (Spanish):</strong> Esta es una transcripción simulada.</p> |
| | `; |
| | } else if (task === 'dub') { |
| | // Generate fake waveform bars |
| | const waveform = document.getElementById('waveform'); |
| | if(waveform) { |
| | for(let i=0; i<30; i++) { |
| | let h = Math.floor(Math.random() * 40) + 10; |
| | let bar = document.createElement('div'); |
| | bar.className = 'bar'; |
| | bar.style.height = h + '%'; |
| | bar.style.animationDelay = (i * 0.1) + 's'; |
| | waveform.appendChild(bar); |
| | } |
| | } |
| | // Result content is already in template, just make sure it shows |
| | } |
| | } |
| | </script> |
| | </body> |
| | </html> |