anycoder-2bce939e / style.css
akhaliq's picture
akhaliq HF Staff
Upload style.css with huggingface_hub
94878b7 verified
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary-color: #007AFF;
--primary-hover: #0051D5;
--background: #FFFFFF;
--surface: #F2F2F7;
--surface-hover: #E5E5EA;
--text-primary: #000000;
--text-secondary: #6D6D80;
--border-color: #C6C6C8;
--success: #34C759;
--error: #FF3B30;
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.15);
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.2);
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--background);
color: var(--text-primary);
height: 100vh;
overflow: hidden;
}
.app-container {
display: flex;
flex-direction: column;
height: 100vh;
max-width: 100%;
}
/* Header */
.header {
background: var(--background);
border-bottom: 1px solid var(--border-color);
padding: 12px 20px;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
background: rgba(255, 255, 255, 0.72);
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
}
.header-left {
display: flex;
align-items: center;
gap: 12px;
}
.logo {
color: var(--primary-color);
}
.header h1 {
font-size: 20px;
font-weight: 600;
letter-spacing: -0.5px;
}
.header-right {
display: flex;
align-items: center;
gap: 16px;
}
.status-indicator {
display: flex;
align-items: center;
gap: 6px;
font-size: 14px;
font-weight: 500;
}
.status-indicator::before {
content: '';
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--error);
}
.status-indicator.online::before {
background: var(--success);
}
.built-with {
font-size: 13px;
color: var(--text-secondary);
text-decoration: none;
transition: var(--transition);
}
.built-with:hover {
color: var(--primary-color);
}
/* Loading Screen */
.loading-screen {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--background);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
transition: opacity 0.3s ease;
}
.loading-content {
text-align: center;
max-width: 320px;
}
.spinner {
width: 48px;
height: 48px;
margin: 0 auto 24px;
border: 3px solid var(--border-color);
border-top-color: var(--primary-color);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-content h2 {
font-size: 24px;
font-weight: 600;
margin-bottom: 8px;
}
.loading-content p {
color: var(--text-secondary);
margin-bottom: 24px;
font-size: 15px;
}
.progress-bar {
width: 100%;
height: 4px;
background: var(--surface);
border-radius: 2px;
overflow: hidden;
margin-bottom: 8px;
}
.progress-fill {
height: 100%;
background: var(--primary-color);
width: 0%;
transition: width 0.3s ease;
}
.progress-text {
font-size: 13px;
color: var(--text-secondary);
}
/* Chat Container */
.chat-container {
flex: 1;
overflow-y: auto;
background: var(--surface);
padding: 20px;
}
.chat-container::-webkit-scrollbar {
width: 6px;
}
.chat-container::-webkit-scrollbar-track {
background: transparent;
}
.chat-container::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 3px;
}
.chat-messages {
max-width: 800px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 16px;
}
.welcome-message {
text-align: center;
padding: 40px 20px;
}
.welcome-message h2 {
font-size: 28px;
font-weight: 600;
margin-bottom: 12px;
}
.welcome-message p {
color: var(--text-secondary);
font-size: 16px;
line-height: 1.5;
}
.message {
display: flex;
gap: 12px;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.message.user {
flex-direction: row-reverse;
}
.message-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 600;
flex-shrink: 0;
}
.message.user .message-avatar {
background: var(--primary-color);
color: white;
}
.message.assistant .message-avatar {
background: var(--surface-hover);
color: var(--text-primary);
}
.message-bubble {
max-width: 70%;
padding: 12px 16px;
border-radius: var(--radius-md);
position: relative;
}
.message.user .message-bubble {
background: var(--primary-color);
color: white;
border-bottom-right-radius: 4px;
}
.message.assistant .message-bubble {
background: var(--background);
border-bottom-left-radius: 4px;
box-shadow: var(--shadow-sm);
}
.message-content {
font-size: 15px;
line-height: 1.5;
word-wrap: break-word;
}
.typing-indicator {
display: flex;
gap: 4px;
padding: 8px 0;
}
.typing-dot {
width: 8px;
height: 8px;
background: var(--text-secondary);
border-radius: 50%;
animation: typing 1.4s infinite;
}
.typing-dot:nth-child(2) {
animation-delay: 0.2s;
}
.typing-dot:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes typing {
0%, 60%, 100% {
transform: translateY(0);
opacity: 0.5;
}
30% {
transform: translateY(-10px);
opacity: 1;
}
}
/* Input Area */
.input-area {
background: var(--background);
border-top: 1px solid var(--border-color);
padding: 16px 20px;
}
.input-container {
max-width: 800px;
margin: 0 auto;
}
.input-wrapper {
display: flex;
gap: 12px;
align-items: flex-end;
}
.message-input {
flex: 1;
background: var(--surface);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
padding: 12px 16px;
font-size: 15px;
font-family: inherit;
resize: none;
outline: none;
transition: var(--transition);
min-height: 44px;
max-height: 120px;
line-height: 1.5;
}
.message-input:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
}
.send-button {
width: 44px;
height: 44px;
border-radius: 50%;
background: var(--primary-color);
border: none;
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
flex-shrink: 0;
}
.send-button:hover:not(:disabled) {
background: var(--primary-hover);
transform: scale(1.05);
}
.send-button:disabled {
background: var(--border-color);
cursor: not-allowed;
opacity: 0.6;
}
.input-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8px;
font-size: 13px;
color: var(--text-secondary);
}
/* Utility Classes */
.hidden {
display: none !important;
}
/* Responsive Design */
@media (max-width: 768px) {
.header {
padding: 10px 16px;
}
.header h1 {
font-size: 18px;
}
.built-with {
display: none;
}
.chat-container {
padding: 16px;
}
.message-bubble {
max-width: 85%;
}
.input-area {
padding: 12px 16px;
}
}
@media (max-width: 480px) {
.welcome-message h2 {
font-size: 24px;
}
.message-bubble {
max-width: 90%;
}
}
=== app.js ===
import { pipeline, TextStreamer } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.0.1";
class AppleStyleChatbot {
constructor() {
this.generator = null;
this.messages = [];
this.isGenerating = false;
this.initElements();
this.initEventListeners();
this.loadModel();
}
initElements() {
// Loading elements
this.loadingScreen = document.getElementById('loading-screen');
this.loadingText = document.getElementById('loading-text');
this.progressFill = document.getElementById('progress-fill');
this.progressText = document.getElementById('progress-text');
// Chat elements
this.chatContainer = document.getElementById('chat-container');
this.chatMessages = document.getElementById('chat-messages');
this.inputArea = document.getElementById('input-area');
this.messageInput = document.getElementById('message-input');
this.sendButton = document.getElementById('send-button');
this.charCount = document.getElementById('char-count');
this.modelStatus = document.getElementById('model-status');
// Remove welcome message when first user message is sent
this.welcomeMessage = null;
}
initEventListeners() {
// Send button click
this.sendButton.addEventListener('click', () => this.sendMessage());
// Enter key to send (Shift+Enter for new line)
this.messageInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
});
// Auto-resize textarea
this.messageInput.addEventListener('input', () => {
this.autoResizeTextarea();
this.updateCharCount();
this.updateSendButton();
});
// Focus input when clicking anywhere in input area
this.inputArea.addEventListener('click', (e) => {
if (e.target.closest('.message-input')) return;
this.messageInput.focus();
});
}
async loadModel() {
try {
this.updateLoadingStatus('Downloading model files...');
this.updateProgress(0);
// Create the text generation pipeline with progress callback
this.generator = await pipeline(
"text-generation",
"onnx-community/gemma-3-270m-it-ONNX",
{
dtype: "fp32",
progress_callback: (progress) => {
if (progress.status === 'progress') {
const percent = Math.round(progress.progress * 100);
this.updateProgress(percent);
if (progress.file) {
this.updateLoadingStatus(`Loading ${progress.file}...`);
}
}
}
}
);
this.updateLoadingStatus('Initializing model...');
this.updateProgress(100);
// Show chat interface
setTimeout(() => {
this.loadingScreen.style.opacity = '0';
setTimeout(() => {
this.loadingScreen.classList.add('hidden');
this.chatContainer.classList.remove('hidden');
this.inputArea.classList.remove('hidden');
this.modelStatus.classList.add('online');
this.modelStatus.textContent = 'Online';
this.messageInput.focus();
}, 300);
}, 500);
} catch (error) {
console.error('Failed to load model:', error);
this.showError('Failed to load AI model. Please refresh the page and try again.');
}
}
updateLoadingStatus(text) {
this.loadingText.textContent = text;
}
updateProgress(percent) {
this.progressFill.style.width = `${percent}%`;
this.progressText.textContent = `${percent}%`;
}
showError(message) {
this.loadingText.textContent = message;
this.loadingText.style.color = 'var(--error)';
setTimeout(() => {
this.loadingScreen.innerHTML = `
<div class="loading-content">
<h2 style="color: var(--error);">Error</h2>
<p>${message}</p>
<button onclick="location.reload()" style="
margin-top: 20px;
padding: 12px 24px;
background: var(--primary-color);
color: white;
border: none;
border-radius: var(--radius-md);
font-size: 15px;
font-weight: 500;
cursor: pointer;
">Reload</button>
</div>
`;
}, 100);
}
async sendMessage() {
const message = this.messageInput.value.trim();
if (!message || this.isGenerating) return;
// Remove welcome message on first user message
if (this.welcomeMessage === null) {
const welcomeEl = document.querySelector('.welcome-message');
if (welcomeEl) {
welcomeEl.style.opacity = '0';
setTimeout(() => welcomeEl.remove(), 300);
}
this.welcomeMessage = false;
}
// Add user message
this.addMessage(message, 'user');
this.messageInput.value = '';
this.autoResizeTextarea();
this.updateCharCount();
this.updateSendButton();
// Disable input during generation
this.isGenerating = true;
this.sendButton.disabled = true;
this.messageInput.disabled = true;
// Add typing indicator
const typingId = this.addTypingIndicator();
try {
// Update messages array with conversation history
this.messages.push({ role: "user", content: message });
// Keep only last 10 messages to avoid context limit
const contextMessages = this.messages.slice(-10);
// Generate response with streaming
const responseText = await this.generateResponse(contextMessages);
// Remove typing indicator and add assistant message
this.removeTypingIndicator(typingId);
this.addMessage(responseText, 'assistant');
// Add to message history
this.messages.push({ role: "assistant", content: responseText });
} catch (error) {
console.error('Generation error:', error);
this.removeTypingIndicator(typingId);
this.addMessage('Sorry, I encountered an error while generating a response. Please try again.', 'assistant', true);
} finally {
// Re-enable input
this.isGenerating = false;
this.sendButton.disabled = false;
this.messageInput.disabled = false;
this.messageInput.focus();
}
}
async generateResponse(messages) {
let fullResponse = '';
try {
// Create a custom streamer to capture the text
const streamer = new TextStreamer(this.generator.tokenizer, {
skip_prompt: true,
skip_special_tokens: true,
callback_function: (text) => {
fullResponse += text;
return text;
},
});
// Generate response
const output = await generator(messages, {
max_new_tokens: 512,
do_sample: false,
temperature: 0.7,
top_p: 0.9,
streamer: streamer,
});
return output[0].generated_text.at(-1).content;
} catch (error) {
console.error('Generation error:', error);
throw error;
}
}
addMessage(text, role, isError = false) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${role}`;
const avatar = document.createElement('div');
avatar.className = 'message-avatar';
avatar.textContent = role === 'user' ? 'U' : 'A';
const bubble = document.createElement('div');
bubble.className = 'message-bubble';
const content = document.createElement('div');
content.className = 'message-content';
if (isError) {
content.style.color = 'var(--error)';
}
content.textContent = text;
bubble.appendChild(content);
messageDiv.appendChild(avatar);
messageDiv.appendChild(bubble);
this.chatMessages.appendChild(messageDiv);
this.scrollToBottom();
return messageDiv;
}
addTypingIndicator() {
const messageDiv = document.createElement('div');
messageDiv.className = 'message assistant';
messageDiv.id = `typing-${Date.now()}`;
const avatar = document.createElement('div');
avatar.className = 'message-avatar';
avatar.textContent = 'A';
const bubble = document.createElement('div');
bubble.className = 'message-bubble';
const typing = document.createElement('div');
typing.className = 'typing-indicator';
typing.innerHTML = '<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>';
bubble.appendChild(typing);
messageDiv.appendChild(avatar);
messageDiv.appendChild(bubble);
this.chatMessages.appendChild(messageDiv);
this.scrollToBottom();
return messageDiv.id;
}
removeTypingIndicator(id) {
const indicator = document.getElementById(id);
if (indicator) {
indicator.style.opacity = '0';
setTimeout(() => indicator.remove(), 300);
}
}
autoResizeTextarea() {
this.messageInput.style.height = 'auto';
this.messageInput.style.height = Math.min(this.messageInput.scrollHeight, 120) + 'px';
}
updateCharCount() {
const count = this.messageInput.value.length;
this.charCount.textContent = `${count} / 2000`;
}
updateSendButton() {
const hasText = this.messageInput.value.trim().length > 0;
this.sendButton.disabled = !hasText || this.isGenerating;
}
scrollToBottom() {
this.chatContainer.scrollTop = this.chatContainer.scrollHeight;
}
}
// Initialize the chatbot when the page loads
document.addEventListener('DOMContentLoaded', () => {
new AppleStyleChatbot();
});
// Fix for the generator reference in generateResponse
let generator;
pipeline(
"text-generation",
"onnx-community/gemma-3-270m-it-ONNX",
{
dtype: "fp32",
}
).then(g => {
generator = g;
});