aipackage/cezen-portal/api-playground.html
2026-06-30 10:51:41 +05:30

645 lines
32 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API Playground — Nexus One AI</title>
<link rel="stylesheet" href="style.css?v=4">
<style>
.pg-layout {
display: grid;
grid-template-columns: 260px 1fr;
height: calc(100vh - 64px);
overflow: hidden;
}
@media(max-width:760px){ .pg-layout { grid-template-columns: 1fr; } .pg-sidebar { display:none; } }
/* ── Sidebar ── */
.pg-sidebar {
border-right: 1px solid var(--bdr); background: var(--navy2);
display: flex; flex-direction: column; overflow: hidden;
}
.pg-sidebar-header { padding: 14px 16px 10px; border-bottom: 1px solid var(--bdr); }
.pg-sidebar-title { font-size: 11px; font-weight: 700; color: var(--lt); text-transform: uppercase; letter-spacing: .5px; }
.pg-sidebar-search {
margin-top: 10px; width: 100%; box-sizing: border-box;
padding: 7px 10px; border-radius: 8px; border: 1.5px solid var(--bdr);
background: var(--bg); color: var(--ink); font-family: inherit; font-size: 12px;
}
.pg-sidebar-search:focus { outline: none; border-color: var(--purple); }
.pg-endpoint-list { flex: 1; overflow-y: auto; padding: 8px; }
.pg-ep-group { margin-bottom: 4px; }
.pg-ep-group-title {
font-size: 10px; font-weight: 800; color: var(--lt);
text-transform: uppercase; letter-spacing: .5px;
padding: 8px 8px 4px;
}
.pg-ep-item {
display: flex; align-items: center; gap: 8px;
padding: 7px 10px; border-radius: 8px; cursor: pointer;
transition: .1s; margin-bottom: 2px;
}
.pg-ep-item:hover { background: rgba(124,58,237,.07); }
.pg-ep-item.active { background: rgba(124,58,237,.12); }
.pg-method {
font-size: 10px; font-weight: 800; padding: 2px 6px; border-radius: 5px;
min-width: 34px; text-align: center; flex-shrink: 0;
}
.pg-method.GET { background: rgba(16,185,129,.15); color: #059669; }
.pg-method.POST { background: rgba(124,58,237,.15); color: var(--purple); }
.pg-method.PUT { background: rgba(245,158,11,.15); color: #d97706; }
.pg-method.DELETE { background: rgba(239,68,68,.12); color: #dc2626; }
.pg-method.PATCH { background: rgba(59,130,246,.15); color: #2563eb; }
.pg-ep-path { font-size: 12px; color: var(--med); font-family: monospace; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pg-ep-item.active .pg-ep-path { color: var(--ink); }
/* ── Main ── */
.pg-main { display: flex; flex-direction: column; overflow: hidden; }
/* Request panel */
.pg-request {
flex-shrink: 0;
border-bottom: 1px solid var(--bdr);
background: var(--navy2);
}
.pg-request-bar { display: flex; align-items: center; gap: 10px; padding: 14px 18px; border-bottom: 1px solid var(--bdr); }
.pg-method-badge {
font-size: 12px; font-weight: 800; padding: 6px 12px;
border-radius: 8px; min-width: 52px; text-align: center;
}
.pg-url-input {
flex: 1; padding: 9px 12px; border-radius: 9px;
border: 1.5px solid var(--bdr); background: var(--bg);
color: var(--ink); font-family: monospace; font-size: 13px;
}
.pg-url-input:focus { outline: none; border-color: var(--purple); }
.pg-send-btn {
padding: 9px 22px; border-radius: 9px; background: var(--purple);
color: white; border: none; font-family: inherit;
font-size: 13px; font-weight: 700; cursor: pointer; transition: .15s;
display: flex; align-items: center; gap: 7px; white-space: nowrap;
}
.pg-send-btn:hover { background: #6d28d9; }
.pg-send-btn:disabled { opacity: .5; cursor: not-allowed; }
.pg-spinner { display: none; width: 14px; height: 14px; border: 2px solid rgba(255,255,255,.3); border-top-color: white; border-radius: 50%; animation: spin .7s linear infinite; }
.pg-send-btn.loading .pg-spinner { display: block; }
.pg-send-btn.loading .pg-send-icon { display: none; }
@keyframes spin { to { transform: rotate(360deg); } }
/* Tabs */
.pg-tabs { display: flex; gap: 0; border-bottom: 1px solid var(--bdr); padding: 0 18px; }
.pg-tab {
padding: 10px 16px; font-size: 12px; font-weight: 700; color: var(--lt);
cursor: pointer; border-bottom: 2px solid transparent; transition: .12s;
background: none; border-top: none; border-left: none; border-right: none;
font-family: inherit;
}
.pg-tab.active { color: var(--purple); border-bottom-color: var(--purple); }
.pg-tab-panel { display: none; padding: 14px 18px; }
.pg-tab-panel.active { display: block; }
.pg-auth-row { display: flex; align-items: center; gap: 10px; }
.pg-auth-row label { font-size: 12px; font-weight: 700; color: var(--med); white-space: nowrap; }
.pg-auth-row input {
flex: 1; padding: 8px 10px; border-radius: 8px;
border: 1.5px solid var(--bdr); background: var(--bg);
color: var(--ink); font-family: monospace; font-size: 12px;
}
.pg-auth-row input:focus { outline: none; border-color: var(--purple); }
.pg-auth-row button {
padding: 8px 14px; border-radius: 8px; background: none;
border: 1.5px solid var(--bdr); color: var(--med);
font-family: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
}
.pg-params-table { width: 100%; border-collapse: collapse; }
.pg-params-table th { font-size: 10px; font-weight: 700; color: var(--lt); text-transform: uppercase; letter-spacing: .4px; padding: 0 0 8px; text-align: left; }
.pg-params-table td { padding: 4px 8px 4px 0; vertical-align: top; }
.pg-param-key, .pg-param-val {
width: 100%; padding: 6px 8px; border-radius: 7px;
border: 1.5px solid var(--bdr); background: var(--bg);
color: var(--ink); font-family: monospace; font-size: 12px;
}
.pg-param-key:focus, .pg-param-val:focus { outline: none; border-color: var(--purple); }
.pg-param-del { background: none; border: none; cursor: pointer; color: var(--lt); font-size: 14px; padding: 4px; }
.pg-param-del:hover { color: #ef4444; }
.pg-add-param { font-size: 12px; font-weight: 600; color: var(--purple); background: none; border: none; cursor: pointer; padding: 6px 0; }
.pg-body-textarea {
width: 100%; box-sizing: border-box; min-height: 130px; padding: 10px 12px;
border-radius: 9px; border: 1.5px solid var(--bdr); background: var(--bg);
color: var(--ink); font-family: monospace; font-size: 12px; resize: vertical;
line-height: 1.6;
}
.pg-body-textarea:focus { outline: none; border-color: var(--purple); }
.pg-format-btn { font-size: 11px; color: var(--purple); background: none; border: none; cursor: pointer; font-family: inherit; padding: 4px 0; }
.pg-desc { font-size: 13px; color: var(--med); line-height: 1.6; margin-bottom: 12px; }
.pg-params-doc { width: 100%; border-collapse: collapse; font-size: 12px; }
.pg-params-doc th { font-size: 10px; font-weight: 700; color: var(--lt); text-transform: uppercase; letter-spacing: .4px; padding: 0 0 6px; text-align: left; }
.pg-params-doc td { padding: 6px 12px 6px 0; border-bottom: 1px solid var(--bdr); color: var(--med); vertical-align: top; }
.pg-params-doc td:first-child { font-family: monospace; color: var(--purple); font-weight: 700; }
.pg-req-badge { font-size: 10px; font-weight: 700; padding: 1px 6px; border-radius: 4px; background: rgba(239,68,68,.1); color: #dc2626; }
/* Response */
.pg-response { flex: 1; overflow: hidden; display: flex; flex-direction: column; background: var(--bg); }
.pg-response-header { padding: 10px 18px; border-bottom: 1px solid var(--bdr); background: var(--navy2); display: flex; align-items: center; gap: 12px; }
.pg-response-title { font-size: 12px; font-weight: 700; color: var(--ink); flex: 1; }
.pg-status-chip { padding: 3px 10px; border-radius: 20px; font-size: 11px; font-weight: 800; }
.pg-status-chip.ok { background: rgba(16,185,129,.12); color: #059669; }
.pg-status-chip.err { background: rgba(239,68,68,.1); color: #dc2626; }
.pg-latency-chip { padding: 3px 10px; border-radius: 20px; font-size: 11px; font-weight: 700; background: rgba(124,58,237,.1); color: var(--purple); }
.pg-copy-btn { background: none; border: 1.5px solid var(--bdr); border-radius: 7px; padding: 4px 10px; cursor: pointer; font-size: 11px; color: var(--med); font-family: inherit; transition: .12s; }
.pg-copy-btn:hover { border-color: var(--purple); color: var(--purple); }
.pg-response-body { flex: 1; overflow-y: auto; padding: 16px 18px; }
.pg-response-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: var(--lt); gap: 10px; }
.pg-response-pre { background: #160D35; color: #e2d9f3; border-radius: 12px; padding: 16px 18px; font-family: monospace; font-size: 12px; line-height: 1.7; white-space: pre-wrap; word-break: break-word; margin: 0; }
/* Endpoint docs inline */
.pg-doc-block { background: rgba(124,58,237,.04); border: 1.5px solid rgba(124,58,237,.15); border-radius: 10px; padding: 14px 16px; margin-bottom: 12px; }
.pg-doc-block h4 { font-size: 13px; font-weight: 800; color: var(--ink); margin: 0 0 6px; }
/* Toast */
#pg-toast { position:fixed;bottom:24px;right:24px;z-index:9999;background:var(--ink);color:white;padding:10px 18px;border-radius:10px;font-size:13px;font-weight:600;opacity:0;transform:translateY(8px);transition:.25s;pointer-events:none; }
#pg-toast.show { opacity:1;transform:translateY(0); }
</style>
</head>
<body>
<header class="topnav">
<a href="index.html" class="brand">Nexus One <span>AI</span></a>
<nav>
<a href="index.html">Home</a>
<a href="quickstart.html">Quick Start</a>
<a href="prompts.html">Prompt Library</a>
<a href="usecases.html">Use Cases</a>
<span class="nav-sep"></span>
<div class="nav-dropdown">
<button class="nav-drop-btn">Help ▾</button>
<div class="nav-drop-menu">
<span class="nav-drop-cat">LEARN /</span>
<a href="quickstart.html">Quick Start</a>
<a href="models.html">Models</a>
<span class="nav-drop-cat">SUPPORT /</span>
<a href="troubleshooting.html">Troubleshoot</a>
<a href="faq.html">FAQ</a>
<span class="nav-drop-cat">MORE /</span>
<a href="glossary.html">Glossary</a>
<a href="whats-new.html">What's New</a>
</div>
</div>
<div class="nav-dropdown">
<button class="nav-drop-btn active">Admin ▾</button>
<div class="nav-drop-menu nav-drop-menu-wide">
<span class="nav-drop-cat">DOCS /</span>
<a href="security.html">Security & Privacy</a>
<a href="admin.html">Admin Guide</a>
<span class="nav-drop-cat">MONITOR /</span>
<a href="dashboard.html">Dashboard</a>
<a href="analytics.html">Usage Analytics</a>
<a href="audit.html">Audit Log</a>
<a href="feedback.html">Feedback & Ratings</a>
<span class="nav-drop-cat">MANAGE /</span>
<a href="users.html">Users</a>
<a href="teams.html">Teams</a>
<a href="models-admin.html">Model Manager</a>
<a href="training.html">Training</a>
<a href="knowledge.html">Knowledge Base</a>
<span class="nav-drop-cat">TOOLS /</span>
<a href="apikeys.html">API Keys</a>
<a href="benchmark.html">Benchmarking</a>
<a href="model-compare.html">Model Compare</a>
<a href="api-playground.html" class="active">API Playground</a>
<a href="guardrails.html">Guardrails</a>
<a href="rag-quality.html">RAG Quality</a>
<a href="router.html">Model Router</a>
<a href="connectors.html">Connectors</a>
<span class="nav-drop-cat">SYSTEM /</span>
<a href="console.html">Console</a>
<a href="settings.html">Settings</a>
</div>
</div>
<div class="nav-dropdown">
<button class="nav-drop-btn">AI Tools ▾</button>
<div class="nav-drop-menu">
<span class="nav-drop-cat">INTELLIGENCE /</span>
<a href="documents.html">Document Intelligence</a>
<a href="chat-multi.html">Multimodal Chat</a>
<a href="prompt-studio.html">Prompt Studio</a>
<a href="meeting.html">Meeting Assistant</a>
<span class="nav-drop-cat">AUTOMATION /</span>
<a href="agents.html">Agent Builder</a>
<a href="schedules.html">Scheduled Jobs</a>
<a href="workflows.html">Workflow Automation</a>
<span class="nav-drop-cat">QUALITY /</span>
<a href="evals.html">AI Eval Suite</a>
<a href="chatrooms.html">Chat Rooms</a>
</div>
</div>
<a href="notifications.html" style="position:relative">🔔</a>
</nav>
<span class="badge" data-brand="tier">Basic Tier</span>
<div id="nav-org-logo" class="nav-org-logo"></div>
</header>
<div class="pg-layout">
<!-- Sidebar -->
<div class="pg-sidebar">
<div class="pg-sidebar-header">
<div class="pg-sidebar-title">🔌 API Playground</div>
<input class="pg-sidebar-search" type="text" placeholder="Search endpoints…" oninput="filterEndpoints(this.value)">
</div>
<div class="pg-endpoint-list" id="pg-ep-list"></div>
</div>
<!-- Main -->
<div class="pg-main">
<!-- Request bar -->
<div class="pg-request">
<div class="pg-request-bar">
<span class="pg-method-badge GET" id="pg-method-badge">GET</span>
<input class="pg-url-input" id="pg-url" type="text" value="/api/health">
<button class="pg-send-btn" id="pg-send-btn" onclick="sendRequest()">
<span class="pg-send-icon">▶ Send</span>
<div class="pg-spinner"></div>
</button>
</div>
<!-- Tabs -->
<div class="pg-tabs">
<button class="pg-tab active" onclick="showTab('auth',this)">Auth</button>
<button class="pg-tab" onclick="showTab('params',this)">Params</button>
<button class="pg-tab" onclick="showTab('body',this)">Body</button>
<button class="pg-tab" onclick="showTab('docs',this)">Docs</button>
</div>
<div class="pg-tab-panel active" id="pg-tab-auth">
<div class="pg-auth-row">
<label>API Key</label>
<input id="pg-apikey" type="password" placeholder="cezen_sk_… (from API Keys page)">
<button onclick="pasteKey()">Paste</button>
<button onclick="loadFromStorage()">Load saved</button>
</div>
<p style="font-size:11px;color:var(--lt);margin-top:8px">Your API key is sent as <code style="background:rgba(124,58,237,.1);color:var(--purple);padding:1px 5px;border-radius:4px">Authorization: Bearer &lt;key&gt;</code> on every request.</p>
</div>
<div class="pg-tab-panel" id="pg-tab-params">
<table class="pg-params-table">
<thead><tr><th>Key</th><th>Value</th><th></th></tr></thead>
<tbody id="pg-params-body">
<tr><td><input class="pg-param-key" placeholder="param" value=""></td><td><input class="pg-param-val" placeholder="value" value=""></td><td><button class="pg-param-del" onclick="this.closest('tr').remove()"></button></td></tr>
</tbody>
</table>
<button class="pg-add-param" onclick="addParam()">+ Add parameter</button>
</div>
<div class="pg-tab-panel" id="pg-tab-body">
<button class="pg-format-btn" onclick="formatJSON()">⚡ Format JSON</button>
<textarea class="pg-body-textarea" id="pg-body" placeholder='{"message": "Hello", "model": "llama3.1:8b"}'></textarea>
</div>
<div class="pg-tab-panel" id="pg-tab-docs">
<div id="pg-docs-content">
<p style="color:var(--lt);font-size:13px">Select an endpoint from the left to see documentation.</p>
</div>
</div>
</div>
<!-- Response -->
<div class="pg-response">
<div class="pg-response-header">
<span class="pg-response-title">Response</span>
<span id="pg-status-chip" class="pg-status-chip" style="display:none"></span>
<span id="pg-latency-chip" class="pg-latency-chip" style="display:none"></span>
<button class="pg-copy-btn" onclick="copyResponse()">📋 Copy</button>
</div>
<div class="pg-response-body" id="pg-response-body">
<div class="pg-response-empty">
<div style="font-size:36px;opacity:.3">🔌</div>
<p style="font-size:13px;color:var(--lt)">Select an endpoint and click <strong>Send</strong></p>
</div>
</div>
</div>
</div>
</div>
<div id="pg-toast"></div>
<script>
// ── Endpoint catalogue ─────────────────────────────────────────────────────────
const ENDPOINTS = [
{ group: 'Chat & Models', method:'POST', path:'/api/chat', desc:'Send a message to a model',
body: '{\n "model": "llama3.1:8b",\n "message": "Explain what a transformer model is in simple terms.",\n "system_prompt": "You are a helpful AI tutor.",\n "temperature": 0.7,\n "max_tokens": 512\n}',
params: [],
doc: { desc: 'Send a chat message to a locally running model via Ollama.', fields: [
{name:'model',type:'string',req:true,desc:'Model name as listed in Model Manager (e.g. llama3.1:8b)'},
{name:'message',type:'string',req:true,desc:'The user message to send'},
{name:'system_prompt',type:'string',req:false,desc:'Optional system prompt to define AI role and behaviour'},
{name:'temperature',type:'float',req:false,desc:'Sampling temperature 0.01.0 (default 0.7)'},
{name:'max_tokens',type:'int',req:false,desc:'Maximum response token count (default 1024)'},
]}
},
{ group: 'Chat & Models', method:'GET', path:'/api/models', desc:'List available models',
body: '', params: [],
doc: { desc: 'Returns all models currently loaded in Ollama on this server.', fields: [] }
},
{ group: 'Chat & Models', method:'GET', path:'/api/metrics', desc:'Live system metrics (GPU, RAM, CPU)',
body: '', params: [],
doc: { desc: 'Returns real-time hardware metrics: GPU VRAM usage, CPU %, RAM used/total.', fields: [] }
},
{ group: 'Documents', method:'POST', path:'/api/docjobs', desc:'Upload a document for processing',
body: '', params: [],
doc: { desc: 'Upload a PDF, DOCX, or TXT file. Returns a job_id to poll for completion. Use multipart/form-data.', fields: [
{name:'file',type:'file',req:true,desc:'Document file (PDF, DOCX, TXT, CSV — max 50 MB)'},
{name:'collection_id',type:'int',req:false,desc:'Optionally add to a RAG knowledge base collection'},
]}
},
{ group: 'Documents', method:'GET', path:'/api/docjobs', desc:'List document processing jobs',
body: '', params: [{k:'limit',v:'20'},{k:'status',v:''}],
doc: { desc: 'Returns a list of document upload and processing jobs.', fields: [
{name:'limit',type:'int',req:false,desc:'Number of results to return (default 20)'},
{name:'status',type:'string',req:false,desc:'Filter by status: pending | processing | done | failed'},
]}
},
{ group: 'Knowledge Base', method:'GET', path:'/api/rag/collections', desc:'List RAG collections',
body: '', params: [],
doc: { desc: 'Returns all knowledge base collections available for RAG queries.', fields: [] }
},
{ group: 'Knowledge Base', method:'POST', path:'/api/rag/query', desc:'Query a knowledge base',
body: '{\n "collection_id": 1,\n "query": "What is our leave encashment policy?",\n "n_results": 5\n}',
params: [],
doc: { desc: 'Perform a semantic search over a RAG collection and return the top-k most relevant chunks.', fields: [
{name:'collection_id',type:'int',req:true,desc:'ID of the knowledge base collection'},
{name:'query',type:'string',req:true,desc:'Natural language question or search query'},
{name:'n_results',type:'int',req:false,desc:'Number of chunks to return (default 5, max 20)'},
]}
},
{ group: 'Agents', method:'GET', path:'/api/agents', desc:'List agents',
body: '', params: [],
doc: { desc: 'Returns all agents created in the Agent Builder.', fields: [] }
},
{ group: 'Agents', method:'POST', path:'/api/agents/{id}/run', desc:'Trigger an agent run',
body: '{\n "input": "Check all contracts expiring in the next 30 days"\n}',
params: [],
doc: { desc: 'Manually trigger an agent and optionally override the input.', fields: [
{name:'input',type:'string',req:false,desc:'Override the agent\'s default input for this run'},
]}
},
{ group: 'Schedules', method:'GET', path:'/api/schedules', desc:'List scheduled jobs',
body: '', params: [],
doc: { desc: 'Returns all scheduled AI jobs and their last run status.', fields: [] }
},
{ group: 'Schedules', method:'POST', path:'/api/schedules/{id}/run', desc:'Trigger job immediately',
body: '', params: [],
doc: { desc: 'Manually execute a scheduled job right now, bypassing the schedule.', fields: [] }
},
{ group: 'Users', method:'GET', path:'/api/users', desc:'List users (admin only)',
body: '', params: [{k:'limit',v:'50'},{k:'role',v:''}],
doc: { desc: 'Admin-only. Returns all user accounts with role and status.', fields: [
{name:'limit',type:'int',req:false,desc:'Max users to return'},
{name:'role',type:'string',req:false,desc:'Filter by role: admin | user'},
]}
},
{ group: 'Notifications', method:'GET', path:'/api/notifications', desc:'Get notifications',
body: '', params: [{k:'unread_only',v:'false'},{k:'source',v:''}],
doc: { desc: 'Returns notifications for the authenticated user. Includes unread count.', fields: [
{name:'unread_only',type:'bool',req:false,desc:'If true, returns only unread notifications'},
{name:'source',type:'string',req:false,desc:'Filter by source: guardrail | scheduler | agent | rag | system'},
]}
},
{ group: 'Notifications', method:'POST', path:'/api/notifications/read-all', desc:'Mark all as read',
body: '', params: [],
doc: { desc: 'Marks all notifications as read for the authenticated user.', fields: [] }
},
{ group: 'Feedback', method:'POST', path:'/api/feedback', desc:'Submit a rating',
body: '{\n "model": "llama3.1:8b",\n "rating": 4,\n "comment": "Good answer, slightly verbose.",\n "prompt_snippet": "Explain RAG in simple terms",\n "page": "chat-multi"\n}',
params: [],
doc: { desc: 'Submit a 15 star rating for an AI response, with optional comment.', fields: [
{name:'model',type:'string',req:true,desc:'The model that generated the response'},
{name:'rating',type:'int',req:true,desc:'Star rating 1 (worst) to 5 (best)'},
{name:'comment',type:'string',req:false,desc:'Optional user comment'},
{name:'prompt_snippet',type:'string',req:false,desc:'Truncated prompt for context (max 500 chars)'},
{name:'page',type:'string',req:false,desc:'Which page/feature the feedback came from'},
]}
},
{ group: 'System', method:'GET', path:'/api/health', desc:'Health check',
body: '', params: [],
doc: { desc: 'Returns status "ok" and API version. No authentication required.', fields: [] }
},
{ group: 'System', method:'GET', path:'/api/system/info', desc:'Hardware & software info',
body: '', params: [],
doc: { desc: 'Returns GPU specs, OS version, uptime, and Nexus One AI version string.', fields: [] }
},
];
let filteredEps = [...ENDPOINTS];
let activeEp = ENDPOINTS[ENDPOINTS.length - 1]; // default to /api/health
function renderEndpointList(eps) {
const el = document.getElementById('pg-ep-list');
const groups = {};
for (const ep of eps) {
if (!groups[ep.group]) groups[ep.group] = [];
groups[ep.group].push(ep);
}
el.innerHTML = Object.entries(groups).map(([g, items]) => `
<div class="pg-ep-group">
<div class="pg-ep-group-title">${g}</div>
${items.map(ep => `
<div class="pg-ep-item ${ep === activeEp ? 'active' : ''}" onclick="selectEndpoint('${ep.method}','${ep.path}')">
<span class="pg-method ${ep.method}">${ep.method}</span>
<span class="pg-ep-path">${ep.path}</span>
</div>
`).join('')}
</div>
`).join('');
}
function filterEndpoints(q) {
q = q.toLowerCase();
filteredEps = q ? ENDPOINTS.filter(e => e.path.toLowerCase().includes(q) || e.desc.toLowerCase().includes(q) || e.group.toLowerCase().includes(q)) : [...ENDPOINTS];
renderEndpointList(filteredEps);
}
function selectEndpoint(method, path) {
activeEp = ENDPOINTS.find(e => e.method === method && e.path === path);
if (!activeEp) return;
// Update URL bar
document.getElementById('pg-url').value = activeEp.path;
const badge = document.getElementById('pg-method-badge');
badge.textContent = method;
badge.className = `pg-method-badge ${method}`;
// Populate body
document.getElementById('pg-body').value = activeEp.body || '';
// Populate params
const tbody = document.getElementById('pg-params-body');
const params = activeEp.params || [];
tbody.innerHTML = params.map(p => `
<tr><td><input class="pg-param-key" placeholder="param" value="${p.k}"></td>
<td><input class="pg-param-val" placeholder="value" value="${p.v}"></td>
<td><button class="pg-param-del" onclick="this.closest('tr').remove()">✕</button></td></tr>
`).join('') || `<tr><td><input class="pg-param-key" placeholder="param"></td><td><input class="pg-param-val" placeholder="value"></td><td><button class="pg-param-del" onclick="this.closest('tr').remove()">✕</button></td></tr>`;
// Docs
const doc = activeEp.doc;
document.getElementById('pg-docs-content').innerHTML = `
<div class="pg-doc-block">
<h4>${method} ${path}</h4>
<p class="pg-desc">${doc.desc}</p>
${doc.fields.length ? `
<table class="pg-params-doc">
<thead><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead>
<tbody>${doc.fields.map(f => `
<tr>
<td>${f.name} ${f.req ? '<span class="pg-req-badge">required</span>' : ''}</td>
<td style="color:var(--lt)">${f.type}</td>
<td>${f.desc}</td>
</tr>`).join('')}
</tbody>
</table>` : ''}
</div>`;
renderEndpointList(filteredEps);
// Clear response
document.getElementById('pg-response-body').innerHTML = `<div class="pg-response-empty"><div style="font-size:36px;opacity:.3">🔌</div><p style="font-size:13px;color:var(--lt)">Click <strong>Send</strong></p></div>`;
document.getElementById('pg-status-chip').style.display = 'none';
document.getElementById('pg-latency-chip').style.display = 'none';
}
// ── Send request ──────────────────────────────────────────────────────────────
async function sendRequest() {
const method = activeEp?.method || 'GET';
const rawPath = document.getElementById('pg-url').value.trim();
const apiKey = document.getElementById('pg-apikey').value.trim();
const bodyText = document.getElementById('pg-body').value.trim();
// Build query params
const paramRows = document.querySelectorAll('#pg-params-body tr');
const params = new URLSearchParams();
paramRows.forEach(row => {
const k = row.querySelector('.pg-param-key')?.value.trim();
const v = row.querySelector('.pg-param-val')?.value.trim();
if (k && v) params.set(k, v);
});
const url = params.toString() ? `${rawPath}?${params}` : rawPath;
const headers = { 'Content-Type': 'application/json' };
if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`;
const btn = document.getElementById('pg-send-btn');
btn.classList.add('loading'); btn.disabled = true;
const t0 = Date.now();
try {
const opts = { method, headers };
if (['POST','PUT','PATCH'].includes(method) && bodyText) {
opts.body = bodyText;
}
const r = await fetch(url, opts);
const elapsed = Date.now() - t0;
let text = await r.text();
let formatted = text;
try { formatted = JSON.stringify(JSON.parse(text), null, 2); } catch {}
showResponse(r.status, elapsed, formatted);
} catch (e) {
const elapsed = Date.now() - t0;
showResponse(0, elapsed, `// Network error or CORS issue\n// ${e.message}\n\n// Tip: If testing from outside the Cezen server,\n// ensure your API key is valid and the server is reachable.`);
}
btn.classList.remove('loading'); btn.disabled = false;
}
function showResponse(status, elapsed, body) {
const statusChip = document.getElementById('pg-status-chip');
const latChip = document.getElementById('pg-latency-chip');
statusChip.style.display = '';
latChip.style.display = '';
if (status === 0) {
statusChip.textContent = 'Error';
statusChip.className = 'pg-status-chip err';
} else {
statusChip.textContent = `${status} ${statusText(status)}`;
statusChip.className = `pg-status-chip ${status < 300 ? 'ok' : 'err'}`;
}
latChip.textContent = `${elapsed}ms`;
// Syntax-highlight JSON
const highlighted = body
.replace(/("(?:[^"\\]|\\.)*")\s*:/g, '<span style="color:#a78bfa">$1</span>:')
.replace(/:\s*("(?:[^"\\]|\\.)*")/g, ': <span style="color:#86efac">$1</span>')
.replace(/:\s*(\d+\.?\d*)/g, ': <span style="color:#fbbf24">$1</span>')
.replace(/:\s*(true|false|null)/g, ': <span style="color:#f472b6">$1</span>');
document.getElementById('pg-response-body').innerHTML = `<pre class="pg-response-pre">${highlighted}</pre>`;
}
function statusText(s) {
const map = {200:'OK',201:'Created',204:'No Content',400:'Bad Request',401:'Unauthorized',403:'Forbidden',404:'Not Found',409:'Conflict',422:'Unprocessable',500:'Internal Server Error'};
return map[s] || '';
}
// ── UI helpers ────────────────────────────────────────────────────────────────
function showTab(name, btn) {
document.querySelectorAll('.pg-tab').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.pg-tab-panel').forEach(p => p.classList.remove('active'));
btn.classList.add('active');
document.getElementById(`pg-tab-${name}`).classList.add('active');
}
function addParam() {
const tr = document.createElement('tr');
tr.innerHTML = `<td><input class="pg-param-key" placeholder="param"></td><td><input class="pg-param-val" placeholder="value"></td><td><button class="pg-param-del" onclick="this.closest('tr').remove()">✕</button></td>`;
document.getElementById('pg-params-body').appendChild(tr);
}
function formatJSON() {
const ta = document.getElementById('pg-body');
try { ta.value = JSON.stringify(JSON.parse(ta.value), null, 2); } catch { showToast('Invalid JSON'); }
}
function copyResponse() {
const pre = document.querySelector('.pg-response-pre');
if (!pre) { showToast('Nothing to copy'); return; }
navigator.clipboard.writeText(pre.innerText).then(() => showToast('Copied'));
}
async function pasteKey() {
try {
const text = await navigator.clipboard.readText();
document.getElementById('pg-apikey').value = text.trim();
showToast('API key pasted');
} catch { showToast('Clipboard access denied'); }
}
function loadFromStorage() {
const keys = JSON.parse(localStorage.getItem('cezen_apikeys_cache') || '[]');
if (keys.length) { document.getElementById('pg-apikey').value = keys[0].key || ''; showToast('Key loaded'); }
else showToast('No saved keys found');
}
function showToast(msg) {
const t = document.getElementById('pg-toast');
t.textContent = msg; t.classList.add('show');
setTimeout(() => t.classList.remove('show'), 2200);
}
// ── Init ──────────────────────────────────────────────────────────────────────
renderEndpointList(ENDPOINTS);
selectEndpoint('GET', '/api/health');
document.getElementById('pg-url').addEventListener('keydown', e => {
if (e.key === 'Enter') sendRequest();
});
</script>
<script src="auth.js"></script>
<script src="branding.js"></script>
</body>
</html>