Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>LocalAI Nexus - Hot Chat & Generate</title> | |
| <!-- Phosphor Icons --> | |
| <script src="https://unpkg.com/@phosphor-icons/web@2.1.1/dist/phosphor.min.js"></script> | |
| <style> | |
| :root { | |
| --bg-dark: #09090b; | |
| --bg-panel: #161618; | |
| --bg-input: #202023; | |
| --accent-primary: #8b5cf6; | |
| --accent-secondary: #ec4899; | |
| --text-main: #e2e2e2; | |
| --text-muted: #88888b; | |
| --border: #2e2e31; | |
| --glow: 0 0 20px rgba(139, 92, 246, 0.15); | |
| --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
| } | |
| * { | |
| box-sizing: box-sizing; | |
| margin: 0; | |
| padding: 0; | |
| scrollbar-width: thin; | |
| scrollbar-color: var(--accent-primary) var(--bg-dark); | |
| } | |
| body { | |
| background-color: var(--bg-dark); | |
| color: var(--text-main); | |
| font-family: var(--font-family); | |
| height: 100vh; | |
| display: flex; | |
| overflow: hidden; | |
| } | |
| /* --- Sidebar --- */ | |
| .sidebar { | |
| width: 280px; | |
| background: var(--bg-panel); | |
| border-right: 1px solid var(--border); | |
| display: flex; | |
| flex-direction: column; | |
| padding: 1.5rem; | |
| transition: transform 0.3s ease; | |
| z-index: 10; | |
| } | |
| .brand { | |
| font-size: 1.2rem; | |
| font-weight: 700; | |
| color: var(--text-main); | |
| margin-bottom: 2rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .brand i { color: var(--accent-primary); } | |
| .system-status { | |
| background: rgba(255, 255, 255, 0.03); | |
| border: 1px solid var(--border); | |
| padding: 1rem; | |
| border-radius: 8px; | |
| margin-bottom: 2rem; | |
| font-size: 0.8rem; | |
| } | |
| .status-row { | |
| display: flex; | |
| justify-content: space-between; | |
| margin-bottom: 0.5rem; | |
| } | |
| .status-val { color: var(--accent-secondary); font-weight: bold; } | |
| .history-list { | |
| flex-grow: 1; | |
| overflow-y: auto; | |
| } | |
| .history-item { | |
| padding: 10px; | |
| border-radius: 6px; | |
| color: var(--text-muted); | |
| cursor: pointer; | |
| transition: 0.2s; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-size: 0.9rem; | |
| } | |
| .history-item:hover { | |
| background: rgba(255, 255, 255, 0.05); | |
| color: var(--text-main); | |
| } | |
| /* --- Main Chat Area --- */ | |
| .main-chat { | |
| flex-grow: 1; | |
| display: flex; | |
| flex-direction: column; | |
| position: relative; | |
| } | |
| .chat-container { | |
| flex-grow: 1; | |
| padding: 2rem; | |
| overflow-y: auto; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1.5rem; | |
| max-width: 900px; | |
| width: 100%; | |
| margin: 0 auto; | |
| } | |
| /* Messages */ | |
| .message { | |
| display: flex; | |
| gap: 1rem; | |
| opacity: 0; | |
| animation: fadeIn 0.3s forwards; | |
| } | |
| @keyframes fadeIn { to { opacity: 1; } } | |
| .message.user { | |
| justify-content: flex-end; | |
| } | |
| .bubble { | |
| max-width: 70%; | |
| padding: 1rem 1.5rem; | |
| border-radius: 12px; | |
| line-height: 1.5; | |
| position: relative; | |
| } | |
| .message.ai .bubble { | |
| background: transparent; | |
| border: 1px solid var(--border); | |
| color: var(--text-main); | |
| } | |
| .message.user .bubble { | |
| background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary)); | |
| color: white; | |
| box-shadow: var(--glow); | |
| } | |
| .message.ai .avatar { | |
| width: 32px; | |
| height: 32px; | |
| background: var(--bg-panel); | |
| border: 1px solid var(--border); | |
| border-radius: 6px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: var(--accent-primary); | |
| } | |
| /* Generated Image Styling */ | |
| .generated-image-container { | |
| margin-top: 10px; | |
| border-radius: 8px; | |
| overflow: hidden; | |
| border: 1px solid var(--border); | |
| position: relative; | |
| } | |
| .generated-image-container img { | |
| width: 100%; | |
| display: block; | |
| transition: transform 0.3s; | |
| } | |
| .generated-image-container:hover img { | |
| transform: scale(1.02); | |
| } | |
| /* --- Input Area --- */ | |
| .input-area-wrapper { | |
| padding: 2rem; | |
| background: var(--bg-dark); | |
| border-top: 1px solid var(--border); | |
| } | |
| .input-container { | |
| max-width: 900px; | |
| margin: 0 auto; | |
| background: var(--bg-input); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| padding: 10px; | |
| display: flex; | |
| flex-direction: column; | |
| box-shadow: var(--glow); | |
| transition: border-color 0.2s; | |
| } | |
| .input-container:focus-within { | |
| border-color: var(--accent-primary); | |
| } | |
| .mode-toggle { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 5px 10px; | |
| font-size: 0.8rem; | |
| color: var(--text-muted); | |
| cursor: pointer; | |
| user-select: none; | |
| } | |
| .mode-toggle.active { | |
| color: var(--accent-secondary); | |
| } | |
| .switch { | |
| position: relative; | |
| display: inline-block; | |
| width: 20px; | |
| height: 20px; | |
| } | |
| .switch input { opacity: 0; width: 0; height: 0; } | |
| .slider { | |
| position: absolute; | |
| cursor: pointer; | |
| top: 0; left: 0; right: 0; bottom: 0; | |
| background-color: #ccc; | |
| transition: .4s; | |
| border-radius: 20px; | |
| } | |
| .slider:before { | |
| position: absolute; | |
| content: ""; | |
| height: 16px; | |
| width: 16px; | |
| left: 2px; | |
| bottom: 2px; | |
| background-color: white; | |
| transition: .4s; | |
| border-radius: 50%; | |
| } | |
| input:checked + .slider { | |
| background-color: var(--accent-secondary); | |
| } | |
| input:checked + .slider:before { | |
| transform: translateX(0px); | |
| } | |
| .text-input-row { | |
| display: flex; | |
| align-items: flex-end; | |
| gap: 10px; | |
| padding-top: 5px; | |
| } | |
| textarea { | |
| flex-grow: 1; | |
| background: transparent; | |
| border: none; | |
| color: var(--text-main); | |
| resize: none; | |
| height: 40px; | |
| font-family: var(--font-family); | |
| font-size: 1rem; | |
| outline: none; | |
| } | |
| .action-btn { | |
| background: var(--bg-panel); | |
| border: 1px solid var(--border); | |
| color: var(--text-main); | |
| width: 40px; | |
| height: 40px; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: 0.2s; | |
| } | |
| .action-btn:hover { | |
| background: var(--accent-primary); | |
| border-color: var(--accent-primary); | |
| } | |
| .action-btn.send { | |
| background: var(--accent-primary); | |
| border: none; | |
| color: white; | |
| } | |
| /* Loading Animation */ | |
| .typing-indicator { | |
| display: flex; | |
| gap: 5px; | |
| padding: 10px; | |
| } | |
| .dot { | |
| width: 6px; | |
| height: 6px; | |
| background: var(--text-muted); | |
| border-radius: 50%; | |
| animation: bounce 1.4s infinite ease-in-out both; | |
| } | |
| .dot:nth-child(1) { animation-delay: -0.32s; } | |
| .dot:nth-child(2) { animation-delay: -0.16s; } | |
| @keyframes bounce { | |
| 0%, 80%, 100% { transform: scale(0); } | |
| 40% { transform: scale(1); } | |
| } | |
| /* Responsive */ | |
| @media (max-width: 768px) { | |
| .sidebar { | |
| position: absolute; | |
| transform: translateX(-100%); | |
| height: 100%; | |
| box-shadow: 10px 0 20px rgba(0,0,0,0.5); | |
| } | |
| .sidebar.open { | |
| transform: translateX(0); | |
| } | |
| .mobile-menu-btn { | |
| display: block; | |
| position: absolute; | |
| top: 15px; | |
| left: 15px; | |
| z-index: 20; | |
| background: var(--bg-panel); | |
| border: 1px solid var(--border); | |
| color: white; | |
| padding: 8px; | |
| border-radius: 4px; | |
| } | |
| } | |
| @media (min-width: 769px) { | |
| .mobile-menu-btn { display: none; } | |
| } | |
| /* Header Link */ | |
| .top-link { | |
| position: absolute; | |
| top: 20px; | |
| right: 20px; | |
| z-index: 100; | |
| background: rgba(0,0,0,0.5); | |
| padding: 8px 12px; | |
| border-radius: 8px; | |
| border: 1px solid var(--border); | |
| font-size: 0.8rem; | |
| color: var(--text-muted); | |
| text-decoration: none; | |
| transition: 0.2s; | |
| } | |
| .top-link:hover { | |
| color: var(--accent-primary); | |
| border-color: var(--accent-primary); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Mobile Menu Toggle --> | |
| <button class="mobile-menu-btn" onclick="toggleSidebar()"> | |
| <i class="ph ph-list"></i> | |
| </button> | |
| <!-- Built with Link --> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="top-link"> | |
| Built with anycoder | |
| </a> | |
| <!-- Sidebar --> | |
| <aside class="sidebar" id="sidebar"> | |
| <div class="brand"> | |
| <i class="ph-fill ph-brain"></i> LocalAI Nexus | |
| </div> | |
| <div class="system-status"> | |
| <div class="status-row"> | |
| <span>GPU Status</span> | |
| <span class="status-val">RTX 4090</span> | |
| </div> | |
| <div class="status-row"> | |
| <span>VRAM Usage</span> | |
| <span class="status-val">12GB / 24GB</span> | |
| </div> | |
| <div class="status-row"> | |
| <span>Model</span> | |
| <span class="status-val">Llama-3-8B</span> | |
| </div> | |
| </div> | |
| <div class="history-list"> | |
| <div class="history-item"><i class="ph ph-chat-circle-text"></i> Cyberpunk City</div> | |
| <div class="history-item"><i class="ph ph-image"></i> Portrait of Cat</div> | |
| <div class="history-item"><i class="ph ph-code"></i> Python Script</div> | |
| <div class="history-item"><i class="ph ph-chat-circle-text"></i> Philosophy</div> | |
| </div> | |
| </aside> | |
| <!-- Main Chat --> | |
| <main class="main-chat"> | |
| <div class="chat-container" id="chat-container"> | |
| <!-- Initial Greeting --> | |
| <div class="message ai"> | |
| <div class="avatar"><i class="ph ph-brain"></i></div> | |
| <div class="bubble"> | |
| System initialized. I am running locally on your machine. I can generate text or create images based on your prompts. What would you like to create today? | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Input Area --> | |
| <div class="input-area-wrapper"> | |
| <div class="input-container"> | |
| <!-- Mode Toggle --> | |
| <div class="mode-toggle" id="mode-toggle" onclick="toggleMode()"> | |
| <label class="switch"> | |
| <input type="checkbox" id="image-mode-check"> | |
| <span class="slider"></span> | |
| </label> | |
| <span>Image Generation Mode</span> | |
| </div> | |
| <!-- Text Input --> | |
| <div class="text-input-row"> | |
| <textarea id="user-input" placeholder="Type a message..." onkeydown="handleEnter(event)"></textarea> | |
| <button class="action-btn send" onclick="sendMessage()"> | |
| <i class="ph ph-paper-plane-right"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <script> | |
| let isImageMode = false; | |
| const chatContainer = document.getElementById('chat-container'); | |
| const userInput = document.getElementById('user-input'); | |
| const modeToggle = document.getElementById('mode-toggle'); | |
| const imageCheck = document.getElementById('image-mode-check'); | |
| // Sidebar Toggle | |
| function toggleSidebar() { | |
| document.getElementById('sidebar').classList.toggle('open'); | |
| } | |
| // Toggle Mode | |
| function toggleMode() { | |
| isImageMode = !isImageMode; | |
| imageCheck.checked = isImageMode; | |
| if(isImageMode) { | |
| modeToggle.classList.add('active'); | |
| userInput.placeholder = "Describe the image you want to generate..."; | |
| } else { | |
| modeToggle.classList.remove('active'); | |
| userInput.placeholder = "Type a message..."; | |
| } | |
| } | |
| // Handle Enter Key | |
| function handleEnter(e) { | |
| if(e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| sendMessage(); | |
| } | |
| } | |
| // Send Message Logic | |
| function sendMessage() { | |
| const text = userInput.value.trim(); | |
| if(!text) return; | |
| // 1. Add User Message | |
| addMessage(text, 'user'); | |
| userInput.value = ''; | |
| // 2. Show Loading State | |
| const loadingId = addLoadingIndicator(); | |
| // 3. Simulate AI Delay and Response | |
| setTimeout(() => { | |
| removeLoadingIndicator(loadingId); | |
| if (isImageMode) { | |
| generateMockImage(text); | |
| } else { | |
| generateMockText(text); | |
| } | |
| }, 1500 + Math.random() * 1000); | |
| } | |
| // UI: Add Message Bubble | |
| function addMessage(content, type, isImage = false) { | |
| const msgDiv = document.createElement('div'); | |
| msgDiv.className = `message ${type}`; | |
| if (type === 'ai') { | |
| msgDiv.innerHTML = ` | |
| <div class="avatar"><i class="ph ph-brain"></i></div> | |
| <div class="bubble">${content}</div> | |
| `; | |
| } else { | |
| msgDiv.innerHTML = ` | |
| <div class="bubble">${content}</div> | |
| `; | |
| } | |
| chatContainer.appendChild(msgDiv); | |
| scrollToBottom(); | |
| } | |
| // UI: Add Loading Indicator | |
| function addLoadingIndicator() { | |
| const id = 'loading-' + Date.now(); | |
| const msgDiv = document.createElement('div'); | |
| msgDiv.className = 'message ai'; | |
| msgDiv.id = id; | |
| msgDiv.innerHTML = ` | |
| <div class="avatar"><i class="ph ph-brain"></i></div> | |
| <div class="bubble"> | |
| <div class="typing-indicator"> | |
| <div class="dot"></div> | |
| <div class="dot"></div> | |
| <div class="dot"></div> | |
| </div> | |
| </div> | |
| `; | |
| chatContainer.appendChild(msgDiv); | |
| scrollToBottom(); | |
| return id; | |
| } | |
| function removeLoadingIndicator(id) { | |
| const el = document.getElementById(id); | |
| if(el) el.remove(); | |
| } | |
| function scrollToBottom() { | |
| chatContainer.scrollTop = chatContainer.scrollHeight; | |
| } | |
| // --- Mock AI Logic --- | |
| function generateMockText(text) { | |
| // Simple heuristic responses | |
| let response = "I've processed your request locally. "; | |
| if(text.toLowerCase().includes("hello")) response += "Greetings! The local node is ready."; | |
| else if(text.toLowerCase().includes("code")) response += "I can certainly help you write code. Please specify the language."; | |
| else if(text.toLowerCase().includes("image") && !isImageMode) response += "You can switch to Image Mode using the toggle below to generate visuals."; | |
| else response += "That's an interesting topic. My local GPU is processing the context..."; | |
| addMessage(response, 'ai'); | |
| } | |
| function generateMockImage(prompt) { | |
| // We use picsum.photos to simulate generation because we can't actually run Stable Diffusion in pure JS without a backend | |
| // We add a random seed to make it look different every time | |
| const seed = Math.floor(Math.random() * 1000); | |
| const imageUrl = `https://picsum.photos/seed/${seed}/600/400`; | |
| const msgDiv = document.createElement('div'); | |
| msgDiv.className = 'message ai'; | |
| msgDiv.innerHTML = ` | |
| <div class="avatar"><i class="ph ph-brain"></i></div> | |
| <div class="bubble"> | |
| <div style="font-size: 0.8rem; color: var(--text-muted); margin-bottom: 5px;">Generated locally based on: "${prompt}"</div> | |
| <div class="generated-image-container"> | |
| <img src="${imageUrl}" alt="Generated Image" loading="lazy"> | |
| </div> | |
| <div style="margin-top: 8px; font-size: 0.8rem; display: flex; gap: 10px;"> | |
| <span><i class="ph ph-download-simple"></i> Save</span> | |
| <span><i class="ph ph-share-network"></i> Share</span> | |
| </div> | |
| </div> | |
| `; | |
| chatContainer.appendChild(msgDiv); | |
| scrollToBottom(); | |
| } | |
| </script> | |
| </body> | |
| </html> |