anycoder-ce7b6184 / index.html
AiCoderv2's picture
Upload folder using huggingface_hub
70a8ce7 verified
raw
history blame
29.2 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);
}
/* Error styling */
.error {
border: 1px solid var(--danger) !important;
background: rgba(255, 92, 124, 0.1) !important;
}
.error-message {
color: var(--danger);
font-size: 12px;
margin-top: 4px;
}
/* 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>Powered by Poe.com API</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 powered by Claude on Poe.com 👋</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. Connected to the claude-haiku-cheap model.</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">
<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 • Connected to Poe.com API
</div>
<div class="spacer"></div>
<div>Using claude-haiku-cheap model via Poe.com</div>
</div>
</footer>
</div>
<script>
// Configuration
const POE_API_KEY = '5AvcWZHjdjrxOuRodUjyTZ2TE_D0tdrN-XantWcBY4E';
const POE_BASE_URL = 'https://api.poe.com/v1';
const MODEL_NAME = 'claude-haiku-cheap';
// 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'),
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' : 'Claude (via Poe.com)';
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();
}
// Poe.com API integration
const PoeAPI = {
abortController: null,
async chatCompletion(messages) {
this.abortController = new AbortController();
try {
const response = await fetch(`${POE_BASE_URL}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${POE_API_KEY}`
},
body: JSON.stringify({
model: MODEL_NAME,
messages: messages,
stream: true,
temperature: 0.7,
max_tokens: 2000
}),
signal: this.abortController.signal
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error?.message || `API request failed with status ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let fullContent = '';
const processChunk = async () => {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) continue;
if (trimmed.startsWith('data: ')) {
const data = trimmed.slice(6);
if (data === '[DONE]') {
return { content: fullContent, done: true };
}
try {
const parsed = JSON.parse(data);
if (parsed.choices?.[0]?.delta?.content) {
fullContent += parsed.choices[0].delta.content;
yield { content: fullContent, done: false };
}
} catch (e) {
// Ignore JSON parse errors for incomplete chunks
}
}
}
}
return { content: fullContent, done: true };
};
return {
[Symbol.asyncIterator]: async function* () {
for await (const chunk of processChunk()) {
yield chunk;
}
}
};
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Request was cancelled');
}
throw error;
}
},
stop() {
if (this.abortController) {
this.abortController.abort();
this.abortController = null;
}
}
};
// Actions
async function sendMessage() {
const text = els.input.value.trim();
if (!text) return;
// Clear any previous errors
els.input.classList.remove('error');
const existingError = els.input.parentNode.querySelector('.error-message');
if (existingError) existingError.remove();
// 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';
// Create AI message placeholder
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');
try {
// Prepare messages for API (last 10 messages to avoid token limits)
const apiMessages = ChatState.data.slice(-10).map(msg => ({
role: msg.role,
content: msg.content
}));
// Get streaming response
const stream = await PoeAPI.chatCompletion(apiMessages);
let finalContent = '';
for await (const chunk of stream) {
finalContent = chunk.content;
contentEl.innerHTML = renderMarkdown(finalContent);
scrollToBottom();
}
// Final update
tempMsg.content = finalContent;
contentEl.innerHTML = renderMarkdown(finalContent);
// Remove typing indicator
const typingIndicator = el.querySelector('.meta:last-child .typing');
if (typingIndicator) {
typingIndicator.parentNode.remove();
}
ChatState.save();
} catch (error) {
console.error('API Error:', error);
// Show error message
tempMsg.content = `❌ **Error**: ${error.message}\n\nPlease check your internet connection and try again.`;
contentEl.innerHTML = renderMarkdown(tempMsg.content);
// Remove typing indicator
const typingIndicator = el.querySelector('.meta:last-child .typing');
if (typingIndicator) {
typingIndicator.parentNode.remove();
}
// Show visual error on input
els.input.classList.add('error');
const errorMsg = document.createElement('div');
errorMsg.className = 'error-message';
errorMsg.textContent = `API Error: ${error.message}`;
els.input.parentNode.appendChild(errorMsg);
ChatState.save();
} finally {
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);
// Stop button
els.stopBtn.addEventListener('click', () => {
PoeAPI.stop();
els.stopBtn.style.display = 'none';
els.sendBtn.disabled = false;
});
// 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 API calls)
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
PoeAPI.stop();
els.stopBtn.style.display = 'none';
els.sendBtn.disabled = false;
}
});
// Initialize
renderAll();
autoResizeTextarea();
// Preload a welcome message if empty
if (ChatState.isEmpty) {
const welcome = {
id: uid(),
role: 'assistant',
content:
`Hello! I'm connected to the Poe.com API using the claude-haiku-cheap model.
I can help you with:
- Code explanations and programming help
- Writing and editing text
- Brainstorming ideas
- Answering questions
- And much more!
Just type your message and I'll respond in real-time. Use Markdown for formatting.`
};
ChatState.data.push(welcome);
ChatState.save();
renderAll();
}
</script>
</body>
</html>