anycoder-d3c2c2f3 / index.html
CharlieBoyer's picture
CharlieBoyer HF Staff
Upload folder using huggingface_hub
f9c7472 verified
Raw
History Blame Contribute Delete
42 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Qwen Chat - AI Assistant</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
colors: {
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e',
},
dark: {
800: '#1e293b',
900: '#0f172a',
950: '#020617',
}
},
animation: {
'fade-in': 'fadeIn 0.5s ease-out',
'slide-up': 'slideUp 0.3s ease-out',
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
'typing': 'typing 1.5s infinite',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
slideUp: {
'0%': { transform: 'translateY(20px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
},
typing: {
'0%, 100%': { opacity: '1' },
'50%': { opacity: '0.3' },
}
}
}
}
}
</script>
<style>
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #475569;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #64748b;
}
/* Glassmorphism */
.glass {
background: rgba(30, 41, 59, 0.7);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.05);
}
.glass-light {
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
/* Code Block Styling */
.code-block {
background: #0d1117;
border-radius: 8px;
overflow: hidden;
margin: 12px 0;
}
.code-header {
background: #161b22;
padding: 8px 16px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #30363d;
}
.code-content {
padding: 16px;
overflow-x: auto;
font-family: 'Fira Code', 'Consolas', monospace;
font-size: 13px;
line-height: 1.6;
color: #e6edf3;
}
.code-content .keyword { color: #ff7b72; }
.code-content .string { color: #a5d6ff; }
.code-content .comment { color: #8b949e; font-style: italic; }
.code-content .function { color: #d2a8ff; }
.code-content .number { color: #79c0ff; }
/* Typing Indicator */
.typing-dot {
width: 6px;
height: 6px;
background: #38bdf8;
border-radius: 50%;
display: inline-block;
animation: typing 1.4s infinite ease-in-out both;
}
.typing-dot:nth-child(1) { animation-delay: -0.32s; }
.typing-dot:nth-child(2) { animation-delay: -0.16s; }
/* Message Animations */
.message-appear {
animation: slideUp 0.3s ease-out forwards;
}
/* Gradient Text */
.gradient-text {
background: linear-gradient(135deg, #38bdf8 0%, #818cf8 50%, #c084fc 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Input Glow */
.input-glow:focus-within {
box-shadow: 0 0 0 2px rgba(56, 189, 248, 0.2), 0 0 20px rgba(56, 189, 248, 0.1);
}
/* Background Pattern */
.bg-pattern {
background-image:
radial-gradient(circle at 20% 50%, rgba(56, 189, 248, 0.08) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(192, 132, 252, 0.06) 0%, transparent 50%),
radial-gradient(circle at 40% 20%, rgba(129, 140, 248, 0.05) 0%, transparent 50%);
}
/* Markdown Styles */
.markdown-body h1 { font-size: 1.5em; font-weight: 700; margin: 16px 0 8px; }
.markdown-body h2 { font-size: 1.25em; font-weight: 600; margin: 14px 0 8px; }
.markdown-body h3 { font-size: 1.1em; font-weight: 600; margin: 12px 0 6px; }
.markdown-body p { margin: 8px 0; line-height: 1.7; }
.markdown-body ul, .markdown-body ol { margin: 8px 0; padding-left: 20px; }
.markdown-body li { margin: 4px 0; }
.markdown-body strong { font-weight: 600; color: #f1f5f9; }
.markdown-body em { font-style: italic; }
.markdown-body blockquote {
border-left: 3px solid #38bdf8;
padding-left: 12px;
margin: 12px 0;
color: #94a3b8;
font-style: italic;
}
.markdown-body pre { margin: 12px 0; }
.markdown-body code {
background: rgba(56, 189, 248, 0.1);
padding: 2px 6px;
border-radius: 4px;
font-size: 0.9em;
color: #7dd3fc;
font-family: 'Fira Code', monospace;
}
.markdown-body pre code {
background: none;
padding: 0;
color: inherit;
}
.markdown-body table {
width: 100%;
border-collapse: collapse;
margin: 12px 0;
}
.markdown-body th, .markdown-body td {
border: 1px solid #334155;
padding: 8px 12px;
text-align: left;
}
.markdown-body th {
background: rgba(56, 189, 248, 0.1);
font-weight: 600;
}
.markdown-body a {
color: #38bdf8;
text-decoration: underline;
}
.markdown-body hr {
border: none;
border-top: 1px solid #334155;
margin: 16px 0;
}
/* Tooltip */
.tooltip {
position: relative;
}
.tooltip::after {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
padding: 4px 8px;
background: #1e293b;
color: #f8fafc;
font-size: 12px;
border-radius: 4px;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
margin-bottom: 4px;
border: 1px solid #334155;
}
.tooltip:hover::after {
opacity: 1;
}
/* Selection */
::selection {
background: rgba(56, 189, 248, 0.3);
color: #f8fafc;
}
</style>
</head>
<body class="bg-dark-950 text-slate-200 font-sans h-screen overflow-hidden bg-pattern">
<!-- App Container -->
<div class="flex h-full">
<!-- Sidebar -->
<aside id="sidebar" class="w-72 bg-dark-900 border-r border-slate-800/50 flex flex-col transition-all duration-300 ease-in-out flex-shrink-0">
<!-- Sidebar Header -->
<div class="p-4 border-b border-slate-800/50">
<div class="flex items-center gap-3 mb-4">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-sky-500 to-violet-500 flex items-center justify-center shadow-lg shadow-sky-500/20">
<i class="fas fa-robot text-white text-lg"></i>
</div>
<div>
<h1 class="font-bold text-lg text-slate-100">Qwen Chat</h1>
<p class="text-xs text-slate-400">AI Assistant</p>
</div>
</div>
<button onclick="newChat()" class="w-full py-2.5 px-4 rounded-lg bg-slate-800 hover:bg-slate-700 border border-slate-700/50 text-slate-200 text-sm font-medium transition-all duration-200 flex items-center justify-center gap-2 hover:border-slate-600 group">
<i class="fas fa-plus text-sky-400 group-hover:scale-110 transition-transform"></i>
New Chat
</button>
</div>
<!-- Chat History -->
<div class="flex-1 overflow-y-auto p-3 space-y-1" id="chatHistory">
<!-- History items will be populated by JS -->
</div>
<!-- Sidebar Footer -->
<div class="p-4 border-t border-slate-800/50 space-y-3">
<!-- API Key Input -->
<div class="relative">
<input
type="password"
id="apiKeyInput"
placeholder="Enter HuggingFace Token"
class="w-full bg-slate-800/50 border border-slate-700/50 rounded-lg px-3 py-2 text-xs text-slate-300 placeholder-slate-500 focus:outline-none focus:border-sky-500/50 focus:ring-1 focus:ring-sky-500/20 transition-all"
>
<button onclick="saveApiKey()" class="absolute right-2 top-1/2 -translate-y-1/2 text-sky-400 hover:text-sky-300 text-xs">
<i class="fas fa-save"></i>
</button>
</div>
<div class="flex items-center justify-between text-xs text-slate-500">
<span class="flex items-center gap-1">
<i class="fas fa-shield-alt text-emerald-500"></i>
Secure
</span>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="hover:text-sky-400 transition-colors flex items-center gap-1">
Built with <span class="text-sky-400 font-medium">anycoder</span>
<i class="fas fa-external-link-alt text-[10px]"></i>
</a>
</div>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 flex flex-col relative min-w-0">
<!-- Mobile Header -->
<div class="lg:hidden flex items-center justify-between p-4 border-b border-slate-800/50 glass">
<button onclick="toggleSidebar()" class="text-slate-400 hover:text-slate-200">
<i class="fas fa-bars text-xl"></i>
</button>
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-sky-500 to-violet-500 flex items-center justify-center">
<i class="fas fa-robot text-white text-sm"></i>
</div>
<span class="font-semibold text-slate-200">Qwen Chat</span>
</div>
<div class="w-8"></div>
</div>
<!-- Chat Area -->
<div id="chatContainer" class="flex-1 overflow-y-auto p-4 lg:p-8 space-y-6 scroll-smooth">
<!-- Welcome Screen -->
<div id="welcomeScreen" class="flex flex-col items-center justify-center h-full text-center px-4 max-w-2xl mx-auto">
<div class="w-20 h-20 rounded-2xl bg-gradient-to-br from-sky-500 to-violet-500 flex items-center justify-center mb-6 shadow-2xl shadow-sky-500/20 animate-pulse-slow">
<i class="fas fa-sparkles text-white text-3xl"></i>
</div>
<h2 class="text-3xl lg:text-4xl font-bold mb-3 gradient-text">How can I help you today?</h2>
<p class="text-slate-400 mb-8 max-w-md">Powered by Qwen 2.5 - Alibaba's latest large language model. Ask me anything about coding, writing, analysis, or creative tasks.</p>
<!-- Quick Suggestions -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 w-full max-w-lg">
<button onclick="sendQuickMessage('Explain quantum computing in simple terms')" class="p-4 rounded-xl bg-slate-800/50 border border-slate-700/50 hover:border-sky-500/30 hover:bg-slate-800 transition-all text-left group">
<div class="w-8 h-8 rounded-lg bg-violet-500/20 flex items-center justify-center mb-3 group-hover:scale-110 transition-transform">
<i class="fas fa-atom text-violet-400"></i>
</div>
<p class="text-sm text-slate-300 font-medium">Explain quantum computing</p>
<p class="text-xs text-slate-500 mt-1">in simple terms</p>
</button>
<button onclick="sendQuickMessage('Write a Python function to sort a list of dictionaries by multiple keys')" class="p-4 rounded-xl bg-slate-800/50 border border-slate-700/50 hover:border-sky-500/30 hover:bg-slate-800 transition-all text-left group">
<div class="w-8 h-8 rounded-lg bg-sky-500/20 flex items-center justify-center mb-3 group-hover:scale-110 transition-transform">
<i class="fas fa-code text-sky-400"></i>
</div>
<p class="text-sm text-slate-300 font-medium">Write Python code</p>
<p class="text-xs text-slate-500 mt-1">sort dictionaries by keys</p>
</button>
<button onclick="sendQuickMessage('Create a marketing strategy for a new eco-friendly product')" class="p-4 rounded-xl bg-slate-800/50 border border-slate-700/50 hover:border-sky-500/30 hover:bg-slate-800 transition-all text-left group">
<div class="w-8 h-8 rounded-lg bg-emerald-500/20 flex items-center justify-center mb-3 group-hover:scale-110 transition-transform">
<i class="fas fa-leaf text-emerald-400"></i>
</div>
<p class="text-sm text-slate-300 font-medium">Marketing strategy</p>
<p class="text-xs text-slate-500 mt-1">for eco-friendly product</p>
</button>
<button onclick="sendQuickMessage('Analyze the pros and cons of remote work for tech companies')" class="p-4 rounded-xl bg-slate-800/50 border border-slate-700/50 hover:border-sky-500/30 hover:bg-slate-800 transition-all text-left group">
<div class="w-8 h-8 rounded-lg bg-amber-500/20 flex items-center justify-center mb-3 group-hover:scale-110 transition-transform">
<i class="fas fa-chart-line text-amber-400"></i>
</div>
<p class="text-sm text-slate-300 font-medium">Analyze remote work</p>
<p class="text-xs text-slate-500 mt-1">pros and cons for tech</p>
</button>
</div>
</div>
<!-- Messages will appear here -->
<div id="messagesArea" class="hidden max-w-3xl mx-auto w-full space-y-6"></div>
</div>
<!-- Input Area -->
<div class="p-4 lg:p-6 border-t border-slate-800/50 glass">
<div class="max-w-3xl mx-auto">
<!-- Model Selector -->
<div class="flex items-center gap-2 mb-3 overflow-x-auto pb-1">
<button onclick="setModel('qwen2.5-72b-instruct')" class="model-btn active px-3 py-1.5 rounded-full text-xs font-medium bg-sky-500/20 text-sky-300 border border-sky-500/30 transition-all whitespace-nowrap" data-model="qwen2.5-72b-instruct">
<i class="fas fa-bolt mr-1"></i>Qwen 2.5 72B
</button>
<button onclick="setModel('qwen2.5-14b-instruct')" class="model-btn px-3 py-1.5 rounded-full text-xs font-medium bg-slate-800 text-slate-400 border border-slate-700 hover:border-slate-600 transition-all whitespace-nowrap" data-model="qwen2.5-14b-instruct">
Qwen 2.5 14B
</button>
<button onclick="setModel('qwen2.5-7b-instruct')" class="model-btn px-3 py-1.5 rounded-full text-xs font-medium bg-slate-800 text-slate-400 border border-slate-700 hover:border-slate-600 transition-all whitespace-nowrap" data-model="qwen2.5-7b-instruct">
Qwen 2.5 7B
</button>
<button onclick="setModel('qwen2.5-coder-32b-instruct')" class="model-btn px-3 py-1.5 rounded-full text-xs font-medium bg-slate-800 text-slate-400 border border-slate-700 hover:border-slate-600 transition-all whitespace-nowrap" data-model="qwen2.5-coder-32b-instruct">
<i class="fas fa-code mr-1"></i>Coder 32B
</button>
</div>
<!-- Input Box -->
<div class="relative input-glow rounded-2xl bg-slate-800/80 border border-slate-700/50 transition-all duration-300">
<textarea
id="messageInput"
rows="1"
placeholder="Message Qwen..."
class="w-full bg-transparent text-slate-200 placeholder-slate-500 px-5 py-4 pr-14 resize-none focus:outline-none max-h-48 text-sm leading-relaxed"
onkeydown="handleKeyDown(event)"
oninput="autoResize(this)"
></textarea>
<div class="absolute right-3 bottom-3 flex items-center gap-2">
<button onclick="clearInput()" class="p-2 text-slate-500 hover:text-slate-300 transition-colors rounded-lg hover:bg-slate-700/50">
<i class="fas fa-times"></i>
</button>
<button onclick="sendMessage()" id="sendBtn" class="p-2.5 bg-sky-500 hover:bg-sky-400 text-white rounded-xl transition-all duration-200 shadow-lg shadow-sky-500/20 hover:shadow-sky-500/30 disabled:opacity-50 disabled:cursor-not-allowed">
<i class="fas fa-paper-plane text-sm"></i>
</button>
</div>
</div>
<p class="text-center text-xs text-slate-600 mt-2">
Qwen can make mistakes. Consider checking important information.
</p>
</div>
</div>
</main>
</div>
<!-- Toast Notification -->
<div id="toast" class="fixed top-4 right-4 px-4 py-3 rounded-xl glass border border-slate-700/50 text-sm text-slate-200 transform translate-x-full transition-transform duration-300 z-50 flex items-center gap-2">
<i class="fas fa-check-circle text-emerald-400"></i>
<span id="toastMessage">Notification</span>
</div>
<script>
// State Management
let currentModel = 'qwen2.5-72b-instruct';
let chatHistory = [];
let currentChatId = null;
let isGenerating = false;
let abortController = null;
// Initialize
document.addEventListener('DOMContentLoaded', () => {
loadApiKey();
loadChatHistory();
createNewChat();
autoResize(document.getElementById('messageInput'));
});
// API Key Management
function saveApiKey() {
const key = document.getElementById('apiKeyInput').value.trim();
if (key) {
localStorage.setItem('hf_token', key);
showToast('API Key saved successfully');
}
}
function loadApiKey() {
const key = localStorage.getItem('hf_token');
if (key) {
document.getElementById('apiKeyInput').value = key;
}
}
function getApiKey() {
return localStorage.getItem('hf_token');
}
// Chat History Management
function loadChatHistory() {
const saved = localStorage.getItem('chatHistory');
if (saved) {
chatHistory = JSON.parse(saved);
renderChatHistory();
}
}
function saveChatHistory() {
localStorage.setItem('chatHistory', JSON.stringify(chatHistory));
}
function renderChatHistory() {
const container = document.getElementById('chatHistory');
container.innerHTML = chatHistory.map(chat => `
<div onclick="loadChat('${chat.id}')" class="group flex items-center gap-3 p-3 rounded-xl cursor-pointer transition-all duration-200 ${chat.id === currentChatId ? 'bg-sky-500/10 border border-sky-500/20' : 'hover:bg-slate-800/50 border border-transparent'}">
<div class="w-8 h-8 rounded-lg ${chat.id === currentChatId ? 'bg-sky-500/20' : 'bg-slate-800'} flex items-center justify-center flex-shrink-0">
<i class="fas fa-message ${chat.id === currentChatId ? 'text-sky-400' : 'text-slate-500'} text-xs"></i>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm text-slate-300 truncate font-medium">${chat.title}</p>
<p class="text-xs text-slate-500 truncate">${new Date(chat.timestamp).toLocaleDateString()}</p>
</div>
<button onclick="event.stopPropagation(); deleteChat('${chat.id}')" class="opacity-0 group-hover:opacity-100 p-1.5 text-slate-500 hover:text-red-400 transition-all rounded-lg hover:bg-red-500/10">
<i class="fas fa-trash-alt text-xs"></i>
</button>
</div>
`).join('');
}
function createNewChat() {
currentChatId = Date.now().toString();
const newChat = {
id: currentChatId,
title: 'New Conversation',
messages: [],
timestamp: Date.now(),
model: currentModel
};
chatHistory.unshift(newChat);
saveChatHistory();
renderChatHistory();
renderMessages();
document.getElementById('welcomeScreen').classList.remove('hidden');
document.getElementById('messagesArea').classList.add('hidden');
}
function newChat() {
createNewChat();
document.getElementById('messageInput').focus();
}
function loadChat(id) {
currentChatId = id;
const chat = chatHistory.find(c => c.id === id);
if (chat) {
currentModel = chat.model || currentModel;
updateModelButtons();
renderMessages();
renderChatHistory();
}
}
function deleteChat(id) {
chatHistory = chatHistory.filter(c => c.id !== id);
saveChatHistory();
renderChatHistory();
if (currentChatId === id) {
createNewChat();
}
}
// Message Rendering
function renderMessages() {
const chat = chatHistory.find(c => c.id === currentChatId);
const messagesArea = document.getElementById('messagesArea');
if (!chat || chat.messages.length === 0) {
document.getElementById('welcomeScreen').classList.remove('hidden');
messagesArea.classList.add('hidden');
return;
}
document.getElementById('welcomeScreen').classList.add('hidden');
messagesArea.classList.remove('hidden');
messagesArea.innerHTML = chat.messages.map((msg, index) => `
<div class="message-appear flex gap-4 ${msg.role === 'user' ? 'flex-row-reverse' : ''}" style="animation-delay: ${index * 0.05}s">
<!-- Avatar -->
<div class="flex-shrink-0">
${msg.role === 'user'
? `<div class="w-8 h-8 rounded-full bg-gradient-to-br from-sky-500 to-violet-500 flex items-center justify-center">
<i class="fas fa-user text-white text-xs"></i>
</div>`
: `<div class="w-8 h-8 rounded-full bg-gradient-to-br from-emerald-500 to-teal-500 flex items-center justify-center">
<i class="fas fa-robot text-white text-xs"></i>
</div>`
}
</div>
<!-- Message Content -->
<div class="flex-1 ${msg.role === 'user' ? 'text-right' : ''} max-w-[85%] lg:max-w-[80%]">
<div class="inline-block text-left">
<div class="flex items-center gap-2 mb-1 ${msg.role === 'user' ? 'justify-end' : ''}">
<span class="text-xs font-medium ${msg.role === 'user' ? 'text-sky-400' : 'text-emerald-400'}">
${msg.role === 'user' ? 'You' : 'Qwen'}
</span>
<span class="text-xs text-slate-600">${formatTime(msg.timestamp)}</span>
</div>
<div class="${msg.role === 'user'
? 'bg-sky-500/10 border border-sky-500/20 text-slate-200'
: 'bg-slate-800/50 border border-slate-700/50 text-slate-300'}
rounded-2xl px-5 py-3.5 text-sm leading-relaxed markdown-body">
${msg.role === 'assistant' ? renderMarkdown(msg.content) : escapeHtml(msg.content)}
</div>
${msg.role === 'assistant' ? `
<div class="flex items-center gap-2 mt-2">
<button onclick="copyMessage('${escapeJs(msg.content)}')" class="tooltip p-1.5 text-slate-500 hover:text-sky-400 transition-colors rounded-lg hover:bg-slate-800" data-tooltip="Copy">
<i class="fas fa-copy text-xs"></i>
</button>
<button onclick="regenerateMessage(${index})" class="tooltip p-1.5 text-slate-500 hover:text-sky-400 transition-colors rounded-lg hover:bg-slate-800" data-tooltip="Regenerate">
<i class="fas fa-rotate-right text-xs"></i>
</button>
<button onclick="likeMessage(${index})" class="tooltip p-1.5 text-slate-500 hover:text-emerald-400 transition-colors rounded-lg hover:bg-slate-800" data-tooltip="Like">
<i class="fas fa-thumbs-up text-xs"></i>
</button>
</div>
` : ''}
</div>
</div>
</div>
`).join('');
// Scroll to bottom
const container = document.getElementById('chatContainer');
container.scrollTop = container.scrollHeight;
}
// Markdown Renderer
function renderMarkdown(text) {
// Escape HTML first
let html = escapeHtml(text);
// Code blocks with language
html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
const language = lang || 'text';
const highlightedCode = syntaxHighlight(code.trim());
return `
<div class="code-block">
<div class="code-header">
<span class="text-xs text-slate-400 font-mono">${language}</span>
<button onclick="copyCode(this)" class="text-xs text-slate-500 hover:text-sky-400 transition-colors flex items-center gap-1">
<i class="fas fa-copy"></i> Copy
</button>
</div>
<div class="code-content">${highlightedCode}</div>
</div>
`;
});
// Inline code
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
// Headers
html = html.replace(/^### (.*$)/gm, '<h3>$1</h3>');
html = html.replace(/^## (.*$)/gm, '<h2>$1</h2>');
html = html.replace(/^# (.*$)/gm, '<h1>$1</h1>');
// Bold and italic
html = html.replace(/\*\*\*(.*?)\*\*\*/g, '<strong><em>$1</em></strong>');
html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
html = html.replace(/\*(.*?)\*/g, '<em>$1</em>');
// Lists
html = html.replace(/^- (.*$)/gm, '<li>$1</li>');
html = html.replace(/(<li>.*<<\/li>\n)+/g, '<ul>$&</ul>');
// Numbered lists
html = html.replace(/^\d+\. (.*$)/gm, '<li>$1</li>');
html = html.replace(/(<li>.*<<\/li>\n)+/g, '<ol>$&</ol>');
// Links
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
// Blockquotes
html = html.replace(/^> (.*$)/gm, '<blockquote>$1</blockquote>');
// Horizontal rules
html = html.replace(/^---$/gm, '<hr>');
// Paragraphs (split by double newline)
html = html.split('\n\n').map(p => {
if (p.trim() && !p.startsWith('<')) {
return `<p>${p.replace(/\n/g, '<br>')}</p>`;
}
return p;
}).join('');
return html;
}
// Syntax Highlighting
function syntaxHighlight(code) {
// Simple syntax highlighting
return code
.replace(/\b(def|class|return|if|else|elif|for|while|import|from|as|try|except|with|lambda|yield|async|await|const|let|var|function|return|if|else|for|while|class|import|export|try|catch)\b/g, '<span class="keyword">$1</span>')
.replace(/(".*?"|'.*?'|`.*?`)/g, '<span class="string">$1</span>')
.replace(/\b(\d+)\b/g, '<span class="number">$1</span>')
.replace(/(\w+)\(/g, '<span class="function">$1</span>(')
.replace(/(#.*$|\/\/.*$)/gm, '<span class="comment">$1</span>');
}
// Utility Functions
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function escapeJs(text) {
return text.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/\n/g, '\\n');
}
function formatTime(timestamp) {
return new Date(timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
// UI Interactions
function setModel(model) {
currentModel = model;
updateModelButtons();
const chat = chatHistory.find(c => c.id === currentChatId);
if (chat) {
chat.model = model;
saveChatHistory();
}
}
function updateModelButtons() {
document.querySelectorAll('.model-btn').forEach(btn => {
if (btn.dataset.model === currentModel) {
btn.classList.add('bg-sky-500/20', 'text-sky-300', 'border-sky-500/30');
btn.classList.remove('bg-slate-800', 'text-slate-400', 'border-slate-700');
} else {
btn.classList.remove('bg-sky-500/20', 'text-sky-300', 'border-sky-500/30');
btn.classList.add('bg-slate-800', 'text-slate-400', 'border-slate-700');
}
});
}
function autoResize(textarea) {
textarea.style.height = 'auto';
textarea.style.height = Math.min(textarea.scrollHeight, 192) + 'px';
}
function handleKeyDown(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
}
function clearInput() {
const input = document.getElementById('messageInput');
input.value = '';
input.style.height = 'auto';
input.focus();
}
function sendQuickMessage(text) {
document.getElementById('messageInput').value = text;
autoResize(document.getElementById('messageInput'));
sendMessage();
}
// Message Actions
function copyMessage(content) {
navigator.clipboard.writeText(content).then(() => {
showToast('Copied to clipboard');
});
}
function copyCode(btn) {
const code = btn.closest('.code-block').querySelector('.code-content').textContent;
navigator.clipboard.writeText(code).then(() => {
const original = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-check"></i> Copied';
setTimeout(() => btn.innerHTML = original, 2000);
});
}
function likeMessage(index) {
showToast('Thanks for your feedback!');
}
function regenerateMessage(index) {
const chat = chatHistory.find(c => c.id === currentChatId);
if (chat && index > 0) {
const userMessage = chat.messages[index - 1];
if (userMessage.role === 'user') {
chat.messages.splice(index - 1, 2);
saveChatHistory();
renderMessages();
sendMessageToAPI(userMessage.content);
}
}
}
// Send Message
async function sendMessage() {
const input = document.getElementById('messageInput');
const content = input.value.trim();
if (!content || isGenerating) return;
// Check API key
const apiKey = getApiKey();
if (!apiKey) {
showToast('Please enter your HuggingFace token first', 'error');
document.getElementById('apiKeyInput').focus();
return;
}
// Add user message
const chat = chatHistory.find(c => c.id === currentChatId);
const userMessage = {
role: 'user',
content: content,
timestamp: Date.now()
};
chat.messages.push(userMessage);
// Update title if first message
if (chat.messages.length === 1) {
chat.title = content.substring(0, 50) + (content.length > 50 ? '...' : '');
}
saveChatHistory();
renderChatHistory();
renderMessages();
// Clear input
input.value = '';
input.style.height = 'auto';
// Send to API
await sendMessageToAPI(content);
}
// API Communication
async function sendMessageToAPI(content) {
isGenerating = true;
const sendBtn = document.getElementById('sendBtn');
sendBtn.disabled = true;
// Add typing indicator
const chat = chatHistory.find(c => c.id === currentChatId);
const assistantMessage = {
role: 'assistant',
content: '',
timestamp: Date.now(),
isTyping: true
};
chat.messages.push(assistantMessage);
renderMessages();
// Show typing animation
const messagesArea = document.getElementById('messagesArea');
const lastMessage = messagesArea.lastElementChild;
const contentDiv = lastMessage.querySelector('.markdown-body');
contentDiv.innerHTML = `
<div class="flex items-center gap-2 py-2">
<span class="typing-dot"></span>
<span class="typing-dot" style="animation-delay: -0.16s"></span>
<span class="typing-dot" style="animation-delay: -0.32s"></span>
</div>
`;
try {
abortController = new AbortController();
const response = await fetch('https://api-inference.huggingface.co/models/Qwen/' + currentModel, {
method: 'POST',
headers: {
'Authorization': `Bearer ${getApiKey()}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
inputs: formatConversation(chat.messages.slice(0, -1)),
parameters: {
max_new_tokens: 2048,
temperature: 0.7,
top_p: 0.9,
return_full_text: false
}
}),
signal: abortController.signal
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Remove typing indicator and add actual response
assistantMessage.isTyping = false;
if (Array.isArray(data) && data[0]?.generated_text) {
assistantMessage.content = data[0].generated_text;
} else if (data.generated_text) {
assistantMessage.content = data.generated_text;
} else {
throw new Error('Unexpected response format');
}
} catch (error) {
if (error.name === 'AbortError') {
assistantMessage.content = 'Generation stopped.';
} else {
assistantMessage.content = `Error: ${error.message}. Please check your API token and try again.`;
}
assistantMessage.isTyping = false;
} finally {
isGenerating = false;
sendBtn.disabled = false;
abortController = null;
saveChatHistory();
renderMessages();
}
}
function formatConversation(messages) {
let prompt = '';
for (const msg of messages) {
if (msg.role === 'user') {
prompt += `|<|im_start|>user\n${msg.content}|<|im_end|>\n`;
} else if (msg.role === 'assistant') {
prompt += `|<|im_start|>assistant\n${msg.content}|<|im_end|>\n`;
}
}
prompt += '|<|im_start|>assistant\n';
return prompt;
}
// Toast Notification
function showToast(message, type = 'success') {
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toastMessage');
toastMessage.textContent = message;
toast.querySelector('i').className = type === 'error' ? 'fas fa-exclamation-circle text-red-400' : 'fas fa-check-circle text-emerald-400';
toast.classList.remove('translate-x-full');
setTimeout(() => {
toast.classList.add('translate-x-full');
}, 3000);
}
// Sidebar Toggle (Mobile)
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
sidebar.classList.toggle('-translate-x-full');
}
// Stop Generation
function stopGeneration() {
if (abortController) {
abortController.abort();
}
}
</script>
</body>
</html>