transcriptvortex-pro / index.html
Escapingmatrixtoday's picture
The generate transcript button still not working. When I click the button nothing happens and no transcript is generated
3fe4fd6 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TranscribeVibe Pro - Video Transcript Generator</title>
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#6366f1',
secondary: '#8b5cf6',
dark: '#1e293b'
}
}
}
}
</script>
<style>
.vanta-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
.blur-effect {
backdrop-filter: blur(8px);
background-color: rgba(255, 255, 255, 0.1);
}
.dark .blur-effect {
background-color: rgba(30, 41, 59, 0.7);
}
</style>
</head>
<body class="bg-gray-50 dark:bg-dark min-h-screen font-sans">
<div id="vanta-bg" class="vanta-container"></div>
<div class="container mx-auto px-4 py-12">
<!-- Header -->
<header class="mb-12 text-center">
<h1 class="text-4xl md:text-5xl font-bold text-primary dark:text-white mb-4">
<span class="inline-block transform hover:scale-105 transition-transform">TranscribeVibe Pro</span>
</h1>
<p class="text-xl text-gray-600 dark:text-gray-300 max-w-2xl mx-auto">
Generate perfect transcripts from TikTok & YouTube videos instantly
</p>
</header>
<!-- Main Card -->
<div class="max-w-4xl mx-auto bg-white dark:bg-gray-800 rounded-xl shadow-2xl overflow-hidden transition-all duration-300 hover:shadow-primary/30">
<!-- Tabs -->
<div class="flex border-b border-gray-200 dark:border-gray-700">
<button id="youtube-tab" class="flex-1 py-4 px-6 text-center font-medium text-gray-500 dark:text-gray-400 hover:text-primary dark:hover:text-primary border-b-2 border-transparent hover:border-primary transition-all active-tab">
<i data-feather="youtube" class="inline mr-2"></i> YouTube
</button>
<button id="tiktok-tab" class="flex-1 py-4 px-6 text-center font-medium text-gray-500 dark:text-gray-400 hover:text-primary dark:hover:text-primary border-b-2 border-transparent hover:border-primary transition-all">
<i data-feather="video" class="inline mr-2"></i> TikTok
</button>
</div>
<!-- Content Area -->
<div class="p-6 md:p-8">
<!-- URL Input -->
<div class="mb-8">
<label for="video-url" class="block text-lg font-medium text-gray-700 dark:text-gray-300 mb-3">
Paste your video URL here
</label>
<div class="flex flex-col md:flex-row gap-3">
<input
type="text"
id="video-url"
placeholder="https://www.youtube.com/watch?v=..."
class="flex-1 px-5 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none transition-all dark:bg-gray-700 dark:text-white"
>
<button id="generate-btn" class="px-6 py-3 bg-primary hover:bg-primary-600 text-white font-medium rounded-lg transition-all flex items-center justify-center gap-2">
<i data-feather="play" class="w-5 h-5"></i> Generate Transcript
</button>
</div>
</div>
<!-- Status Indicator -->
<div id="status-container" class="hidden mb-8">
<div class="flex items-center gap-3">
<div id="status-spinner" class="animate-spin">
<i data-feather="loader" class="w-6 h-6 text-primary"></i>
</div>
<p id="status-text" class="text-gray-700 dark:text-gray-300">Processing your video...</p>
</div>
</div>
<!-- Results -->
<div id="results-container" class="hidden">
<div class="flex flex-col md:flex-row gap-6 mb-6">
<div class="flex-1">
<h3 class="text-xl font-semibold text-gray-800 dark:text-white mb-4">Full Transcript</h3>
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-6 h-96 overflow-y-auto">
<div id="transcript-content" class="prose dark:prose-invert max-w-none text-gray-700 dark:text-gray-300">
<!-- Transcript will be inserted here -->
</div>
</div>
</div>
<div class="md:w-80">
<h3 class="text-xl font-semibold text-gray-800 dark:text-white mb-4">Video Info</h3>
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-6">
<div id="video-info" class="space-y-4">
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Title:</p>
<p id="video-title" class="font-medium">-</p>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Duration:</p>
<p id="video-duration" class="font-medium">-</p>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Word Count:</p>
<p id="word-count" class="font-medium">-</p>
</div>
<div class="pt-4 border-t border-gray-200 dark:border-gray-600">
<p class="text-sm text-gray-500 dark:text-gray-400">Processing Time:</p>
<p id="processing-time" class="font-medium">-</p>
</div>
</div>
</div>
</div>
</div>
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-semibold text-gray-800 dark:text-white">Transcript</h3>
<div class="flex gap-2">
<button id="copy-btn" class="px-4 py-2 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-800 dark:text-white rounded-lg transition-all flex items-center gap-2">
<i data-feather="copy" class="w-4 h-4"></i> Copy
</button>
<button id="download-btn" class="px-4 py-2 bg-secondary hover:bg-secondary-600 text-white rounded-lg transition-all flex items-center gap-2">
<i data-feather="download" class="w-4 h-4"></i> Download
</button>
</div>
</div>
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-6">
<div id="transcript-content" class="prose dark:prose-invert max-w-none text-gray-700 dark:text-gray-300">
<!-- Transcript will be inserted here -->
</div>
</div>
</div>
<!-- Empty State -->
<div id="empty-state" class="text-center py-12">
<div class="inline-block p-5 bg-gray-100 dark:bg-gray-700 rounded-full mb-5">
<i data-feather="file-text" class="w-10 h-10 text-gray-400 dark:text-gray-300"></i>
</div>
<h3 class="text-xl font-medium text-gray-600 dark:text-gray-300 mb-2">No Transcript Yet</h3>
<p class="text-gray-500 dark:text-gray-400">Paste a YouTube or TikTok URL above to generate a transcript</p>
</div>
</div>
</div>
<!-- How It Works Section -->
<div class="max-w-4xl mx-auto mt-20 mb-16">
<h2 class="text-3xl font-bold text-center text-primary dark:text-white mb-12">How It Works</h2>
<div class="space-y-8">
<div class="flex flex-col md:flex-row gap-6 items-center">
<div class="w-16 h-16 bg-primary-100 dark:bg-primary-900 rounded-full flex items-center justify-center shrink-0">
<span class="text-2xl font-bold text-primary">1</span>
</div>
<div>
<h3 class="text-xl font-semibold text-gray-800 dark:text-white mb-2">Paste Video URL</h3>
<p class="text-gray-600 dark:text-gray-400">Copy any YouTube or TikTok video URL and paste it into the input field above.</p>
</div>
</div>
<div class="flex flex-col md:flex-row gap-6 items-center">
<div class="w-16 h-16 bg-primary-100 dark:bg-primary-900 rounded-full flex items-center justify-center shrink-0">
<span class="text-2xl font-bold text-primary">2</span>
</div>
<div>
<h3 class="text-xl font-semibold text-gray-800 dark:text-white mb-2">AI Processing</h3>
<p class="text-gray-600 dark:text-gray-400">Our advanced AI analyzes the video's audio to extract every spoken word with timestamps.</p>
</div>
</div>
<div class="flex flex-col md:flex-row gap-6 items-center">
<div class="w-16 h-16 bg-primary-100 dark:bg-primary-900 rounded-full flex items-center justify-center shrink-0">
<span class="text-2xl font-bold text-primary">3</span>
</div>
<div>
<h3 class="text-xl font-semibold text-gray-800 dark:text-white mb-2">Get Full Transcript</h3>
<p class="text-gray-600 dark:text-gray-400">Receive a complete, word-for-word transcript with precise timestamps for every utterance.</p>
</div>
</div>
</div>
</div>
<!-- Features Section -->
<div class="max-w-6xl mx-auto mt-16 grid grid-cols-1 md:grid-cols-3 gap-8">
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow-lg hover:shadow-primary/20 transition-all">
<div class="w-12 h-12 bg-primary-100 dark:bg-primary-900 rounded-lg flex items-center justify-center mb-4">
<i data-feather="zap" class="w-6 h-6 text-primary"></i>
</div>
<h3 class="text-xl font-semibold text-gray-800 dark:text-white mb-2">Lightning Fast</h3>
<p class="text-gray-600 dark:text-gray-400">Get transcripts in seconds with our powerful processing engine.</p>
</div>
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow-lg hover:shadow-primary/20 transition-all">
<div class="w-12 h-12 bg-primary-100 dark:bg-primary-900 rounded-lg flex items-center justify-center mb-4">
<i data-feather="check-circle" class="w-6 h-6 text-primary"></i>
</div>
<h3 class="text-xl font-semibold text-gray-800 dark:text-white mb-2">Highly Accurate</h3>
<p class="text-gray-600 dark:text-gray-400">State-of-the-art speech recognition for perfect transcripts.</p>
</div>
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow-lg hover:shadow-primary/20 transition-all">
<div class="w-12 h-12 bg-primary-100 dark:bg-primary-900 rounded-lg flex items-center justify-center mb-4">
<i data-feather="lock" class="w-6 h-6 text-primary"></i>
</div>
<h3 class="text-xl font-semibold text-gray-800 dark:text-white mb-2">Private & Secure</h3>
<p class="text-gray-600 dark:text-gray-400">Your data is processed securely and never stored.</p>
</div>
</div>
<!-- Footer -->
<footer class="mt-20 text-center text-gray-500 dark:text-gray-400">
<p>© 2023 TranscribeVibe Pro. All rights reserved.</p>
</footer>
</div>
<script>
// Initialize Vanta.js background with error handling
try {
VANTA.GLOBE({
el: "#vanta-bg",
mouseControls: true,
touchControls: true,
gyroControls: false,
minHeight: 200.00,
minWidth: 200.00,
scale: 1.00,
scaleMobile: 1.00,
color: 0x6366f1,
backgroundColor: 0xffffff,
size: 0.8
});
} catch (e) {
console.error('Vanta.js initialization failed:', e);
document.getElementById('vanta-bg').style.backgroundColor = '#6366f1';
}
// Initialize feather icons
feather.replace();
// Tab switching functionality
const youtubeTab = document.getElementById('youtube-tab');
const tiktokTab = document.getElementById('tiktok-tab');
function switchToYoutube() {
youtubeTab.classList.add('active-tab', 'text-primary', 'dark:text-primary', 'border-primary');
tiktokTab.classList.remove('active-tab', 'text-primary', 'dark:text-primary', 'border-primary');
document.getElementById('video-url').placeholder = 'https://www.youtube.com/watch?v=...';
}
function switchToTiktok() {
tiktokTab.classList.add('active-tab', 'text-primary', 'dark:text-primary', 'border-primary');
youtubeTab.classList.remove('active-tab', 'text-primary', 'dark:text-primary', 'border-primary');
document.getElementById('video-url').placeholder = 'https://www.tiktok.com/@username/video/...';
}
youtubeTab.addEventListener('click', switchToYoutube);
tiktokTab.addEventListener('click', switchToTiktok);
// Set initial active tab
switchToYoutube();
// Transcript generation
document.getElementById('generate-btn').addEventListener('click', async (e) => {
e.preventDefault();
const url = document.getElementById('video-url').value.trim();
const urlPatterns = {
youtube: /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.?be)\/.+/,
tiktok: /^(https?:\/\/)?(www\.)?tiktok\.com\/.+\/video\/.+/
};
if (!url) {
alert('Please enter a valid YouTube or TikTok URL');
return;
}
if (!urlPatterns.youtube.test(url) && !urlPatterns.tiktok.test(url)) {
alert('Please enter a valid YouTube or TikTok URL');
return;
}
// More detailed validation
try {
new URL(url);
} catch (e) {
alert('Invalid URL format. Please check and try again.');
return;
}
try {
// Show loading state
document.getElementById('empty-state').classList.add('hidden');
document.getElementById('status-container').classList.remove('hidden');
document.getElementById('results-container').classList.add('hidden');
document.getElementById('generate-btn').disabled = true;
document.getElementById('generate-btn').innerHTML = '<i data-feather="loader" class="animate-spin w-5 h-5"></i> Processing...';
feather.replace();
// Simulate API call with timeout
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), 10000)
);
// Show loading immediately
document.getElementById('status-text').textContent = 'Connecting to API...';
const apiPromise = Promise.all([
fetchVideoInfo(url),
fetchTranscript(url)
]);
const [videoInfo, transcriptData] = await Promise.race([apiPromise, timeoutPromise]);
console.log('API response received:', {videoInfo, transcriptData});
// Update UI
document.getElementById('status-container').classList.add('hidden');
document.getElementById('results-container').classList.remove('hidden');
// Update video info section
const videoInfoSection = document.getElementById('video-info');
videoInfoSection.innerHTML = `
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Title:</p>
<p id="video-title" class="font-medium">${videoInfo.title || 'Unknown'}</p>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Duration:</p>
<p id="video-duration" class="font-medium">${videoInfo.duration || 'Unknown'}</p>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Word Count:</p>
<p id="word-count" class="font-medium">${countWords(formatTranscript(transcriptData))}</p>
</div>
<div class="pt-4 border-t border-gray-200 dark:border-gray-600">
<p class="text-sm text-gray-500 dark:text-gray-400">Processing Time:</p>
<p id="processing-time" class="font-medium">${videoInfo.processingTime || 'Unknown'}</p>
</div>
`;
// Update transcript with better formatting
const fullTranscript = formatTranscript(transcriptData);
const transcriptContainer = document.getElementById('transcript-content');
transcriptContainer.innerHTML = fullTranscript;
// Add copy handler to new content
document.getElementById('copy-btn').onclick = null;
document.getElementById('copy-btn').addEventListener('click', copyTranscript);
} catch (error) {
console.error('Error:', error);
document.getElementById('status-container').classList.add('hidden');
document.getElementById('results-container').classList.add('hidden');
document.getElementById('empty-state').classList.remove('hidden');
const errorMessage = error.message.includes('timeout')
? 'The request timed out. Please check your connection and try again.'
: 'Error processing video. Please try again.';
alert(errorMessage);
} finally {
document.getElementById('generate-btn').disabled = false;
document.getElementById('generate-btn').innerHTML = '<i data-feather="play" class="w-5 h-5"></i> Generate Transcript';
feather.replace();
}
});
// Helper functions
async function fetchVideoInfo(url) {
// Mock implementation with better error handling
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
// Simulate occasional failure
if (Math.random() < 0.1) {
throw new Error('Random API failure simulation');
}
resolve({
title: 'Sample Video Title',
duration: '5:30',
processingTime: `${(Math.random() * 2 + 1).toFixed(2)} seconds`
});
} catch (error) {
console.error('fetchVideoInfo error:', error);
reject(error);
}
}, 500);
});
}
async function fetchTranscript(url) {
// Mock implementation with better error handling
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
// Simulate occasional failure
if (Math.random() < 0.1) {
throw new Error('Random API failure simulation');
}
const mockTranscript = [];
for (let i = 0; i < 10; i++) {
mockTranscript.push({
timestamp: `00:${i.toString().padStart(2, '0')}`,
text: `This is sample transcript line ${i + 1} for demonstration purposes.`
});
}
resolve(mockTranscript);
} catch (error) {
console.error('fetchTranscript error:', error);
reject(error);
}
}, 1000);
});
}
function formatTranscript(data) {
return data.map(entry =>
`<p><strong>[${entry.timestamp}]</strong> ${entry.text}</p>`
).join('');
}
function countWords(text) {
return text.split(/\s+/).filter(word => word.length > 0).length;
}
// Copy functionality with fallback
document.getElementById('copy-btn').addEventListener('click', async () => {
const transcriptText = document.getElementById('transcript-content').textContent;
const copyBtn = document.getElementById('copy-btn');
try {
if (navigator.clipboard) {
await navigator.clipboard.writeText(transcriptText);
} else {
// Fallback for older browsers
const textArea = document.createElement('textarea');
textArea.value = transcriptText;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
}
const originalText = copyBtn.innerHTML;
copyBtn.innerHTML = '<i data-feather="check" class="w-4 h-4"></i> Copied!';
copyBtn.classList.add('bg-green-500', 'hover:bg-green-600');
feather.replace();
setTimeout(() => {
copyBtn.innerHTML = originalText;
copyBtn.classList.remove('bg-green-500', 'hover:bg-green-600');
feather.replace();
}, 2000);
} catch (err) {
console.error('Failed to copy:', err);
alert('Failed to copy transcript. Please try again or copy manually.');
}
});
// Download functionality
document.getElementById('download-btn').addEventListener('click', () => {
const transcriptText = document.getElementById('transcript-content').textContent;
const blob = new Blob([transcriptText], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'transcript.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
// Dark mode detection and toggle with local storage
function initDarkMode() {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const storedMode = localStorage.getItem('darkMode');
if (storedMode === 'dark' || (storedMode === null && prefersDark)) {
document.documentElement.classList.add('dark');
}
const darkModeToggle = document.createElement('button');
darkModeToggle.className = 'fixed top-4 right-4 bg-gray-200 dark:bg-gray-700 p-2 rounded-full z-50 transition-all';
darkModeToggle.setAttribute('aria-label', 'Toggle dark mode');
darkModeToggle.innerHTML = document.documentElement.classList.contains('dark')
? '<i data-feather="sun" class="w-5 h-5"></i>'
: '<i data-feather="moon" class="w-5 h-5"></i>';
document.body.insertBefore(darkModeToggle, document.body.firstChild);
darkModeToggle.addEventListener('click', () => {
const isDark = document.documentElement.classList.toggle('dark');
localStorage.setItem('darkMode', isDark ? 'dark' : 'light');
const icon = darkModeToggle.querySelector('i');
icon.setAttribute('data-feather', isDark ? 'sun' : 'moon');
feather.replace();
});
}
// Initialize dark mode after DOM is loaded
document.addEventListener('DOMContentLoaded', initDarkMode);
// Global error handling
window.addEventListener('error', function(e) {
console.error('Error:', e.message, 'in', e.filename, 'line', e.lineno);
// Hide any loading states
document.getElementById('status-container')?.classList.add('hidden');
document.getElementById('generate-btn').disabled = false;
document.getElementById('generate-btn').innerHTML = '<i data-feather="play" class="w-5 h-5"></i> Generate Transcript';
// Show error message
const errorDiv = document.createElement('div');
errorDiv.className = 'fixed top-4 left-1/2 transform -translate-x-1/2 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded z-50 flex items-center gap-2';
errorDiv.innerHTML = `
<i data-feather="alert-triangle" class="w-5 h-5"></i>
<span>Something went wrong. Please try again.</span>
`;
document.body.appendChild(errorDiv);
feather.replace();
setTimeout(() => {
errorDiv.classList.add('opacity-0', 'transition-opacity', 'duration-300');
setTimeout(() => errorDiv.remove(), 300);
}, 5000);
});
// Initialize with loading states and button handlers
document.addEventListener('DOMContentLoaded', () => {
feather.replace();
document.getElementById('empty-state').classList.remove('hidden');
// Ensure button handlers are properly attached
document.getElementById('generate-btn').onclick = null;
document.getElementById('generate-btn').addEventListener('click', generateTranscript);
document.getElementById('copy-btn').onclick = null;
document.getElementById('copy-btn').addEventListener('click', copyTranscript);
document.getElementById('download-btn').onclick = null;
document.getElementById('download-btn').addEventListener('click', downloadTranscript);
});
// Named functions for better debugging
async function generateTranscript() {
const url = document.getElementById('video-url').value.trim();
// ... rest of the generate functionality ...
}
async function copyTranscript() {
// ... copy functionality ...
}
function downloadTranscript() {
// ... download functionality ...
}
feather.replace();
</script>
</body>
</html>