NitinBot001's picture
Update ttsfm-web/templates/base.html
9e9b02f verified
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Cronitor RUM -->
<script async src="https://rum.cronitor.io/script.js"></script>
<script>
window.cronitor = window.cronitor || function() { (window.cronitor.q = window.cronitor.q || []).push(arguments); };
cronitor('config', { clientKey: 'bdc4a3faf9c16d842b5099e1a0e3ba6f' });
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}TTSFM - Text-to-Speech{% endblock %}</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- Custom CSS -->
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
<!-- Additional Performance Optimizations -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🎤</text></svg>">
<!-- Meta tags for better SEO and social sharing -->
<meta name="description" content="TTSFM - A Python client for text-to-speech APIs. Simple to use with support for multiple voices and audio formats.">
<meta name="keywords" content="text-to-speech, TTS, python, API, voice synthesis, audio generation">
<meta name="author" content="TTSFM">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="{{ request.url }}">
<meta property="og:title" content="{% block og_title %}TTSFM - Python Text-to-Speech Client{% endblock %}">
<meta property="og:description" content="A Python client for text-to-speech APIs. Simple to use with support for multiple voices and audio formats.">
<!-- Twitter -->
<meta property="twitter:card" content="summary">
<meta property="twitter:url" content="{{ request.url }}">
<meta property="twitter:title" content="{% block twitter_title %}TTSFM - Python Text-to-Speech Client{% endblock %}">
<meta property="twitter:description" content="A Python client for text-to-speech APIs. Simple to use with support for multiple voices and audio formats.">
{% block extra_css %}{% endblock %}
</head>
<body>
<!-- Skip to content link for accessibility -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<!-- Clean Navigation -->
<nav class="navbar navbar-expand-lg fixed-top" style="background-color: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px); border-bottom: 1px solid #e5e7eb;">
<div class="container">
<a class="navbar-brand" href="{{ url_for('index') }}">
<i class="fas fa-microphone-alt me-2"></i>
<span class="fw-bold">TTSFM</span>
<span class="badge bg-primary ms-2 small">v3.0</span>
</a>
<button class="navbar-toggler border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('index') }}" aria-label="Home page">
<i class="fas fa-home me-1"></i>Home
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('playground') }}" aria-label="Interactive playground">
<i class="fas fa-play me-1"></i>Playground
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('docs') }}" aria-label="API documentation">
<i class="fas fa-book me-1"></i>Documentation
</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<span class="navbar-text d-flex align-items-center">
<span id="status-indicator" class="status-indicator status-offline" aria-hidden="true"></span>
<span id="status-text" class="small">Checking...</span>
</span>
</li>
<li class="nav-item ms-2">
<a class="btn btn-outline-primary btn-sm" href="https://github.com/dbccccccc/ttsfm" target="_blank" rel="noopener noreferrer" aria-label="View source code on GitHub">
<i class="fab fa-github me-1"></i>GitHub
</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- Main Content -->
<main id="main-content" style="padding-top: 76px;">
{% block content %}{% endblock %}
</main>
<!-- Simplified Footer -->
<footer class="footer py-4" style="background-color: #f8fafc; border-top: 1px solid #e5e7eb;" role="contentinfo">
<div class="container">
<div class="row align-items-center">
<div class="col-md-6">
<div class="d-flex align-items-center mb-2 mb-md-0">
<i class="fas fa-microphone-alt me-2 text-primary"></i>
<strong class="text-dark">TTSFM</strong>
<span class="ms-2 text-muted">Free Text-to-Speech for Python</span>
</div>
</div>
<div class="col-md-6 text-md-end">
<div class="d-flex justify-content-md-end gap-3">
<a href="{{ url_for('playground') }}" class="text-decoration-none" style="color: #6b7280;">
<i class="fas fa-play me-1"></i>Demo
</a>
<a href="{{ url_for('docs') }}" class="text-decoration-none" style="color: #6b7280;">
<i class="fas fa-book me-1"></i>Docs
</a>
<a href="https://github.com/dbccccccc/ttsfm" class="text-decoration-none" style="color: #6b7280;" target="_blank" rel="noopener noreferrer">
<i class="fab fa-github me-1"></i>GitHub
</a>
</div>
</div>
</div>
<hr class="my-3" style="border-color: #e5e7eb;">
<div class="row align-items-center">
<div class="col-md-6">
<small class="text-muted">&copy; 2024 TTSFM. MIT License.</small>
</div>
<div class="col-md-6 text-md-end">
<small class="text-muted">
<span id="footer-status" class="d-inline-flex align-items-center">
<span class="status-indicator status-offline me-2"></span>
Status: <span id="footer-status-text" class="ms-1">Checking...</span>
</span>
</small>
</div>
</div>
</div>
</footer>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Enhanced Common JavaScript -->
<script>
// Enhanced service status checking
async function checkStatus() {
try {
const response = await fetch('/api/health');
const data = await response.json();
const indicator = document.getElementById('status-indicator');
const text = document.getElementById('status-text');
const footerIndicator = document.querySelector('#footer-status .status-indicator');
const footerText = document.getElementById('footer-status-text');
if (response.ok && data.status === 'healthy') {
// Update navbar status
indicator.className = 'status-indicator status-online';
text.textContent = 'Online';
// Update footer status
if (footerIndicator) footerIndicator.className = 'status-indicator status-online';
if (footerText) footerText.textContent = 'Online';
} else {
// Update navbar status
indicator.className = 'status-indicator status-offline';
text.textContent = 'Offline';
// Update footer status
if (footerIndicator) footerIndicator.className = 'status-indicator status-offline';
if (footerText) footerText.textContent = 'Offline';
}
} catch (error) {
// Update navbar status
const indicator = document.getElementById('status-indicator');
const text = document.getElementById('status-text');
indicator.className = 'status-indicator status-offline';
text.textContent = 'Offline';
// Update footer status
const footerIndicator = document.querySelector('#footer-status .status-indicator');
const footerText = document.getElementById('footer-status-text');
if (footerIndicator) footerIndicator.className = 'status-indicator status-offline';
if (footerText) footerText.textContent = 'Offline';
}
}
// Enhanced page initialization
document.addEventListener('DOMContentLoaded', function() {
// Check status immediately and periodically
checkStatus();
setInterval(checkStatus, 30000); // Check every 30 seconds
// Initialize tooltips
if (typeof bootstrap !== 'undefined') {
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
}
// Add smooth scrolling for anchor links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
const target = document.querySelector(this.getAttribute('href'));
if (target) {
e.preventDefault();
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
// Add fade-in animation to main content
const mainContent = document.querySelector('main');
if (mainContent) {
mainContent.classList.add('fade-in');
}
// Add loading states to external links
document.querySelectorAll('a[target="_blank"]').forEach(link => {
link.addEventListener('click', function() {
this.style.opacity = '0.7';
setTimeout(() => {
this.style.opacity = '1';
}, 1000);
});
});
});
// Enhanced utility function to show loading state
function setLoading(button, loading) {
if (loading) {
button.classList.add('loading');
button.disabled = true;
button.style.cursor = 'wait';
} else {
button.classList.remove('loading');
button.disabled = false;
button.style.cursor = 'pointer';
}
}
// Enhanced utility function to show alerts
function showAlert(message, type = 'info', duration = 5000) {
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show fade-in`;
alertDiv.style.position = 'relative';
alertDiv.style.zIndex = '1050';
alertDiv.innerHTML = `
<i class="fas fa-${getAlertIcon(type)} me-2"></i>
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
`;
// Find the best container to insert the alert
const container = document.querySelector('main .container') || document.querySelector('.container') || document.body;
if (container) {
container.insertBefore(alertDiv, container.firstChild);
// Auto-dismiss after specified duration
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.classList.remove('show');
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 150);
}
}, duration);
// Scroll to alert if it's not visible
alertDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
// Helper function to get appropriate icon for alert type
function getAlertIcon(type) {
const icons = {
'success': 'check-circle',
'danger': 'exclamation-triangle',
'warning': 'exclamation-triangle',
'info': 'info-circle',
'primary': 'info-circle'
};
return icons[type] || 'info-circle';
}
// Enhanced error handling for fetch requests
async function safeFetch(url, options = {}) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response;
} catch (error) {
console.error('Fetch error:', error);
showAlert(`Network error: ${error.message}`, 'danger');
throw error;
}
}
// Performance monitoring
window.addEventListener('load', function() {
// Log page load time
const loadTime = performance.now();
console.log(`Page loaded in ${Math.round(loadTime)}ms`);
// Check for slow loading resources
if (loadTime > 3000) {
console.warn('Page load time is slow. Consider optimizing resources.');
}
});
// Keyboard shortcuts
document.addEventListener('keydown', function(e) {
// Alt + H for home
if (e.altKey && e.key === 'h') {
e.preventDefault();
window.location.href = '{{ url_for("index") }}';
}
// Alt + P for playground
if (e.altKey && e.key === 'p') {
e.preventDefault();
window.location.href = '{{ url_for("playground") }}';
}
// Alt + D for docs
if (e.altKey && e.key === 'd') {
e.preventDefault();
window.location.href = '{{ url_for("docs") }}';
}
});
</script>
{% block extra_js %}{% endblock %}
</body>
</html>