Alibi_AI / index.html
AlibiAI's picture
Update index.html
68d260a verified
raw
history blame
20 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat</title>
<link rel="manifest" href="/manifest.json">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
width: 100%;
max-width: 800px;
height: 600px;
display: flex;
flex-direction: column;
overflow: hidden;
animation: slideUp 0.5s ease-out;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px 20px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.header .avatar {
width: 35px;
height: 35px;
border-radius: 50%;
background: #ffffff33;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 10px;
}
.header h1 {
font-size: 20px;
font-weight: 500;
flex: 1;
}
.message-counter {
background: #ef4444;
color: white;
font-size: 12px;
padding: 4px 8px;
border-radius: 12px;
line-height: 1;
}
.status {
padding: 10px 20px;
background: #f7f9fc;
border-bottom: 1px solid #e1e8ed;
display: flex;
align-items: center;
gap: 10px;
}
.status-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
background: #fbbf24;
animation: pulse 2s infinite;
}
.status-indicator.ready {
background: #10b981;
animation: none;
}
.status-indicator.error {
background: #ef4444;
animation: none;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.chat-container {
flex: 1;
overflow-y: auto;
padding: 20px;
display: flex;
flex-direction: column;
gap: 15px;
}
.message {
display: flex;
gap: 10px;
animation: fadeIn 0.3s ease-in;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.message.user {
flex-direction: row-reverse;
}
.message-bubble {
max-width: 70%;
padding: 12px 16px;
border-radius: 18px;
word-wrap: break-word;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}
.message.user .message-bubble {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-bottom-right-radius: 4px;
}
.message.ai .message-bubble {
background: #f1f3f5;
color: #1a1a1a;
border-bottom-left-radius: 4px;
}
.avatar {
width: 35px;
height: 35px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
color: white;
flex-shrink: 0;
}
.message.user .avatar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.message.ai .avatar {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
}
.typing-indicator {
display: none;
padding: 12px 16px;
background: #f1f3f5;
border-radius: 18px;
border-bottom-left-radius: 4px;
width: fit-content;
}
.typing-indicator.active {
display: block;
}
.typing-indicator span {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: #667eea;
margin: 0 2px;
animation: typing 1.4s infinite;
}
.typing-indicator span:nth-child(2) {
animation-delay: 0.2s;
}
.typing-indicator span:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes typing {
0%, 60%, 100% {
transform: translateY(0);
}
30% {
transform: translateY(-10px);
}
}
.input-container {
padding: 20px;
background: #f7f9fc;
border-top: 1px solid #e1e8ed;
}
.input-wrapper {
display: flex;
gap: 10px;
}
.message-input {
flex: 1;
padding: 12px 16px;
border: 2px solid #e1e8ed;
border-radius: 25px;
font-size: 14px;
outline: none;
transition: all 0.3s ease;
}
.message-input:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.send-button {
padding: 12px 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 25px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 5px;
}
.send-button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
}
.send-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.error-message {
background: #fee;
color: #c00;
padding: 10px;
border-radius: 8px;
margin: 10px 20px;
display: none;
}
.error-message.show {
display: block;
}
.footer {
text-align: center;
padding: 10px;
font-size: 12px;
color: #666;
}
.footer a {
color: #667eea;
text-decoration: none;
}
@media (max-width: 600px) {
.container {
height: 100vh;
max-width: 100%;
border-radius: 0;
}
.message-bubble {
max-width: 85%;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="message-counter">3</div>
<div class="avatar" id="friendAvatar"></div>
<h1 id="friendName">Loading...</h1>
</div>
<div class="status">
<div class="status-indicator" id="statusIndicator"></div>
<span id="statusText">Connecting...</span>
</div>
<div class="error-message" id="errorMessage"></div>
<div class="chat-container" id="chatContainer">
<div class="message ai">
<div class="avatar" id="aiAvatar">AI</div>
<div class="message-bubble">
Hey! What's up?
</div>
</div>
</div>
<div class="input-container">
<div class="input-wrapper">
<input
type="text"
class="message-input"
id="messageInput"
placeholder="Message..."
disabled
>
<button class="send-button" id="sendButton" disabled>
<span>Send</span>
<svg width="16" height="16" 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>
<div class="footer">
Powered by <a href="https://huggingface.co/HuggingFaceTB/SmolLM-135M-Instruct" target="_blank">SmolLM</a> from Hugging Face (Apache 2.0).
</div>
</div>
<script type="module">
// Register service worker for PWA
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('Service Worker registered'))
.catch(err => console.error('Service Worker registration failed:', err));
});
}
import { pipeline, env } from 'https://cdn.jsdelivr.net/npm/@huggingface/[email protected]';
// Configure environment for browser usage
env.allowLocalModels = false;
env.useBrowserCache = true;
let generator = null;
let isProcessing = false;
let conversationHistory = [];
let modelId = /mobile|android|iphone|ipad/.test(navigator.userAgent.toLowerCase())
? 'HuggingFaceTB/SmolLM-135M-Instruct'
: 'HuggingFaceTB/SmolLM2-360M-Instruct';
const chatContainer = document.getElementById('chatContainer');
const messageInput = document.getElementById('messageInput');
const sendButton = document.getElementById('sendButton');
const statusIndicator = document.getElementById('statusIndicator');
const statusText = document.getElementById('statusText');
const errorMessage = document.getElementById('errorMessage');
const friendNameElement = document.getElementById('friendName');
const friendAvatar = document.getElementById('friendAvatar');
const aiAvatar = document.getElementById('aiAvatar');
// Prompt for friend's name
let friendName = prompt('Who are you chatting with?', 'Alex');
friendName = friendName ? friendName.trim() : 'Alex';
friendNameElement.textContent = friendName;
friendAvatar.textContent = friendName[0].toUpperCase();
aiAvatar.textContent = friendName[0].toUpperCase();
// Check browser compatibility
function checkBrowserCompatibility() {
const ua = navigator.userAgent.toLowerCase();
const isMobile = /mobile|android|iphone|ipad/.test(ua);
const isChrome = ua.includes('chrome') && !ua.includes('edge');
const isEdge = ua.includes('edg/');
const isSafari = ua.includes('safari') && !ua.includes('chrome');
return { isMobile, isChrome, isEdge, isSafari };
}
// Check WebGPU support
async function checkWebGPU() {
if (!navigator.gpu) return false;
try {
const adapter = await navigator.gpu.requestAdapter();
return !!adapter;
} catch (e) {
console.error('WebGPU check failed:', e);
return false;
}
}
// Initialize the model with fallback
async function initializeModel(attemptSmallerModel = false) {
try {
statusText.textContent = `Connecting...`;
generator = await pipeline(
'text-generation',
attemptSmallerModel ? 'HuggingFaceTB/SmolLM-135M-Instruct' : modelId
);
statusIndicator.classList.add('ready');
statusText.textContent = 'Connected';
messageInput.disabled = false;
sendButton.disabled = false;
messageInput.focus();
} catch (error) {
console.error('Error loading model:', error, error.stack);
if (!attemptSmallerModel && error.message.includes('memory')) {
console.warn('Memory error detected, trying smaller model...');
modelId = 'HuggingFaceTB/SmolLM-135M-Instruct';
initializeModel(true);
} else {
statusIndicator.classList.add('error');
statusText.textContent = 'Offline';
showError(`Oops, can't connect right now. Try refreshing?`);
}
}
}
// Start compatibility and model initialization
const browser = checkBrowserCompatibility();
if (browser.isMobile && (browser.isSafari || (!browser.isChrome && !browser.isEdge))) {
statusText.textContent = 'Connecting...';
showError('Hey, this works best on Chrome or Edge. Give one of those a try?');
initializeModel();
} else {
checkWebGPU().then(supported => {
if (!supported) {
statusText.textContent = 'Connecting...';
showError('Running a bit slow, but we’re good! Try a short message.');
initializeModel();
} else {
initializeModel();
}
});
}
function showError(message) {
errorMessage.textContent = message;
errorMessage.classList.add('show');
setTimeout(() => {
errorMessage.classList.remove('show');
}, 5000);
}
function addMessage(content, isUser = false) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${isUser ? 'user' : 'ai'}`;
const avatar = document.createElement('div');
avatar.className = 'avatar';
avatar.textContent = isUser ? 'You' : friendName[0].toUpperCase();
const bubble = document.createElement('div');
bubble.className = 'message-bubble';
bubble.textContent = content;
messageDiv.appendChild(avatar);
messageDiv.appendChild(bubble);
chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
// Store in conversation history (limit to 2 for mobile)
if (isUser) {
conversationHistory.push(`user: ${content}`);
} else {
conversationHistory.push(`assistant: ${content}`);
}
if (conversationHistory.length > 2) {
conversationHistory = conversationHistory.slice(-2);
}
}
function showTypingIndicator() {
const typingDiv = document.createElement('div');
typingDiv.className = 'message ai';
typingDiv.id = 'typingIndicator';
const avatar = document.createElement('div');
avatar.className = 'avatar';
avatar.textContent = friendName[0].toUpperCase();
const indicator = document.createElement('div');
indicator.className = 'typing-indicator active';
indicator.innerHTML = '<span></span><span></span><span></span>';
typingDiv.appendChild(avatar);
typingDiv.appendChild(indicator);
chatContainer.appendChild(typingDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
function removeTypingIndicator() {
const indicator = document.getElementById('typingIndicator');
if (indicator) {
indicator.remove();
}
}
async function generateResponse(userMessage) {
if (!generator || isProcessing) return;
isProcessing = true;
sendButton.disabled = true;
messageInput.disabled = true;
showTypingIndicator();
try {
const history = conversationHistory.length > 0
? conversationHistory.join('\n') + '\n'
: '';
const prompt = `<|im_start|>user\n${history}${userMessage}\n<|im_end|>\n<|im_start|>assistant\n`;
const output = await generator(prompt, {
max_new_tokens: 100,
temperature: 0.8,
top_p: 0.9,
return_full_text: false
});
removeTypingIndicator();
let response = output[0].generated_text.trim();
response = response.replace(/<\|im_end\|>|<\|im_start\|>.*$/g, '').trim();
if (response) {
addMessage(response);
} else {
addMessage("Not sure what to say... Wanna try that again?");
console.warn('Empty or invalid response received from model');
showError('Hmm, I got nothing. Try a different question?');
}
} catch (error) {
console.error('Error generating response:', error, error.stack);
removeTypingIndicator();
let errorMsg = error.message || 'Unknown error';
if (error.message.includes('memory')) {
errorMsg = 'Phone’s a bit overloaded. Close some apps?';
} else if (error.message.includes('WebGPU')) {
errorMsg = 'Need a better connection or browser. Try Chrome?';
}
addMessage("Oops, something’s up! Try again?");
showError(`Can’t reply right now: ${errorMsg}`);
} finally {
isProcessing = false;
sendButton.disabled = false;
messageInput.disabled = false;
messageInput.focus();
}
}
async function handleSend() {
const inputMessage = messageInput.value.trim();
if (!inputMessage || !generator || isProcessing) return;
addMessage(inputMessage, true);
messageInput.value = '';
await generateResponse(inputMessage);
}
// Event listeners
sendButton.addEventListener('click', handleSend);
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSend();
}
});
</script>
</body>
</html>