【api 测活工具++】纯前端版本+ 模型验证 (v1.3 版本已加入官转验证 去新帖)

参考了这位老哥的代码

纯本地前端版本,解决卡死问题

先上链接 https://api-checker.kici.me/

手动输入模型 or 下拉框+可全选

检测模型一致 and 可用性
测试结果

v1.1 新增一键复制结果& 版权信息

v1.1.5 修复两个小问题

        if (urlMatch) {
            //去除末尾/后的空格 其他字符 保留到最后一个/前面
            let cleanUrl = urlMatch[0].match(/(.*)\/.*/)[1];
            //如果. 存在则使用
            if (cleanUrl.includes('.')) {
                document.getElementById('api_url').value = cleanUrl;
                console.log(cleanUrl);
            } else {
                document.getElementById('api_url').value = urlMatch[0];
                console.log(urlMatch[0]);
            }
        }
let results = {
        valid: [],
        invalid: [],
        inconsistent: [],
    };

    async function testModels() {
        results = {
            valid: [],
            invalid: [],
            inconsistent: [],
        };

**v1.1.6 部分地区 无法live2d ** 去除live2d
cfwoker

addEventListener('fetch', event => {
    event.respondWith(handleRequest(event.request))
    })
    
    async function handleRequest(request) {
    const html = `
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>API 信息测活</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"], .copy-btn {
            width: 30%;
            padding: 10px;
            border: none;
            cursor: pointer;
            margin-top: 10px;
        }
        .copy-btn {
            margin-left: 10%;
            width: 20%;
        }
        .copy-btn2{
            width: 20%;
        }

        .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 {
            width: 90%; /* 或任何其他固定宽度 */
            margin-left: auto;
            margin-right: auto;
            border-collapse: collapse;
            margin-top: 20px;
        }

        th, td {
            border: 1px solid #ddd;
            padding: 8px;
            text-align: left;
        }

        th {
            background-color: #f2f2f2;
        }


        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 添加样式 */
        .td3 {
            width: 100px; /* 或者设置一个具体的宽度 */
        }

        .copy-buttons {
            margin: 10px 0;
        }
        .copyright {
            margin-top: 20px;
            text-align: center;
            font-size: 14px;
            color: #666;
        }

        .copyright img {
            width: 24px;
            height: 24px;
            border-radius: 50%;
            margin-right: 5px;
            vertical-align: middle;
        }

        .copyright a {
            color: #1e88e5;
            text-decoration: none;
        }

        .copyright a:hover {
            text-decoration: underline;
        }
        </style>
    </head>
    <body>
    <div class="container">
        <h1>API 信息测活</h1>
        <h3>(适配 oneapi/newapi 等中转格式)</h3>
    
        <form id="apiForm">
                      <textarea id="api_info" name="api_info"
                                placeholder="懒人专用文本框,支持同时粘贴接口地址和密钥,智能提取,如:https://api.openai.com,sk-TodayIsThursdayVme50ForKFC"></textarea>
            <input type="text" id="api_url" name="api_url" placeholder="接口地址,如:https://api.openai.com" value="">
            <input type="text" id="api_key" name="api_key" placeholder="密钥,如:sk-TodayIsThursdayVme50ForKFC" value="">
            <div class="model-input-container" id="model-input-container">
                <input type="text" id="model_name" name="model_name" placeholder="模型名称,用逗号分隔多个模型">
                <input type="button" value="获取模型列表" style="height: 50px" onclick="getModelList()">
            </div>
            <div id="modelCheckboxes"></div>
            <div class="model-timeout-concurrency">
                <div class="model-timeout">
                    <label for="model_timeout">设置请求超时(秒):</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">设置请求并发数量:</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="测试模型" onclick="testModels()" class="submit-query">
                <input type="button" value="检查额度" onclick="checkQuota()" class="check-quota">
                <input type="button" value="清空表单" onclick="clearForm()" class="clear-form">
            </div>
        </form>
    </div>
    <div class="copyright">
        <!--    不要删除感谢 Rick 和 Megasoft 的贡献-->
        <p> © 2024 linuxdoer 版权所有<br>贡献者:<a href="https://linux.do/u/rick" target="_blank"><img
                src="https://linux.do/user_avatar/linux.do/rick/288/137821_2.png" alt="rick">rick </a>和<a
                href="https://linux.do/u/zhong_little" target="_blank"><img
                src="https://linux.do/user_avatar/linux.do/zhong_little/288/104887_2.png" alt="Megasoft">Megasoft</a></p>
    </div>
    <div id="results" class="response-container"></div>
    
    <script>
      
        // 智能提取API信息
        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) {
                let cleanUrl = urlMatch[0].match(/(.*)\\/.*\/)[1];
                if (cleanUrl.includes('.')) {
                    document.getElementById('api_url').value = cleanUrl;
                    console.log(cleanUrl);
                } else {
                document.getElementById('api_url').value = urlMatch[0];
                    console.log(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('获取模型列表失败: ' + 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 个模型</div>';
    
                      content += \`
                              <div class="layui-form-item">
                                  <input type="checkbox" lay-skin="primary" lay-filter="checkAll" title="全选">
                              </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: '选择模型',
                          content: content,
                          area: ['300px', '400px'],
                          btn: ['确定', '取消'],
                          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 (index, item) {
                                      item.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(\`已选择 \${selectedCount} 个模型\`);
              }
              let results = {
                          valid: [],
                          invalid: [],
                          inconsistent: []
                      };
    
              async function testModels() {
                results = {
                        valid: [],
                        invalid: [],
                        inconsistent: []
                   };
                  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('请输入至少一个模型名称或从列表中选择模型');
                      });
                      return;
                  }
    
                  layui.use('layer', function () {
                      var layer = layui.layer;
                      layer.load();
    
                      
    
                      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(\`测试 API 节点:\${apiUrl} 测试模型:\${model} 模型一致,响应时间:\${responseTime.toFixed(2)} 秒\`);
                                  } else {
                                      results.inconsistent.push({model, returnedModel, responseTime});
                                      console.log(\`测试 API 节点:\${apiUrl} 测试模型:\${model} 模型不一致,期望:\${model},实际:\${returnedModel},响应时间:\${responseTime.toFixed(2)} 秒\`);
                                  }
                              } else {
                                  response_text = await response.text();
                                  results.invalid.push({model, response_text})
                                  console.log(\`测试 API 节点:\${apiUrl} 测试模型:\${model} 模型不可用,响应:\${response.status} \${response.statusText} \${response_text}\`);
                              }
                          } catch (error) {
                              if (error.name === 'AbortError') {
                                  results.invalid.push({model, error: '超时'});
                                  console.log(\`测试 API 节点:\${apiUrl} 测试模型:\${model} 模型不可用(超时)\`);
                              } else {
                                  results.invalid.push({model, error: error.message});
                                  console.log(\`测试 API 节点:\${apiUrl} 测试模型:\${model} 模型不可用,错误:\${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.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(\`测试总结:<br>
                                总共测试了 \${totalCount} 个模型<br>
                                其中:<br>
                                - \${validCount} 个模型可用且一致<br>
                                - \${inconsistentCount} 个模型可用但不一致<br>
                                - \${invalidCount} 个模型不可用\`,
                            {title: '测试结果总结'}
                        );
                    });
                }
    
    
                // 显示测试结果
                function displayResults(results) {
            var resultsDiv = document.getElementById('results');
            var content = '<h2>测试结果</h2>' +
                '<div class="copy-buttons">' +
                '<div className="submit-container">'+
                '<button class="check-quota copy-btn" onclick="copyConsistentModels()">复制一致模型</button>' +
                '<button class="check-quota copy-btn" onclick="copyConsistentAndInconsistentModels()">复制可用模型</button>' +
                '<button class="check-quota copy-btn"onclick="copyConsistentAndInconsistentReturedModels()">复制所有可用原始模型</button>' +
                '</div>' +
                '</div>' +
                '<table>' +
                '<tr>' +
                '<th class="td1">状态</th>' +
                '<th class="td2">模型名称</th>' +
                '<th class="td3">响应时间 (秒)</th>' +
                '<th class="td4">备注</th>' +
                '</tr>';
    
                    results.valid.forEach(function (r) {
                        content += '<tr>' +
                            '<td class="td1 td1-ok">模型一致可用</td>' +
                            '<td class="td2"><span class="copy-btn2" 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" >模型不一致,tnnd掺假?</td>' +
                            '<td class="td2"><span class="copy-btn2" onclick="copyText(\\'' + r.model + '\\')">' + r.model + '</span></td>' +
                            '<td class="td3">' + r.responseTime.toFixed(2) + '</td>' +
                            '<td class="td4">返回模型: ' + r.returnedModel + '</td>' +
                            '</tr>';
                    });
    
                    results.invalid.forEach(function (r) {
                        content += '<tr>' +
                            '<td class="td1 ">模型不可用,干啥呢</td>' +
                            '<td class="td2"><span class="copy-btn2" 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('模型名已复制到剪贴板');
                        });
                    }).catch(err => {
                        console.error('复制失败:', 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)} $\` : '无法获得额度信息';
    
                                // 获取使用情况
                                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 = '无法计算剩余额度';
                                }
    
                                const showInfo = \`可用额度为: \${remainInfo}\\n\\n已用额度为: \${usedInfo}\\n\\n总额度为: \${quotaInfo}\`;
                                layer.closeAll('loading');
                                layer.alert(showInfo);
                            })
                            .catch(error => {
                                layer.closeAll('loading');
                                layer.alert('检查额度失败: ' + error.message);
                            });
                    });
                }
    
                // 清空表单
                function clearForm() {
                    document.getElementById('apiForm').reset();
                    document.getElementById('results').innerHTML = '';
                }
                // 复制一致模型
        function copyConsistentModels() {
            var models = results.valid.map(function (r) {
                return r.model;
            });
            copyText(models.join(','));
        }
    
        // 复制所有可用模型去重
        function copyConsistentAndInconsistentModels() {
            var models = results.valid.map(function (r) {
                return r.model;
            })
                .concat(results.inconsistent.map(function (r) {
                    return r.model;
                }));
            var uniqueModels = Array.from(new Set(models)); // 去重
            copyText(uniqueModels.join(','));
        }
    
    
        // 复制一致和不一致模型的原始模型的函数名称 去重
        function copyConsistentAndInconsistentReturedModels() {
            var models = results.valid.map(function (r) {
                return r.model;
            })
                .concat(results.inconsistent.map(function (r) {
                    return r.returnedModel;
                }));
            var uniqueModels = Array.from(new Set(models)); // 去重
            //去除undefined
            uniqueModels = uniqueModels.filter(function (s) {
                return s && s.trim();
            });
            copyText(uniqueModels.join(','));
        }
            </script>
    </body>
    </html>
    
    `;
    
    return new Response(html, {
    headers: {
    "content-type": "text/html;charset=UTF-8",
    },
    });
    }
        
      
        
      
        
      
html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>API 信息测活</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"], .copy-btn {
            width: 30%;
            padding: 10px;
            border: none;
            cursor: pointer;
            margin-top: 10px;
        }

        .copy-btn {
            margin-left: 10%;
            width: 20%;
        }

        .copy-btn2 {
            width: 20%;
        }

        .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 {
            width: 90%; /* 或任何其他固定宽度 */
            margin-left: auto;
            margin-right: auto;
            border-collapse: collapse;
            margin-top: 20px;
        }

        th, td {
            border: 1px solid #ddd;
            padding: 8px;
            text-align: left;
        }

        th {
            background-color: #f2f2f2;
        }


        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 添加样式 */
        .td3 {
            width: 100px; /* 或者设置一个具体的宽度 */
        }

        .copy-buttons {
            margin: 10px 0;
        }

        .copyright {
            margin-top: 20px;
            text-align: center;
            font-size: 14px;
            color: #666;
        }

        .copyright img {
            width: 24px;
            height: 24px;
            border-radius: 50%;
            margin-right: 5px;
            vertical-align: middle;
        }

        .copyright a {
            color: #1e88e5;
            text-decoration: none;
        }

        .copyright a:hover {
            text-decoration: underline;
        }
    </style>
</head>
<body>
<div class="container">
    <h1>API 信息测活</h1>
    <h3>(适配 oneapi/newapi 等中转格式)</h3>

    <form id="apiForm">
            <textarea id="api_info" name="api_info"
                      placeholder="懒人专用文本框,支持同时粘贴接口地址和密钥,智能提取,如:https://api.openai.com,sk-TodayIsThursdayVme50ForKFC"></textarea>
        <input type="text" id="api_url" name="api_url" placeholder="接口地址,如:https://api.openai.com" value="">
        <input type="text" id="api_key" name="api_key" placeholder="密钥,如:sk-TodayIsThursdayVme50ForKFC" value="">
        <div class="model-input-container" id="model-input-container">
            <input type="text" id="model_name" name="model_name" placeholder="支持手动模型名称,用逗号分隔多个模型">
            <input type="button" value="获取模型列表" style="height: 50px" onclick="getModelList()">
        </div>
        <div id="modelCheckboxes"></div>
        <div class="model-timeout-concurrency">
            <div class="model-timeout">
                <label for="model_timeout">设置请求超时(秒):</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">设置请求并发数量:</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="测试模型" onclick="testModels()" class="submit-query">
            <input type="button" value="检查额度" onclick="checkQuota()" class="check-quota">
            <input type="button" value="清空表单" onclick="clearForm()" class="clear-form">
        </div>
    </form>
</div>
<div class="copyright">
    <!--    不要删除感谢 Rick 和 Megasoft 的贡献-->
    <p> © 2024 linuxdo 版权所有<br>贡献者:<a href="https://linux.do/u/rick" target="_blank"><img
            src="https://linux.do/user_avatar/linux.do/rick/288/137821_2.png" alt="rick">rick </a>和<a
            href="https://linux.do/u/zhong_little" target="_blank"><img
            src="https://linux.do/user_avatar/linux.do/zhong_little/288/104887_2.png" alt="Megasoft">Megasoft</a></p>
</div>
<div id="results" class="response-container"></div>

<script>
    // 智能提取API信息
    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) {
            //去除末尾/后的空格 其他字符 保留到最后一个/前面
            let cleanUrl = urlMatch[0].match(/(.*)\/.*/)[1];
            //如果. 存在则使用
            if (cleanUrl.includes('.')) {
                document.getElementById('api_url').value = cleanUrl;
                console.log(cleanUrl);
            } else {
                document.getElementById('api_url').value = urlMatch[0];
                console.log(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('获取模型列表失败: ' + 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 个模型</div>';

            content += `
                    <div class="layui-form-item">
                        <input type="checkbox" lay-skin="primary" lay-filter="checkAll" title="全选">
                    </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: '选择模型',
                content: content,
                area: ['300px', '400px'],
                btn: ['确定', '取消'],
                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 (index, item) {
                            item.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(`已选择 ${selectedCount} 个模型`);
    }

    let results = {
        valid: [],
        invalid: [],
        inconsistent: [],
    };

    async function testModels() {
        results = {
            valid: [],
            invalid: [],
            inconsistent: [],
        };
        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('请输入至少一个模型名称或从列表中选择模型');
            });
            return;
        }

        layui.use('layer', function () {
            var layer = layui.layer;
            layer.load();


            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(`测试 API 节点:${apiUrl} 测试模型:${model} 模型一致,响应时间:${responseTime.toFixed(2)} 秒`);
                        } else {
                            results.inconsistent.push({model, returnedModel, responseTime});
                            console.log(`测试 API 节点:${apiUrl} 测试模型:${model} 模型不一致,期望:${model},实际:${returnedModel},响应时间:${responseTime.toFixed(2)} 秒`);
                        }
                    } else {
                        response_text = await response.text();
                        results.invalid.push({model, response_text})
                        console.log(`测试 API 节点:${apiUrl} 测试模型:${model} 模型不可用,响应:${response.status} ${response.statusText} ${response_text}`);
                    }
                } catch (error) {
                    if (error.name === 'AbortError') {
                        results.invalid.push({model, error: '超时'});
                        console.log(`测试 API 节点:${apiUrl} 测试模型:${model} 模型不可用(超时)`);
                    } else {
                        results.invalid.push({model, error: error.message});
                        console.log(`测试 API 节点:${apiUrl} 测试模型:${model} 模型不可用,错误:${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.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(`测试总结:<br>
                    总共测试了 ${totalCount} 个模型<br>
                    其中:<br>
                    - ${validCount} 个模型可用且一致<br>
                    - ${inconsistentCount} 个模型可用但不一致<br>
                    - ${invalidCount} 个模型不可用`,
                {title: '测试结果总结'}
            );
        });
    }


    // 显示测试结果
    // 显示测试结果
    function displayResults(results) {
        var resultsDiv = document.getElementById('results');
        var content = '<h2>测试结果</h2>' +
            '<div class="copy-buttons">' +
            '<div className="submit-container">' +
            '<button class="check-quota copy-btn" onclick="copyConsistentModels()">复制一致模型</button>' +
            '<button class="check-quota copy-btn" onclick="copyConsistentAndInconsistentModels()">复制可用模型</button>' +
            '<button class="check-quota copy-btn"onclick="copyConsistentAndInconsistentReturedModels()">复制可用用原始模型</button>' +
            '</div>' +
            '</div>' +
            '<table>' +
            '<tr>' +
            '<th class="td1">状态</th>' +
            '<th class="td2">模型名称</th>' +
            '<th class="td3">响应时间 (秒)</th>' +
            '<th class="td4">备注</th>' +
            '</tr>';

        results.valid.forEach(function (r) {
            content += '<tr>' +
                '<td class="td1 td1-ok">模型一致可用</td>' +
                '<td class="td2"><span class="copy-btn2"" 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" >模型不一致,tnnd掺假?</td>' +
                '<td class="td2"><span class="copy-btn2"" onclick="copyText(\'' + r.model + '\')">' + r.model + '</span></td>' +
                '<td class="td3">' + r.responseTime.toFixed(2) + '</td>' +
                '<td class="td4">返回模型: ' + r.returnedModel + '</td>' +
                '</tr>';
        });

        results.invalid.forEach(function (r) {
            content += '<tr>' +
                '<td class="td1 ">模型不可用,干啥呢</td>' +
                '<td class="td2"><span class="copy-btn2" 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('模型名已复制到剪贴板');
            });
        }).catch(err => {
            console.error('复制失败:', 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)} $` : '无法获得额度信息';

                    // 获取使用情况
                    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 = '无法计算剩余额度';
                    }

                    const showInfo = `可用额度为: ${remainInfo}\n\n已用额度为: ${usedInfo}\n\n总额度为: ${quotaInfo}`;
                    layer.closeAll('loading');
                    layer.alert(showInfo);
                })
                .catch(error => {
                    layer.closeAll('loading');
                    layer.alert('检查额度失败: ' + error.message);
                });
        });
    }

    // 清空表单
    function clearForm() {
        document.getElementById('apiForm').reset();
        document.getElementById('results').innerHTML = '';
    }

    // 复制一致模型
    function copyConsistentModels() {
        var models = results.valid.map(function (r) {
            return r.model;
        });
        copyText(models.join(','));
    }

    // 复制所有可用模型去重
    function copyConsistentAndInconsistentModels() {
        var models = results.valid.map(function (r) {
            return r.model;
        })
            .concat(results.inconsistent.map(function (r) {
                return r.model;
            }));
        var uniqueModels = Array.from(new Set(models)); // 去重
        copyText(uniqueModels.join(','));
    }


    // 复制一致和不一致模型的原始模型的函数名称 去重
    function copyConsistentAndInconsistentReturedModels() {
        var models = results.valid.map(function (r) {
            return r.model;
        })
            .concat(results.inconsistent.map(function (r) {
                return r.returnedModel;
            }));
        var uniqueModels = Array.from(new Set(models)); // 去重
        //去除undefined
        uniqueModels = uniqueModels.filter(function (s) {
            return s && s.trim();
        });
        copyText(uniqueModels.join(','));
    }


</script>
</body>
</html>

161 个赞

一致性怎么测的??

4 个赞

返回的模型名称。newapi oneapi 暂时没有修改这个

4 个赞

哦,这样的,奇怪了,返回有很多undefined,还有用gpt4o冒充mini的,这么神奇

4 个赞

好东西,先测试下。

4 个赞

undefined 一般是逆向 特别是国产ai

2 个赞

明白了 :+1:

2 个赞

good very

2 个赞

不错哦~还挺整齐的

1 个赞

感谢热佬分享

2 个赞

点赞支持,感谢分享!

棒棒哒:+1::+1::+1:

1 个赞

很不错,可以一键选择测活api的模型列表,可以看反馈信息,而且测活速度也很快,希望可以一键复制可用模型(虽然按模型名可以复制一个,但还是麻烦),一键复制模型一致的和模型不一致的真模型,最好可以用","隔开,就可以直接放api中转站了

1 个赞

呜呜呜,难道我的官方Gemini API也是逆向吗

4 个赞

ok 我去newapi 啥时候跟新的 ,我以为不能批量来着 ,所以只弄了单个复制

已经用上了…点赞…

17 个赞

越来越智能化了

2 个赞

是的,从公益站测活后放自己中转站,自己中转站测活后放nextchat,都可以批量复制,批量复制比较方便

6 个赞

不错,好工具,马上试试

4 个赞

240行加个替换,可以防呆

const apiUrl = document.getElementById('api_url').value.replace(/\/+$/, '');
4 个赞