Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>Shami's Agent System</title> | |
| <style> | |
| :root { | |
| --bg: #0a0a0a; --surface: #111; --surface2: #1a1a1a; | |
| --border: #222; --text: #e0e0e0; --text-dim: #666; | |
| --accent: #4a9eff; --accent-dim: #1a3a5c; | |
| --green: #4ade80; --yellow: #fbbf24; --red: #f87171; | |
| } | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; background: var(--bg); color: var(--text); height: 100vh; display: flex; } | |
| /* Sidebar */ | |
| .sidebar { width: 260px; background: var(--surface); border-right: 1px solid var(--border); display: flex; flex-direction: column; flex-shrink: 0; } | |
| .sidebar-header { padding: 20px; border-bottom: 1px solid var(--border); } | |
| .sidebar-header h1 { font-size: 16px; color: #fff; letter-spacing: -0.3px; } | |
| .sidebar-header p { font-size: 11px; color: var(--text-dim); margin-top: 4px; } | |
| .agent-list { flex: 1; overflow-y: auto; padding: 8px; } | |
| .agent-btn { width: 100%; text-align: left; padding: 12px 14px; background: none; border: 1px solid transparent; border-radius: 8px; color: var(--text); cursor: pointer; margin-bottom: 4px; font-size: 13px; display: flex; align-items: center; gap: 10px; transition: all 0.15s; } | |
| .agent-btn:hover { background: var(--surface2); border-color: var(--border); } | |
| .agent-btn.active { background: var(--accent-dim); border-color: var(--accent); color: #fff; } | |
| .agent-btn .icon { font-size: 18px; width: 28px; text-align: center; } | |
| .agent-btn .info { flex: 1; } | |
| .agent-btn .name { font-weight: 600; display: block; } | |
| .agent-btn .desc { font-size: 11px; color: var(--text-dim); display: block; margin-top: 2px; } | |
| .agent-btn.active .desc { color: #aaa; } | |
| .sidebar-footer { padding: 12px 16px; border-top: 1px solid var(--border); font-size: 11px; color: var(--text-dim); } | |
| .sidebar-footer a { color: var(--accent); text-decoration: none; } | |
| /* Main area */ | |
| .main { flex: 1; display: flex; flex-direction: column; min-width: 0; } | |
| .main-header { padding: 14px 24px; border-bottom: 1px solid var(--border); background: var(--surface); display: flex; align-items: center; justify-content: space-between; } | |
| .main-header h2 { font-size: 15px; color: #fff; } | |
| .main-header .tools { font-size: 11px; color: var(--text-dim); } | |
| .main-header .tools span { background: var(--surface2); padding: 2px 8px; border-radius: 4px; margin-left: 4px; border: 1px solid var(--border); } | |
| /* Chat area */ | |
| .chat { flex: 1; overflow-y: auto; padding: 24px; display: flex; flex-direction: column; gap: 16px; } | |
| .msg { max-width: 85%; padding: 14px 18px; border-radius: 12px; line-height: 1.6; font-size: 14px; } | |
| .msg.user { align-self: flex-end; background: var(--accent-dim); border: 1px solid #2a4a6c; } | |
| .msg.assistant { align-self: flex-start; background: var(--surface2); border: 1px solid var(--border); } | |
| .msg pre { background: var(--bg); padding: 12px; border-radius: 8px; overflow-x: auto; margin: 10px 0; font-size: 13px; border: 1px solid var(--border); } | |
| .msg code { font-family: 'SF Mono', 'Fira Code', monospace; font-size: 13px; } | |
| .msg p code { background: var(--bg); padding: 2px 6px; border-radius: 4px; } | |
| .msg table { border-collapse: collapse; margin: 10px 0; width: 100%; } | |
| .msg th, .msg td { border: 1px solid var(--border); padding: 6px 10px; text-align: left; font-size: 13px; } | |
| .msg th { background: var(--bg); } | |
| .msg h1, .msg h2, .msg h3 { margin: 12px 0 6px; color: #fff; } | |
| .msg h1 { font-size: 18px; } .msg h2 { font-size: 16px; } .msg h3 { font-size: 14px; } | |
| .msg ul, .msg ol { padding-left: 20px; margin: 6px 0; } | |
| .msg a { color: var(--accent); } | |
| .msg strong { color: #fff; } | |
| /* Tool call indicator */ | |
| .tool-indicator { align-self: flex-start; font-size: 12px; color: var(--text-dim); padding: 6px 12px; background: var(--surface); border: 1px solid var(--border); border-radius: 6px; display: flex; align-items: center; gap: 6px; } | |
| .tool-indicator .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--green); animation: pulse 1s infinite; } | |
| @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } } | |
| /* Meta info after response */ | |
| .meta { align-self: flex-start; font-size: 11px; color: var(--text-dim); padding: 4px 0; } | |
| /* Input */ | |
| .input-area { padding: 16px 24px; border-top: 1px solid var(--border); background: var(--surface); display: flex; gap: 12px; } | |
| .input-area textarea { flex: 1; padding: 12px 16px; background: var(--surface2); border: 1px solid var(--border); border-radius: 8px; color: var(--text); font-size: 14px; font-family: inherit; outline: none; resize: none; min-height: 44px; max-height: 120px; } | |
| .input-area textarea:focus { border-color: var(--accent); } | |
| .input-area button { padding: 12px 24px; background: var(--accent); color: #fff; border: none; border-radius: 8px; font-size: 14px; cursor: pointer; font-weight: 500; align-self: flex-end; } | |
| .input-area button:hover { background: #3a8eef; } | |
| .input-area button:disabled { background: #333; cursor: not-allowed; } | |
| /* Welcome */ | |
| .welcome { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 40px; } | |
| .welcome h2 { font-size: 22px; margin-bottom: 8px; color: #fff; } | |
| .welcome p { color: var(--text-dim); font-size: 14px; max-width: 400px; line-height: 1.6; } | |
| .examples { margin-top: 24px; display: flex; flex-direction: column; gap: 8px; } | |
| .example-btn { padding: 10px 16px; background: var(--surface2); border: 1px solid var(--border); border-radius: 8px; color: var(--text); cursor: pointer; font-size: 13px; text-align: left; transition: all 0.15s; } | |
| .example-btn:hover { border-color: var(--accent); background: var(--accent-dim); } | |
| @media (max-width: 768px) { | |
| body { flex-direction: column; } | |
| .sidebar { width: 100%; height: auto; max-height: 40vh; border-right: none; border-bottom: 1px solid var(--border); } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="sidebar"> | |
| <div class="sidebar-header"> | |
| <h1>Shami's Agents</h1> | |
| <p>AI-powered tools for real work</p> | |
| </div> | |
| <div class="agent-list" id="agentList"></div> | |
| <div class="sidebar-footer"> | |
| Groq + Tavily · <a href="/health" target="_blank">API Health</a> · <a href="/docs" target="_blank">API Docs</a> | |
| </div> | |
| </div> | |
| <div class="main"> | |
| <div class="main-header" id="mainHeader"> | |
| <h2 id="headerTitle">Select an Agent</h2> | |
| <div class="tools" id="headerTools"></div> | |
| </div> | |
| <div class="chat" id="chat"> | |
| <div class="welcome" id="welcome"> | |
| <h2>What do you need done?</h2> | |
| <p>Pick an agent from the sidebar, or try an example below.</p> | |
| <div class="examples" id="examples"></div> | |
| </div> | |
| </div> | |
| <div class="input-area"> | |
| <textarea id="input" placeholder="Give the agent a goal..." rows="1"></textarea> | |
| <button id="send" onclick="sendMessage()">Run</button> | |
| </div> | |
| </div> | |
| <script> | |
| let currentAgent = null; | |
| let agents = {}; | |
| const EXAMPLES = { | |
| search: "Find 5 remote AI/LLM engineer jobs paying $5K+/month, Europe or Gulf region", | |
| research: "Compare Supabase vs Firebase vs PlanetScale for a SaaS with 10K users", | |
| review: "Review this PR: https://github.com/vercel/next.js/pull/78498", | |
| upwork: "Draft a proposal for this job: I need a developer to build a custom AI chatbot for my e-commerce store that can answer product questions using our catalog", | |
| n8n: "Hi, I found your profile on Upwork. We need someone to build an AI agent that processes customer support tickets automatically. Budget is $2000-3000. Are you available?", | |
| }; | |
| // Load agents | |
| fetch("/agents").then(r => r.json()).then(data => { | |
| agents = {}; | |
| const list = document.getElementById("agentList"); | |
| const examples = document.getElementById("examples"); | |
| data.agents.forEach(a => { | |
| agents[a.id] = a; | |
| const btn = document.createElement("button"); | |
| btn.className = "agent-btn"; | |
| btn.dataset.id = a.id; | |
| btn.innerHTML = `<span class="icon">${a.icon}</span><span class="info"><span class="name">${a.name}</span><span class="desc">${a.description}</span></span>`; | |
| btn.onclick = () => selectAgent(a.id); | |
| list.appendChild(btn); | |
| if (EXAMPLES[a.id]) { | |
| const ex = document.createElement("button"); | |
| ex.className = "example-btn"; | |
| ex.textContent = `${a.icon} ${EXAMPLES[a.id].slice(0, 60)}...`; | |
| ex.onclick = () => { selectAgent(a.id); document.getElementById("input").value = EXAMPLES[a.id]; sendMessage(); }; | |
| examples.appendChild(ex); | |
| } | |
| }); | |
| }); | |
| function selectAgent(id) { | |
| currentAgent = id; | |
| const a = agents[id]; | |
| document.querySelectorAll(".agent-btn").forEach(b => b.classList.toggle("active", b.dataset.id === id)); | |
| document.getElementById("headerTitle").textContent = `${a.icon} ${a.name}`; | |
| document.getElementById("headerTools").innerHTML = a.tools.map(t => `<span>${t}</span>`).join(""); | |
| document.getElementById("welcome")?.remove(); | |
| document.getElementById("input").placeholder = `Goal for ${a.name}...`; | |
| document.getElementById("input").focus(); | |
| } | |
| const input = document.getElementById("input"); | |
| input.addEventListener("keydown", e => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); | |
| input.addEventListener("input", () => { input.style.height = "auto"; input.style.height = Math.min(input.scrollHeight, 120) + "px"; }); | |
| async function sendMessage() { | |
| const msg = input.value.trim(); | |
| if (!msg || !currentAgent) { if (!currentAgent) alert("Pick an agent first"); return; } | |
| input.value = ""; | |
| input.style.height = "auto"; | |
| const btn = document.getElementById("send"); | |
| btn.disabled = true; | |
| const chat = document.getElementById("chat"); | |
| chat.innerHTML += `<div class="msg user">${esc(msg)}</div>`; | |
| const toolDiv = document.createElement("div"); | |
| toolDiv.className = "tool-indicator"; | |
| toolDiv.innerHTML = `<span class="dot"></span> ${agents[currentAgent].name} working...`; | |
| chat.appendChild(toolDiv); | |
| chat.scrollTop = chat.scrollHeight; | |
| const start = Date.now(); | |
| try { | |
| const res = await fetch(`/agent/${currentAgent}`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ message: msg, conversation_id: `web-${currentAgent}-${Date.now()}` }), | |
| }); | |
| const data = await res.json(); | |
| toolDiv.remove(); | |
| if (!res.ok) { | |
| chat.innerHTML += `<div class="msg assistant" style="border-color:var(--red)">Error: ${esc(data.detail || JSON.stringify(data))}</div>`; | |
| } else { | |
| chat.innerHTML += `<div class="msg assistant">${renderMd(data.result)}</div>`; | |
| const elapsed = ((Date.now() - start) / 1000).toFixed(1); | |
| const toolsInfo = data.tools_used.length > 0 ? ` · Tools: ${data.tools_used.join(", ")}` : ""; | |
| chat.innerHTML += `<div class="meta">${data.tool_calls_made} tool calls · ${data.iterations} iterations · ${elapsed}s${toolsInfo}</div>`; | |
| } | |
| } catch (e) { | |
| toolDiv.remove(); | |
| chat.innerHTML += `<div class="msg assistant" style="border-color:var(--red)">Connection error: ${esc(e.message)}</div>`; | |
| } | |
| chat.scrollTop = chat.scrollHeight; | |
| btn.disabled = false; | |
| input.focus(); | |
| } | |
| function esc(s) { return s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"); } | |
| function renderMd(s) { | |
| // Basic markdown rendering | |
| let html = esc(s); | |
| // Code blocks | |
| html = html.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => `<pre><code>${code}</code></pre>`); | |
| // Inline code | |
| html = html.replace(/`([^`]+)`/g, "<code>$1</code>"); | |
| // Headers | |
| html = html.replace(/^### (.+)$/gm, "<h3>$1</h3>"); | |
| html = html.replace(/^## (.+)$/gm, "<h2>$1</h2>"); | |
| html = html.replace(/^# (.+)$/gm, "<h1>$1</h1>"); | |
| // Bold | |
| html = html.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>"); | |
| // Italic | |
| html = html.replace(/\*([^*]+)\*/g, "<em>$1</em>"); | |
| // Links | |
| html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>'); | |
| // Lists | |
| html = html.replace(/^- (.+)$/gm, "<li>$1</li>"); | |
| html = html.replace(/(<li>.*<\/li>\n?)+/g, m => `<ul>${m}</ul>`); | |
| // Line breaks | |
| html = html.replace(/\n\n/g, "</p><p>"); | |
| html = html.replace(/\n/g, "<br>"); | |
| return `<p>${html}</p>`; | |
| } | |
| </script> | |
| </body> | |
| </html> | |