Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>K-AI API — Feel free to AI</title> | |
| <meta name="description" content="K-AI API — Free AI proxy API. No signup, no API keys. Feel free to AI."> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link | |
| href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap" | |
| rel="stylesheet"> | |
| <!-- Chart.js --> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <style> | |
| :root { | |
| --bg-primary: #0a0a0f; | |
| --bg-secondary: #12121a; | |
| --bg-card: #16161f; | |
| --bg-card-hover: #1c1c28; | |
| --border: #2a2a3a; | |
| --text-primary: #f0f0f5; | |
| --text-secondary: #9898aa; | |
| --text-muted: #6b6b80; | |
| --accent: #6366f1; | |
| --accent-hover: #818cf8; | |
| --accent-glow: rgba(99, 102, 241, 0.15); | |
| --gradient-hero: linear-gradient(135deg, #6366f1 0%, #a855f7 50%, #ec4899 100%); | |
| --radius-lg: 16px; | |
| --radius-sm: 8px; | |
| /* Ranking Colors */ | |
| --success: #22c55e; | |
| --error: #ef4444; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Inter', -apple-system, sans-serif; | |
| background: var(--bg-primary); | |
| color: var(--text-primary); | |
| line-height: 1.6; | |
| } | |
| .container { | |
| max-width: 1000px; | |
| margin: 0 auto; | |
| padding: 0 24px; | |
| } | |
| /* ─── Hero ─── */ | |
| .hero { | |
| text-align: center; | |
| padding: 100px 0 80px; | |
| background: radial-gradient(circle at top center, rgba(99, 102, 241, 0.08) 0%, transparent 70%); | |
| } | |
| .hero h1 { | |
| font-size: 72px; | |
| font-weight: 900; | |
| letter-spacing: -3px; | |
| background: var(--gradient-hero); | |
| -webkit-background-clip: text; | |
| background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| margin-bottom: 8px; | |
| } | |
| .hero .motto { | |
| font-size: 24px; | |
| font-weight: 300; | |
| letter-spacing: 2px; | |
| color: var(--text-secondary); | |
| text-transform: uppercase; | |
| } | |
| .hero .desc { | |
| margin-top: 24px; | |
| font-size: 18px; | |
| color: var(--text-muted); | |
| max-width: 600px; | |
| margin-left: auto; | |
| margin-right: auto; | |
| } | |
| /* ─── Section ─── */ | |
| section { | |
| padding: 60px 0; | |
| border-top: 1px solid var(--bg-secondary); | |
| } | |
| .section-title { | |
| font-size: 28px; | |
| font-weight: 700; | |
| margin-bottom: 40px; | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| } | |
| .section-title .icon { | |
| width: 32px; | |
| height: 32px; | |
| background: var(--bg-card); | |
| border-radius: 8px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 16px; | |
| } | |
| /* ─── Endpoint Block ─── */ | |
| .endpoint-block { | |
| background: var(--bg-card); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius-lg); | |
| overflow: hidden; | |
| margin-bottom: 40px; | |
| } | |
| .endpoint-header { | |
| padding: 24px; | |
| border-bottom: 1px solid var(--border); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: flex-start; | |
| } | |
| .endpoint-title h3 { | |
| font-size: 20px; | |
| font-weight: 700; | |
| margin-bottom: 4px; | |
| } | |
| .endpoint-title p { | |
| color: var(--text-secondary); | |
| font-size: 14px; | |
| } | |
| .method-badge { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 13px; | |
| font-weight: 700; | |
| padding: 4px 10px; | |
| border-radius: 6px; | |
| background: rgba(99, 102, 241, 0.2); | |
| color: #818cf8; | |
| } | |
| .method-badge.GET { | |
| background: rgba(34, 197, 94, 0.2); | |
| color: #4ade80; | |
| } | |
| .method-badge.LIVE { | |
| background: rgba(245, 158, 11, 0.2); | |
| color: #fbbf24; | |
| } | |
| .endpoint-body { | |
| display: grid; | |
| grid-template-columns: 1.2fr 0.8fr; | |
| } | |
| .endpoint-body.full-width { | |
| grid-template-columns: 1fr; | |
| } | |
| @media (max-width: 800px) { | |
| .endpoint-body, | |
| #analytics-body { | |
| flex-direction: column ; | |
| display: flex ; | |
| } | |
| } | |
| .endpoint-docs { | |
| padding: 24px; | |
| border-right: 1px solid var(--border); | |
| } | |
| .endpoint-demo { | |
| padding: 24px; | |
| background: var(--bg-secondary); | |
| } | |
| .param-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| font-size: 14px; | |
| margin-top: 16px; | |
| } | |
| .param-table th { | |
| text-align: left; | |
| color: var(--text-muted); | |
| font-weight: 600; | |
| padding-bottom: 8px; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .param-table td { | |
| padding: 12px 0; | |
| /* More padding for ranking */ | |
| border-bottom: 1px solid var(--border); | |
| color: var(--text-secondary); | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 13px; | |
| } | |
| .param-table tr:last-child td { | |
| border-bottom: none; | |
| } | |
| .param-name { | |
| font-family: 'JetBrains Mono', monospace; | |
| color: var(--accent-hover); | |
| } | |
| .param-desc { | |
| color: var(--text-secondary); | |
| } | |
| .param-req { | |
| color: var(--text-muted); | |
| font-size: 12px; | |
| margin-left: 6px; | |
| } | |
| /* ─── Interactive Demo ─── */ | |
| .demo-label { | |
| font-size: 11px; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| color: var(--text-muted); | |
| margin-bottom: 12px; | |
| display: block; | |
| } | |
| .demo-input { | |
| width: 100%; | |
| background: var(--bg-input); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius-sm); | |
| padding: 10px; | |
| color: var(--text-primary); | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 13px; | |
| margin-bottom: 12px; | |
| } | |
| .demo-input:focus { | |
| outline: none; | |
| border-color: var(--accent); | |
| } | |
| .demo-select { | |
| width: 100%; | |
| background: var(--bg-card); | |
| color: var(--text-primary); | |
| padding: 10px; | |
| margin-bottom: 12px; | |
| border-radius: var(--radius-sm); | |
| border: 1px solid var(--border); | |
| } | |
| .demo-btn { | |
| width: 100%; | |
| padding: 8px 12px; | |
| /* Reduced padding */ | |
| background: var(--accent); | |
| color: white; | |
| border: none; | |
| border-radius: var(--radius-sm); | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .demo-btn:hover { | |
| background: var(--accent-hover); | |
| } | |
| .demo-btn:disabled { | |
| opacity: 0.5; | |
| cursor: wait; | |
| } | |
| .demo-response { | |
| margin-top: 16px; | |
| background: #0d0d14; | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius-sm); | |
| padding: 12px; | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 11px; | |
| /* Reduced font size */ | |
| color: var(--text-secondary); | |
| white-space: pre-wrap; | |
| display: none; | |
| max-height: 500px; | |
| /* Scrollable */ | |
| /* Scrollable */ | |
| overflow-y: auto; | |
| } | |
| .demo-response pre { | |
| white-space: pre-wrap; | |
| word-wrap: break-word; | |
| margin: 0; | |
| font-family: inherit; | |
| } | |
| .demo-status { | |
| margin-top: 12px; | |
| font-weight: 600; | |
| font-size: 13px; | |
| display: none; | |
| } | |
| .demo-response.visible { | |
| display: block; | |
| } | |
| .demo-response.success { | |
| border-color: rgba(34, 197, 94, 0.3); | |
| color: #4ade80; | |
| } | |
| .demo-response.error { | |
| border-color: rgba(239, 68, 68, 0.3); | |
| color: #f87171; | |
| } | |
| /* ─── Ranking Badges & Styles ─── */ | |
| .rank-badge { | |
| display: inline-block; | |
| width: 24px; | |
| height: 24px; | |
| line-height: 24px; | |
| text-align: center; | |
| border-radius: 50%; | |
| background: var(--border); | |
| color: var(--text-muted); | |
| font-weight: bold; | |
| font-size: 11px; | |
| } | |
| tr:nth-child(1) .rank-badge { | |
| background: #Eab308; | |
| color: #000; | |
| } | |
| /* Gold */ | |
| tr:nth-child(2) .rank-badge { | |
| background: #94a3b8; | |
| color: #000; | |
| } | |
| /* Silver */ | |
| tr:nth-child(3) .rank-badge { | |
| background: #b45309; | |
| color: #fff; | |
| } | |
| /* Bronze */ | |
| .score-good { | |
| color: var(--success); | |
| } | |
| .score-bad { | |
| color: var(--error); | |
| } | |
| /* Charts */ | |
| canvas { | |
| max-height: 250px; | |
| width: 100%; | |
| } | |
| /* ─── Footer ─── */ | |
| footer { | |
| text-align: center; | |
| padding: 40px; | |
| color: var(--text-muted); | |
| font-size: 14px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="hero"> | |
| <div class="container"> | |
| <h1>K-AI API</h1> | |
| <p class="motto">Feel free to AI</p> | |
| <p class="desc">The completely free AI proxy. No signup. No keys. Just code.</p> | |
| </div> | |
| </div> | |
| <div class="container"> | |
| <!-- POST /chat (Basic) --> | |
| <div class="endpoint-block"> | |
| <div class="endpoint-header"> | |
| <div class="endpoint-title"> | |
| <h3>Chat Completion</h3> | |
| <p>Send a message and get a response from the best available AI.</p> | |
| </div> | |
| <span class="method-badge">POST /v1/chat/completions</span> | |
| </div> | |
| <div class="endpoint-body"> | |
| <div class="endpoint-docs"> | |
| <span class="demo-label">Parameters</span> | |
| <table class="param-table"> | |
| <tr> | |
| <td><span class="param-name">message</span></td> | |
| <td><span class="param-desc">Your prompt</span><span class="param-req">(required)</span> | |
| </td> | |
| </tr> | |
| </table> | |
| <br> | |
| <span class="demo-label">Example Request</span> | |
| <div class="demo-response visible" style="color: #a78bfa;"> | |
| curl https://kiwa001-kai-api-gateway.hf.space/v1/chat/completions \ | |
| -H "Content-Type: application/json" \ | |
| -H "Authorization: Bearer YOUR_API_KEY" \ | |
| -d '{"model": "gemini-3-flash", "messages": [{"role": "user", "content": "Hello!"}]}' | |
| </div> | |
| <p style="color: var(--text-muted); font-size: 13px; margin-top: 10px;"> | |
| <strong>Note:</strong> Get your API key from the dashboard above. Dashboard requests don't require authentication. | |
| </p> | |
| </div> | |
| <div class="endpoint-demo"> | |
| <span class="demo-label">Try It Live</span> | |
| <textarea id="chat-basic-input" class="demo-input" rows="3" | |
| placeholder='{"message": "What is AI?"}'>What is the capital of France?</textarea> | |
| <select id="chat-basic-model" class="demo-select" style="margin-top:10px;"> | |
| <option value="gemini-3-flash">gemini-3-flash (Default)</option> | |
| <option value="gpt-4o">gpt-4o</option> | |
| <option value="gpt-4o-mini">gpt-4o-mini</option> | |
| <option value="glm-4">glm-4</option> | |
| <option value="mistral-large">mistral-large</option> | |
| </select> | |
| <button class="demo-btn" onclick="runDemo('chat-basic')">Run Request ▶</button> | |
| <div id="chat-basic-status" class="demo-status"></div> | |
| <select id="chat-basic-model" class="demo-select" style="margin-top:10px;"> | |
| <option value="gemini-3-flash">gemini-3-flash (Default)</option> | |
| <option value="gpt-4o">gpt-4o</option> | |
| <option value="gpt-4o-mini">gpt-4o-mini</option> | |
| <option value="glm-4">glm-4</option> | |
| <option value="mistral-large">mistral-large</option> | |
| </select> | |
| <div id="chat-basic-res" class="demo-response"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- GET /models --> | |
| <div class="endpoint-block"> | |
| <div class="endpoint-header"> | |
| <div class="endpoint-title"> | |
| <h3>List Models</h3> | |
| <p>Get all currently available AI models.</p> | |
| </div> | |
| <span class="method-badge GET">GET /models</span> | |
| </div> | |
| <div class="endpoint-body"> | |
| <div class="endpoint-docs"> | |
| <p style="color:var(--text-secondary); font-size:14px;">Returns a JSON list of all models supported | |
| by the API, ranked by quality.</p> | |
| </div> | |
| <div class="endpoint-demo"> | |
| <span class="demo-label">Try It Live</span> | |
| <button class="demo-btn" onclick="runDemo('models')">Fetch Models ▶</button> | |
| <div id="models-res" class="demo-response"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- POST /search & /deep_research --> | |
| <div class="endpoint-block"> | |
| <div class="endpoint-header"> | |
| <div class="endpoint-title"> | |
| <h3>Web Search & Research</h3> | |
| <p>Reverse-engineered web search and deep content gathering. Requires API key (free, no token deduction).</p> | |
| </div> | |
| <span class="method-badge">POST /search</span> | |
| </div> | |
| <div class="endpoint-body"> | |
| <div class="endpoint-docs"> | |
| <span class="demo-label">Endpoints</span> | |
| <ul style="color:var(--text-secondary); font-size:13px; margin-left:18px; margin-bottom:10px;"> | |
| <li><code>/search</code>: Standard web search (Links)</li> | |
| <li><code>/deep_research</code>: Deep content gathering (Scraper)</li> | |
| </ul> | |
| <span class="demo-label">Parameters</span> | |
| <table class="param-table"> | |
| <tr> | |
| <td><span class="param-name">query</span></td> | |
| <td><span class="param-desc">Search topic</span><span class="param-req">(required)</span> | |
| </td> | |
| </tr> | |
| </table> | |
| <br> | |
| <span class="demo-label">Example Request</span> | |
| <div class="demo-response visible" style="color: #a78bfa; font-size: 11px;"> | |
| curl -X POST https://kiwa001-kai-api-gateway.hf.space/search \ | |
| -H "Content-Type: application/json" \ | |
| -H "Authorization: Bearer YOUR_API_KEY" \ | |
| -d '{"query": "Machine Learning", "limit": 5}' | |
| </div> | |
| <p style="color: var(--text-muted); font-size: 12px; margin-top: 8px;"> | |
| <strong>Note:</strong> Web searches are free but require an API key. | |
| </p> | |
| </div> | |
| <div class="endpoint-demo"> | |
| <span class="demo-label">Try It Live</span> | |
| <input id="search-query" class="demo-input" placeholder="Query" value="When was Python released?"> | |
| <select id="search-mode" class="demo-select"> | |
| <option value="search">Simple Search (Links)</option> | |
| <option value="deep">Deep Research (Content Gathering)</option> | |
| </select> | |
| <button class="demo-btn" onclick="runSearch()">Execute Search ▶</button> | |
| <div id="search-res" class="demo-response"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <footer>K-AI API — Feel free to AI</footer> | |
| <script> | |
| // Global flag for Pie Chart | |
| window.pieChartRendered = false; | |
| async function runDemo(type) { | |
| const resBox = document.getElementById(type + '-res'); | |
| const statusBox = document.getElementById(type + '-status'); | |
| resBox.className = 'demo-response visible'; | |
| resBox.style.display = 'none'; // Hide content while loading | |
| resBox.innerHTML = ''; | |
| if (statusBox) { | |
| statusBox.style.display = 'block'; | |
| statusBox.innerHTML = 'Sending Request... <span class="loading-spin"></span>'; | |
| statusBox.style.color = 'var(--text-muted)'; | |
| } | |
| const startTime = Date.now(); | |
| try { | |
| let url, body, method = 'POST'; | |
| let headers = { | |
| 'Content-Type': 'application/json' | |
| // No Authorization header needed for dashboard requests | |
| }; | |
| if (type === 'chat-basic') { | |
| // Simple Chat | |
| const inputVal = document.getElementById('chat-basic-input').value; | |
| const modelVal = document.getElementById('chat-basic-model').value || "gemini-3-flash"; | |
| if (!inputVal) { alert("Please enter a message"); return; } | |
| url = '/v1/chat/completions'; | |
| body = { | |
| model: modelVal, | |
| messages: [{ role: "user", content: inputVal }] | |
| }; | |
| } | |
| else if (type === 'chat-adv') { | |
| // Advanced Chat | |
| const model = document.getElementById('chat-adv-model').value || "gemini-3-flash"; | |
| const userMsg = document.getElementById('chat-adv-msg').value; | |
| if (!userMsg) { alert("Please enter a message"); return; } | |
| url = '/v1/chat/completions'; | |
| body = { | |
| model: model, | |
| messages: [ | |
| { role: "user", content: userMsg } | |
| ] | |
| }; | |
| } | |
| else if (type === 'models') { | |
| // List Models | |
| url = '/models'; // This is GET, no body | |
| method = 'GET'; | |
| body = undefined; | |
| // Keep models as public? Or require auth? | |
| // Usually /models is authenticated in OpenAI but let's keep it open for now or add auth. | |
| } | |
| const response = await fetch(url, { | |
| method: method, | |
| headers: headers, | |
| body: body ? JSON.stringify(body) : undefined | |
| }); | |
| const data = await response.json(); | |
| const duration = Date.now() - startTime; | |
| if (!response.ok) throw new Error(data.detail || 'Request failed'); | |
| if (!response.ok) throw new Error(data.detail || 'Request failed'); | |
| if (statusBox) { | |
| statusBox.innerHTML = `Success (${duration}ms)`; | |
| statusBox.style.color = 'var(--success)'; | |
| } | |
| resBox.style.display = 'block'; | |
| resBox.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`; | |
| } catch (err) { | |
| resBox.innerHTML = ` | |
| <div style="margin-bottom:5px; font-weight:bold; color:var(--error);"> | |
| Error | |
| </div> | |
| <pre>${err.message}</pre> | |
| `; | |
| } | |
| } | |
| async function runSearch() { | |
| const query = document.getElementById('search-query').value; | |
| const mode = document.getElementById('search-mode').value; | |
| const resBox = document.getElementById('search-res'); | |
| if (!query) { alert("Please enter a query"); return; } | |
| resBox.className = 'demo-response visible'; | |
| resBox.textContent = '⏳ Searching... (Deep Research may take 10s+)'; | |
| const endpoint = mode === 'deep' ? '/deep_research' : '/search'; | |
| try { | |
| const res = await fetch(endpoint, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ query: query }) | |
| }); | |
| const data = await res.json(); | |
| resBox.className = 'demo-response visible ' + (res.ok ? 'success' : 'error'); | |
| resBox.innerText = JSON.stringify(data, null, 2); | |
| } catch (e) { | |
| resBox.innerText = 'Error: ' + e.message; | |
| } | |
| } | |
| </script> | |
| <!-- API Key Usage Checker Section --> | |
| <section style="border-top: 1px solid var(--border); padding: 60px 0; background: var(--bg-secondary);"> | |
| <div class="container"> | |
| <h2 class="section-title">Check Your API Usage</h2> | |
| <p style="color: var(--text-muted); margin-bottom: 30px;">Enter your API key to see your current token usage and remaining quota.</p> | |
| <div style="background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 30px; max-width: 800px; margin: 0 auto;"> | |
| <div style="display: flex; gap: 15px; margin-bottom: 25px; flex-wrap: wrap;"> | |
| <input type="text" id="lookup-token-input" placeholder="Enter your API key (sk-...)" | |
| style="flex: 1; min-width: 300px; background: var(--bg-primary); border: 1px solid var(--border); color: var(--text-primary); | |
| padding: 14px 18px; border-radius: 8px; font-family: 'JetBrains Mono', monospace; font-size: 15px;"> | |
| <button onclick="lookupTokenUsage()" | |
| style="background: var(--accent); color: white; border: none; padding: 14px 28px; | |
| border-radius: 8px; font-weight: 600; cursor: pointer; font-size: 15px; white-space: nowrap;"> | |
| Check Usage | |
| </button> | |
| </div> | |
| <div id="lookup-error" style="display: none; margin-bottom: 20px; padding: 12px 16px; background: rgba(239, 68, 68, 0.1); | |
| border: 1px solid var(--error); border-radius: 8px; color: var(--error); font-size: 14px;"> | |
| </div> | |
| <div id="lookup-result" style="display: none;"> | |
| <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 25px; margin-bottom: 25px;"> | |
| <div style="background: var(--bg-primary); padding: 20px; border-radius: 8px; border: 1px solid var(--border);"> | |
| <div style="color: var(--text-muted); font-size: 13px; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px;">Key Name</div> | |
| <div id="lookup-name" style="font-size: 20px; font-weight: 600; color: var(--text-primary);">-</div> | |
| </div> | |
| <div style="background: var(--bg-primary); padding: 20px; border-radius: 8px; border: 1px solid var(--border);"> | |
| <div style="color: var(--text-muted); font-size: 13px; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px;">Status</div> | |
| <div id="lookup-status" style="font-size: 20px; font-weight: 600;">-</div> | |
| </div> | |
| </div> | |
| <div style="background: var(--bg-primary); padding: 25px; border-radius: 8px; border: 1px solid var(--border);"> | |
| <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;"> | |
| <span style="color: var(--text-muted); font-size: 14px;">Token Usage</span> | |
| <span id="lookup-percentage" style="font-weight: 700; font-size: 18px; color: var(--text-primary);">0%</span> | |
| </div> | |
| <div style="width: 100%; height: 10px; background: var(--border); border-radius: 5px; overflow: hidden; margin-bottom: 15px;"> | |
| <div id="lookup-progress" style="height: 100%; width: 0%; background: var(--success); transition: all 0.3s ease;"></div> | |
| </div> | |
| <div style="display: flex; justify-content: space-between; font-family: 'JetBrains Mono', monospace; font-size: 14px;"> | |
| <span id="lookup-used" style="color: var(--text-muted);">Used: 0</span> | |
| <span id="lookup-remaining" style="color: var(--success); font-weight: 600;">Remaining: 1000</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <script> | |
| // API Key Usage Lookup Function | |
| async function lookupTokenUsage() { | |
| const tokenInput = document.getElementById('lookup-token-input'); | |
| const token = tokenInput.value.trim(); | |
| const resultDiv = document.getElementById('lookup-result'); | |
| const errorDiv = document.getElementById('lookup-error'); | |
| if (!token) { | |
| errorDiv.style.display = 'block'; | |
| errorDiv.textContent = 'Please enter your API key'; | |
| resultDiv.style.display = 'none'; | |
| return; | |
| } | |
| if (!token.startsWith('sk-')) { | |
| errorDiv.style.display = 'block'; | |
| errorDiv.textContent = 'Invalid API key format. Should start with "sk-"'; | |
| resultDiv.style.display = 'none'; | |
| return; | |
| } | |
| // Show loading | |
| resultDiv.style.display = 'block'; | |
| errorDiv.style.display = 'none'; | |
| document.getElementById('lookup-name').textContent = 'Loading...'; | |
| try { | |
| const res = await fetch('/qaz/keys/lookup', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ token: token }) | |
| }); | |
| if (!res.ok) { | |
| let errorMessage = 'Key not found'; | |
| try { | |
| const error = await res.json(); | |
| errorMessage = error.detail || error.message || JSON.stringify(error) || 'Key not found'; | |
| } catch (parseError) { | |
| errorMessage = 'Key not found or server error'; | |
| } | |
| throw new Error(errorMessage); | |
| } | |
| const data = await res.json(); | |
| // Update display | |
| document.getElementById('lookup-name').textContent = data.name || 'Unnamed Key'; | |
| const statusEl = document.getElementById('lookup-status'); | |
| if (data.is_active) { | |
| statusEl.textContent = 'Active'; | |
| statusEl.style.color = 'var(--success)'; | |
| } else { | |
| statusEl.textContent = 'Inactive'; | |
| statusEl.style.color = 'var(--error)'; | |
| } | |
| const usagePercent = data.limit_tokens > 0 ? Math.round((data.usage_tokens / data.limit_tokens) * 100) : 0; | |
| document.getElementById('lookup-percentage').textContent = usagePercent + '%'; | |
| document.getElementById('lookup-used').textContent = `Used: ${data.usage_tokens.toLocaleString()}`; | |
| document.getElementById('lookup-remaining').textContent = `Remaining: ${data.remaining.toLocaleString()}`; | |
| const progressEl = document.getElementById('lookup-progress'); | |
| progressEl.style.width = Math.min(usagePercent, 100) + '%'; | |
| // Color coding | |
| if (usagePercent > 90) { | |
| progressEl.style.background = 'var(--error)'; | |
| document.getElementById('lookup-remaining').style.color = 'var(--error)'; | |
| } else if (usagePercent > 50) { | |
| progressEl.style.background = 'var(--warning)'; | |
| document.getElementById('lookup-remaining').style.color = 'var(--warning)'; | |
| } else { | |
| progressEl.style.background = 'var(--success)'; | |
| document.getElementById('lookup-remaining').style.color = 'var(--success)'; | |
| } | |
| } catch (e) { | |
| errorDiv.style.display = 'block'; | |
| const errorMsg = e.message || (typeof e === 'object' ? JSON.stringify(e) : String(e)); | |
| errorDiv.textContent = 'Error: ' + errorMsg; | |
| resultDiv.style.display = 'none'; | |
| } | |
| } | |
| // Allow Enter key to submit | |
| document.getElementById('lookup-token-input')?.addEventListener('keypress', function(e) { | |
| if (e.key === 'Enter') { | |
| lookupTokenUsage(); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |