Agent Template

Chill Dev Research Buddy

A friendly all-purpose coding and research chat agent that helps brainstorm, explain code, plan features, debug ideas, and do lightweight research-oriented reasoning in a relaxed conversational style.

coding research chat brainstorming debugging assistant
Hugagent Sep 06, 2026 1 use

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

Chill Dev Research Buddy 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 coding research chat brainstorming debugging assistant
Template Preview

Template Metadata

Slug
chill-dev-research-buddy
Created By
Hugagent
Created
Sep 06, 2026
Usage Count
1

Tags

coding research chat brainstorming debugging assistant

Code Statistics

HTML Lines
88
CSS Lines
337
JS Lines
207
Python Lines
54

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">
    <aside class="sidebar">
      <div class="brand-card">
        <div class="brand-emoji">🧠</div>
        <div>
          <h1>{{ agent_name }}</h1>
          <p>{{ description }}</p>
        </div>
      </div>

      <div class="panel">
        <h2>⚡ What I’m good at</h2>
        <ul class="feature-list">
          <li>💻 Write and explain code</li>
          <li>🔎 Think through research questions</li>
          <li>🛠️ Debug ideas and architecture</li>
          <li>😌 Keep it friendly and low-pressure</li>
        </ul>
      </div>

      <div class="panel">
        <h2>💬 Try one</h2>
        <div class="chip-list" id="example-chips">
          <button class="chip" type="button" data-prompt="Build a simple Python expense tracker CLI">Python expense tracker CLI</button>
          <button class="chip" type="button" data-prompt="Explain this JavaScript error and how to fix it">Explain a JavaScript error</button>
          <button class="chip" type="button" data-prompt="Help me research authentication options for a side project">Research auth options</button>
          <button class="chip" type="button" data-prompt="Turn my rough app idea into a build plan">Build plan from an idea</button>
        </div>
      </div>
    </aside>

    <main class="workspace">
      <section class="chat-panel">
        <div class="chat-header">
          <div>
            <h2>☕ Dev & Research Chat</h2>
            <p class="subtext" id="status-text">Initializing agent…</p>
          </div>
          <button id="clear-chat-btn" class="secondary-btn" type="button">Clear chat</button>
        </div>

        <div id="inline-error" class="inline-error hidden" role="alert"></div>

        <div id="results-container" class="messages" aria-live="polite">
          <div class="empty-state" id="empty-state">
            <div class="empty-emoji">🌿</div>
            <h3>Your chill coding desk is ready</h3>
            <p>Ask for code, debugging, comparisons, research summaries, or a step-by-step plan.</p>
            <ul>
              <li>💻 Generate code snippets and starter files</li>
              <li>🔍 Compare tools, libraries, and approaches</li>
              <li>🧭 Break big ideas into practical next steps</li>
              <li>🗣️ Keep the conversation natural and friendly</li>
            </ul>
          </div>
        </div>

        <form id="chat-form" class="composer">
          <label class="sr-only" for="user-input">Describe your coding or research task</label>
          <textarea id="user-input" rows="5" placeholder="Ask your dev buddy something chill… e.g. 'Help me build a small React to-do app with local storage' or 'Research the pros and cons of SQLite vs PostgreSQL for a small SaaS'" required></textarea>
          <div class="composer-actions">
            <div class="hint">Friendly, practical answers for coding, planning, and research.</div>
            <button id="send-btn" class="primary-btn" type="submit">Send</button>
          </div>
        </form>
      </section>
    </main>
  </div>

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

  <script>{{ js_code }}</script>
</body>
</html>
:root {
  --bg: #0b1020;
  --bg-soft: #121933;
  --panel: rgba(18, 25, 51, 0.88);
  --panel-2: rgba(24, 33, 67, 0.92);
  --text: #edf2ff;
  --muted: #aeb8d9;
  --accent: #7c9cff;
  --accent-2: #8ef0cf;
  --border: rgba(255,255,255,0.11);
  --danger: #ff8f8f;
  --shadow: 0 18px 50px rgba(0,0,0,0.28);
  --radius-xl: 24px;
  --radius-lg: 18px;
  --radius-md: 14px;
  --radius-sm: 10px;
  --font: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--font);
  background:
    radial-gradient(circle at top left, rgba(124,156,255,0.18), transparent 26%),
    radial-gradient(circle at top right, rgba(142,240,207,0.12), transparent 24%),
    linear-gradient(180deg, #0a0f1d 0%, #0d1326 100%);
  color: var(--text);
}

.hidden { display: none !important; }
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.app-shell {
  display: grid;
  grid-template-columns: 340px 1fr;
  gap: 20px;
  padding: 24px;
  min-height: 100vh;
}

.sidebar, .chat-panel, .panel, .brand-card {
  backdrop-filter: blur(10px);
}

.sidebar {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.brand-card, .panel, .chat-panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow);
}

.brand-card {
  padding: 20px;
  display: flex;
  gap: 14px;
  align-items: flex-start;
}

.brand-card h1 {
  margin: 0 0 8px;
  font-size: 1.35rem;
}

.brand-card p, .panel p, .subtext, .hint {
  margin: 0;
  color: var(--muted);
  line-height: 1.5;
}

.brand-emoji, .empty-emoji {
  width: 52px;
  height: 52px;
  display: grid;
  place-items: center;
  border-radius: 16px;
  background: linear-gradient(135deg, rgba(124,156,255,0.25), rgba(142,240,207,0.2));
  font-size: 1.6rem;
  flex-shrink: 0;
}

.panel {
  padding: 18px;
}

.panel h2, .chat-header h2 {
  margin: 0 0 12px;
  font-size: 1rem;
}

.feature-list {
  margin: 0;
  padding-left: 18px;
  color: var(--muted);
  line-height: 1.7;
}

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

.chip, .primary-btn, .secondary-btn {
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 10px 14px;
  font: inherit;
  cursor: pointer;
  transition: transform 0.15s ease, background 0.2s ease, border-color 0.2s ease, opacity 0.2s ease;
}

.chip {
  background: rgba(255,255,255,0.04);
  color: var(--text);
  text-align: left;
}

.chip:hover, .primary-btn:hover, .secondary-btn:hover {
  transform: translateY(-1px);
}

.workspace {
  min-width: 0;
}

.chat-panel {
  display: grid;
  grid-template-rows: auto auto 1fr auto;
  min-height: calc(100vh - 48px);
}

.chat-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  padding: 20px 20px 14px;
  border-bottom: 1px solid var(--border);
}

.inline-error {
  margin: 14px 20px 0;
  padding: 12px 14px;
  border-radius: var(--radius-md);
  border: 1px solid rgba(255,143,143,0.35);
  background: rgba(255,143,143,0.12);
  color: #ffd5d5;
}

.messages {
  padding: 20px;
  overflow: auto;
  min-height: 360px;
}

.empty-state {
  max-width: 720px;
  margin: 24px auto;
  padding: 28px;
  text-align: left;
  border-radius: var(--radius-lg);
  border: 1px dashed var(--border);
  background: rgba(255,255,255,0.03);
}

.empty-state h3 {
  margin: 14px 0 8px;
}

.empty-state p, .empty-state li {
  color: var(--muted);
  line-height: 1.6;
}

.message {
  max-width: 900px;
  margin-bottom: 16px;
  display: flex;
  gap: 12px;
}

.message-bubble {
  flex: 1;
  padding: 14px 16px;
  border-radius: 18px;
  border: 1px solid var(--border);
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.message.user .message-bubble {
  background: rgba(124,156,255,0.16);
}

.message.assistant .message-bubble {
  background: rgba(255,255,255,0.05);
}

.message-label {
  width: 34px;
  height: 34px;
  border-radius: 12px;
  display: grid;
  place-items: center;
  background: linear-gradient(135deg, rgba(124,156,255,0.24), rgba(142,240,207,0.18));
  border: 1px solid var(--border);
  flex-shrink: 0;
}

.composer {
  border-top: 1px solid var(--border);
  padding: 18px 20px 20px;
  display: grid;
  gap: 12px;
}

textarea {
  width: 100%;
  resize: vertical;
  min-height: 120px;
  max-height: 320px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: rgba(7, 12, 24, 0.7);
  color: var(--text);
  padding: 16px;
  font: inherit;
  line-height: 1.5;
}

textarea::placeholder { color: #8f9abb; }

.composer-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
}

.primary-btn {
  background: linear-gradient(135deg, var(--accent), #97aefe);
  color: #08101e;
  border-color: transparent;
  font-weight: 700;
}

.secondary-btn {
  background: rgba(255,255,255,0.04);
  color: var(--text);
}

button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
  transform: none;
}

button:focus-visible, textarea:focus-visible, .chip:focus-visible {
  outline: 3px solid rgba(142,240,207,0.42);
  outline-offset: 2px;
}

.loading-overlay {
  position: fixed;
  inset: 0;
  background: rgba(5, 9, 19, 0.58);
  display: grid;
  place-items: center;
  z-index: 40;
}

.spinner-card {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: 24px 28px;
  box-shadow: var(--shadow);
  text-align: center;
}

.spinner {
  width: 42px;
  height: 42px;
  margin: 0 auto 12px;
  border-radius: 50%;
  border: 4px solid rgba(255,255,255,0.16);
  border-top-color: var(--accent-2);
  animation: spin 0.9s linear infinite;
}

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

@media (max-width: 1200px) {
  .app-shell {
    grid-template-columns: 1fr;
  }
  .chat-panel {
    min-height: 75vh;
  }
}

@media (max-width: 768px) {
  .app-shell {
    padding: 14px;
    gap: 14px;
  }
  .chat-header, .composer, .messages {
    padding-left: 14px;
    padding-right: 14px;
  }
  .composer-actions {
    flex-direction: column;
    align-items: stretch;
  }
  .secondary-btn, .primary-btn {
    width: 100%;
  }
}
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
};

const els = {};

function cacheElements() {
  els.statusText = document.getElementById('status-text');
  els.inlineError = document.getElementById('inline-error');
  els.resultsContainer = document.getElementById('results-container');
  els.emptyState = document.getElementById('empty-state');
  els.chatForm = document.getElementById('chat-form');
  els.userInput = document.getElementById('user-input');
  els.sendBtn = document.getElementById('send-btn');
  els.clearChatBtn = document.getElementById('clear-chat-btn');
  els.loadingOverlay = document.getElementById('loading-overlay');
  els.exampleChips = Array.from(document.querySelectorAll('.chip'));
}

function showError(message) {
  els.inlineError.textContent = message;
  els.inlineError.classList.remove('hidden');
}

function clearError() {
  els.inlineError.textContent = '';
  els.inlineError.classList.add('hidden');
}

function setProcessing(isProcessing) {
  state.processing = isProcessing;
  els.sendBtn.disabled = isProcessing || !state.ready;
  els.clearChatBtn.disabled = isProcessing;
  els.userInput.disabled = isProcessing;
  els.exampleChips.forEach((chip) => { chip.disabled = isProcessing; });
  els.loadingOverlay.classList.toggle('hidden', !isProcessing);
}

function setStatus(message) {
  els.statusText.textContent = message;
}

function ensureEmptyStateHidden() {
  if (els.emptyState) {
    els.emptyState.classList.add('hidden');
  }
}

window.addMessage = function(type, content) {
  ensureEmptyStateHidden();

  const messageEl = document.createElement('div');
  messageEl.className = 'message ' + (type === 'user' ? 'user' : 'assistant');

  const labelEl = document.createElement('div');
  labelEl.className = 'message-label';
  labelEl.textContent = type === 'user' ? '🧑' : '🤖';

  const bubbleEl = document.createElement('div');
  bubbleEl.className = 'message-bubble';
  bubbleEl.innerHTML = escapeHtml(content || '');

  messageEl.appendChild(labelEl);
  messageEl.appendChild(bubbleEl);
  els.resultsContainer.appendChild(messageEl);
  els.resultsContainer.scrollTop = els.resultsContainer.scrollHeight;
};

async function checkReady() {
  const pyodideReady = !!window.pyodideReady;
  const localNeedsModel = window.PROVIDER === 'local';
  const localLoaded = !localNeedsModel || (window.agentManager && window.agentManager.isLoaded);

  if (!pyodideReady) {
    state.ready = false;
    setStatus('Initializing agent…');
    setProcessing(false);
    return false;
  }

  if (localNeedsModel && !localLoaded) {
    state.ready = false;
    setStatus('Load a local model from the top bar to start chatting.');
    setProcessing(false);
    return false;
  }

  state.ready = true;
  setStatus('Ready for coding, research, and chill chat.');
  setProcessing(false);
  return true;
}

async function sendQuery() {
  clearError();
  const query = els.userInput.value.trim();

  if (!query) {
    showError('Please enter a coding, research, or chat request first.');
    return;
  }

  const isReady = await checkReady();
  if (!isReady) {
    showError(window.PROVIDER === 'local'
      ? 'The agent is not ready yet. If you are using a local model, load it from the top bar first.'
      : 'The agent is still initializing. Please wait a moment and try again.');
    return;
  }

  window.addMessage('user', query);
  els.userInput.value = '';
  setProcessing(true);
  setStatus('Thinking…');

  try {
    window.pyodide.globals.set('user_query', query);
    const response = await window.pyodide.runPythonAsync('await process_user_query(user_query)');
    window.addMessage('assistant', String(response || ''));
    setStatus('Ready for the next idea.');
  } catch (error) {
    console.error(error);
    showError('Something went wrong while generating a reply. Please try again.');
    setStatus('Error occurred.');
  } finally {
    setProcessing(false);
    els.userInput.focus();
  }
}

async function clearChat() {
  clearError();
  els.resultsContainer.innerHTML = '';
  if (els.emptyState) {
    els.resultsContainer.appendChild(els.emptyState);
    els.emptyState.classList.remove('hidden');
  }
  try {
    if (window.pyodideReady) {
      await window.pyodide.runPythonAsync('_clear_memory()');
    }
  } catch (error) {
    console.warn('Could not clear Python memory:', error);
  }
  setStatus(state.ready ? 'Chat cleared. Ready for a fresh start.' : 'Initializing agent…');
}

function attachHandlers() {
  els.chatForm.addEventListener('submit', async (event) => {
    event.preventDefault();
    if (!state.processing) {
      await sendQuery();
    }
  });

  els.clearChatBtn.addEventListener('click', async () => {
    if (!state.processing) {
      await clearChat();
    }
  });

  els.exampleChips.forEach((chip) => {
    chip.addEventListener('click', () => {
      els.userInput.value = chip.dataset.prompt || '';
      clearError();
      els.userInput.focus();
    });
  });
}

function initialize() {
  cacheElements();
  attachHandlers();
  setProcessing(false);
  checkReady();

  if (window.agentManager && typeof window.agentManager.addEventListener === 'function') {
    window.agentManager.addEventListener('load', checkReady);
  }
}

document.addEventListener('pyodide-ready', () => {
  window.pyodideReady = true;
  checkReady();
});

if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', initialize);
} else {
  initialize();
}

setInterval(() => {
  if (!state.processing) {
    checkReady();
  }
}, 2000);
conversation_memory = []
MAX_MEMORY = 8
MAX_CHARS = int("[[[MAX_CHARS|6000]]]")


def _clear_memory() -> str:
    global conversation_memory
    conversation_memory = []
    return "ok"


async def process_user_query(query: str) -> str:
    """Handle a friendly coding and research chat request.

    Args:
        query: The user's latest message.

    Returns:
        A helpful conversational response.
    """
    global conversation_memory

    clean_query = (query or "").strip()
    if not clean_query:
        return "Please send a message, and I’ll help with coding, research, or planning."

    clean_query = clean_query[:MAX_CHARS]

    history_lines = []
    for item in conversation_memory[-MAX_MEMORY:]:
        role = item.get("role", "user")
        content = item.get("content", "")[:1200]
        history_lines.append(f"{role.upper()}: {content}")

    history_text = "\n".join(history_lines)

    prompt = (
        "You are continuing a friendly developer and research chat. "
        "Use the conversation when useful. "
        "Be practical, clear, and relaxed. "
        "If the user asks for code, provide code blocks in plain text formatting. "
        "If the user asks for research, give a concise answer with tradeoffs and next steps when helpful.\n\n"
        f"Conversation so far:\n{history_text}\n\n"
        f"User message:\n{clean_query}"
    )

    answer = await agentop_llm.generate(prompt, globals().get("TEMPLATE_SYSTEM_PROMPT", ""))
    answer = (answer or "").strip()

    conversation_memory.append({"role": "user", "content": clean_query})
    conversation_memory.append({"role": "assistant", "content": answer})
    conversation_memory = conversation_memory[-(MAX_MEMORY * 2):]

    return answer or "I’m here and ready — want to try that again with a bit more detail?"