645 lines
32 KiB
HTML
645 lines
32 KiB
HTML
<!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 <key></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.0–1.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 1–5 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>
|