anycoder-ce7b6184 / index.html
AiCoderv2's picture
Upload folder using huggingface_hub
45eb4b4 verified
raw
history blame
32.5 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>AnyCoder Chat — AI Chatbot</title>
<style>
:root {
--bg: #0b1020;
--bg2: #0f1427;
--panel: rgba(255,255,255,0.06);
--panel-strong: rgba(255,255,255,0.12);
--text: #e6e8f0;
--muted: #a9b1c6;
--accent: #7c5cff;
--accent-2: #00d4ff;
--danger: #ff5c7c;
--success: #2bd67b;
--shadow: 0 10px 30px rgba(0,0,0,0.35);
--radius: 16px;
--radius-sm: 10px;
--radius-lg: 20px;
--blur: 12px;
--glass: rgba(255,255,255,0.06);
--glass-strong: rgba(255,255,255,0.12);
}
.theme-light {
--bg: #f7f8fc;
--bg2: #eef1fb;
--panel: rgba(255,255,255,0.7);
--panel-strong: rgba(255,255,255,0.9);
--text: #14151a;
--muted: #4b4f5c;
--accent: #6b5cff;
--accent-2: #00a6ff;
--danger: #e11d48;
--success: #16a34a;
--glass: rgba(255,255,255,0.7);
--glass-strong: rgba(255,255,255,0.9);
--shadow: 0 10px 30px rgba(10, 20, 50, 0.15);
}
* { box-sizing: border-box; }
html, body {
height: 100%;
}
body {
margin: 0;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji";
color: var(--text);
background: radial-gradient(1200px 700px at 10% 10%, rgba(124,92,255,0.15), transparent 60%),
radial-gradient(900px 600px at 90% 20%, rgba(0,212,255,0.12), transparent 60%),
linear-gradient(180deg, var(--bg), var(--bg2));
background-attachment: fixed;
overflow: hidden;
}
.app {
display: grid;
grid-template-rows: auto 1fr auto;
height: 100dvh;
max-height: 100dvh;
}
/* Header */
.header {
display: flex;
align-items: center;
gap: 14px;
padding: 14px clamp(12px, 2vw, 24px);
position: sticky;
top: 0;
z-index: 5;
background: linear-gradient(180deg, rgba(0,0,0,0.25), rgba(0,0,0,0)) ;
backdrop-filter: blur(8px);
}
.brand {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 14px;
background: var(--panel);
border: 1px solid var(--panel-strong);
border-radius: var(--radius);
box-shadow: var(--shadow);
backdrop-filter: blur(var(--blur));
}
.logo {
width: 36px;
height: 36px;
border-radius: 10px;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
display: grid;
place-items: center;
color: white;
font-weight: 900;
letter-spacing: 0.5px;
box-shadow: 0 8px 20px rgba(124,92,255,0.35), inset 0 0 20px rgba(255,255,255,0.2);
}
.brand h1 {
margin: 0;
font-size: 18px;
letter-spacing: 0.3px;
}
.brand small {
display: block;
color: var(--muted);
font-size: 12px;
margin-top: 2px;
}
.header-actions {
margin-left: auto;
display: flex;
align-items: center;
gap: 8px;
}
.btn {
border: 1px solid var(--panel-strong);
background: var(--panel);
color: var(--text);
padding: 10px 14px;
border-radius: 12px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.2s ease;
backdrop-filter: blur(var(--blur));
box-shadow: var(--shadow);
user-select: none;
}
.btn:hover { transform: translateY(-1px); background: var(--glass-strong); }
.btn:active { transform: translateY(0); }
.btn.primary {
background: linear-gradient(135deg, var(--accent), var(--accent-2));
border-color: transparent;
color: white;
box-shadow: 0 10px 25px rgba(124,92,255,0.35);
}
.btn.ghost { background: transparent; border-color: var(--panel-strong); }
.btn.small { padding: 8px 10px; border-radius: 10px; font-size: 13px; }
.btn .icon { width: 18px; height: 18px; }
/* Chat area */
.chat {
position: relative;
overflow: hidden;
}
.messages {
position: absolute;
inset: 0;
overflow-y: auto;
padding: 16px clamp(10px, 2vw, 24px) 24px;
scroll-behavior: smooth;
}
.messages::-webkit-scrollbar { width: 10px; }
.messages::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, var(--panel-strong), transparent);
border-radius: 8px;
}
.empty {
height: 100%;
display: grid;
place-items: center;
pointer-events: none;
color: var(--muted);
}
.empty-inner {
text-align: center;
max-width: 680px;
padding: 30px;
border-radius: var(--radius);
background: var(--panel);
border: 1px solid var(--panel-strong);
backdrop-filter: blur(var(--blur));
box-shadow: var(--shadow);
}
.chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 14px;
justify-content: center;
}
.chip {
padding: 8px 12px;
border-radius: 999px;
background: var(--glass);
border: 1px solid var(--panel-strong);
cursor: pointer;
transition: all 0.2s ease;
font-size: 13px;
}
.chip:hover { transform: translateY(-1px); background: var(--glass-strong); }
.msg {
display: grid;
grid-template-columns: 36px 1fr;
gap: 10px;
margin: 10px auto;
max-width: min(900px, 92vw);
align-items: flex-start;
}
.msg.user { grid-template-columns: 1fr 36px; }
.avatar {
width: 36px;
height: 36px;
border-radius: 10px;
background: var(--panel);
border: 1px solid var(--panel-strong);
display: grid;
place-items: center;
backdrop-filter: blur(var(--blur));
}
.avatar.ai {
background: linear-gradient(135deg, rgba(124,92,255,0.3), rgba(0,212,255,0.3));
border-color: transparent;
}
.bubble {
padding: 12px 14px;
border-radius: 14px;
background: var(--panel);
border: 1px solid var(--panel-strong);
backdrop-filter: blur(var(--blur));
box-shadow: var(--shadow);
line-height: 1.5;
white-space: pre-wrap;
word-wrap: break-word;
}
.msg.user .bubble {
background: linear-gradient(135deg, rgba(124,92,255,0.15), rgba(0,212,255,0.15));
border-color: transparent;
}
.bubble .meta {
font-size: 12px;
color: var(--muted);
margin-bottom: 6px;
}
.bubble .content { min-height: 20px; }
.bubble pre {
background: rgba(0,0,0,0.35);
padding: 10px 12px;
border-radius: 10px;
overflow-x: auto;
border: 1px solid rgba(255,255,255,0.12);
}
.bubble code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
background: rgba(255,255,255,0.08);
padding: 2px 6px;
border-radius: 6px;
border: 1px solid rgba(255,255,255,0.1);
}
.typing {
display: inline-flex;
gap: 4px;
align-items: center;
}
.dot {
width: 6px;
height: 6px;
background: var(--muted);
border-radius: 50%;
opacity: 0.7;
animation: blink 1.2s infinite ease-in-out;
}
.dot:nth-child(2) { animation-delay: 0.15s; }
.dot:nth-child(3) { animation-delay: 0.3s; }
@keyframes blink {
0%, 80%, 100% { transform: translateY(0); opacity: 0.7; }
40% { transform: translateY(-3px); opacity: 1; }
}
/* Composer */
.composer {
position: sticky;
bottom: 0;
padding: 12px clamp(10px, 2vw, 24px) 16px;
background: linear-gradient(0deg, rgba(0,0,0,0.25), rgba(0,0,0,0));
backdrop-filter: blur(8px);
}
.composer-inner {
margin: 0 auto;
max-width: min(980px, 95vw);
background: var(--panel);
border: 1px solid var(--panel-strong);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
padding: 10px;
display: grid;
grid-template-columns: 1fr auto;
gap: 8px;
backdrop-filter: blur(var(--blur));
}
.input-wrap {
display: flex;
align-items: flex-end;
gap: 8px;
padding: 6px;
background: var(--glass);
border: 1px solid var(--panel-strong);
border-radius: 12px;
}
textarea {
width: 100%;
resize: none;
border: none;
outline: none;
background: transparent;
color: var(--text);
font: inherit;
line-height: 1.4;
max-height: 180px;
padding: 8px;
}
.send {
display: flex;
align-items: center;
gap: 8px;
}
.hint {
margin-top: 6px;
color: var(--muted);
font-size: 12px;
display: flex;
align-items: center;
gap: 10px;
justify-content: space-between;
padding: 0 4px;
flex-wrap: wrap;
}
.kbd {
border: 1px solid var(--panel-strong);
background: var(--panel);
padding: 2px 6px;
border-radius: 6px;
font-size: 12px;
color: var(--muted);
}
/* Utility */
.row { display: flex; align-items: center; gap: 10px; }
.spacer { flex: 1; }
/* Responsive tweaks */
@media (max-width: 700px) {
.brand h1 { font-size: 16px; }
.brand small { display: none; }
.msg { max-width: 96vw; }
.composer-inner { grid-template-columns: 1fr; }
.send { justify-content: flex-end; }
}
</style>
</head>
<body>
<div class="app" id="app">
<header class="header">
<div class="brand" title="AnyCoder Chat">
<div class="logo">AI</div>
<div>
<h1>AnyCoder Chat</h1>
<small>Your friendly AI assistant</small>
</div>
</div>
<div class="header-actions">
<button class="btn small ghost" id="newChatBtn" title="Start a new chat">
<svg class="icon" viewBox="0 0 24 24" fill="none"><path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
New
</button>
<button class="btn small ghost" id="exportBtn" title="Export chat as JSON">
<svg class="icon" viewBox="0 0 24 24" fill="none"><path d="M12 16V4m0 0l-4 4m4-4l4 4M4 20h16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
Export
</button>
<button class="btn small ghost" id="themeBtn" title="Toggle theme">
<svg class="icon" id="themeIcon" viewBox="0 0 24 24" fill="none"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
Theme
</button>
<a class="btn small" href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" rel="noopener noreferrer" title="Visit AnyCoder on Hugging Face">
<svg class="icon" viewBox="0 0 24 24" fill="none"><path d="M14 3h7v7M10 14L21 3M21 14v7H3V3h7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
Built with anycoder
</a>
</div>
</header>
<main class="chat" id="chat">
<div class="messages" id="messages"></div>
<div class="empty" id="empty">
<div class="empty-inner">
<h2 style="margin:0 0 8px 0">Hi! I’m AnyCoder Chat 👋</h2>
<p style="margin:0;color:var(--muted)">Ask me to explain code, brainstorm ideas, draft emails, or just chat. I can format responses with Markdown, code blocks, and links.</p>
<div class="chips" id="chips">
<div class="chip">Explain closures in JavaScript</div>
<div class="chip">Help me write a Python script</div>
<div class="chip">Summarize: "Effective Remote Work"</div>
<div class="chip">Brainstorm app features</div>
<div class="chip">Draft a polite email</div>
</div>
</div>
</div>
</main>
<footer class="composer">
<div class="composer-inner">
<div class="input-wrap">
<textarea id="input" rows="1" placeholder="Message AnyCoder..."></textarea>
</div>
<div class="send">
<div class="row">
<label for="speed" style="font-size:12px;color:var(--muted);display:flex;align-items:center;gap:6px">
Speed
<input id="speed" type="range" min="0" max="50" value="10" />
</label>
<button class="btn ghost small" id="stopBtn" title="Stop generating" style="display:none">
<svg class="icon" viewBox="0 0 24 24" fill="none"><rect x="6" y="6" width="12" height="12" rx="2" stroke="currentColor" stroke-width="2"/></svg>
Stop
</button>
</div>
<button class="btn primary" id="sendBtn" title="Send message">
<svg class="icon" viewBox="0 0 24 24" fill="none"><path d="M22 2L11 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M22 2l-7 20-4-9-9-4 20-7z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
Send
</button>
</div>
</div>
<div class="hint">
<div class="row" style="gap:6px">
<span class="kbd">Enter</span> to send • <span class="kbd">Shift + Enter</span> for newline • Ask for code, summaries, or explanations
</div>
<div class="spacer"></div>
<div>Local demo chatbot — no external API required</div>
</div>
</footer>
</div>
<script>
// Utility: local storage wrapper
const store = {
get(key, fallback) { try { return JSON.parse(localStorage.getItem(key)) ?? fallback; } catch { return fallback; } },
set(key, value) { localStorage.setItem(key, JSON.stringify(value)); },
remove(key) { localStorage.removeItem(key); }
};
// Utility: escape HTML
const escapeHtml = (str) => str
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
// Utility: minimal markdown renderer (safe-ish)
function renderMarkdown(md) {
if (!md) return "";
// Extract code blocks first to avoid double-processing inside them
const codeBlocks = [];
md = md.replace(/```([\s\S]*?)```/g, (_, code) => {
const placeholder = `@@CODEBLOCK_${codeBlocks.length}@@`;
codeBlocks.push(code);
return placeholder;
});
// Escape HTML
md = escapeHtml(md);
// Restore code blocks with <pre><code>
codeBlocks.forEach((code, i) => {
const safe = escapeHtml(code);
md = md.replace(`@@CODEBLOCK_${i}@@`, `<pre><code>${safe}</code></pre>`);
});
// Inline code: `code`
md = md.replace(/`([^`]+)`/g, `<code>$1</code>`);
// Bold: **text**
md = md.replace(/\*\*([^*]+)\*\*/g, `<strong>$1</strong>`);
// Italic: *text* (avoid overlapping with bold)
md = md.replace(/(^|[^*])\*([^*\n]+)\*/g, '$1<em>$2</em>');
// Links: [text](url)
md = md.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, `<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>`);
// Line breaks -> paragraphs
md = md.split(/\n{2,}/).map(chunk => `<p>${chunk.replace(/\n/g, "<br>")}</p>`).join("");
return md;
}
// Utility: simple UUID
const uid = () => (crypto?.randomUUID?.() || `id_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`);
// Elements
const els = {
app: document.getElementById('app'),
messages: document.getElementById('messages'),
empty: document.getElementById('empty'),
chips: document.getElementById('chips'),
input: document.getElementById('input'),
sendBtn: document.getElementById('sendBtn'),
stopBtn: document.getElementById('stopBtn'),
newChatBtn: document.getElementById('newChatBtn'),
exportBtn: document.getElementById('exportBtn'),
themeBtn: document.getElementById('themeBtn'),
themeIcon: document.getElementById('themeIcon'),
speed: document.getElementById('speed'),
chat: document.getElementById('chat')
};
// Theme
function applyTheme(theme) {
if (theme === 'light') document.body.classList.add('theme-light');
else document.body.classList.remove('theme-light');
els.themeIcon.innerHTML = theme === 'light'
? '<path d="M12 3v2m0 14v2m9-9h-2M5 12H3m14.95 6.95l-1.414-1.414M7.464 7.464L6.05 6.05m11.314 0l-1.414 1.414M7.464 16.536l-1.414 1.414" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle cx="12" cy="12" r="4" stroke="currentColor" stroke-width="2"/>'
: '<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>';
}
const savedTheme = store.get('anycoder_theme', 'dark');
applyTheme(savedTheme);
els.themeBtn.addEventListener('click', () => {
const next = document.body.classList.contains('theme-light') ? 'dark' : 'light';
store.set('anycoder_theme', next);
applyTheme(next);
});
// Chat state
const ChatState = {
data: store.get('anycoder_chat', []),
get isEmpty() { return this.data.length === 0; },
save() { store.set('anycoder_chat', this.data); }
};
// Renderers
function messageElement(msg, { streaming = false } = {}) {
const isUser = msg.role === 'user';
const el = document.createElement('div');
el.className = `msg ${isUser ? 'user' : 'ai'}`;
el.dataset.id = msg.id;
const avatar = document.createElement('div');
avatar.className = `avatar ${isUser ? '' : 'ai'}`;
avatar.innerHTML = isUser
? '<svg class="icon" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="8" r="4" stroke="currentColor" stroke-width="2"/><path d="M4 20c0-4 4-6 8-6s8 2 8 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>'
: '<svg class="icon" viewBox="0 0 24 24" fill="none"><path d="M12 3l8 4v5c0 5-3.5 9-8 9S4 17 4 12V7l8-4z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/><path d="M9 12l2 2 4-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
const bubble = document.createElement('div');
bubble.className = 'bubble';
const meta = document.createElement('div');
meta.className = 'meta';
meta.textContent = isUser ? 'You' : 'AnyCoder';
const content = document.createElement('div');
content.className = 'content';
if (isUser) {
content.textContent = msg.content;
} else {
content.innerHTML = renderMarkdown(msg.content);
}
bubble.appendChild(meta);
bubble.appendChild(content);
if (isUser) {
// user: [avatar right]
el.appendChild(bubble);
el.appendChild(avatar);
} else {
el.appendChild(avatar);
el.appendChild(bubble);
}
// Typing indicator if streaming
if (!isUser && streaming) {
const typing = document.createElement('div');
typing.className = 'meta';
typing.style.marginTop = '6px';
typing.innerHTML = '<span class="typing"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span>';
bubble.appendChild(typing);
}
return el;
}
function scrollToBottom() {
els.messages.scrollTop = els.messages.scrollHeight;
}
function refreshEmpty() {
els.empty.style.display = ChatState.isEmpty ? 'grid' : 'none';
}
function renderAll() {
els.messages.innerHTML = '';
ChatState.data.forEach(msg => {
els.messages.appendChild(messageElement(msg));
});
refreshEmpty();
scrollToBottom();
}
// AI Engine (local, no external APIs)
const AI = {
abort: null,
async respond(prompt, opts = {}) {
// Streaming: return a controller with write/close
const controller = {
write: (chunk) => {},
close: () => {}
};
const text = buildResponse(prompt);
const speed = Number(els.speed.value) || 10; // 0..50 (higher is faster)
const minDelay = 5; // min ms per chunk
const maxDelay = 60; // max ms per chunk
// Map speed: 0 slow -> maxDelay, 50 fast -> minDelay
const delay = speed === 0 ? 30 : Math.max(minDelay, maxDelay - speed);
const chunkSize = Math.max(2, Math.floor(60 - speed)); // chars per chunk
let i = 0;
const tempMsgId = uid();
const tempMsg = { id: tempMsgId, role: 'assistant', content: '' };
ChatState.data.push(tempMsg);
ChatState.save();
const el = messageElement(tempMsg, { streaming: true });
els.messages.appendChild(el);
refreshEmpty();
scrollToBottom();
const contentEl = el.querySelector('.content');
function writeNext() {
const end = Math.min(text.length, i + chunkSize);
const chunk = text.slice(i, end);
i = end;
// append chunk as plain text, then re-render markdown for preview? To keep it simple, we'll append plain safe HTML.
// But we want markdown; accumulate into tempMsg.content and re-render.
tempMsg.content = text.slice(0, i);
contentEl.innerHTML = renderMarkdown(tempMsg.content);
scrollToBottom();
if (i < text.length) {
AI.abort = setTimeout(writeNext, delay);
} else {
controller.close?.();
}
}
AI.abort = setTimeout(writeNext, delay);
controller.close = () => {
if (AI.abort) clearTimeout(AI.abort);
// Ensure final render
tempMsg.content = text;
contentEl.innerHTML = renderMarkdown(tempMsg.content);
// Remove typing indicator
const t = el.querySelector('.meta:last-child');
if (t && t.querySelector('.typing')) t.remove();
ChatState.save();
AI.abort = null;
};
return controller;
},
stop() {
if (AI.abort) clearTimeout(AI.abort);
AI.abort = null;
}
};
function buildResponse(prompt) {
const p = prompt.trim();
const lower = p.toLowerCase();
// Commands
if (/^(\/help|\/h)$/.test(lower)) {
return [
"Here are some things you can try:",
"",
"- Ask for explanations: “Explain closures in JavaScript”",
"- Get code help: “Write a Python script to download files”",
"- Brainstorm: “Ideas for a habit tracker app”",
"- Draft text: “Write a polite email to reschedule a meeting”",
"- Summaries: “Summarize the key points of [topic]”",
"",
"Tips:",
"- Shift+Enter for a newline",
"- Use backticks for inline code and triple backticks for code blocks",
"- Use **bold** and *italic*",
""
].join("\n");
}
if (/^(\/clear|\/c)$/.test(lower)) {
ChatState.data = [];
ChatState.save();
renderAll();
return "Cleared the chat.";
}
if (/^(\/time|\/date)$/.test(lower)) {
const now = new Date();
return `Current date and time: ${now.toLocaleString()}`;
}
if (/^(\/joke)$/.test(lower)) {
return joke();
}
// Heuristic responses
if (lower.includes('weather')) {
return "I don't have live weather data in this demo, but here's what I'd do: " +
"I'd call a weather API (like OpenWeatherMap) with your location, parse the JSON, and display conditions. " +
"You can also show hourly and daily forecasts with charts.";
}
if (lower.includes('remind')) {
return "Reminders aren't persisted in this offline demo, but you could store them in localStorage or IndexedDB. " +
"For scheduling notifications, use the Notification API and setTimeout or a service worker.";
}
if (lower.includes('password') || lower.includes('random')) {
return generatePassword();
}
if (lower.includes('explain') && lower.includes('closure')) {
return "A closure is a function paired with the lexical environment in which it was declared. " +
"It lets the inner function remember variables from the outer scope even after the outer function has finished executing.\n\n" +
"Example:\n```js\nfunction counter() {\n let n = 0;\n return () => ++n;\n}\nconst inc = counter();\ninc(); // 1\ninc(); // 2\n```\nHere, the returned function closes over `n`, keeping it alive between calls.";
}
if (lower.includes('write') && (lower.includes('python') || lower.includes('script'))) {
return `Here's a tiny Python script to download a file:\n\`\`\`python\nimport requests\n\nurl = "https://example.com/file.pdf"\nresp = requests.get(url)\nwith open("file.pdf", "wb") as f:\n f.write(resp.content)\nprint("Saved to file.pdf")\n\`\`\`\nTip: Use tqdm for progress bars and pathlib for paths.`;
}
if (lower.includes('brainstorm') || lower.includes('ideas')) {
return "Let’s brainstorm features for a habit tracker app:\n" +
"- Daily streaks with visual badges\n" +
"- Habit templates and categories\n" +
"- Reminders with the Notification API\n" +
"- Calendar heatmap view\n" +
"- Sync via a backend (or locally with IndexedDB)\n" +
"- Smart suggestions based on your schedule\n" +
"- Gamification: points, levels, challenges";
}
if (lower.includes('email') || lower.includes('draft')) {
return "Draft email example:\n\n" +
"Subject: Rescheduling our meeting\n\n" +
"Hi [Name],\n\n" +
"I hope you’re doing well. Due to an unexpected conflict, I need to reschedule our meeting originally planned for [Day, Time]. " +
"Would [Alternative Day, Time] work for you? I’m happy to adjust.\n\n" +
"Thanks for your understanding!\n\n" +
"Best,\n[Your Name]";
}
if (lower.includes('summarize') || lower.includes('summary')) {
return "To summarize effectively:\n" +
"1) Identify the thesis or main claim\n" +
"2) Extract key arguments and evidence\n" +
"3) Note counterpoints if relevant\n" +
"4) Conclude with implications or takeaways\n\n" +
"For long texts, chunk by sections and summarize each, then combine.";
}
// Fallback helpful response
const tips = [
"Here are some tips to get better results:",
"- Be specific about what you want (goal, constraints, examples)",
"- Mention your audience or tone (e.g., beginner-friendly)",
"- Provide sample input/output formats",
"- For code, specify language and error messages (if any)"
];
const examples = [
"",
"Examples you can try:",
"- Explain closures in JavaScript with a small example",
"- Write a Python script to download files with a progress bar",
"- Brainstorm app features for a habit tracker",
"- Draft a polite email to reschedule a meeting",
""
].join("\n");
const fallback = [
`You said: “${prompt}”`,
"",
"I can help with coding, explanations, brainstorming, drafting, and more. I support Markdown, code blocks, and links.",
tips.join("\n"),
examples
].join("\n");
return fallback;
}
function joke() {
const jokes = [
"Why do programmers prefer dark mode? Because light attracts bugs.",
"There are 10 kinds of people: those who understand binary and those who don’t.",
"A SQL query walks into a bar, walks up to two tables and asks: 'Can I join you?'",
"To understand recursion, you must first understand recursion."
];
return jokes[Math.floor(Math.random() * jokes.length)];
}
function generatePassword() {
const len = 16;
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%^&*";
let out = "";
const cryptoObj = window.crypto || window.msCrypto;
if (cryptoObj?.getRandomValues) {
const arr = new Uint32Array(len);
cryptoObj.getRandomValues(arr);
for (let i = 0; i < len; i++) {
out += chars[arr[i] % chars.length];
}
} else {
for (let i = 0; i < len; i++) {
out += chars[Math.floor(Math.random() * chars.length)];
}
}
return `Generated password (length ${len}):\n\`${out}\`\nTip: Use a password manager and enable 2FA.`;
}
// Actions
async function sendMessage() {
const text = els.input.value.trim();
if (!text) return;
// Add user message
const userMsg = { id: uid(), role: 'user', content: text, ts: Date.now() };
ChatState.data.push(userMsg);
ChatState.save();
els.messages.appendChild(messageElement(userMsg));
refreshEmpty();
scrollToBottom();
// Clear input
els.input.value = "";
autoResizeTextarea();
els.input.focus();
// Generate AI response
els.sendBtn.disabled = true;
els.stopBtn.style.display = 'inline-flex';
const controller = await AI.respond(text);
els.stopBtn.onclick = () => {
AI.stop();
// finalize message
controller.close?.();
els.stopBtn.style.display = 'none';
els.sendBtn.disabled = false;
};
// When streaming ends
const waitEnd = () => new Promise(resolve => {
const check = () => {
if (!AI.abort) resolve();
else setTimeout(check, 80);
};
check();
});
await waitEnd();
els.stopBtn.style.display = 'none';
els.sendBtn.disabled = false;
}
function newChat() {
ChatState.data = [];
ChatState.save();
renderAll();
}
function exportChat() {
const blob = new Blob([JSON.stringify(ChatState.data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `anycoder_chat_${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}
// Input behaviors
function autoResizeTextarea() {
const ta = els.input;
ta.style.height = 'auto';
ta.style.height = Math.min(180, ta.scrollHeight) + 'px';
}
els.input.addEventListener('input', autoResizeTextarea);
els.input.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
els.sendBtn.addEventListener('click', sendMessage);
els.newChatBtn.addEventListener('click', newChat);
els.exportBtn.addEventListener('click', exportChat);
// Chips
els.chips.addEventListener('click', (e) => {
const chip = e.target.closest('.chip');
if (!chip) return;
els.input.value = chip.textContent.trim();
autoResizeTextarea();
els.input.focus();
});
// Stop generation when switching tabs (saves CPU)
document.addEventListener('visibilitychange', () => {
if (document.hidden) AI.stop();
});
// Initialize
renderAll();
autoResizeTextarea();
// Preload a welcome message if empty
if (ChatState.isEmpty) {
const welcome = { id: uid(), role: 'assistant', content:
`Welcome! I’m your built‑in demo assistant.
- Ask me to explain code, brainstorm ideas, draft text, or summarize topics.
- Use Markdown: **bold**, *italic*, \`inline code\`, and triple backticks for code blocks.
- Try: /help, /time, /joke, /clear`};
ChatState.data.push(welcome);
ChatState.save();
renderAll();
}
</script>
</body>
</html>