| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>API Information Activity Testing</title> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <link rel="icon" href="https://openai.com/favicon.ico" type="image/x-icon"> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/layui/2.6.8/css/layui.min.css"> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/layui/2.6.8/layui.min.js"></script> |
| <style> |
| body { |
| font-family: Arial, sans-serif; |
| margin: 40px; |
| } |
| |
| .container { |
| width: 100%; |
| max-width: 600px; |
| margin: auto; |
| padding: 20px; |
| box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); |
| } |
| |
| .response-container { |
| width: 100%; |
| margin: 20px auto 0; |
| max-width: 1000px; |
| padding: 20px; |
| box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); |
| font-size: 16px; |
| } |
| |
| input[type="text"], textarea { |
| width: 100%; |
| padding: 10px; |
| margin: 10px 0; |
| box-sizing: border-box; |
| } |
| |
| textarea { |
| height: 100px; |
| } |
| |
| .submit-container { |
| display: flex; |
| justify-content: space-between; |
| flex-wrap: wrap; |
| } |
| |
| .submit-container input[type="button"] { |
| width: 30%; |
| padding: 10px; |
| border: none; |
| cursor: pointer; |
| margin-top: 10px; |
| } |
| |
| .submit-query { |
| background-color: #007bff; |
| color: white; |
| } |
| |
| .check-quota { |
| background-color: #28a745; |
| color: white; |
| } |
| |
| .clear-form { |
| background-color: #dc3545; |
| color: white; |
| } |
| |
| .model-input-container { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| margin-bottom: 10px; |
| } |
| |
| .model-input-container input[type="text"] { |
| width: 70%; |
| margin-right: 10px; |
| } |
| |
| .model-input-container input[type="button"] { |
| width: 28%; |
| background-color: #fe9307; |
| color: white; |
| border: none; |
| cursor: pointer; |
| } |
| |
| h1 { |
| font-weight: bold; |
| margin-bottom: 20px; |
| } |
| |
| h2, h3 { |
| margin-top: 20px; |
| text-align: center; |
| } |
| |
| .error { |
| color: red; |
| margin-bottom: 20px; |
| } |
| |
| |
| .response-container pre { |
| white-space: pre-wrap; |
| border: 1px solid #ddd; |
| padding: 10px; |
| background-color: #f9f9f9; |
| margin-bottom: 20px; |
| } |
| |
| .model-timeout-concurrency { |
| display: flex; |
| justify-content: space-between; |
| margin-top: 10px; |
| } |
| |
| .model-timeout, .model-concurrency { |
| width: 48%; |
| } |
| |
| .model-timeout input, .model-concurrency input { |
| width: 100%; |
| } |
| |
| table { |
| |
| border-collapse: collapse; |
| margin-top: 20px; |
| } |
| |
| th, td { |
| border: 1px solid #ddd; |
| padding: 8px; |
| text-align: left; |
| } |
| |
| th { |
| background-color: #f2f2f2; |
| } |
| |
| .copy-btn { |
| cursor: pointer; |
| color: blue; |
| text-decoration: underline; |
| } |
| |
| h1, h2 { |
| color: #007bff; |
| text-align: center; |
| } |
| |
| .td1 { |
| width: 250px; |
| } |
| |
| .td1-ok { |
| color: green; |
| } |
| |
| .td1-no { |
| color: coral; |
| } |
| |
| .td2 { |
| width: 200px; |
| } |
| |
| .td4 { |
| max-width: 350px; |
| max-height: 100px; |
| } |
| |
| |
| .td3 { |
| width: 100px; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <h1>API Information Live Test</h1> |
| <h3>(Compatible with oneapi/newapi relay formats)</h3> |
|
|
| <form id="apiForm"> |
| <textarea id="api_info" name="api_info" |
| placeholder="Lazy person’s text box, supports simultaneous pasting of interface address and key, intelligent extraction, e.g., https://api.openai.com, sk-NiansuhAI"></textarea> |
| <input type="text" id="api_url" name="api_url" placeholder="Interface address, e.g., https://api.openai.com" value=""> |
| <input type="text" id="api_key" name="api_key" placeholder="Key, e.g., sk-NiansuhAI" value=""> |
| <div class="model-input-container" id="model-input-container"> |
| <input type="text" id="model_name" name="model_name" placeholder="Model name, separate multiple models with commas"> |
| <input type="button" value="Get Model List" style="height: 50px" onclick="getModelList()"> |
| </div> |
| <div id="modelCheckboxes"></div> |
| <div class="model-timeout-concurrency"> |
| <div class="model-timeout"> |
| <label for="model_timeout">Set Request Timeout (seconds):</label> |
| <input type="number" style="height: 30px;width: 70px" id="model_timeout" name="model_timeout" value="10" |
| min="1"> |
| </div> |
| <div class="model-concurrency" style="height: 50px"> |
| <label for="model_concurrency">Set Request Concurrency:</label> |
| <input type="number" style="height: 30px;width: 70px" id="model_concurrency" name="model_concurrency" |
| value="5" min="1"> |
| </div> |
| </div> |
| <div class="submit-container"> |
| <input type="button" value="Test Model" onclick="testModels()" class="submit-query"> |
| <input type="button" value="Check Quota" onclick="checkQuota()" class="check-quota"> |
| <input type="button" value="Clear Form" onclick="clearForm()" class="clear-form"> |
| </div> |
| </form> |
| </div> |
| <div id="results" class="response-container"></div> |
|
|
| <script> |
| |
| document.getElementById('api_info').addEventListener('input', function () { |
| let text = this.value; |
| let urlPattern = /(https?:\/\/[^\s,。、!,;;\n]+)/; |
| let keyPattern = /(sk-[a-zA-Z0-9]+)/; |
| |
| let urlMatch = text.match(urlPattern); |
| let keyMatch = text.match(keyPattern); |
| |
| if (urlMatch) { |
| document.getElementById('api_url').value = urlMatch[0]; |
| } |
| if (keyMatch) { |
| document.getElementById('api_key').value = keyMatch[0]; |
| } |
| }); |
| |
| function getModelList() { |
| const apiUrl = document.getElementById('api_url').value; |
| const apiKey = document.getElementById('api_key').value; |
| console.log(apiUrl, apiKey); |
| |
| layui.use('layer', function () { |
| var layer = layui.layer; |
| |
| layer.load(); |
| fetch(`${apiUrl}/v1/models`, { |
| headers: { |
| 'Authorization': `Bearer ${apiKey}`, |
| 'Content-Type': 'application/json' |
| } |
| }) |
| .then(response => response.json()) |
| .then(data => { |
| layer.closeAll('loading'); |
| const models = data.data.map(model => model.id); |
| models.sort(); |
| displayModelCheckboxes(models); |
| }) |
| .catch(error => { |
| layer.closeAll('loading'); |
| layer.alert('Failed to get model list: ' + error.message); |
| }); |
| }); |
| } |
| |
| |
| function displayModelCheckboxes(models) { |
| layui.use(['layer', 'form'], function () { |
| var layer = layui.layer; |
| var form = layui.form; |
| |
| let content = '<div style="padding: 20px;"><form class="layui-form">'; |
| content += '<div id="selectedCount">0 models selected</div>'; |
| |
| content += ` |
| <div class="layui-form-item"> |
| <input type="checkbox" lay-skin="primary" lay-filter="checkAll" title="Select All"> |
| </div> |
| `; |
| |
| models.forEach((model, index) => { |
| content += ` |
| <div class="layui-form-item"> |
| <input type="checkbox" name="models[${index}]" value="${model}" title="${model}" lay-skin="primary"> |
| </div> |
| `; |
| }); |
| |
| content += '</form></div>'; |
| |
| layer.open({ |
| type: 1, |
| title: 'Select Models', |
| content: content, |
| area: ['300px', '400px'], |
| btn: ['Confirm', 'Cancel'], |
| success: function (layero, index) { |
| form.render('checkbox'); |
| form.on('checkbox', function (data) { |
| updateSelectedCount(layero); |
| }); |
| form.on('checkbox(checkAll)', function (data) { |
| var child = layero.find('input[type="checkbox"]').not(data.elem); |
| child.each(function () { |
| this.checked = data.elem.checked; |
| }); |
| form.render('checkbox'); |
| updateSelectedCount(layero); |
| }); |
| }, |
| yes: function (index, layero) { |
| const selectedModels = layero.find('input[name^="models"]:checked').map(function () { |
| return this.value; |
| }).get(); |
| document.getElementById('model_name').value = selectedModels.join(','); |
| layer.close(index); |
| } |
| }); |
| }); |
| } |
| |
| |
| |
| function updateSelectedCount(layero) { |
| const selectedCount = layero.find('input[name^="models"]:checked').length; |
| layero.find('#selectedCount').text(`Selected ${selectedCount} Model`); |
| } |
| |
| async function testModels() { |
| const apiUrl = document.getElementById('api_url').value; |
| const apiKey = document.getElementById('api_key').value; |
| const modelNames = document.getElementById('model_name').value.split(',').map(m => m.trim()).filter(m => m); |
| const timeout = parseInt(document.getElementById('model_timeout').value) * 1000; |
| const concurrency = parseInt(document.getElementById('model_concurrency').value); |
| |
| if (modelNames.length === 0) { |
| layui.use('layer', function () { |
| var layer = layui.layer; |
| layer.alert('Please enter at least one model name or select a model from the list.'); |
| }); |
| return; |
| } |
| |
| layui.use('layer', function () { |
| var layer = layui.layer; |
| layer.load(); |
| |
| const results = { |
| valid: [], |
| invalid: [], |
| inconsistent: [] |
| }; |
| |
| async function testModel(model) { |
| const controller = new AbortController(); |
| const id = setTimeout(() => controller.abort(), timeout); |
| const startTime = Date.now(); |
| |
| var response_text; |
| try { |
| const response = await fetch(`${apiUrl}/v1/chat/completions`, { |
| method: 'POST', |
| headers: { |
| 'Authorization': `Bearer ${apiKey}`, |
| 'Content-Type': 'application/json' |
| }, |
| body: JSON.stringify({ |
| model: model, |
| messages: [{role: "user", content: "Say this is a test!"}] |
| }), |
| signal: controller.signal |
| }); |
| |
| const endTime = Date.now(); |
| const responseTime = (endTime - startTime) / 1000; |
| |
| if (response.ok) { |
| const data = await response.json(); |
| const returnedModel = data.model; |
| if (returnedModel === model) { |
| results.valid.push({model, responseTime}); |
| console.log(`Test API endpoint:${apiUrl} Test model:${model} Model consistent, response time:${responseTime.toFixed(2)} seconds`); |
| } else { |
| results.inconsistent.push({model, returnedModel, responseTime}); |
| console.log(`Test API endpoint:${apiUrl} Test model:${model} Model inconsistent, expected:${model},Actual:${returnedModel},Response time:${responseTime.toFixed(2)} seconds`); |
| } |
| } else { |
| response_text = await response.text(); |
| results.invalid.push({model, response_text}) |
| console.log(`Test API endpoint:${apiUrl} Test model:${model} Model unavailable, response:${response.status} ${response.statusText} ${response_text}`); |
| } |
| } catch (error) { |
| if (error.name === 'AbortError') { |
| results.invalid.push({model, error: 'Timeout'}); |
| console.log(`Test API endpoint: ${apiUrl} Test model: ${model} Model unavailable (timeout)`); |
| } else { |
| results.invalid.push({model, error: error.message}); |
| console.log(`Test API endpoint: ${apiUrl} Test model: ${model} Model unavailable, error: ${error.message}`); |
| } |
| } finally { |
| clearTimeout(id); |
| } |
| } |
| |
| async function runBatch(models) { |
| const promises = models.map(model => testModel(model)); |
| await Promise.all(promises); |
| } |
| |
| async function runAllTests() { |
| for (let i = 0; i < modelNames.length; i += concurrency) { |
| const batch = modelNames.slice(i, i + concurrency); |
| await runBatch(batch); |
| } |
| |
| layer.closeAll('loading'); |
| displayResults(results); |
| showSummary(results); |
| } |
| |
| runAllTests().catch(error => { |
| layer.closeAll('loading'); |
| layer.alert('Error occurred while testing the model:' + error.message); |
| }); |
| }); |
| } |
| |
| function showSummary(results) { |
| const validCount = results.valid.length; |
| const inconsistentCount = results.inconsistent.length; |
| const invalidCount = results.invalid.length; |
| const totalCount = validCount + inconsistentCount + invalidCount; |
| |
| layui.use('layer', function () { |
| var layer = layui.layer; |
| layer.alert(`Test summary:<br> |
| Tested a total of ${totalCount} models<br> |
| Among them:<br> |
| - ${validCount} Models are available and consistent<br> |
| - ${inconsistentCount} Models are available but inconsistent<br> |
| - ${invalidCount} Models are unavailable`, |
| {title: 'Summary of test results'} |
| ); |
| }); |
| } |
| |
| |
| |
| function displayResults(results) { |
| var resultsDiv = document.getElementById('results'); |
| var content = '<h2>Test results</h2>' + |
| '<table>' + |
| '<tr>' + |
| '<th class="td1">Status</th>' + |
| '<th class="td2">Model name</th>' + |
| '<th class="td3">Response time (seconds)</th>' + |
| '<th class="td4">Remarks</th>' + |
| '</tr>'; |
| |
| results.valid.forEach(function (r) { |
| content += '<tr>' + |
| '<td class="td1 td1-ok">Models are consistent and available</td>' + |
| '<td class="td2"><span class="copy-btn" onclick="copyText(\'' + r.model + '\')">' + r.model + '</span></td>' + |
| '<td class="td3">' + r.responseTime.toFixed(2) + '</td>' + |
| '<td class="td4">good</td>' + |
| '</tr>'; |
| }); |
| |
| results.inconsistent.forEach(function (r) { |
| content += '<tr>' + |
| '<td class="td1 td1-no" >Models are inconsistent; is TNND faking it?</td>' + |
| '<td class="td2"><span class="copy-btn" onclick="copyText(\'' + r.model + '\')">' + r.model + '</span></td>' + |
| '<td class="td3">' + r.responseTime.toFixed(2) + '</td>' + |
| '<td class="td4">Return: ' + r.returnedModel + '</td>' + |
| '</tr>'; |
| }); |
| |
| results.invalid.forEach(function (r) { |
| content += '<tr>' + |
| '<td class="td1 ">Models are unavailable; what’s happening?</td>' + |
| '<td class="td2"><span class="copy-btn" onclick="copyText(\'' + r.model + '\')">' + r.model + '</span></td>' + |
| '<td class="td3">-</td>' + |
| '<td class="td4">' + (r.response_text || r.error) + '</td>' + |
| '</tr>'; |
| }); |
| |
| content += '</table>'; |
| resultsDiv.innerHTML = content; |
| } |
| |
| |
| function copyText(text) { |
| navigator.clipboard.writeText(text).then(() => { |
| layui.use('layer', function () { |
| var layer = layui.layer; |
| layer.msg('Model name has been copied to the clipboard'); |
| }); |
| }).catch(err => { |
| console.error('Copy failed:', err); |
| }); |
| } |
| |
| |
| |
| function checkQuota() { |
| const apiUrl = document.getElementById('api_url').value; |
| const apiKey = document.getElementById('api_key').value; |
| |
| layui.use('layer', function () { |
| var layer = layui.layer; |
| layer.load(); |
| |
| let quotaInfo, usedInfo, remainInfo; |
| |
| |
| fetch(`${apiUrl}/dashboard/billing/subscription`, { |
| headers: {'Authorization': `Bearer ${apiKey}`} |
| }) |
| .then(response => response.json()) |
| .then(quotaData => { |
| quotaInfo = quotaData.hard_limit_usd ? `${quotaData.hard_limit_usd.toFixed(2)} $` : 'Unable to retrieve quota information'; |
| |
| |
| const today = new Date(); |
| const year = today.getFullYear(); |
| const month = String(today.getMonth() + 1).padStart(2, '0'); |
| const day = String(today.getDate()).padStart(2, '0'); |
| const startDate = `${year}-${month}-01`; |
| const endDate = `${year}-${month}-${day}`; |
| |
| return fetch(`${apiUrl}/dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`, { |
| headers: {'Authorization': `Bearer ${apiKey}`} |
| }); |
| }) |
| .then(response => response.json()) |
| .then(usageData => { |
| usedInfo = `${(usageData.total_usage / 100).toFixed(2)} $`; |
| |
| |
| const quotaNumber = parseFloat(quotaInfo); |
| const usedNumber = parseFloat(usedInfo); |
| if (!isNaN(quotaNumber) && !isNaN(usedNumber)) { |
| remainInfo = `${(quotaNumber - usedNumber).toFixed(2)} $`; |
| } else { |
| remainInfo = 'Unable to calculate remaining quota'; |
| } |
| |
| const showInfo = `Available quota is: ${remainInfo}\n\nUsed quota is ${usedInfo}\n\nTotal quota is: ${quotaInfo}`; |
| layer.closeAll('loading'); |
| layer.alert(showInfo); |
| }) |
| .catch(error => { |
| layer.closeAll('loading'); |
| layer.alert('Quota check failed":" ' + error.message); |
| }); |
| }); |
| } |
| |
| |
| function clearForm() { |
| document.getElementById('apiForm').reset(); |
| document.getElementById('results').innerHTML = ''; |
| } |
| </script> |
| </body> |
| </html> |