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.
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.
Template Metadata
- Slug
- chill-dev-research-buddy
- Created By
- Hugagent
- Created
- Sep 06, 2026
- Usage Count
- 1
Tags
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, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
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?"
Name your agent
Based on . You'll get your own private copy to try and customize.