feat: show api balance (#582)

* feat: show api balance

* Update index.ts

* 保留小数点后五位

* perf: 判断优化

---------

Co-authored-by: Redon <790348264@qq.com>
main
pzcn 2 years ago committed by GitHub
parent 116ed7b0bf
commit e46d3685a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -24,6 +24,7 @@
"common:cleanup": "rimraf node_modules && rimraf pnpm-lock.yaml" "common:cleanup": "rimraf node_modules && rimraf pnpm-lock.yaml"
}, },
"dependencies": { "dependencies": {
"axios": "^1.3.4",
"chatgpt": "^5.0.10", "chatgpt": "^5.0.10",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"esno": "^0.16.3", "esno": "^0.16.3",

@ -4,6 +4,7 @@ specifiers:
'@antfu/eslint-config': ^0.35.3 '@antfu/eslint-config': ^0.35.3
'@types/express': ^4.17.17 '@types/express': ^4.17.17
'@types/node': ^18.14.6 '@types/node': ^18.14.6
axios: ^1.3.4
chatgpt: ^5.0.10 chatgpt: ^5.0.10
dotenv: ^16.0.3 dotenv: ^16.0.3
eslint: ^8.35.0 eslint: ^8.35.0
@ -18,6 +19,7 @@ specifiers:
typescript: ^4.9.5 typescript: ^4.9.5
dependencies: dependencies:
axios: 1.3.4
chatgpt: 5.0.10 chatgpt: 5.0.10
dotenv: 16.0.3 dotenv: 16.0.3
esno: 0.16.3 esno: 0.16.3
@ -766,6 +768,10 @@ packages:
es-shim-unscopables: 1.0.0 es-shim-unscopables: 1.0.0
dev: true dev: true
/asynckit/0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: false
/atomically/2.0.1: /atomically/2.0.1:
resolution: {integrity: sha512-sxBhVZUFBFhqSAsYMM3X2oaUi2NVDJ8U026FsIusM8gYXls9AYs/eXzgGrufs1Qjpkxi9zunds+75QUFz+m7UQ==} resolution: {integrity: sha512-sxBhVZUFBFhqSAsYMM3X2oaUi2NVDJ8U026FsIusM8gYXls9AYs/eXzgGrufs1Qjpkxi9zunds+75QUFz+m7UQ==}
dependencies: dependencies:
@ -778,6 +784,16 @@ packages:
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dev: true 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: /balanced-match/1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true dev: true
@ -966,6 +982,13 @@ packages:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true 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: /commander/4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@ -1082,6 +1105,11 @@ packages:
object-keys: 1.1.1 object-keys: 1.1.1
dev: true dev: true
/delayed-stream/1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
dev: false
/depd/2.0.0: /depd/2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@ -1852,12 +1880,31 @@ packages:
resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
dev: true 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: /for-each/0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
dependencies: dependencies:
is-callable: 1.2.7 is-callable: 1.2.7
dev: true 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: /formdata-polyfill/4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'} engines: {node: '>=12.20.0'}
@ -2918,6 +2965,10 @@ packages:
ipaddr.js: 1.9.1 ipaddr.js: 1.9.1
dev: false dev: false
/proxy-from-env/1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
dev: false
/punycode/2.3.0: /punycode/2.3.0:
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
engines: {node: '>=6'} engines: {node: '>=6'}

@ -5,6 +5,7 @@ import { ChatGPTAPI, ChatGPTUnofficialProxyAPI } from 'chatgpt'
import { SocksProxyAgent } from 'socks-proxy-agent' import { SocksProxyAgent } from 'socks-proxy-agent'
import { HttpsProxyAgent } from 'https-proxy-agent' import { HttpsProxyAgent } from 'https-proxy-agent'
import fetch from 'node-fetch' import fetch from 'node-fetch'
import axios from 'axios'
import { sendResponse } from '../utils' import { sendResponse } from '../utils'
import { isNotEmptyString } from '../utils/is' import { isNotEmptyString } from '../utils/is'
import type { ApiModel, ChatContext, ChatGPTUnofficialProxyAPIOptions, ModelConfig } from '../types' 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() { async function chatConfig() {
const balance = await fetchBalance()
const reverseProxy = process.env.API_REVERSE_PROXY ?? '-' 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 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<ModelConfig>({ return sendResponse<ModelConfig>({
type: 'Success', 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 type { ChatContext, ChatMessage }
export { chatReplyProcess, chatConfig } export { chatReplyProcess, chatConfig, currentModel }

@ -20,6 +20,7 @@ export interface ModelConfig {
timeoutMs?: number timeoutMs?: number
socksProxy?: string socksProxy?: string
httpsProxy?: string httpsProxy?: string
balance?: string
} }
export type ApiModel = 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI' | undefined export type ApiModel = 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI' | undefined

@ -10,6 +10,7 @@ interface ConfigState {
apiModel?: string apiModel?: string
socksProxy?: string socksProxy?: string
httpsProxy?: string httpsProxy?: string
balance?: string
} }
const loading = ref(false) const loading = ref(false)
@ -55,6 +56,7 @@ onMounted(() => {
</p> </p>
</div> </div>
<p>{{ $t("setting.api") }}{{ config?.apiModel ?? '-' }}</p> <p>{{ $t("setting.api") }}{{ config?.apiModel ?? '-' }}</p>
<p>{{ $t("setting.balance") }}{{ config?.balance ?? '-' }}</p>
<p>{{ $t("setting.reverseProxy") }}{{ config?.reverseProxy ?? '-' }}</p> <p>{{ $t("setting.reverseProxy") }}{{ config?.reverseProxy ?? '-' }}</p>
<p>{{ $t("setting.timeout") }}{{ config?.timeoutMs ?? '-' }}</p> <p>{{ $t("setting.timeout") }}{{ config?.timeoutMs ?? '-' }}</p>
<p>{{ $t("setting.socks") }}{{ config?.socksProxy ?? '-' }}</p> <p>{{ $t("setting.socks") }}{{ config?.socksProxy ?? '-' }}</p>

@ -63,6 +63,7 @@ export default {
timeout: 'Timeout', timeout: 'Timeout',
socks: 'Socks', socks: 'Socks',
httpsProxy: 'HTTPS Proxy', httpsProxy: 'HTTPS Proxy',
balance: 'API Balance',
}, },
store: { store: {
local: 'Local', local: 'Local',

@ -63,6 +63,7 @@ export default {
timeout: '超时', timeout: '超时',
socks: 'Socks', socks: 'Socks',
httpsProxy: 'HTTPS Proxy', httpsProxy: 'HTTPS Proxy',
balance: 'API余额',
}, },
store: { store: {
local: '本地', local: '本地',

@ -63,6 +63,7 @@ export default {
timeout: '逾時', timeout: '逾時',
socks: 'Socks', socks: 'Socks',
httpsProxy: 'HTTPS Proxy', httpsProxy: 'HTTPS Proxy',
balance: 'API余額',
}, },
store: { store: {
local: '本機', local: '本機',

Loading…
Cancel
Save