anycoder-f83c97b9 / index.html
razvanab's picture
Upload folder using huggingface_hub
bd67099 verified
<!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 --- */
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);
}
/* --- Layout --- */
.app-container {
display: flex;
flex: 1;
height: calc(100vh - 70px);
}
/* --- Sidebar --- */
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 Content --- */
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);
}
/* --- Drag Drop Zone --- */
.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 --- */
.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 Button --- */
.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 Section --- */
.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 Styling */
.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 Bar */
.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;
}
/* Animations */
@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); }
}
/* Responsive */
@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; /* Hide on mobile for cleaner UI */
}
}
</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">
<!-- Dynamic Content will be injected here -->
</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">
<!-- Generated via JS -->
</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>