141 lines
4.2 KiB
TypeScript
141 lines
4.2 KiB
TypeScript
import { Result } from '@kevisual/query';
|
|
import { CNB } from '../../src/index.ts';
|
|
import { useKey } from '@kevisual/context';
|
|
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
export const getConfig = async (opts: { token?: string }) => {
|
|
const kevisualEnv = useKey('KEVISUAL_ENV')
|
|
const isCNB = useKey('CNB');
|
|
let isProduction = kevisualEnv !== 'development' || (isCNB && !kevisualEnv);
|
|
const baseUrl = isProduction ? 'https://kevisual.cn/api/router' : 'https://kevisual.xiongxiao.me/api/router';
|
|
const res = await fetch(baseUrl, {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
path: 'config',
|
|
key: 'get',
|
|
data: {
|
|
key: "cnb_center_config.json"
|
|
}
|
|
}),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${opts.token!}`
|
|
},
|
|
}).then(res => res.json());
|
|
return res as Result<{
|
|
id: string, key: 'cnb_center_config.json', data: {
|
|
CNB_API_KEY: string,
|
|
CNB_COOKIE: string
|
|
}
|
|
}>;
|
|
}
|
|
type CNBItem = {
|
|
username: string,
|
|
token: string,
|
|
cookie?: string
|
|
runAt?: number
|
|
owner?: boolean
|
|
cnb: CNB
|
|
cnbAi: ReturnType<typeof createOpenAICompatible>
|
|
}
|
|
// const repo = useKey('CNB_REPO_SLUG_LOWERCASE') as string || 'kevision/kevision';
|
|
// export const cnbAi = createOpenAICompatible({
|
|
// baseURL: `https://api.cnb.cool/${repo}/-/ai/`,
|
|
// name: 'custom-cnb',
|
|
// apiKey: token,
|
|
// });
|
|
export class CNBManager {
|
|
cnbMap: Map<string, CNBItem> = new Map()
|
|
constructor() {
|
|
setInterval(() => {
|
|
this.clearExpiredCNB()
|
|
}, 1000 * 60 * 30) // 每30分钟清理一次过期的 CNB 实例
|
|
}
|
|
getDefaultCNB() {
|
|
const cnbItem = this.cnbMap.get('default')
|
|
if (!cnbItem) {
|
|
throw new Error('Default CNB not found')
|
|
}
|
|
return cnbItem
|
|
}
|
|
async getCNB(opts?: { username?: string, kevisualToken?: string }): Promise<CNBItem | null> {
|
|
const username = opts?.username
|
|
const cnbItem = this.cnbMap.get(username)
|
|
if (cnbItem) {
|
|
cnbItem.runAt = Date.now()
|
|
return cnbItem
|
|
}
|
|
|
|
const res = await getConfig({ token: opts?.kevisualToken })
|
|
if (res.code === 200) {
|
|
const cookie = res.data?.data?.CNB_COOKIE
|
|
const token = res.data?.data?.CNB_API_KEY
|
|
if (token) {
|
|
return this.addCNB({ username, token, cookie })
|
|
}
|
|
} else {
|
|
console.error('获取 CNB 配置失败', username, res)
|
|
}
|
|
return null
|
|
}
|
|
/**
|
|
* 通过上下文获取 CNB 实例(直接返回 cnb 对象)
|
|
* @param ctx
|
|
* @returns CNB 实例
|
|
*/
|
|
async getContext(ctx: any) {
|
|
const item = await this.getCNBItem(ctx)
|
|
return item.cnb
|
|
}
|
|
async getCNBItem(ctx: any) {
|
|
const tokenUser = ctx?.state?.tokenUser
|
|
const username = tokenUser?.username
|
|
if (!username) {
|
|
ctx.throw(403, 'Unauthorized')
|
|
}
|
|
if (username === 'default') {
|
|
return this.getDefaultCNB()
|
|
}
|
|
const kevisualToken = ctx.query?.token;
|
|
const item = await this.getCNB({ username, kevisualToken });
|
|
if (!item) {
|
|
ctx.throw(400, '不存在的 CNB 配置项,请检查 登录 Token 是否正确,或添加 CNB 配置')
|
|
}
|
|
return item;
|
|
}
|
|
addCNB(opts: Partial<CNBItem>) {
|
|
if (!opts.username || !opts.token) {
|
|
throw new Error('username and token are required')
|
|
}
|
|
const exist = this.cnbMap.get(opts.username)
|
|
if (exist) {
|
|
exist.runAt = Date.now()
|
|
return exist
|
|
}
|
|
const cnb = opts?.cnb || new CNB({ token: opts.token, cookie: opts.cookie });
|
|
opts.cnb = cnb;
|
|
opts.runAt = Date.now()
|
|
const repoSlug = useKey('CNB_REPO_SLUG_LOWERCASE') as string || 'kevision/kevision';
|
|
opts.cnbAi = createOpenAICompatible({
|
|
baseURL: `https://api.cnb.cool/${repoSlug}/-/ai/`,
|
|
name: `custom-cnb-${opts.username}`,
|
|
apiKey: opts.token,
|
|
})
|
|
this.cnbMap.set(opts.username, opts as CNBItem)
|
|
return opts as CNBItem
|
|
}
|
|
// 定期清理过期的 CNB 实例,默认过期时间为 1 小时
|
|
clearExpiredCNB(expireTime = 1000 * 60 * 60) {
|
|
const now = Date.now()
|
|
for (const [username, item] of this.cnbMap.entries()) {
|
|
if (username === 'default') {
|
|
continue
|
|
}
|
|
if (item.runAt && now - item.runAt > expireTime) {
|
|
this.cnbMap.delete(username)
|
|
}
|
|
}
|
|
}
|
|
clearUsername(username: string) {
|
|
this.cnbMap.delete(username)
|
|
}
|
|
} |