|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>i3 Model Series</title> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet"> |
|
|
<style> |
|
|
|
|
|
body { margin: 0; font-family: 'Inter', sans-serif; background: #fff; color: #333; } |
|
|
|
|
|
|
|
|
nav { |
|
|
background: #ffffff; |
|
|
border-bottom: 1px solid #eee; |
|
|
padding: 1rem 2rem; |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
position: sticky; |
|
|
top: 0; |
|
|
z-index: 1000; |
|
|
} |
|
|
.nav-logo { font-weight: 700; font-size: 1.2rem; color: #111; text-decoration: none; cursor: pointer;} |
|
|
.nav-links a { |
|
|
margin-left: 20px; |
|
|
text-decoration: none; |
|
|
color: #666; |
|
|
font-size: 0.9rem; |
|
|
transition: color 0.2s; |
|
|
cursor: pointer; |
|
|
} |
|
|
.nav-links a:hover, .nav-links a.active { color: #007bff; } |
|
|
|
|
|
|
|
|
#app-content { |
|
|
padding: 40px 20px; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
align-items: center; |
|
|
min-height: 80vh; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
|
|
|
<nav> |
|
|
<a class="nav-logo" onclick="route('home')">i3 Series</a> |
|
|
<div class="nav-links"> |
|
|
<a onclick="route('home')" id="nav-home">Home</a> |
|
|
<a onclick="route('timeline')" id="nav-timeline">Timeline</a> |
|
|
<a onclick="route('chat')" id="nav-chat">Chat <i class="fas fa-sparkles" style="font-size: 0.7em; color: #007bff;"></i></a> |
|
|
<a onclick="route('about')" id="nav-about">About</a> |
|
|
</div> |
|
|
</nav> |
|
|
|
|
|
<div id="app-content"></div> |
|
|
|
|
|
<footer style="text-align: center; padding: 20px; color: #999; font-size: 0.9rem; border-top: 1px solid #eee;"> |
|
|
<p>i3 Model Series © 2025</p> |
|
|
</footer> |
|
|
|
|
|
<script type="module"> |
|
|
|
|
|
import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client@latest/dist/index.min.js"; |
|
|
|
|
|
|
|
|
const MODEL_CONFIG = { |
|
|
'200m': { |
|
|
|
|
|
spaceId: "FlameF0X/i3-200m", |
|
|
endpoint: "/predict", |
|
|
name: "i3-200m" |
|
|
}, |
|
|
'80m': { |
|
|
spaceId: "FlameF0X/i3-80m-with-streaming", |
|
|
endpoint: "/generate_text", |
|
|
name: "i3-80m" |
|
|
} |
|
|
}; |
|
|
|
|
|
const routes = { |
|
|
'home': 'pages/home.html', |
|
|
'timeline': 'pages/timeline.html', |
|
|
'chat': 'pages/chat.html', |
|
|
'about': 'pages/about.html' |
|
|
}; |
|
|
|
|
|
|
|
|
window.route = async function(pageName) { |
|
|
document.querySelectorAll('.nav-links a').forEach(el => el.classList.remove('active')); |
|
|
const activeLink = document.getElementById(`nav-${pageName}`); |
|
|
if(activeLink) activeLink.classList.add('active'); |
|
|
|
|
|
const contentDiv = document.getElementById('app-content'); |
|
|
|
|
|
try { |
|
|
const response = await fetch(routes[pageName]); |
|
|
if (!response.ok) throw new Error('Page not found'); |
|
|
const html = await response.text(); |
|
|
contentDiv.innerHTML = html; |
|
|
|
|
|
if(pageName === 'timeline') initTimelineAnimations(); |
|
|
if(pageName === 'chat') initChatInterface(); |
|
|
|
|
|
const newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?page=' + pageName; |
|
|
window.history.pushState({ path: newUrl }, '', newUrl); |
|
|
|
|
|
} catch (error) { |
|
|
console.error(error); |
|
|
contentDiv.innerHTML = "<h2 style='text-align:center; color:#999;'>404 - Page not found</h2>"; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function initTimelineAnimations() { |
|
|
const spaceBtn = document.getElementById('i3SpaceBtn'); |
|
|
if(spaceBtn) { |
|
|
spaceBtn.addEventListener('click', () => window.open('https://huggingface.co/spaces/FlameF0X/i3-200m', '_blank')); |
|
|
} |
|
|
|
|
|
const observer = new IntersectionObserver((entries) => { |
|
|
entries.forEach(entry => { |
|
|
if (entry.isIntersecting) { |
|
|
entry.target.style.opacity = 1; |
|
|
entry.target.style.transform = 'translateY(0)'; |
|
|
observer.unobserve(entry.target); |
|
|
} |
|
|
}); |
|
|
}, { threshold: 0.1 }); |
|
|
|
|
|
document.querySelectorAll('.timeline-item').forEach(item => { |
|
|
item.style.opacity = 0; |
|
|
item.style.transform = 'translateY(20px)'; |
|
|
item.style.transition = 'opacity 0.5s ease, transform 0.5s ease'; |
|
|
observer.observe(item); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function initChatInterface() { |
|
|
const input = document.getElementById('user-input'); |
|
|
const btn = document.getElementById('send-btn'); |
|
|
const container = document.getElementById('messages-container'); |
|
|
const modelSelector = document.getElementById('model-selector'); |
|
|
|
|
|
function addMessage(text, sender, modelName = 'i3') { |
|
|
const div = document.createElement('div'); |
|
|
div.className = `message ${sender}`; |
|
|
div.innerHTML = sender === 'bot' ? `<strong>${modelName}:</strong> ${text}` : text; |
|
|
container.appendChild(div); |
|
|
container.scrollTop = container.scrollHeight; |
|
|
return div; |
|
|
} |
|
|
|
|
|
async function handleSend() { |
|
|
const prompt = input.value.trim(); |
|
|
if(!prompt) return; |
|
|
|
|
|
const selectedKey = modelSelector.value; |
|
|
const modelConfig = MODEL_CONFIG[selectedKey]; |
|
|
|
|
|
addMessage(prompt, 'user'); |
|
|
input.value = ''; |
|
|
input.disabled = true; |
|
|
btn.disabled = true; |
|
|
|
|
|
const loadingBubble = addMessage('<i class="fas fa-circle-notch fa-spin"></i> Thinking...', 'bot', modelConfig.name); |
|
|
|
|
|
try { |
|
|
const maxTokens = parseInt(document.getElementById('param-tokens').value); |
|
|
const temp = parseFloat(document.getElementById('param-temp').value); |
|
|
const topK = parseInt(document.getElementById('param-topk').value); |
|
|
|
|
|
console.log(`Connecting to ${modelConfig.spaceId}...`); |
|
|
|
|
|
|
|
|
const app = await Client.connect(modelConfig.spaceId); |
|
|
|
|
|
const result = await app.predict(modelConfig.endpoint, { |
|
|
param_0: prompt, |
|
|
param_1: maxTokens, |
|
|
param_2: temp, |
|
|
param_3: topK |
|
|
}); |
|
|
|
|
|
loadingBubble.innerHTML = `<strong>${modelConfig.name}:</strong> ${result.data[0]}`; |
|
|
|
|
|
} catch (error) { |
|
|
console.error("API Error:", error); |
|
|
loadingBubble.innerHTML = `<strong>Error:</strong> Failed to connect.<br><small>${error.message}</small>`; |
|
|
loadingBubble.style.background = "#fff0f0"; |
|
|
loadingBubble.style.color = "#d00"; |
|
|
} finally { |
|
|
input.disabled = false; |
|
|
btn.disabled = false; |
|
|
input.focus(); |
|
|
container.scrollTop = container.scrollHeight; |
|
|
} |
|
|
} |
|
|
|
|
|
btn.onclick = handleSend; |
|
|
input.onkeypress = (e) => { |
|
|
if(e.key === 'Enter') handleSend(); |
|
|
}; |
|
|
} |
|
|
|
|
|
const urlParams = new URLSearchParams(window.location.search); |
|
|
const currentPage = urlParams.get('page') || 'home'; |
|
|
window.route(currentPage); |
|
|
</script> |
|
|
</body> |
|
|
</html> |