Agent Template

Everyday AI Chatbot

A simple, polished chatbot for everyday questions, brainstorming, writing help, and quick explanations.

chatbot assistant general-purpose conversation

Preview Mode

This is a preview with sample data. The template uses placeholders like which will be replaced with actual agent data.

About This Template

Everyday AI Chatbot is a browser-executable AI agent template built on AgentOp. It runs entirely in the browser using Python (via Pyodide) and can be deployed without a server — just download the generated HTML file and open it locally or host it anywhere.

Topics chatbot assistant general-purpose conversation
Template Preview

Template Metadata

Slug
everyday-ai-chatbot
Created By
[email protected]
Created
Sep 21, 2026
Usage Count
1

Tags

chatbot assistant general-purpose conversation

Code Statistics

HTML Lines
76
CSS Lines
294
JS Lines
221
Python Lines
12

Source Code

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>{{ agent_name }}</title>
  <style>{{ css_code }}</style>
</head>
<body>
  <div class="app-shell">
    <header class="hero">
      <div>
        <div class="eyebrow">💬 Everyday Chat</div>
        <h1>{{ agent_name }}</h1>
        <p class="subtitle">{{ description }}</p>
      </div>
      <div class="status-wrap">
        <span id="status-badge" class="status-badge">Initializing…</span>
      </div>
    </header>

    <main class="chat-layout">
      <section class="chat-panel">
        <div id="results-container" class="messages" aria-live="polite"></div>

        <div id="empty-state" class="empty-state">
          <div class="empty-card">
            <h2>💡 Start a conversation</h2>
            <p>Use this chatbot for everyday thinking and writing tasks.</p>
            <ul>
              <li>✍️ Draft emails, messages, and outlines</li>
              <li>🧠 Brainstorm ideas and solve problems</li>
              <li>📚 Get simple explanations and summaries</li>
              <li>🔎 Compare options and think through decisions</li>
            </ul>
          </div>
        </div>
      </section>

      <aside class="sidebar">
        <div class="card">
          <h3>✨ Try a prompt</h3>
          <div class="prompt-list">
            <button class="chip prompt-chip" data-prompt="Help me write a friendly thank-you note after an interview">Interview thank-you note</button>
            <button class="chip prompt-chip" data-prompt="Explain photosynthesis like I am 12 years old">Explain photosynthesis simply</button>
            <button class="chip prompt-chip" data-prompt="Brainstorm weekend activities for a family with two kids">Family weekend ideas</button>
            <button class="chip prompt-chip" data-prompt="Give me a step-by-step plan to learn basic Python in 30 days">30-day Python learning plan</button>
          </div>
        </div>

        <div class="card">
          <h3>📝 Message composer</h3>
          <form id="chat-form" class="composer-form">
            <label for="user-input" class="label">Your chat message</label>
            <textarea id="user-input" rows="6" placeholder="Ask your chatbot for help with writing, ideas, explanations, or everyday questions..."></textarea>
            <div id="inline-error" class="inline-error hidden"></div>
            <div class="composer-actions">
              <button id="send-button" type="submit" class="primary-btn">Send message</button>
              <button id="clear-button" type="button" class="secondary-btn">Clear chat</button>
            </div>
          </form>
        </div>
      </aside>
    </main>
  </div>

  <div id="loading-overlay" class="loading-overlay hidden" aria-hidden="true">
    <div class="spinner-card">
      <div class="spinner"></div>
      <p>Thinking about your chat message…</p>
    </div>
  </div>

  <script>{{ js_code }}</script>
</body>
</html>
:root {
  --bg: #0b1020;
  --panel: #121933;
  --panel-2: #18213f;
  --surface: #f7f9fc;
  --text: #ebf0ff;
  --muted: #a9b6d3;
  --ink: #172033;
  --line: rgba(255,255,255,0.08);
  --accent: #7c9cff;
  --accent-2: #5eead4;
  --user: #26345f;
  --assistant: #f3f6ff;
  --error: #ff7b7b;
  --success: #86efac;
  --shadow: 0 18px 50px rgba(0,0,0,0.24);
  --radius: 18px;
  --radius-sm: 12px;
  --font: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; font-family: var(--font); background: linear-gradient(180deg, #0b1020 0%, #0e1530 100%); color: var(--text); }
body { min-height: 100vh; }
.hidden { display: none !important; }

.app-shell {
  max-width: 1440px;
  margin: 0 auto;
  padding: 24px;
}

.hero {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  align-items: flex-start;
  margin-bottom: 20px;
}

.eyebrow {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--accent-2);
  margin-bottom: 8px;
}

.hero h1 {
  margin: 0 0 8px;
  font-size: clamp(28px, 4vw, 44px);
}

.subtitle {
  margin: 0;
  color: var(--muted);
  max-width: 760px;
  line-height: 1.5;
}

.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: rgba(255,255,255,0.08);
  border: 1px solid var(--line);
  color: var(--text);
  border-radius: 999px;
  padding: 10px 14px;
  font-size: 14px;
}

.chat-layout {
  display: grid;
  grid-template-columns: 1.6fr 0.9fr;
  gap: 20px;
  min-height: 72vh;
}

.chat-panel, .card {
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

.chat-panel {
  position: relative;
  overflow: hidden;
  min-height: 640px;
  display: flex;
  flex-direction: column;
}

.messages {
  padding: 22px;
  flex: 1;
  overflow-y: auto;
}

.message {
  margin-bottom: 16px;
  display: flex;
}

.message.user { justify-content: flex-end; }
.message.assistant, .message.system { justify-content: flex-start; }

.bubble {
  max-width: min(82%, 820px);
  padding: 14px 16px;
  border-radius: 18px;
  line-height: 1.55;
  white-space: pre-wrap;
  word-break: break-word;
}

.message.user .bubble {
  background: var(--user);
  color: var(--text);
  border-bottom-right-radius: 6px;
}

.message.assistant .bubble, .message.system .bubble {
  background: var(--assistant);
  color: var(--ink);
  border-bottom-left-radius: 6px;
}

.empty-state {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  pointer-events: none;
}

.empty-card {
  max-width: 560px;
  background: rgba(255,255,255,0.05);
  border: 1px dashed rgba(255,255,255,0.18);
  border-radius: var(--radius);
  padding: 28px;
}

.empty-card h2 { margin-top: 0; }
.empty-card p, .empty-card li { color: var(--muted); line-height: 1.6; }

.sidebar {
  display: grid;
  gap: 20px;
  align-content: start;
}

.card {
  padding: 18px;
}

.card h3 {
  margin-top: 0;
  margin-bottom: 14px;
}

.prompt-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.chip {
  border: 1px solid rgba(124,156,255,0.25);
  background: rgba(124,156,255,0.12);
  color: var(--text);
  padding: 10px 12px;
  border-radius: 999px;
  cursor: pointer;
  font: inherit;
}

.label {
  display: block;
  margin-bottom: 8px;
  color: var(--muted);
  font-size: 14px;
}

textarea {
  width: 100%;
  resize: vertical;
  min-height: 140px;
  background: rgba(255,255,255,0.06);
  color: var(--text);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: var(--radius-sm);
  padding: 14px;
  font: inherit;
}

textarea::placeholder { color: #9ca8c4; }

.composer-actions {
  display: flex;
  gap: 12px;
  margin-top: 14px;
}

.primary-btn, .secondary-btn {
  border: none;
  border-radius: 12px;
  padding: 12px 16px;
  font: inherit;
  cursor: pointer;
  transition: transform 0.15s ease, opacity 0.15s ease, background 0.15s ease;
}

.primary-btn {
  background: linear-gradient(135deg, var(--accent), #9b8cff);
  color: white;
  font-weight: 600;
}

.secondary-btn {
  background: rgba(255,255,255,0.08);
  color: var(--text);
  border: 1px solid var(--line);
}

.primary-btn:hover, .secondary-btn:hover, .chip:hover { transform: translateY(-1px); }
.primary-btn:disabled, .secondary-btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
}

.inline-error {
  margin-top: 10px;
  color: var(--error);
  font-size: 14px;
}

.loading-overlay {
  position: fixed;
  inset: 0;
  background: rgba(5,10,20,0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 20;
}

.spinner-card {
  background: #10182d;
  color: var(--text);
  padding: 22px 24px;
  border-radius: 16px;
  border: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: 14px;
}

.spinner {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 3px solid rgba(255,255,255,0.18);
  border-top-color: var(--accent-2);
  animation: spin 0.9s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

button:focus-visible, textarea:focus-visible {
  outline: 3px solid rgba(94,234,212,0.45);
  outline-offset: 2px;
}

@media (max-width: 1200px) {
  .chat-layout {
    grid-template-columns: 1fr;
  }
  .chat-panel {
    min-height: 520px;
  }
}

@media (max-width: 768px) {
  .app-shell { padding: 16px; }
  .hero { flex-direction: column; }
  .composer-actions { flex-direction: column; }
  .bubble { max-width: 92%; }
}
function escapeHtml(str) {
  return String(str)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

const state = {
  ready: false,
  processing: false
};

function isLocalProvider() {
  return String(window.PROVIDER || '').toLowerCase() === 'local';
}

function modelLoaded() {
  if (!isLocalProvider()) return true;
  return !!(window.agentManager && window.agentManager.isLoaded);
}

function setStatus(text) {
  const badge = document.getElementById('status-badge');
  if (badge) badge.textContent = text;
}

function showError(msg) {
  const el = document.getElementById('inline-error');
  el.textContent = msg;
  el.classList.remove('hidden');
}

function clearError() {
  const el = document.getElementById('inline-error');
  el.textContent = '';
  el.classList.add('hidden');
}

function setProcessing(on) {
  state.processing = on;
  const overlay = document.getElementById('loading-overlay');
  const sendBtn = document.getElementById('send-button');
  const clearBtn = document.getElementById('clear-button');
  const input = document.getElementById('user-input');
  if (overlay) overlay.classList.toggle('hidden', !on);
  if (sendBtn) sendBtn.disabled = on || !state.ready || !modelLoaded();
  if (clearBtn) clearBtn.disabled = on;
  if (input) input.disabled = on;
}

function updateReadyState() {
  if (!window.pyodideReady) {
    state.ready = false;
    setStatus('Initializing…');
  } else if (isLocalProvider() && !modelLoaded()) {
    state.ready = false;
    setStatus('Load a local model from the top bar');
  } else {
    state.ready = true;
    setStatus('Ready to chat');
  }
  setProcessing(false);
}

function hideEmptyStateIfNeeded() {
  const container = document.getElementById('results-container');
  const empty = document.getElementById('empty-state');
  if (!container || !empty) return;
  empty.classList.toggle('hidden', container.children.length > 0);
}

window.addMessage = function(type, content) {
  const container = document.getElementById('results-container');
  if (!container) return;

  hideEmptyStateIfNeeded();

  let last = container.lastElementChild;
  if (last && last.dataset && last.dataset.streaming === 'true' && last.dataset.role === type) {
    const bubble = last.querySelector('.bubble');
    if (bubble) {
      bubble.innerHTML = escapeHtml(content).replace(/\n/g, '<br>');
      container.scrollTop = container.scrollHeight;
      return;
    }
  }

  const row = document.createElement('div');
  row.className = 'message ' + (type === 'user' ? 'user' : 'assistant');
  row.dataset.role = type;
  row.dataset.streaming = 'true';

  const bubble = document.createElement('div');
  bubble.className = 'bubble';
  bubble.innerHTML = escapeHtml(content).replace(/\n/g, '<br>');

  row.appendChild(bubble);
  container.appendChild(row);
  container.scrollTop = container.scrollHeight;
};

function finalizeLastAssistantMessage() {
  const container = document.getElementById('results-container');
  if (!container || !container.lastElementChild) return;
  container.lastElementChild.dataset.streaming = 'false';
}

function appendStaticMessage(type, content) {
  const container = document.getElementById('results-container');
  if (!container) return;
  hideEmptyStateIfNeeded();
  const row = document.createElement('div');
  row.className = 'message ' + (type === 'user' ? 'user' : 'assistant');
  row.dataset.role = type;
  row.dataset.streaming = 'false';
  const bubble = document.createElement('div');
  bubble.className = 'bubble';
  bubble.innerHTML = escapeHtml(content).replace(/\n/g, '<br>');
  row.appendChild(bubble);
  container.appendChild(row);
  container.scrollTop = container.scrollHeight;
}

async function handleSendMessage() {
  clearError();
  const input = document.getElementById('user-input');
  const raw = input.value;
  const message = raw.trim();

  if (!message) {
    showError('Please enter a chat message before sending.');
    return;
  }

  if (!window.pyodideReady) {
    showError('The chatbot is still initializing. Please wait a moment.');
    updateReadyState();
    return;
  }

  if (isLocalProvider() && !modelLoaded()) {
    showError('Please load a local model from the top bar before chatting.');
    updateReadyState();
    return;
  }

  appendStaticMessage('user', message);
  input.value = '';
  setProcessing(true);

  try {
    window.pyodide.globals.set('user_query', message);
    const result = await window.pyodide.runPythonAsync('await process_user_query(user_query)');
    if (typeof result === 'string' && result.trim()) {
      appendStaticMessage('assistant', result);
    }
    finalizeLastAssistantMessage();
  } catch (err) {
    showError('Something went wrong while generating the chat reply. Please try again.');
    appendStaticMessage('assistant', 'Sorry — I hit an error while working on that message. Please try again.');
  } finally {
    setProcessing(false);
    hideEmptyStateIfNeeded();
  }
}

function handleClearChat() {
  clearError();
  const container = document.getElementById('results-container');
  if (container) container.innerHTML = '';
  hideEmptyStateIfNeeded();
  const input = document.getElementById('user-input');
  if (input) input.focus();
}

function attachEventHandlers() {
  const form = document.getElementById('chat-form');
  const clearBtn = document.getElementById('clear-button');
  const chips = document.querySelectorAll('.prompt-chip');

  form.addEventListener('submit', async function(e) {
    e.preventDefault();
    if (state.processing) return;
    await handleSendMessage();
  });

  clearBtn.addEventListener('click', function() {
    if (state.processing) return;
    handleClearChat();
  });

  chips.forEach(function(chip) {
    chip.addEventListener('click', function() {
      const prompt = chip.getAttribute('data-prompt') || '';
      const input = document.getElementById('user-input');
      input.value = prompt;
      input.focus();
      clearError();
    });
  });
}

function initUi() {
  attachEventHandlers();
  updateReadyState();
  hideEmptyStateIfNeeded();
}

if (window.pyodideReady) {
  initUi();
} else {
  document.addEventListener('pyodide-ready', function() {
    window.pyodideReady = true;
    updateReadyState();
  });
  window.addEventListener('load', initUi);
}

setInterval(updateReadyState, 1500);
async def process_user_query(query: str) -> str:
    """Generate a helpful chatbot reply.

    Args:
        query: The user's chat message.

    Returns:
        The assistant reply as plain text.
    """
    prompt = f"User message:\n{query.strip()}\n\nWrite a helpful reply."
    answer = await agentop_llm.generate(prompt, globals().get("TEMPLATE_SYSTEM_PROMPT", ""))
    return answer.strip()