Agent Template

Idea Clarifier Chat Canvas

A lightweight chat-style agent template that helps users turn rough, unclear, or fragmented input into a clearer request, summary, or next-step plan.

clarification brainstorming chat writing-assistant planning

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

Idea Clarifier Chat Canvas 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 clarification brainstorming chat writing-assistant planning
Template Preview

Template Metadata

Slug
idea-clarifier-chat-canvas
Created By
[email protected]
Created
Aug 18, 2026
Usage Count
1

Tags

clarification brainstorming chat writing-assistant planning

Code Statistics

HTML Lines
92
CSS Lines
231
JS Lines
145
Python Lines
24

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="side-panel">
      <div class="brand-card">
        <div class="brand-emoji">🧭</div>
        <div>
          <h1>{{ agent_name }}</h1>
          <p>{{ description }}</p>
        </div>
      </div>

      <div class="panel-block">
        <h2>📝 Input mode</h2>
        <label for="mode-select" class="field-label">Choose the kind of help</label>
        <select id="mode-select">
          <option value="clarify">Clarify my idea</option>
          <option value="summarize">Summarize my notes</option>
          <option value="rewrite">Rewrite into clean prose</option>
          <option value="plan">Turn into next steps</option>
        </select>
      </div>

      <div class="panel-block">
        <h2>✨ Example prompts</h2>
        <div class="chip-list">
          <button type="button" class="chip-btn" data-prompt="Please clarify this rough idea into a clean request: I want some sort of dashboard for tracking team stuff and deadlines.">Clarify a rough request</button>
          <button type="button" class="chip-btn" data-prompt="Summarize these scattered notes into 3 bullets: launch timing unclear, website copy needs revision, ask sales for customer objections.">Summarize scattered notes</button>
          <button type="button" class="chip-btn" data-prompt="Rewrite this into a cleaner brief: need app maybe for students maybe habit tracker with reminders and progress, simple and motivating.">Rewrite as a brief</button>
          <button type="button" class="chip-btn" data-prompt="Turn this into next steps: We need a podcast concept, guest shortlist, publishing plan, and sponsorship ideas.">Create next steps</button>
        </div>
      </div>
    </aside>

    <main class="main-panel">
      <section class="composer-card">
        <div class="section-head">
          <h2>💬 Idea workspace</h2>
          <p>Paste messy text, fragments, or unclear thoughts. This assistant will organize them into something more useful.</p>
        </div>

        <div class="field-wrap">
          <label for="user-input" class="field-label">Your rough notes or request</label>
          <textarea id="user-input" placeholder="Example: I need help turning this half-formed product idea into a short brief with goals, audience, and next steps..."></textarea>
        </div>

        <div class="actions-row">
          <button id="send-btn" type="button">Refine this idea</button>
        </div>

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

      <section class="results-card">
        <div class="section-head">
          <h2>📌 Clarified output</h2>
          <p>Your refined response will appear below.</p>
        </div>

        <div id="results-container" class="results-container">
          <div class="empty-state" id="empty-state">
            <div class="empty-emoji">🪄</div>
            <h3>Ready to untangle rough ideas</h3>
            <ul>
              <li>Turn fragments into a clear request</li>
              <li>Extract the key points from messy notes</li>
              <li>Rewrite vague drafts into cleaner language</li>
              <li>Create practical next steps from ambiguity</li>
            </ul>
          </div>
        </div>
      </section>
    </main>
  </div>

  <div id="loading-overlay" class="loading-overlay hidden" aria-hidden="true">
    <div class="spinner-card">
      <div class="spinner"></div>
      <p>Refining your idea...</p>
    </div>
  </div>

  <script>{{ js_code }}</script>
</body>
</html>
:root {
  --bg: #f4f7fb;
  --panel: #ffffff;
  --panel-soft: #eef4ff;
  --text: #172033;
  --muted: #5f6b85;
  --border: #d8e1f0;
  --accent: #4f7cff;
  --accent-strong: #2f5fe6;
  --danger: #c93c3c;
  --shadow: 0 18px 45px rgba(24, 39, 75, 0.10);
  --radius-xl: 24px;
  --radius-lg: 18px;
  --radius-md: 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; background: var(--bg); color: var(--text); font-family: var(--font); }
body { min-height: 100vh; }

.hidden { display: none !important; }

.app-shell {
  display: grid;
  grid-template-columns: 340px 1fr;
  gap: 24px;
  max-width: 1400px;
  margin: 0 auto;
  padding: 24px;
}

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

.side-panel {
  padding: 22px;
  height: fit-content;
}

.brand-card {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--border);
}

.brand-emoji, .empty-emoji {
  font-size: 32px;
}

.brand-card h1, .section-head h2, .panel-block h2, .empty-state h3 {
  margin: 0 0 8px 0;
}

.brand-card p, .section-head p, .panel-block p, .empty-state li {
  color: var(--muted);
  line-height: 1.55;
}

.panel-block {
  margin-top: 22px;
}

.main-panel {
  display: grid;
  grid-template-rows: auto 1fr;
  gap: 24px;
}

.composer-card, .results-card {
  padding: 24px;
}

.field-wrap {
  margin-top: 18px;
}

.field-label {
  display: block;
  font-weight: 700;
  margin-bottom: 10px;
}

textarea, select {
  width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 14px 16px;
  font: inherit;
  color: var(--text);
  background: #fff;
}

textarea {
  min-height: 180px;
  resize: vertical;
  line-height: 1.55;
}

textarea:focus-visible, select:focus-visible, button:focus-visible {
  outline: 3px solid rgba(79, 124, 255, 0.25);
  outline-offset: 2px;
}

.actions-row {
  margin-top: 16px;
  display: flex;
  gap: 12px;
  align-items: center;
}

button {
  border: 0;
  border-radius: 999px;
  padding: 12px 18px;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
  transition: transform 0.15s ease, background 0.15s ease, opacity 0.15s ease;
}

#send-btn {
  background: var(--accent);
  color: white;
}

#send-btn:hover { background: var(--accent-strong); transform: translateY(-1px); }
button:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }

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

.chip-btn {
  text-align: left;
  background: var(--panel-soft);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 14px;
}

.results-container {
  margin-top: 18px;
  min-height: 320px;
  background: linear-gradient(180deg, #fbfcff 0%, #f6f9ff 100%);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 18px;
}

.message {
  padding: 14px 16px;
  border-radius: 16px;
  margin-bottom: 12px;
  white-space: pre-wrap;
  line-height: 1.6;
}

.message.user {
  background: #eef4ff;
  border: 1px solid #cfe0ff;
}

.message.assistant {
  background: #ffffff;
  border: 1px solid var(--border);
}

.inline-error {
  margin-top: 14px;
  background: #fff2f2;
  color: var(--danger);
  border: 1px solid #f0c9c9;
  border-radius: 12px;
  padding: 12px 14px;
}

.empty-state ul {
  margin: 10px 0 0 18px;
  padding: 0;
}

.loading-overlay {
  position: fixed;
  inset: 0;
  background: rgba(18, 25, 40, 0.38);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.spinner-card {
  background: white;
  border-radius: 20px;
  padding: 24px 28px;
  box-shadow: var(--shadow);
  display: flex;
  align-items: center;
  gap: 14px;
}

.spinner {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 3px solid #d9e3ff;
  border-top-color: var(--accent);
  animation: spin 0.9s linear infinite;
}

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

@media (max-width: 1200px) {
  .app-shell { grid-template-columns: 1fr; }
}

@media (max-width: 768px) {
  .app-shell { padding: 16px; gap: 16px; }
  .composer-card, .results-card, .side-panel { padding: 18px; }
  #send-btn { width: 100%; }
  .actions-row { flex-direction: column; align-items: stretch; }
}
function escapeHtml(str) {
  return String(str)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

(function () {
  const userInput = document.getElementById('user-input');
  const modeSelect = document.getElementById('mode-select');
  const sendBtn = document.getElementById('send-btn');
  const resultsContainer = document.getElementById('results-container');
  const emptyState = document.getElementById('empty-state');
  const inlineError = document.getElementById('inline-error');
  const loadingOverlay = document.getElementById('loading-overlay');
  const chipButtons = Array.from(document.querySelectorAll('.chip-btn'));

  let isReady = false;

  function setError(message) {
    if (!message) {
      inlineError.textContent = '';
      inlineError.classList.add('hidden');
      return;
    }
    inlineError.textContent = message;
    inlineError.classList.remove('hidden');
  }

  function setLoading(loading) {
    sendBtn.disabled = loading;
    loadingOverlay.classList.toggle('hidden', !loading);
    loadingOverlay.setAttribute('aria-hidden', loading ? 'false' : 'true');
  }

  function checkReady() {
    const pyodideReady = !!window.pyodideReady;
    if (!pyodideReady) {
      return { ok: false, message: 'The idea clarifier is still initializing. Please wait a moment and try again.' };
    }
    if (window.PROVIDER === 'local') {
      if (!window.agentManager || !window.agentManager.isLoaded) {
        return { ok: false, message: 'Please load a local model from the top bar before refining your idea.' };
      }
    }
    return { ok: true, message: '' };
  }

  window.addMessage = function (type, content) {
    if (emptyState) emptyState.classList.add('hidden');
    const safeType = type === 'user' ? 'user' : 'assistant';
    const existing = resultsContainer.querySelector('.message.streaming');
    if (existing && safeType === 'assistant') {
      existing.classList.remove('streaming');
      existing.classList.add('assistant');
      existing.innerHTML = escapeHtml(content);
      return;
    }
    const div = document.createElement('div');
    div.className = 'message ' + safeType;
    div.innerHTML = escapeHtml(content);
    resultsContainer.appendChild(div);
    resultsContainer.scrollTop = resultsContainer.scrollHeight;
  };

  function addStreamingPlaceholder() {
    if (emptyState) emptyState.classList.add('hidden');
    const div = document.createElement('div');
    div.className = 'message assistant streaming';
    div.innerHTML = escapeHtml('Working on your clarified version...');
    resultsContainer.appendChild(div);
    resultsContainer.scrollTop = resultsContainer.scrollHeight;
  }

  async function handleSubmit() {
    setError('');
    const readiness = checkReady();
    if (!readiness.ok) {
      setError(readiness.message);
      return;
    }

    const text = userInput.value.trim();
    const mode = modeSelect.value;

    if (!text) {
      setError('Please paste your rough notes or request so the idea clarifier has something to refine.');
      return;
    }

    const composedQuery = 'Mode: ' + mode + '\n\nUser input:\n' + text;

    try {
      setLoading(true);
      window.addMessage('user', text);
      addStreamingPlaceholder();
      window.pyodide.globals.set('user_query', composedQuery);
      const result = await window.pyodide.runPythonAsync('await process_user_query(user_query)');
      const streaming = resultsContainer.querySelector('.message.streaming');
      if (streaming) {
        streaming.classList.remove('streaming');
        streaming.classList.add('assistant');
        streaming.innerHTML = escapeHtml(result || 'No response was returned.');
      } else {
        window.addMessage('assistant', result || 'No response was returned.');
      }
    } catch (err) {
      const streaming = resultsContainer.querySelector('.message.streaming');
      if (streaming) streaming.remove();
      setError('Something went wrong while refining your idea. Please try again.');
      console.error(err);
    } finally {
      setLoading(false);
    }
  }

  sendBtn.addEventListener('click', handleSubmit);

  chipButtons.forEach((btn) => {
    btn.addEventListener('click', () => {
      userInput.value = btn.getAttribute('data-prompt') || '';
      userInput.focus();
      setError('');
    });
  });

  modeSelect.addEventListener('change', () => {
    setError('');
  });

  userInput.addEventListener('input', () => {
    setError('');
  });

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

  if (window.pyodideReady) {
    isReady = true;
  }
})();
async def process_user_query(query: str) -> str:
    """Process a rough user request into a clearer output.

    Args:
        query: The user's raw request, including selected mode and text.

    Returns:
        A refined and useful response.
    """
    system_prompt = globals().get("TEMPLATE_SYSTEM_PROMPT", "")
    if not system_prompt:
        system_prompt = (
            "You clarify messy user input. "
            "Read the request carefully. "
            "If the input is vague, still be helpful. "
            "Follow the selected mode if present. "
            "For clarify mode, produce a cleaner request. "
            "For summarize mode, produce a short summary with bullets. "
            "For rewrite mode, rewrite into polished prose. "
            "For plan mode, produce practical next steps. "
            "Be concise, structured, and useful."
        )
    result = await call_llm(query, system_prompt)
    return str(result)