diff --git a/README.md b/README.md index 461963c..71998e8 100644 --- a/README.md +++ b/README.md @@ -9,40 +9,9 @@ Telegram交流群:https://t.me/ai_cn2023 ## 最新示意图(2023.08.28) -![1693219507531.png](https://cdn-img.czl.net/2023/08/28/64ec7ab5a1ee0.png) -![ce5f86c2-76cc-44cf-887a-0aff8b4a1f1e.jpeg](https://cdn-img.czl.net/2023/08/28/64ec7abb90408.jpeg) -![b90a990e-bb8f-47e5-9101-ac841a727993.jpeg](https://cdn-img.czl.net/2023/08/28/64ec7abc78659.jpeg) - -## 更新日志 -### 8月26日更新 -1. 有效检查改为发送请求,不再使用正则; - -### 8月22日更新 - -1. 组织信息增加`userid`和`创建时间`; - -### 7月29日更新(点个star吧) - -1. 新增GPT-3.5查询、绑卡信息(人名和地址)、组织名称、邮箱、组织ID、是否有效; -2. 使用sk查询不再整体报错,而是展示可以查询到的内容; - -### 7月22日更新后,如要查看完整信息,需登录一次账号,F12查看sess码,使用sess码进行查询 - -7月22日更新后,已删除key校验规则,需使用sess码进行查询。使用key只能查询部分信息。 - -![1689957580942.png](https://cdn-img.czl.net/2023/07/22/64bab4daba587.png) - -## sess查询示例(120刀4.0key) - -![1690562370550.png](https://cdn-img.czl.net/2023/07/29/64c3ef5003257.png) - -## sk查询示例(120刀4.0key) - -![1690562289330.png](https://cdn-img.czl.net/2023/07/29/64c3eefec26cd.png) - -## sk查询示例(5刀未绑卡key) - -![1690562178945.png](https://cdn-img.czl.net/2023/07/29/64c3ee9070310.png) +![img_v2_29c139e9-9114-42bc-a23d-cba36e635d1g.jpg](https://cdn-img.czl.net/2023/08/28/64eca918b5668.jpg) +![img_v2_388e4f50-a9ee-452d-b0f1-16773bdfc9fg.jpg](https://cdn-img.czl.net/2023/08/28/64eca91cc90fe.jpg) +![img_v2_0c1ed439-59b4-4a4a-bf82-b43bd97458ag.jpg](https://cdn-img.czl.net/2023/08/28/64eca9202c6d7.jpg) ## SESS ID获取方法 @@ -55,8 +24,6 @@ Telegram交流群:https://t.me/ai_cn2023 ``` html ``` -## 反代代码示例 -![image](https://github.com/woodchen-ink/openai-billing-query/assets/95951386/0bcdb51b-de08-49bc-bd01-5bf731f53d02) ## 怎么部署 下载index.html直接打开就行,除了背景图片,没有任何外部资源。 diff --git a/index.html b/index.html index 1a2dd56..85aea54 100644 --- a/index.html +++ b/index.html @@ -6,365 +6,7 @@ OpenAIAPI 信息查询 - + @@ -415,8 +57,9 @@ GPT-4 32K 绑卡 - 绑卡信息 - 组织信息 + 绑卡信息 + 组织信息 + 速率 是否有效 @@ -432,439 +75,7 @@

- - - + diff --git a/static/css.css b/static/css.css new file mode 100644 index 0000000..e79aba9 --- /dev/null +++ b/static/css.css @@ -0,0 +1,357 @@ +:root { + --color-primary: #2EA7E0; + --color-primary-dark: #2482AD; + --color-primary-alpha: #75C2E6; + --body-color: #ffffff; + --body-bg: #000000; + --border-color: #f1ebeb00; +} + +body { + background: url('https://cdn-img.czl.net/2023/05/23/pjbczr.webp') no-repeat center center fixed; + /* 自定义背景图 */ + background-size: cover; + width: 90%; + max-width: 1400px; + margin-left: auto; + margin-right: auto; + padding-left: 2rem; + padding-right: 2rem; + color: var(--body-color); + /* background: var(--body-bg); */ + font-family: system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif; + line-height: 1.5; + -webkit-tap-highlight-color: rgb(252, 247, 247); + text-rendering: optimizelegibility; + -webkit-font-smoothing: antialiased; +} + +.query { + max-width: 50rem; + margin: auto; +} + +a { + color: var(--color-primary); + text-decoration: none; + transition: color .3s; +} + +a:hover { + color: var(--color-primary-alpha); + text-decoration: underline; +} + +h1 { + font-size: 2.5rem; + text-align: center; +} + +main[x-cloak] { + opacity: 0; +} + +main:not([x-cloak]) { + opacity: 1; + transition: opacity .3s; +} + +textarea { + -webkit-appearance: none; + appearance: none; + display: block; + width: 100%; + padding: .5rem 1rem; + border: 1px solid var(--border-color); + border-radius: .25rem; + box-sizing: border-box; + color: #33404d; + line-height: inherit; + font-size: 1rem; + transition: border .3s, box-shadow .3s; +} + +textarea:focus { + box-shadow: 0 0 0 .25rem var(--color-primary-alpha); + border-color: var(--color-primary); + outline: 0; +} + +input { + -webkit-appearance: none; + appearance: none; + display: block; + width: 100%; + padding: .5rem 1rem; + border: 1px solid var(--border-color); + border-radius: .25rem; + box-sizing: border-box; + color: #33404d; + line-height: inherit; + font-size: 1rem; + transition: border .3s, box-shadow .3s; +} + +input:focus { + box-shadow: 0 0 0 .25rem var(--color-primary-alpha); + border-color: var(--color-primary); + outline: 0; +} + +details { + margin: 1rem 0 2rem; + border: 1px solid var(--border-color); + border-radius: .25rem; + /* transition: background .3s; */ +} + +details[open] { + background: #fff; +} + +details summary { + padding: .5rem 1rem; + font-weight: 500; + user-select: none; + cursor: pointer; + opacity: .8; + outline: 0; +} + +details div { + padding: 1rem; + border-top: 1px solid var(--border-color); +} + +details small { + margin: 0; + font-size: .875rem; + line-height: 2; +} + +button { + appearance: none; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + margin: auto; + max-width: 50rem; + margin-bottom: 1rem; + padding: .5rem .75rem; + border: 1px solid var(--color-primary); + border-radius: .25rem; + background: var(--color-primary); + color: #fff; + font-size: 1rem; + font-weight: 500; + line-height: inherit; + cursor: pointer; + user-select: none; + transition: border .3s, background .3s, ; +} + +button:hover { + border-color: var(--color-primary-dark); + background: var(--color-primary-dark); +} + +button:focus { + box-shadow: 0 0 0 .25rem var(--color-primary-alpha); + border-color: var(--color-primary); + outline: 0; +} + +button:disabled { + background: var(--color-primary); + border-color: var(--color-primary); + opacity: .6; + cursor: not-allowed; +} + +button.loading::before { + content: ''; + display: inline-block; + margin-right: .5rem; + border: 2px solid #fff; + border-top-color: transparent; + border-bottom-color: transparent; + border-radius: 50%; + width: .75rem; + height: .75rem; + animation: rotate .5s linear infinite; +} + +footer { + /* padding: 2px; */ + /* border-top: 1px solid var(--border-color); */ + text-align: center; + /* opacity: .5; */ + /* position: fixed; */ + /* bottom: 0; */ + /* left: 0; */ + margin-top: 20px; + /* width: 100%; */ + /* background-color:#212121; */ + line-height: unset !important; + line-height: normal; +} + +footer .p { + display: flex; + margin-block-start: 0 !important; + margin-block-end: 0 !important; + padding: 0; + color: rgb(255, 255, 255); +} + +.success, +.error { + margin-bottom: 1rem; + padding: .5rem 1rem; + border-radius: .25rem; + color: #fff; + text-align: center; + opacity: 1; + transition: opacity .3s; +} + +.success { + /* border: 1px solid #12b886; */ + background: #38d9a9; +} + +.error { + /* border: 1px solid #b32142; */ + background: #b32142; +} + +@keyframes rotate { + 100% { + transform: rotate(360deg); + } +} + +/* 设置API URL选择器和自定义URL输入框的样式 */ +select#api-url-select, +input#custom-url-input { + width: 100%; + height: 2.4rem; + font-size: 1rem; + background-color: #212121; + margin-bottom: .5rem; + padding: .5rem .75rem; + color: #ffffff; + border: none; + padding: .5rem .75rem; + border-radius: .25rem; + margin-right: 10px; + margin-top: 2vhpx; + /* 添加了顶部边距 */ +} + + + +/* 隐藏自定义API链接输入框 */ +input#custom-url-input { + display: none; + height: 50px; + /* 添加高度 */ +} + + + +/* 下面的代码定义了结果表格的样式 */ +#result-table { + border-collapse: collapse; + width: 100%; + margin-top: 20px; + font-size: 10px; + border-radius: 5px; + border: none; + overflow: hidden; +} + +/* 下面的代码定义了结果表格表头的样式 */ +#result-table th { + background-color: var(--color-primary); + color: #ffffff; + font-weight: 400; + height: 20px; + padding: 10px 5px; + text-align: left; + /* border: 1px solid #dcdcdc; */ +} + +/* 下面的代码定义了结果表格数据单元格的样式 */ +#result-table td { + height: 20px; + text-align: left; + padding: 10px 5px; + /* border: 1px solid #d3d3d3; */ +} + +/* 下面的代码定义了结果表格奇数行的背景颜色 */ +#result-table tbody tr:nth-child(odd) { + background-color: #252422; +} + +/* 下面的代码定义了结果表格偶数行的背景颜色 */ +#result-table tbody tr:nth-child(even) { + background-color: #31302d; +} + +/* 下面的代码定义了结果表格表头的宽度 */ +#result-table .table-header { + width: 25%; +} + +/* 下面的代码定义了结果表格数据单元格的宽度、字体加粗和颜色 */ +#result-table .table-data { + width: 25%; + font-weight: bold; + color: #1c248b; +} + +/* 下面的代码定义了结果表格数据单元格的样式 */ +#result-table .status-error { + height: 20px; + text-align: left; + padding: 10px 5px; + /* border: 1px solid #dcdcdc; */ +} + +/* 下面的代码定义了一个类名为status-ok的样式,用于设置成功状态的文本颜色 */ +.status-ok { + color: #2d8d2d; +} + +/* 下面的代码定义了一个类名为status-error的样式,用于设置错误状态的文本颜色 */ +.status-error { + color: #ed0808; +} + +#api-key-input { + width: 100%; + height: 100px; + /* border: 1px solid #999; */ + background-color: #fff; +} + +.emoji { + font-size: 30px; + /* 调整为适当的大小 */ +} + +button.loading::before { + content: ""; + display: inline-block; + margin-right: .5rem; + border: 2px solid #fff; + border-top-color: transparent; + border-bottom-color: transparent; + border-radius: 50%; + width: .75rem; + height: .75rem; + animation: rotate .5s linear infinite; +} \ No newline at end of file diff --git a/static/js.js b/static/js.js new file mode 100644 index 0000000..f89c8bd --- /dev/null +++ b/static/js.js @@ -0,0 +1,456 @@ +let queriedApiKeys = []; +let serialNumber = 1; + + +async function checkBilling(apiKey, apiUrl) { + const now = new Date(); + let startDate = new Date(now - 90 * 24 * 60 * 60 * 1000); + const endDate = new Date(now.getTime() + 24 * 60 * 60 * 1000); + const subDate = new Date(now); + subDate.setDate(1); + + const headers = { + "Authorization": "Bearer " + apiKey, + "Content-Type": "application/json" + }; + const modelsCheck = `${apiUrl}/v1/models`; + const urlSubscription = `${apiUrl}/v1/dashboard/billing/subscription`; + let urlUsage = `${apiUrl}/v1/dashboard/billing/usage?start_date=${formatDate(startDate)}&end_date=${formatDate(endDate)}`; + const urlsetid = apiUrl + '/v1/organizations'; + const urlPaymentmethods = `${apiUrl}/v1/dashboard/billing/payment_methods`; + const urlRatelimits = `${apiUrl}/v1/dashboard/rate_limits`; + + try { + let totalAmount, totalUsage, remaining, GPT35CheckResult, GPT4CheckResult, GPT432kCheckResult, setid, isSubscrible; + let SubscribleInformation = {}; + let SubInformation; + let errors = {}; + + let response = await fetch(urlSubscription, { headers }); + + let currentDate = new Date(); + const subscriptionData = await response.json(); + const expiryDate = new Date(subscriptionData.access_until * 1000 + 8 * 60 * 60 * 1000); + const formattedDate = `${expiryDate.getFullYear()}-${(expiryDate.getMonth() + 1).toString().padStart(2, '0')}-${expiryDate.getDate().toString().padStart(2, '0')}`; + + try { + totalAmount = subscriptionData.system_hard_limit_usd; + + if (totalAmount > 20) { + startDate = subDate; + urlUsage = `${apiUrl}/v1/dashboard/billing/usage?start_date=${formatDate(startDate)}&end_date=${formatDate(endDate)}`; + response = await fetch(urlUsage, { headers }); + const usageData = await response.json(); + } + response = await fetch(urlUsage, { headers }); + const usageData = await response.json(); + totalUsage = usageData.total_usage / 100; + remaining = currentDate > expiryDate ? "❌过期" : (totalAmount - totalUsage).toFixed(3); + } catch (error) { + console.error(error); + errors['subscription'] = error.message; + } + //获取是否绑卡 + try { + if (subscriptionData.plan.id.includes('payg')) { + switch (subscriptionData.billing_mechanism) { + case 'advance': + isSubscrible = '✅预付费'; + break; + case 'arrears': + isSubscrible = '✅已欠费'; + break; + case null: + isSubscrible = '✅后付费'; + break; + default: + isSubscrible = '✅'; + } + } else { + isSubscrible = '❌'; + } + } catch (error) { + console.error(error); + } + + //获取绑卡信息 + try { + // 从 subscriptionData 中获取 SubscribleInformation 的值... + SubscribleInformation.account_name = subscriptionData.account_name; + SubscribleInformation.po_number = subscriptionData.po_number; + SubscribleInformation.billing_email = subscriptionData.billing_email; + SubscribleInformation.tax_ids = subscriptionData.tax_ids; + + let billingAddress = subscriptionData.billing_address; // 定义并赋值 billingAddress + let businessAddress = subscriptionData.business_address; // 定义并赋值 businessAddress + + SubInformation = "名称: " + SubscribleInformation.account_name + "\n"; + SubInformation += "PO号: " + SubscribleInformation.po_number + "\n"; + SubInformation += "帐单邮箱: " + SubscribleInformation.billing_email + "\n"; + SubInformation += "税号: " + SubscribleInformation.tax_ids + "\n"; + //使用 JavaScript 的可选链式调用来确定是否为null,避免异常控制台报错 + SubInformation += "账单地址: " + (billingAddress?.line1 ? billingAddress.line1 : '') + ", " + (billingAddress?.city ? billingAddress.city : '') + ", " + (billingAddress?.state ? billingAddress.state : '') + ", " + (billingAddress?.country ? billingAddress.country : '') + ", " + (billingAddress?.postal_code ? billingAddress.postal_code : '') + "\n"; + SubInformation += "商业地址: " + (businessAddress?.line1 ? businessAddress.line1 : '') + ", " + (businessAddress?.city ? businessAddress.city : '') + ", " + (businessAddress?.state ? businessAddress.state : '') + ", " + (businessAddress?.country ? businessAddress.country : '') + ", " + (businessAddress?.postal_code ? businessAddress.postal_code : '\n'); + + // 获取付款方法信息 + response = await fetch(urlPaymentmethods, { headers }); + const paymentMethodsData = await response.json(); + + if (paymentMethodsData.data && paymentMethodsData.data.length > 0) { + paymentMethodsData.data.forEach((paymentMethod, index) => { + if (paymentMethod.type === 'card' && paymentMethod.card) { + const cardInfo = paymentMethod.card; + SubInformation += `\n\n付款方式 ${index + 1}: \n`; + SubInformation += paymentMethod.is_default ? "默认卡:\n" : "非默认卡:\n"; // Add default label + SubInformation += `卡种: ${cardInfo.brand} \n`; // Card Brand + SubInformation += `后4位: ${cardInfo.last4} \n`; // Card Last4 + SubInformation += `卡日期: ${cardInfo.exp_month}/${cardInfo.exp_year} \n`; // Card Expiry + SubInformation += `卡国家: ${cardInfo.country}`; // Card Country + } + }); + } + + } + catch (error) { + console.error(error); + } + + //组织信息 + try { + response = await fetch(urlsetid, { headers }); + const setiddata = await response.json(); + setid = ''; + const emailStartIndex = setiddata.data[0].description.lastIndexOf(' ') + 1; + const email = setiddata.data[0].description.substring(emailStartIndex); + const title = setiddata.data[0].title; + const id1 = setiddata.data[0].id; + const name = setiddata.data[0].name; + const description = setiddata.data[0].description; + const createdTimestamp = setiddata.data[0].created; + const createdDate = new Date(createdTimestamp * 1000); + + // 格式化日期 + const formattedDate = `${createdDate.getFullYear()}.${(createdDate.getMonth() + 1).toString().padStart(2, '0')}.${createdDate.getDate().toString().padStart(2, '0')}`; + + const timeAndZone = createdDate.toString().match(/\d{2}:\d{2}:\d{2} \w+/)[0]; + + if (typeof setiddata.data[1] !== 'undefined') { + const id2 = setiddata.data[1].id; + setid = `${title}\n${email}\n${id1}\n${id2}\n${name}\n${description}\n${formattedDate} ${timeAndZone}`; + } else { + setid = `${title}\n${email}\n${id1}\n${name}\n${description}\n${formattedDate} ${timeAndZone}`; + } + if (typeof setiddata.data[1] !== 'undefined') { + const id2 = setiddata.data[1].id; + setid = `人名: ${title}\n邮箱: ${email}\n组织: ${id1} ${id2}\nUserID:${name}\n\n注册时间:${formattedDate} ${timeAndZone}`; + } else { + setid = `人名: ${title}\n邮箱: ${email}\n组织: ${id1} \nUserID:${name}\n注册时间:${formattedDate} ${timeAndZone}`; + } + + } catch (error) { + console.error(error); + errors['setid'] = error.message; + } + + //获取速率 + let rateLimits; + try { + const response = await fetch(urlRatelimits, { headers }); + const rateLimitsData = await response.json(); + rateLimits = { + 'gpt-3.5-turbo': rateLimitsData['gpt-3.5-turbo'], + 'gpt-3.5-turbo-16k': rateLimitsData['gpt-3.5-turbo-16k'], + 'gpt-4': rateLimitsData['gpt-4'], + 'gpt-4-32k': rateLimitsData['gpt-4-32k'] + }; + } catch (error) { + console.error(error); + errors['rateLimits'] = error.message; + } + + + + // 初始化模型查询结果 + GPT35CheckResult = '❌'; + GPT4CheckResult = '❌'; + GPT432kCheckResult = '❌'; + //3.5模型查询 + let GPT35CheckSuccess = false; // 初始化为 false + try { + const modelsCheckResponse = await fetch(modelsCheck, { headers }); + const modelsCheckData = await modelsCheckResponse.json(); + GPT35CheckSuccess = GPT35CheckResult = Array.isArray(modelsCheckData.data) && modelsCheckData.data.some(item => item.id.includes('gpt-3.5-turbo')) ? '✅' : '❌'; + } catch (error) { + console.error(error); + errors['modelsCheck'] = error.message; + } + //4模型查询 + try { + const modelsCheckResponse = await fetch(modelsCheck, { headers }); + const modelsCheckData = await modelsCheckResponse.json(); + GPT4CheckResult = Array.isArray(modelsCheckData.data) && modelsCheckData.data.some(item => item.id.includes('gpt-4')) ? '✅' : '❌'; + GPT432kCheckResult = Array.isArray(modelsCheckData.data) && modelsCheckData.data.some(item => item.id.includes('gpt-4-32k')) ? '✅' : '❌'; + } catch (error) { + console.error(error); + errors['modelsCheck'] = error.message; + } + // 是否有效查询 + async function checkCompletion(apiKey, apiUrl) { + const urlCompletion = `${apiUrl}/v1/chat/completions`; + const headers = { + "Authorization": "Bearer " + apiKey, + "Content-Type": "application/json" + }; + const postBody = JSON.stringify({ + "model": "gpt-3.5-turbo", + "messages": [{ + "role": "user", + "content": "Hello" + }], + "max_tokens": 5 + }); + + let response = await fetch(urlCompletion, { + method: 'POST', + headers: headers, + body: postBody + }); + + let data = await response.json(); + // 判断请求是否成功 + if (response.status === 200) { + return ['✅', data.usage.total_tokens]; // 返回状态和 total_tokens + } else { + return ['❌', null]; + } + } + // 调用 checkCompletion 函数并获取结果 + let completionCheckResult = await checkCompletion(apiKey, apiUrl); + //返回值 + return [totalAmount, totalUsage, remaining, formattedDate, GPT35CheckResult, GPT4CheckResult, GPT432kCheckResult, isSubscrible, SubInformation, setid, errors, GPT35CheckSuccess, completionCheckResult, rateLimits]; + } catch (error) { + return ["Error", null, null, null, null, null, null, null, null, null]; + } +} + + + + +function formatDate(date) { + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const day = date.getDate().toString().padStart(2, '0'); + + return `${year}-${month}-${day}`; +} + +//查询函数 +function sendRequest() { + let button = document.querySelector("button"); + button.textContent = "加载中..."; + button.disabled = true; + button.classList.add("loading") + + let apiKeyInput = document.getElementById("api-key-input"); + let apiUrlSelect = document.getElementById("api-url-select"); + let customUrlInput = document.getElementById("custom-url-input"); + let table = document.getElementById("result-table"); + let h2 = document.getElementById("result-head"); + h2.style.visibility = "visible"; + table.style.visibility = "visible"; + + if (apiKeyInput.value.trim() === "") { + alert("请填写API KEY"); + return; + } + + document.getElementById("result-table").getElementsByTagName('tbody')[0].innerHTML = ""; + + let apiUrl = ""; + if (apiUrlSelect.value === "custom") { + if (customUrlInput.value.trim() === "") { + alert("请设置API链接"); + return; + } else { + apiUrl = customUrlInput.value.trim(); + if (!apiUrl.startsWith("http://") && !apiUrl.startsWith("https://")) { + apiUrl = "https://" + apiUrl; + } + } + } else { + apiUrl = apiUrlSelect.value; + } + + let apiKeys = apiKeyInput.value.split(/[,\s,\n]+/); + + if (apiKeys.length === 0) { + alert("未匹配到 API-KEY,请检查输入内容"); + return; + } + + alert("成功匹配到 API Key,确认后开始查询:" + apiKeys); + + let tableBody = document.querySelector("#result-table tbody"); + for (let i = 0; i < apiKeys.length; i++) { + let apiKey = apiKeys[i].trim(); + + if (queriedApiKeys.includes(apiKey)) { + console.log(`API KEY ${apiKey} 已查询过,跳过此次查询`); + continue; + } + queriedApiKeys.push(apiKey); + + checkBilling(apiKey, apiUrl).then((data) => { + data = data.map(item => { + if (item === undefined) { + return 'Not Found.' + } else { + return item + } + } + ) + + let row = document.createElement("tr"); + + let serialNumberCell = document.createElement("td"); // 创建序列号单元格 + serialNumberCell.textContent = serialNumber; // 设置序列号文本 + row.appendChild(serialNumberCell); // 将序列号单元格添加到行中 + + let apiKeyCell = document.createElement("td"); + apiKeyCell.textContent = apiKey.replace(/^(.{5}).*(.{4})$/, "$1***$2"); + row.appendChild(apiKeyCell); + + console.log('查看查询结果', data); // 添加 console.log 以查看 data 的值 + + if (data[0] === undefined) { + let errorMessageCell = document.createElement("td"); + errorMessageCell.colSpan = "8"; + errorMessageCell.classList.add("status-error"); + errorMessageCell.textContent = "不正确或已失效的API-KEY"; + row.appendChild(errorMessageCell); + } else { + let totalAmount = document.createElement("td"); + totalAmount.textContent = data[0]; + row.appendChild(totalAmount); + + let totalUsedCell = document.createElement("td"); + if (!isNaN(data[1])) { + totalUsedCell.textContent = data[1].toFixed(3); + } else { + totalUsedCell.textContent = '❌' + } + row.appendChild(totalUsedCell); + + let totalAvailableCell = document.createElement("td"); + totalAvailableCell.textContent = typeof data[2] === 'number' ? data[2] : data[2]; + row.appendChild(totalAvailableCell); + let progressCell = document.createElement("td"); + let progressContainer = document.createElement("div"); // 添加一个新的容器元素 + progressContainer.style.width = "100%"; + progressContainer.style.height = "20px"; + progressContainer.style.backgroundColor = "#f3f3f3"; // 设置容器的背景色为灰白色 + let progressBar = document.createElement("div"); + progressBar.style.width = (data[1] / data[0] * 100).toFixed(2) + "%"; + progressBar.style.height = "20px"; + progressBar.style.backgroundColor = "#4CAF50"; + progressBar.style.position = "relative"; // 设置进度条的 position 为 relative + progressBar.textContent = (data[1] / data[0] * 100).toFixed(2) + "%"; // 显示百分比 + progressBar.style.textAlign = "right"; // 设置文本对齐方式为右对齐 + progressBar.style.paddingRight = "5px"; // 设置右边距以确保文本不超出边界 + progressBar.style.color = "black"; // 设置文本颜色为白色 + progressContainer.appendChild(progressBar); // 将进度条添加到容器中 + progressCell.appendChild(progressContainer); // 将容器添加到单元格中 + row.appendChild(progressCell); + + + + let expireTime = document.createElement("td"); + expireTime.textContent = data[3]; + row.appendChild(expireTime); + + let GPT35CheckResult = document.createElement("td"); + GPT35CheckResult.textContent = data[4]; + row.appendChild(GPT35CheckResult); + + let GPT4CheckResult = document.createElement("td"); + GPT4CheckResult.textContent = data[5]; + row.appendChild(GPT4CheckResult); + + let GPT432kCheckResult = document.createElement("td"); + GPT432kCheckResult.textContent = data[6]; + row.appendChild(GPT432kCheckResult); + + let isSubscribe = document.createElement("td"); + isSubscribe.style.whiteSpace = "pre"; // 添加这一行来保留换行 + isSubscribe.textContent = data[7]; + row.appendChild(isSubscribe); + + let SubInformation = document.createElement("td"); + let SubInformationContainer = document.createElement("div"); + SubInformationContainer.style.whiteSpace = "pre-wrap"; + SubInformationContainer.textContent = data[8]; + SubInformation.appendChild(SubInformationContainer); + row.appendChild(SubInformation); + + + let setidCell = document.createElement("td"); + let setidCellContainer = document.createElement("div"); + setidCellContainer.style.whiteSpace = "pre-wrap"; + setidCellContainer.textContent = data[9]; + setidCell.appendChild(setidCellContainer); + row.appendChild(setidCell); + + let rateLimitsDataCell = document.createElement("td"); + let rateLimitsDataContainer = document.createElement("div"); + rateLimitsDataContainer.style.whiteSpace = "pre-wrap"; + if (data[13]) { // checking if rateLimits data exists + let rateLimitsData = data[13]; + let models = ['gpt-3.5-turbo', 'gpt-3.5-turbo-16k', 'gpt-4', 'gpt-4-32k']; + for (let model of models) { + if (rateLimitsData[model]) { + rateLimitsDataContainer.textContent += `${model}:\n\tRPM: ${rateLimitsData[model].max_requests_per_1_minute}\n\tTPM: ${rateLimitsData[model].max_tokens_per_1_minute}\n\n`; + } else { + rateLimitsDataContainer.textContent += model + ": ❌\n"; + } + } + } + + rateLimitsDataCell.appendChild(rateLimitsDataContainer); + row.appendChild(rateLimitsDataCell); + + + // 是否有效列 + let completionCheckResultCell = document.createElement("td"); + completionCheckResultCell.innerHTML = `${data[12][0]}
消耗${data[12][1]} tokens`; // 使用 innerHTML 添加两行内容 + row.appendChild(completionCheckResultCell); + + + } + tableBody.appendChild(row); + + if (i === apiKeys.length - 1) { + queriedApiKeys = []; + } + serialNumber++; // 增加序列号 + h2.style.display = 'block'; + table.style.display = 'table'; + + button.textContent = "查询"; + button.disabled = false; + button.classList.remove("loading") + }) + + } +} + +let apiUrlSelect = document.getElementById("api-url-select"); +let customUrlInput = document.getElementById("custom-url-input"); + +apiUrlSelect.addEventListener("change", function () { + if (apiUrlSelect.value === "custom") { + customUrlInput.style.display = "inline-block"; + customUrlInput.style.marginTop = "5px"; + } else { + customUrlInput.style.display = "none"; + } +}); \ No newline at end of file