Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AI HDU - Self Help Desk</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=Lexend:wght@100..900&display=swap" rel="stylesheet"> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| font-family: "Lexend", sans-serif; | |
| } | |
| body { | |
| /* font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, sans-serif; */ | |
| line-height: 1.5; | |
| color: #1d1d1f; | |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| /* Top Navigation Bar */ | |
| .top-nav { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| height: 60px; | |
| background: rgba(255, 255, 255, 0.6); | |
| backdrop-filter: blur(10px); | |
| -webkit-backdrop-filter: blur(10px); | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.2); | |
| z-index: 1000; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin: 10px; | |
| border-radius: 20px; | |
| box-shadow: 0 0px 10px rgba(0, 0, 0, 0.1); | |
| } | |
| .nav-content { | |
| /* max-width: 1200px; */ | |
| width: 100%; | |
| padding: 0 24px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| } | |
| .nav-title { | |
| font-size: 1.1rem; | |
| font-weight: 600; | |
| color: #1d1d1f; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .nav-stats { | |
| font-size: 0.85rem; | |
| color: #86868b; | |
| display: flex; | |
| align-items: center; | |
| gap: 16px; | |
| } | |
| /* Main Container */ | |
| .container { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| padding: 80px 20px 120px; | |
| } | |
| /* Content Grid */ | |
| .blog-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); | |
| gap: 16px; | |
| margin-top: 20px; | |
| } | |
| .blog-card { | |
| background: rgba(255, 255, 255, 0.8); | |
| backdrop-filter: blur(20px); | |
| -webkit-backdrop-filter: blur(20px); | |
| border: 1px solid rgba(255, 255, 255, 0.3); | |
| border-radius: 12px; | |
| padding: 16px; | |
| transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); | |
| cursor: pointer; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .blog-card:hover { | |
| transform: translateY(-2px); | |
| background: rgba(255, 255, 255, 0.9); | |
| border-color: rgba(0, 122, 255, 0.3); | |
| box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); | |
| } | |
| .blog-title { | |
| font-size: 1.1rem; | |
| font-weight: 600; | |
| color: #1d1d1f; | |
| margin-bottom: 6px; | |
| line-height: 1.3; | |
| display: -webkit-box; | |
| -webkit-line-clamp: 2; | |
| line-clamp: 2; | |
| -webkit-box-orient: vertical; | |
| overflow: hidden; | |
| } | |
| .blog-excerpt { | |
| color: #86868b; | |
| margin-bottom: 12px; | |
| line-height: 1.4; | |
| font-size: 0.9rem; | |
| display: -webkit-box; | |
| -webkit-line-clamp: 2; | |
| line-clamp: 2; | |
| -webkit-box-orient: vertical; | |
| overflow: hidden; | |
| } | |
| .blog-meta { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| font-size: 0.8rem; | |
| color: #86868b; | |
| } | |
| .blog-date { | |
| color: #007aff; | |
| font-weight: 500; | |
| } | |
| .blog-size { | |
| background: rgba(0, 122, 255, 0.1); | |
| color: #007aff; | |
| padding: 2px 6px; | |
| border-radius: 6px; | |
| font-weight: 500; | |
| } | |
| .match-indicators { | |
| font-size: 0.75rem; | |
| color: #007aff; | |
| margin-bottom: 6px; | |
| font-weight: 500; | |
| } | |
| /* Floating Search Bar */ | |
| .search-container { | |
| position: fixed; | |
| bottom: 24px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background: rgba(255, 255, 255, 0.1); | |
| backdrop-filter: blur(1px); | |
| -webkit-backdrop-filter: blur(1px); | |
| border: 1px solid rgba(255, 255, 255, 0.3); | |
| border-radius: 20px; | |
| padding: 20px; | |
| max-width: 500px; | |
| width: calc(100% - 48px); | |
| box-shadow: 0 0px 10px rgba(0, 0, 0, 0.1); | |
| z-index: 999; | |
| } | |
| .search-box { | |
| position: relative; | |
| } | |
| .search-input { | |
| width: 100%; | |
| padding: 10px 15px; | |
| font-size: 1rem; | |
| border: none; | |
| background: rgba(255, 255, 255, 0.9); | |
| border-radius: 10px; | |
| outline: none; | |
| transition: all 0.3s ease; | |
| color: #1d1d1f; | |
| } | |
| .search-input::placeholder { | |
| color: #86868b; | |
| } | |
| .search-input:focus { | |
| background: rgba(255, 255, 255, 0.9); | |
| box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.3); | |
| } | |
| .search-icon { | |
| position: absolute; | |
| right: 12px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| color: #86868b; | |
| font-size: 1rem; | |
| } | |
| .search-highlight { | |
| background: rgba(255, 214, 10, 0.7); | |
| color: #1d1d1f; | |
| padding: 1px 2px; | |
| border-radius: 3px; | |
| font-weight: 600; | |
| } | |
| .no-results { | |
| text-align: center; | |
| padding: 60px 20px; | |
| color: #86868b; | |
| background: rgba(255, 255, 255, 0.6); | |
| backdrop-filter: blur(20px); | |
| -webkit-backdrop-filter: blur(20px); | |
| border-radius: 16px; | |
| border: 1px solid rgba(255, 255, 255, 0.3); | |
| } | |
| .no-results h3 { | |
| font-size: 1.3rem; | |
| margin-bottom: 8px; | |
| color: #1d1d1f; | |
| font-weight: 600; | |
| } | |
| .loading { | |
| text-align: center; | |
| padding: 40px; | |
| color: #007aff; | |
| font-size: 1rem; | |
| background: rgba(255, 255, 255, 0.6); | |
| backdrop-filter: blur(20px); | |
| -webkit-backdrop-filter: blur(20px); | |
| border-radius: 16px; | |
| border: 1px solid rgba(255, 255, 255, 0.3); | |
| } | |
| .error { | |
| background: rgba(255, 59, 48, 0.1); | |
| border: 1px solid rgba(255, 59, 48, 0.3); | |
| color: #d70015; | |
| padding: 16px; | |
| border-radius: 12px; | |
| margin: 20px 0; | |
| backdrop-filter: blur(20px); | |
| -webkit-backdrop-filter: blur(20px); | |
| } | |
| /* Smooth scrolling */ | |
| html { | |
| scroll-behavior: smooth; | |
| } | |
| /* Custom scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: rgba(255, 255, 255, 0.1); | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: rgba(0, 0, 0, 0.2); | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: rgba(0, 0, 0, 0.3); | |
| } | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 80px 16px 120px; | |
| } | |
| .blog-grid { | |
| grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); | |
| gap: 12px; | |
| } | |
| .nav-content { | |
| padding: 0 16px; | |
| } | |
| .nav-stats { | |
| flex-direction: column; | |
| gap: 4px; | |
| align-items: flex-end; | |
| font-size: 0.8rem; | |
| } | |
| .search-container { | |
| bottom: 16px; | |
| width: calc(100% - 32px); | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| .blog-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .nav-stats { | |
| display: none; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Top Navigation --> | |
| <div class="top-nav"> | |
| <div class="nav-content"> | |
| <div class="nav-title"> | |
| <span>π€</span> | |
| <span>Artificial Intelligence Horizontal Division Unit - Self Help Desk</span> | |
| </div> | |
| <div class="nav-stats"> | |
| <span id="resultsCount">Loading...</span> | |
| <span id="lastUpdated"></span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Main Container --> | |
| <div class="container"> | |
| <div id="blogContainer"> | |
| <div class="loading"> | |
| π Loading documentation files... | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Floating Search Bar --> | |
| <div class="search-container"> | |
| <div class="search-box"> | |
| <input type="text" class="search-input" placeholder="Search documentation..." id="searchInput"> | |
| <span class="search-icon">π</span> | |
| </div> | |
| </div> | |
| <script> | |
| class DocumentationHub { | |
| constructor() { | |
| this.blogs = []; | |
| this.filteredBlogs = []; | |
| this.allBlogsCache = []; // Cache for offline search | |
| this.searchInput = document.getElementById('searchInput'); | |
| this.blogContainer = document.getElementById('blogContainer'); | |
| this.resultsCount = document.getElementById('resultsCount'); | |
| this.lastUpdated = document.getElementById('lastUpdated'); | |
| this.searchTimeout = null; // For debouncing | |
| this.init(); | |
| } | |
| async init() { | |
| await this.loadBlogs(); | |
| this.setupEventListeners(); | |
| this.renderBlogs(); | |
| this.updateStats(); | |
| } | |
| async loadBlogs() { | |
| try { | |
| // Try to fetch from the local API first | |
| try { | |
| const response = await fetch('/api/blogs'); | |
| if (response.ok) { | |
| this.blogs = await response.json(); | |
| this.allBlogsCache = [...this.blogs]; // Cache for offline search | |
| this.filteredBlogs = [...this.blogs]; | |
| return; | |
| } | |
| } catch (error) { | |
| console.log('Local API not available, using sample data'); | |
| } | |
| // Fallback to sample data | |
| const sampleBlogs = [ | |
| { | |
| filename: 'getting-started.md', | |
| title: 'Getting Started Guide', | |
| excerpt: 'Learn how to get started with our platform and set up your first project.', | |
| size: '2.5 KB', | |
| lastModified: '2025-06-20' | |
| }, | |
| { | |
| filename: 'api-documentation.md', | |
| title: 'API Documentation', | |
| excerpt: 'Complete reference for all API endpoints, authentication, and examples.', | |
| size: '15.2 KB', | |
| lastModified: '2025-06-25' | |
| }, | |
| { | |
| filename: 'troubleshooting.md', | |
| title: 'Troubleshooting Guide', | |
| excerpt: 'Common issues and their solutions to help you resolve problems quickly.', | |
| size: '8.7 KB', | |
| lastModified: '2025-06-22' | |
| } | |
| ]; | |
| this.blogs = sampleBlogs; | |
| this.allBlogsCache = [...sampleBlogs]; | |
| this.filteredBlogs = [...this.blogs]; | |
| } catch (error) { | |
| this.showError('Failed to load documentation files'); | |
| } | |
| } | |
| setupEventListeners() { | |
| this.searchInput.addEventListener('input', (e) => { | |
| // Debounce search for better performance | |
| if (this.searchTimeout) { | |
| clearTimeout(this.searchTimeout); | |
| } | |
| this.searchTimeout = setTimeout(() => { | |
| this.searchBlogs(e.target.value); | |
| }, 300); | |
| }); | |
| this.searchInput.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') { | |
| if (this.searchTimeout) { | |
| clearTimeout(this.searchTimeout); | |
| } | |
| this.searchBlogs(e.target.value); | |
| } | |
| }); | |
| } | |
| async searchBlogs(query) { | |
| const searchTerm = query.toLowerCase().trim(); | |
| if (!searchTerm) { | |
| this.filteredBlogs = [...this.allBlogsCache]; | |
| this.renderBlogs(); | |
| this.updateStats(); | |
| return; | |
| } | |
| // Show loading state for search | |
| this.showSearchLoading(); | |
| try { | |
| // Try to use server-side search API for full-text search | |
| const response = await fetch(`/api/search?q=${encodeURIComponent(searchTerm)}`); | |
| if (response.ok) { | |
| const searchData = await response.json(); | |
| this.filteredBlogs = searchData.results; | |
| this.renderBlogs(searchTerm); | |
| this.updateStats(searchData.query); | |
| return; | |
| } | |
| } catch (error) { | |
| console.log('Server search not available, using client-side search'); | |
| } | |
| // Fallback to client-side search (title, excerpt, filename only) | |
| this.filteredBlogs = this.allBlogsCache.filter(blog => | |
| blog.title.toLowerCase().includes(searchTerm) || | |
| blog.excerpt.toLowerCase().includes(searchTerm) || | |
| blog.filename.toLowerCase().includes(searchTerm) | |
| ); | |
| this.renderBlogs(searchTerm); | |
| this.updateStats(searchTerm); | |
| } | |
| showSearchLoading() { | |
| this.blogContainer.innerHTML = ` | |
| <div class="loading"> | |
| π Searching through documentation... | |
| </div> | |
| `; | |
| } | |
| renderBlogs(searchTerm = '') { | |
| if (this.filteredBlogs.length === 0) { | |
| const noResultsHTML = searchTerm | |
| ? `<div class="no-results"> | |
| <h3>π No documents found for "${searchTerm}"</h3> | |
| <p>Try different keywords or browse all available documentation.</p> | |
| </div>` | |
| : `<div class="no-results"> | |
| <h3>π No documents available</h3> | |
| <p>Add markdown files to the blogs directory to get started.</p> | |
| </div>`; | |
| this.blogContainer.innerHTML = noResultsHTML; | |
| return; | |
| } | |
| const blogHTML = this.filteredBlogs.map(blog => { | |
| // Highlight search terms in title and excerpt | |
| let highlightedTitle = blog.title; | |
| let highlightedExcerpt = blog.excerpt; | |
| if (searchTerm) { | |
| highlightedTitle = this.highlightText(blog.title, searchTerm); | |
| highlightedExcerpt = this.highlightText(blog.excerpt, searchTerm); | |
| } | |
| // Show match indicators if available | |
| let matchIndicators = ''; | |
| if (blog.matches) { | |
| const indicators = []; | |
| if (blog.matches.filename) indicators.push('π filename'); | |
| if (blog.matches.title) indicators.push('π title'); | |
| if (blog.matches.content) indicators.push('π content'); | |
| if (indicators.length > 0) { | |
| matchIndicators = `<div class="match-indicators">Found in: ${indicators.join(', ')}</div>`; | |
| } | |
| } | |
| return ` | |
| <div class="blog-card" onclick="window.openBlog('${blog.filename}')"> | |
| <div class="blog-title">${highlightedTitle}</div> | |
| <div class="blog-excerpt">${highlightedExcerpt}</div> | |
| ${matchIndicators} | |
| <div class="blog-meta"> | |
| <span class="blog-date">π ${blog.lastModified}</span> | |
| <span class="blog-size">π ${blog.size}</span> | |
| </div> | |
| </div> | |
| `; | |
| }).join(''); | |
| this.blogContainer.innerHTML = `<div class="blog-grid">${blogHTML}</div>`; | |
| } | |
| highlightText(text, searchTerm) { | |
| if (!searchTerm) return text; | |
| const regex = new RegExp(`(${this.escapeRegExp(searchTerm)})`, 'gi'); | |
| return text.replace(regex, '<mark class="search-highlight">$1</mark>'); | |
| } | |
| escapeRegExp(string) { | |
| return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | |
| } | |
| updateStats(searchQuery = '') { | |
| const total = this.allBlogsCache.length; | |
| const shown = this.filteredBlogs.length; | |
| if (searchQuery) { | |
| this.resultsCount.textContent = `${shown} of ${total} documents found`; | |
| } else if (this.searchInput.value.trim()) { | |
| this.resultsCount.textContent = `${shown} of ${total} documents`; | |
| } else { | |
| this.resultsCount.textContent = `${total} documents`; | |
| } | |
| this.lastUpdated.textContent = `Updated ${new Date().toLocaleDateString()}`; | |
| } | |
| showError(message) { | |
| this.blogContainer.innerHTML = ` | |
| <div class="error"> | |
| <strong>Error:</strong> ${message} | |
| </div> | |
| `; | |
| } | |
| } | |
| // Global function to open blog files | |
| window.openBlog = function (filename) { | |
| // Open the blog viewer in a new window | |
| const blogUrl = `./blog-viewer.html?file=${encodeURIComponent(filename)}`; | |
| window.open(blogUrl, '_blank', 'width=1000,height=700,scrollbars=yes,resizable=yes'); | |
| }; | |
| // Initialize the documentation hub | |
| document.addEventListener('DOMContentLoaded', () => { | |
| new DocumentationHub(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |