Download index.html from Ashok75/react: direct link, hf CLI and curl.
- Browser
- Download file 26.3 kB
-
https://huggingface.co/spaces/Ashok75/react/resolve/main/index.html
- Command line
-
hf download hf://spaces/Ashok75/react/index.html
-
curl -L -o index.html https://huggingface.co/spaces/Ashok75/react/resolve/main/index.html
26.3 kB
| <!-- this is a test chat for app.py in space to test the response and | |
| streaming capabilities of the app.py return response and llm --> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Nanbeige4.1-3B Chat</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| :root { | |
| --bg-primary: #0f0f0f; | |
| --bg-secondary: #1a1a1a; | |
| --bg-tertiary: #252525; | |
| --accent: #00d4aa; | |
| --accent-hover: #00b894; | |
| --text-primary: #ffffff; | |
| --text-secondary: #a0a0a0; | |
| --border: #333333; | |
| --user-msg: #1a3d35; | |
| --assistant-msg: #252525; | |
| --error: #ff4757; | |
| --shadow: 0 4px 20px rgba(0, 0, 0, 0.5); | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; | |
| background: var(--bg-primary); | |
| color: var(--text-primary); | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| } | |
| /* Header */ | |
| .header { | |
| background: var(--bg-secondary); | |
| border-bottom: 1px solid var(--border); | |
| padding: 1rem 1.5rem; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| box-shadow: var(--shadow); | |
| z-index: 10; | |
| } | |
| .logo { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| } | |
| .logo-icon { | |
| width: 36px; | |
| height: 36px; | |
| background: linear-gradient(135deg, var(--accent), var(--accent-hover)); | |
| border-radius: 10px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-weight: bold; | |
| font-size: 1.2rem; | |
| } | |
| .logo-text { | |
| font-size: 1.25rem; | |
| font-weight: 600; | |
| letter-spacing: -0.5px; | |
| } | |
| .logo-text span { | |
| color: var(--accent); | |
| } | |
| .status { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| font-size: 0.875rem; | |
| color: var(--text-secondary); | |
| } | |
| .status-dot { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| background: var(--text-secondary); | |
| transition: background 0.3s ease; | |
| } | |
| .status-dot.online { | |
| background: var(--accent); | |
| box-shadow: 0 0 10px var(--accent); | |
| } | |
| .status-dot.error { | |
| background: var(--error); | |
| box-shadow: 0 0 10px var(--error); | |
| } | |
| /* Main Chat Area */ | |
| .chat-container { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 2rem; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1.5rem; | |
| scroll-behavior: smooth; | |
| } | |
| .chat-container::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| .chat-container::-webkit-scrollbar-track { | |
| background: transparent; | |
| } | |
| .chat-container::-webkit-scrollbar-thumb { | |
| background: var(--border); | |
| border-radius: 4px; | |
| } | |
| .chat-container::-webkit-scrollbar-thumb:hover { | |
| background: var(--text-secondary); | |
| } | |
| .welcome-message { | |
| text-align: center; | |
| padding: 3rem 1rem; | |
| color: var(--text-secondary); | |
| } | |
| .welcome-message h2 { | |
| color: var(--text-primary); | |
| margin-bottom: 0.5rem; | |
| font-size: 1.5rem; | |
| } | |
| .message { | |
| display: flex; | |
| gap: 1rem; | |
| max-width: 900px; | |
| margin: 0 auto; | |
| width: 100%; | |
| animation: fadeIn 0.3s ease; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .message-avatar { | |
| width: 36px; | |
| height: 36px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1rem; | |
| flex-shrink: 0; | |
| font-weight: 600; | |
| } | |
| .user-message .message-avatar { | |
| background: var(--accent); | |
| color: var(--bg-primary); | |
| } | |
| .assistant-message .message-avatar { | |
| background: var(--bg-tertiary); | |
| border: 1px solid var(--border); | |
| } | |
| .message-content { | |
| flex: 1; | |
| padding: 1rem 1.25rem; | |
| border-radius: 12px; | |
| line-height: 1.6; | |
| font-size: 0.95rem; | |
| white-space: pre-wrap; | |
| word-wrap: break-word; | |
| } | |
| .user-message .message-content { | |
| background: var(--user-msg); | |
| border: 1px solid rgba(0, 212, 170, 0.2); | |
| } | |
| .assistant-message .message-content { | |
| background: var(--assistant-msg); | |
| border: 1px solid var(--border); | |
| } | |
| .message-content.loading { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .typing-indicator { | |
| display: flex; | |
| gap: 4px; | |
| } | |
| .typing-indicator span { | |
| width: 6px; | |
| height: 6px; | |
| background: var(--text-secondary); | |
| border-radius: 50%; | |
| animation: bounce 1.4s infinite ease-in-out both; | |
| } | |
| .typing-indicator span:nth-child(1) { animation-delay: -0.32s; } | |
| .typing-indicator span:nth-child(2) { animation-delay: -0.16s; } | |
| @keyframes bounce { | |
| 0%, 80%, 100% { transform: scale(0); } | |
| 40% { transform: scale(1); } | |
| } | |
| /* Input Area */ | |
| .input-container { | |
| background: var(--bg-secondary); | |
| border-top: 1px solid var(--border); | |
| padding: 1.5rem; | |
| display: flex; | |
| justify-content: center; | |
| } | |
| .input-wrapper { | |
| max-width: 900px; | |
| width: 100%; | |
| position: relative; | |
| display: flex; | |
| gap: 0.75rem; | |
| align-items: flex-end; | |
| background: var(--bg-tertiary); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| padding: 0.75rem; | |
| transition: border-color 0.2s ease, box-shadow 0.2s ease; | |
| } | |
| .input-wrapper:focus-within { | |
| border-color: var(--accent); | |
| box-shadow: 0 0 0 3px rgba(0, 212, 170, 0.1); | |
| } | |
| textarea { | |
| flex: 1; | |
| background: transparent; | |
| border: none; | |
| color: var(--text-primary); | |
| font-size: 0.95rem; | |
| resize: none; | |
| max-height: 200px; | |
| min-height: 24px; | |
| font-family: inherit; | |
| line-height: 1.5; | |
| padding: 0.25rem 0.5rem; | |
| } | |
| textarea:focus { | |
| outline: none; | |
| } | |
| textarea::placeholder { | |
| color: var(--text-secondary); | |
| } | |
| .send-btn { | |
| background: var(--accent); | |
| color: var(--bg-primary); | |
| border: none; | |
| width: 36px; | |
| height: 36px; | |
| border-radius: 10px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all 0.2s ease; | |
| flex-shrink: 0; | |
| } | |
| .send-btn:hover:not(:disabled) { | |
| background: var(--accent-hover); | |
| transform: scale(1.05); | |
| } | |
| .send-btn:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } | |
| .send-btn svg { | |
| width: 18px; | |
| height: 18px; | |
| } | |
| /* Settings Panel */ | |
| .settings-btn { | |
| background: transparent; | |
| border: 1px solid var(--border); | |
| color: var(--text-secondary); | |
| padding: 0.5rem 1rem; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-size: 0.875rem; | |
| transition: all 0.2s ease; | |
| } | |
| .settings-btn:hover { | |
| border-color: var(--accent); | |
| color: var(--accent); | |
| } | |
| .settings-panel { | |
| position: fixed; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%) scale(0.95); | |
| background: var(--bg-secondary); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| padding: 1.5rem; | |
| width: 90%; | |
| max-width: 400px; | |
| box-shadow: var(--shadow); | |
| opacity: 0; | |
| visibility: hidden; | |
| transition: all 0.2s ease; | |
| z-index: 100; | |
| } | |
| .settings-panel.active { | |
| opacity: 1; | |
| visibility: visible; | |
| transform: translate(-50%, -50%) scale(1); | |
| } | |
| .settings-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: rgba(0, 0, 0, 0.7); | |
| opacity: 0; | |
| visibility: hidden; | |
| transition: all 0.2s ease; | |
| z-index: 99; | |
| } | |
| .settings-overlay.active { | |
| opacity: 1; | |
| visibility: visible; | |
| } | |
| .settings-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 1.5rem; | |
| } | |
| .settings-title { | |
| font-size: 1.25rem; | |
| font-weight: 600; | |
| } | |
| .close-settings { | |
| background: none; | |
| border: none; | |
| color: var(--text-secondary); | |
| cursor: pointer; | |
| font-size: 1.5rem; | |
| line-height: 1; | |
| } | |
| .setting-item { | |
| margin-bottom: 1.25rem; | |
| } | |
| .setting-label { | |
| display: block; | |
| font-size: 0.875rem; | |
| color: var(--text-secondary); | |
| margin-bottom: 0.5rem; | |
| } | |
| .setting-input { | |
| width: 100%; | |
| background: var(--bg-tertiary); | |
| border: 1px solid var(--border); | |
| color: var(--text-primary); | |
| padding: 0.75rem; | |
| border-radius: 8px; | |
| font-size: 0.9rem; | |
| } | |
| .setting-input:focus { | |
| outline: none; | |
| border-color: var(--accent); | |
| } | |
| /* Code blocks */ | |
| pre { | |
| background: var(--bg-primary); | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| padding: 1rem; | |
| overflow-x: auto; | |
| margin: 0.75rem 0; | |
| } | |
| code { | |
| font-family: 'Courier New', monospace; | |
| font-size: 0.9em; | |
| } | |
| pre code { | |
| color: #e0e0e0; | |
| } | |
| /* Responsive */ | |
| @media (max-width: 768px) { | |
| .chat-container { | |
| padding: 1rem; | |
| } | |
| .header { | |
| padding: 1rem; | |
| } | |
| .logo-text { | |
| font-size: 1rem; | |
| } | |
| } | |
| /* Empty state suggestions */ | |
| .suggestions { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
| gap: 1rem; | |
| max-width: 800px; | |
| margin: 2rem auto; | |
| padding: 0 1rem; | |
| } | |
| .suggestion-card { | |
| background: var(--bg-secondary); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| padding: 1rem; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| text-align: left; | |
| } | |
| .suggestion-card:hover { | |
| border-color: var(--accent); | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 12px rgba(0, 212, 170, 0.1); | |
| } | |
| .suggestion-card h4 { | |
| color: var(--accent); | |
| margin-bottom: 0.5rem; | |
| font-size: 0.9rem; | |
| } | |
| .suggestion-card p { | |
| color: var(--text-secondary); | |
| font-size: 0.85rem; | |
| line-height: 1.4; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header class="header"> | |
| <div class="logo"> | |
| <div class="logo-icon">N</div> | |
| <div class="logo-text">Nanbeige<span>4.1-3B</span></div> | |
| </div> | |
| <div style="display: flex; gap: 1rem; align-items: center;"> | |
| <div class="status"> | |
| <div class="status-dot" id="statusDot"></div> | |
| <span id="statusText">Checking...</span> | |
| </div> | |
| <button class="settings-btn" onclick="toggleSettings()">⚙️ Settings</button> | |
| <button class="settings-btn" onclick="clearChat()">🗑️ Clear</button> | |
| </div> | |
| </header> | |
| <div class="settings-overlay" id="settingsOverlay" onclick="toggleSettings()"></div> | |
| <div class="settings-panel" id="settingsPanel"> | |
| <div class="settings-header"> | |
| <h3 class="settings-title">Settings</h3> | |
| <button class="close-settings" onclick="toggleSettings()">×</button> | |
| </div> | |
| <div class="setting-item"> | |
| <label class="setting-label">Temperature (0.0 - 2.0)</label> | |
| <input type="number" class="setting-input" id="temperature" min="0" max="2" step="0.1" value="0.6"> | |
| </div> | |
| <div class="setting-item"> | |
| <label class="setting-label">Max Tokens (1 - 4096)</label> | |
| <input type="number" class="setting-input" id="maxTokens" min="1" max="4096" step="1" value="2048"> | |
| </div> | |
| <div class="setting-item"> | |
| <label class="setting-label">System Prompt</label> | |
| <textarea class="setting-input" id="systemPrompt" rows="3" placeholder="Optional system instructions..."></textarea> | |
| </div> | |
| </div> | |
| <main class="chat-container" id="chatContainer"> | |
| <div class="welcome-message" id="welcomeMessage"> | |
| <h2>Welcome to Nanbeige4.1-3B</h2> | |
| <p>A powerful language model for conversation and assistance</p> | |
| <div class="suggestions"> | |
| <div class="suggestion-card" onclick="sendSuggestion('Explain quantum computing in simple terms')"> | |
| <h4>💡 Explain a concept</h4> | |
| <p>"Explain quantum computing in simple terms"</p> | |
| </div> | |
| <div class="suggestion-card" onclick="sendSuggestion('Write a Python function to calculate fibonacci numbers')"> | |
| <h4>💻 Code assistance</h4> | |
| <p>"Write a Python function to calculate fibonacci numbers"</p> | |
| </div> | |
| <div class="suggestion-card" onclick="sendSuggestion('Help me brainstorm ideas for a sci-fi short story')"> | |
| <h4>✨ Creative writing</h4> | |
| <p>"Help me brainstorm ideas for a sci-fi short story"</p> | |
| </div> | |
| <div class="suggestion-card" onclick="sendSuggestion('What are the latest developments in AI?')"> | |
| <h4>🤖 General knowledge</h4> | |
| <p>"What are the latest developments in AI?"</p> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <div class="input-container"> | |
| <div class="input-wrapper"> | |
| <textarea | |
| id="messageInput" | |
| placeholder="Type your message..." | |
| rows="1" | |
| onkeydown="handleKeyDown(event)" | |
| oninput="autoResize(this)" | |
| ></textarea> | |
| <button class="send-btn" id="sendBtn" onclick="sendMessage()"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| <script> | |
| let messages = []; | |
| let isStreaming = false; | |
| const API_BASE = window.location.origin; | |
| // Check health on load | |
| checkHealth(); | |
| setInterval(checkHealth, 30000); | |
| async function checkHealth() { | |
| try { | |
| const response = await fetch(`${API_BASE}/health`); | |
| const data = await response.json(); | |
| const dot = document.getElementById('statusDot'); | |
| const text = document.getElementById('statusText'); | |
| if (data.model_loaded) { | |
| dot.className = 'status-dot online'; | |
| text.textContent = 'Online'; | |
| } else { | |
| dot.className = 'status-dot'; | |
| text.textContent = 'Loading...'; | |
| } | |
| } catch (error) { | |
| document.getElementById('statusDot').className = 'status-dot error'; | |
| document.getElementById('statusText').textContent = 'Offline'; | |
| } | |
| } | |
| function toggleSettings() { | |
| document.getElementById('settingsPanel').classList.toggle('active'); | |
| document.getElementById('settingsOverlay').classList.toggle('active'); | |
| } | |
| function clearChat() { | |
| messages = []; | |
| document.getElementById('chatContainer').innerHTML = ` | |
| <div class="welcome-message" id="welcomeMessage"> | |
| <h2>Welcome to Nanbeige4.1-3B</h2> | |
| <p>Start a new conversation</p> | |
| </div> | |
| `; | |
| } | |
| function autoResize(textarea) { | |
| textarea.style.height = 'auto'; | |
| textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px'; | |
| } | |
| function handleKeyDown(e) { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| sendMessage(); | |
| } | |
| } | |
| function sendSuggestion(text) { | |
| document.getElementById('messageInput').value = text; | |
| sendMessage(); | |
| } | |
| async function sendMessage() { | |
| const input = document.getElementById('messageInput'); | |
| const text = input.value.trim(); | |
| if (!text || isStreaming) return; | |
| // Hide welcome message | |
| const welcome = document.getElementById('welcomeMessage'); | |
| if (welcome) welcome.style.display = 'none'; | |
| // Add user message | |
| addMessage('user', text); | |
| messages.push({ role: 'user', content: text }); | |
| // Clear input | |
| input.value = ''; | |
| input.style.height = 'auto'; | |
| // Show loading | |
| isStreaming = true; | |
| document.getElementById('sendBtn').disabled = true; | |
| const loadingId = addLoadingMessage(); | |
| try { | |
| const temperature = parseFloat(document.getElementById('temperature').value) || 0.6; | |
| const maxTokens = parseInt(document.getElementById('maxTokens').value) || 2048; | |
| const systemPrompt = document.getElementById('systemPrompt').value.trim(); | |
| let chatMessages = [...messages]; | |
| if (systemPrompt && messages.length === 1) { | |
| chatMessages.unshift({ role: 'system', content: systemPrompt }); | |
| } | |
| const response = await fetch(`${API_BASE}/chat`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| messages: chatMessages, | |
| stream: true, | |
| temperature: temperature, | |
| max_tokens: maxTokens | |
| }) | |
| }); | |
| if (!response.ok) throw new Error('Failed to get response'); | |
| // Remove loading, prepare for streaming | |
| removeLoadingMessage(loadingId); | |
| const assistantId = addMessage('assistant', ''); | |
| const reader = response.body.getReader(); | |
| const decoder = new TextDecoder(); | |
| let buffer = ''; | |
| let fullContent = ''; | |
| while (true) { | |
| const { done, value } = await reader.read(); | |
| if (done) break; | |
| buffer += decoder.decode(value, { stream: true }); | |
| const lines = buffer.split('\n'); | |
| buffer = lines.pop(); | |
| for (const line of lines) { | |
| if (line.startsWith('data: ')) { | |
| try { | |
| const data = JSON.parse(line.slice(6)); | |
| if (data.type === 'token') { | |
| fullContent += data.content; | |
| updateMessage(assistantId, fullContent); | |
| // allow DOM to repaint between tokens in case chunks arrive together | |
| await new Promise(r => setTimeout(r, 0)); | |
| } else if (data.type === 'done') { | |
| messages.push({ role: 'assistant', content: fullContent }); | |
| } | |
| } catch (e) { | |
| console.error('Parse error:', e); | |
| } | |
| } | |
| } | |
| } | |
| } catch (error) { | |
| removeLoadingMessage(loadingId); | |
| addMessage('assistant', `Error: ${error.message}. Please check if the API is running.`); | |
| } finally { | |
| isStreaming = false; | |
| document.getElementById('sendBtn').disabled = false; | |
| } | |
| } | |
| function addMessage(role, content) { | |
| const container = document.getElementById('chatContainer'); | |
| const id = 'msg-' + Date.now(); | |
| const div = document.createElement('div'); | |
| div.className = `message ${role}-message`; | |
| div.id = id; | |
| div.innerHTML = ` | |
| <div class="message-avatar">${role === 'user' ? 'U' : '🤖'}</div> | |
| <div class="message-content">${formatContent(content)}</div> | |
| `; | |
| container.appendChild(div); | |
| scrollToBottom(); | |
| return id; | |
| } | |
| function updateMessage(id, content) { | |
| const msg = document.getElementById(id); | |
| if (msg) { | |
| msg.querySelector('.message-content').innerHTML = formatContent(content); | |
| scrollToBottom(); | |
| } | |
| } | |
| function addLoadingMessage() { | |
| const id = 'loading-' + Date.now(); | |
| const container = document.getElementById('chatContainer'); | |
| const div = document.createElement('div'); | |
| div.className = 'message assistant-message'; | |
| div.id = id; | |
| div.innerHTML = ` | |
| <div class="message-avatar">🤖</div> | |
| <div class="message-content loading"> | |
| <div class="typing-indicator"> | |
| <span></span> | |
| <span></span> | |
| <span></span> | |
| </div> | |
| </div> | |
| `; | |
| container.appendChild(div); | |
| scrollToBottom(); | |
| return id; | |
| } | |
| function removeLoadingMessage(id) { | |
| const el = document.getElementById(id); | |
| if (el) el.remove(); | |
| } | |
| function formatContent(text) { | |
| // Escape HTML | |
| text = text.replace(/&/g, '&') | |
| .replace(/</g, '<') | |
| .replace(/>/g, '>'); | |
| // Format code blocks | |
| text = text.replace(/```(\w+)?\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>'); | |
| text = text.replace(/`([^`]+)`/g, '<code>$1</code>'); | |
| // Format bold and italic | |
| text = text.replace(/\*\*\*(.*?)\*\*\*/g, '<strong><em>$1</em></strong>'); | |
| text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>'); | |
| text = text.replace(/\*(.*?)\*/g, '<em>$1</em>'); | |
| // Convert newlines to <br> outside of pre blocks | |
| text = text.replace(/\n/g, '<br>'); | |
| return text; | |
| } | |
| function scrollToBottom() { | |
| const container = document.getElementById('chatContainer'); | |
| container.scrollTop = container.scrollHeight; | |
| } | |
| </script> | |
| </body> | |
| </html> |