From e46d3685a0bea23fce6fc68617de9ea7f95521fc Mon Sep 17 00:00:00 2001 From: pzcn Date: Fri, 17 Mar 2023 09:21:25 +0800 Subject: [PATCH] feat: show api balance (#582) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: show api balance * Update index.ts * 保留小数点后五位 * perf: 判断优化 --------- Co-authored-by: Redon <790348264@qq.com> --- service/package.json | 1 + service/pnpm-lock.yaml | 51 +++++++++++++++++++++++++ service/src/chatgpt/index.ts | 34 +++++++++++++++-- service/src/types.ts | 1 + src/components/common/Setting/About.vue | 2 + src/locales/en-US.ts | 1 + src/locales/zh-CN.ts | 1 + src/locales/zh-TW.ts | 1 + 8 files changed, 88 insertions(+), 4 deletions(-) diff --git a/service/package.json b/service/package.json index 4b74e5a..6311f31 100644 --- a/service/package.json +++ b/service/package.json @@ -24,6 +24,7 @@ "common:cleanup": "rimraf node_modules && rimraf pnpm-lock.yaml" }, "dependencies": { + "axios": "^1.3.4", "chatgpt": "^5.0.10", "dotenv": "^16.0.3", "esno": "^0.16.3", diff --git a/service/pnpm-lock.yaml b/service/pnpm-lock.yaml index e2e251f..8e4b85e 100644 --- a/service/pnpm-lock.yaml +++ b/service/pnpm-lock.yaml @@ -4,6 +4,7 @@ specifiers: '@antfu/eslint-config': ^0.35.3 '@types/express': ^4.17.17 '@types/node': ^18.14.6 + axios: ^1.3.4 chatgpt: ^5.0.10 dotenv: ^16.0.3 eslint: ^8.35.0 @@ -18,6 +19,7 @@ specifiers: typescript: ^4.9.5 dependencies: + axios: 1.3.4 chatgpt: 5.0.10 dotenv: 16.0.3 esno: 0.16.3 @@ -766,6 +768,10 @@ packages: es-shim-unscopables: 1.0.0 dev: true + /asynckit/0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + /atomically/2.0.1: resolution: {integrity: sha512-sxBhVZUFBFhqSAsYMM3X2oaUi2NVDJ8U026FsIusM8gYXls9AYs/eXzgGrufs1Qjpkxi9zunds+75QUFz+m7UQ==} dependencies: @@ -778,6 +784,16 @@ packages: engines: {node: '>= 0.4'} dev: true + /axios/1.3.4: + resolution: {integrity: sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==} + dependencies: + follow-redirects: 1.15.2 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /balanced-match/1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true @@ -966,6 +982,13 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true + /combined-stream/1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + /commander/4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1082,6 +1105,11 @@ packages: object-keys: 1.1.1 dev: true + /delayed-stream/1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + /depd/2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -1852,12 +1880,31 @@ packages: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} dev: true + /follow-redirects/1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + /for-each/0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: is-callable: 1.2.7 dev: true + /form-data/4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + /formdata-polyfill/4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -2918,6 +2965,10 @@ packages: ipaddr.js: 1.9.1 dev: false + /proxy-from-env/1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + /punycode/2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} diff --git a/service/src/chatgpt/index.ts b/service/src/chatgpt/index.ts index 7b20d23..b76c780 100644 --- a/service/src/chatgpt/index.ts +++ b/service/src/chatgpt/index.ts @@ -5,6 +5,7 @@ import { ChatGPTAPI, ChatGPTUnofficialProxyAPI } from 'chatgpt' import { SocksProxyAgent } from 'socks-proxy-agent' import { HttpsProxyAgent } from 'https-proxy-agent' import fetch from 'node-fetch' +import axios from 'axios' import { sendResponse } from '../utils' import { isNotEmptyString } from '../utils/is' import type { ApiModel, ChatContext, ChatGPTUnofficialProxyAPIOptions, ModelConfig } from '../types' @@ -99,14 +100,35 @@ async function chatReplyProcess( } } +async function fetchBalance() { + const OPENAI_API_KEY = process.env.OPENAI_API_KEY + if (!isNotEmptyString(OPENAI_API_KEY)) + return Promise.resolve('-') + + try { + const headers = { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${OPENAI_API_KEY}`, + } + const response = await axios.get('https://api.openai.com/dashboard/billing/credit_grants', { headers }) + const balance = response.data.total_available ?? 0 + return Promise.resolve(balance.toFixed(3)) + } + catch { + return Promise.resolve('-') + } +} + async function chatConfig() { + const balance = await fetchBalance() const reverseProxy = process.env.API_REVERSE_PROXY ?? '-' - const socksProxy = (process.env.SOCKS_PROXY_HOST && process.env.SOCKS_PROXY_PORT) ? (`${process.env.SOCKS_PROXY_HOST}:${process.env.SOCKS_PROXY_PORT}`) : '-' const httpsProxy = (process.env.HTTPS_PROXY || process.env.ALL_PROXY) ?? '-' - + const socksProxy = (process.env.SOCKS_PROXY_HOST && process.env.SOCKS_PROXY_PORT) + ? (`${process.env.SOCKS_PROXY_HOST}:${process.env.SOCKS_PROXY_PORT}`) + : '-' return sendResponse({ type: 'Success', - data: { apiModel, reverseProxy, timeoutMs, socksProxy, httpsProxy }, + data: { apiModel, reverseProxy, timeoutMs, socksProxy, httpsProxy, balance }, }) } @@ -133,6 +155,10 @@ function setupProxy(options: ChatGPTAPIOptions | ChatGPTUnofficialProxyAPIOption } } +function currentModel(): ApiModel { + return apiModel +} + export type { ChatContext, ChatMessage } -export { chatReplyProcess, chatConfig } +export { chatReplyProcess, chatConfig, currentModel } diff --git a/service/src/types.ts b/service/src/types.ts index 995894c..12c8b04 100644 --- a/service/src/types.ts +++ b/service/src/types.ts @@ -20,6 +20,7 @@ export interface ModelConfig { timeoutMs?: number socksProxy?: string httpsProxy?: string + balance?: string } export type ApiModel = 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI' | undefined diff --git a/src/components/common/Setting/About.vue b/src/components/common/Setting/About.vue index e6d503a..4314415 100644 --- a/src/components/common/Setting/About.vue +++ b/src/components/common/Setting/About.vue @@ -10,6 +10,7 @@ interface ConfigState { apiModel?: string socksProxy?: string httpsProxy?: string + balance?: string } const loading = ref(false) @@ -55,6 +56,7 @@ onMounted(() => {

{{ $t("setting.api") }}:{{ config?.apiModel ?? '-' }}

+

{{ $t("setting.balance") }}:{{ config?.balance ?? '-' }}

{{ $t("setting.reverseProxy") }}:{{ config?.reverseProxy ?? '-' }}

{{ $t("setting.timeout") }}:{{ config?.timeoutMs ?? '-' }}

{{ $t("setting.socks") }}:{{ config?.socksProxy ?? '-' }}

diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index b1b88ba..0e9e3d3 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -63,6 +63,7 @@ export default { timeout: 'Timeout', socks: 'Socks', httpsProxy: 'HTTPS Proxy', + balance: 'API Balance', }, store: { local: 'Local', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 813b637..e47b850 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -63,6 +63,7 @@ export default { timeout: '超时', socks: 'Socks', httpsProxy: 'HTTPS Proxy', + balance: 'API余额', }, store: { local: '本地', diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index 3ed65df..2b8ffda 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -63,6 +63,7 @@ export default { timeout: '逾時', socks: 'Socks', httpsProxy: 'HTTPS Proxy', + balance: 'API余額', }, store: { local: '本機',