update
This commit is contained in:
@@ -2,7 +2,9 @@ import { app } from '@/app.ts'
|
|||||||
import { asr } from './modules/index.ts'
|
import { asr } from './modules/index.ts'
|
||||||
app.route({
|
app.route({
|
||||||
path: 'asr',
|
path: 'asr',
|
||||||
key: 'text'
|
key: 'text',
|
||||||
|
middleware: ['auth'],
|
||||||
|
description: '语音转文字,将base64的音频数据转换为文字, 参数: base64Audio, 为base64编码的音频数据',
|
||||||
}).define(async (ctx) => {
|
}).define(async (ctx) => {
|
||||||
const base64Audio = ctx.query.base64Audio as string
|
const base64Audio = ctx.query.base64Audio as string
|
||||||
if (!base64Audio) {
|
if (!base64Audio) {
|
||||||
|
|||||||
@@ -152,7 +152,12 @@ app
|
|||||||
const tokenUser = ctx.state.tokenUser;
|
const tokenUser = ctx.state.tokenUser;
|
||||||
let isUser = !!tokenUser;
|
let isUser = !!tokenUser;
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
list: app.router.routes.map((item) => {
|
list: app.router.routes.filter(item => {
|
||||||
|
if (item.id === 'auth' || item.id === 'auth-can' || item.id === 'check-auth-admin' || item.id === 'auth-admin') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}).map((item) => {
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
path: item.path,
|
path: item.path,
|
||||||
|
|||||||
51
src/routes/ai/cost.ts
Normal file
51
src/routes/ai/cost.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { redis } from '@/app.ts'
|
||||||
|
|
||||||
|
export class AICost {
|
||||||
|
/**
|
||||||
|
* 获取余额
|
||||||
|
* @param userId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static async getAmount(userId: string): Promise<number> {
|
||||||
|
const cost = await redis.get(`ai:cost:amount:${userId}`);
|
||||||
|
if (!cost) {
|
||||||
|
return 2 * 100 * 10000;// 默认200万调用量 等于 2M 的额度,每一个月的初始额度,每一个月进行重置。
|
||||||
|
}
|
||||||
|
return cost ? parseFloat(cost) : 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 扣除余额
|
||||||
|
* @param userId
|
||||||
|
* @param amount
|
||||||
|
*/
|
||||||
|
static async decuctAmount(userId: string, amount: number): Promise<void> {
|
||||||
|
const key = `ai:cost:amount:${userId}`;
|
||||||
|
const current = await this.getAmount(userId);
|
||||||
|
const newAmount = Math.max(0, current - amount);
|
||||||
|
await redis.set(key, newAmount.toString());
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 检查余额是否足够
|
||||||
|
* @param userId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static async checkAmount(userId: string): Promise<boolean> {
|
||||||
|
const current = await this.getAmount(userId);
|
||||||
|
return current >= 10; // 最低余额10
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 增加余额
|
||||||
|
* @param userId
|
||||||
|
* @param amount
|
||||||
|
*/
|
||||||
|
static async addAmount(userId: string, amount: number): Promise<void> {
|
||||||
|
const key = `ai:cost:amount:${userId}`;
|
||||||
|
const current = await this.getAmount(userId);
|
||||||
|
const newAmount = current + amount;
|
||||||
|
await redis.set(key, newAmount.toString());
|
||||||
|
}
|
||||||
|
static async setAmount(userId: string, amount: number): Promise<void> {
|
||||||
|
const key = `ai:cost:amount:${userId}`;
|
||||||
|
await redis.set(key, amount.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
121
src/routes/ai/index.ts
Normal file
121
src/routes/ai/index.ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import { app, ai } from '@/app.ts';
|
||||||
|
import { AICost } from './cost.ts';
|
||||||
|
|
||||||
|
const description = `ai调用工具,参数说明:
|
||||||
|
- model: 使用的模型,默认qwen-plus,可选qwen-plus、qwen-flash、qwen-max、qwen-coder-plus等
|
||||||
|
- messages: 消息数组,格式为[{role: 'user'|'assistant'|'system', content: string}],与OpenAI的chat-completions接口一致
|
||||||
|
- question: 兼容提问参数,如果传入messages则忽略question参数
|
||||||
|
- isJson: 是否只返回json内容,默认false,如果为true则会尝试从AI返回的内容中提取json对象返回`;
|
||||||
|
|
||||||
|
app.route({
|
||||||
|
path: 'ai',
|
||||||
|
key: '',
|
||||||
|
description,
|
||||||
|
middleware: ['auth']
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const query = ctx.query || {};
|
||||||
|
const tokenUser = ctx.state.tokenUser;
|
||||||
|
const userId = tokenUser.id;
|
||||||
|
const isJson = query.isJson ?? false;
|
||||||
|
const model = query.model || 'qwen-plus';
|
||||||
|
const hasEnough = await AICost.checkAmount(userId);
|
||||||
|
if (!hasEnough) {
|
||||||
|
ctx.throw(402, 'token 余额不足,请充值后重试');
|
||||||
|
}
|
||||||
|
const messages = query.messages || [];
|
||||||
|
if (messages.length === 0 && query.question) {
|
||||||
|
messages.push({ role: 'user', content: query.question });
|
||||||
|
} else if (messages.length === 0) {
|
||||||
|
ctx.throw(400, 'messages不能为空');
|
||||||
|
}
|
||||||
|
await ai.chat(messages, { model, messages });
|
||||||
|
const text = ai.responseText;
|
||||||
|
const cost = ai.total_tokens;
|
||||||
|
let payload: any = undefined;
|
||||||
|
await AICost.decuctAmount(userId, cost);
|
||||||
|
if (isJson) {
|
||||||
|
payload = ai.utils.extractJsonFromMarkdown(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
text,
|
||||||
|
cost: cost,
|
||||||
|
model,
|
||||||
|
payload
|
||||||
|
}
|
||||||
|
}).addTo(app)
|
||||||
|
|
||||||
|
app.route({
|
||||||
|
path: 'ai',
|
||||||
|
key: 'cost',
|
||||||
|
description: '获取AI调用余额',
|
||||||
|
middleware: ['auth']
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const tokenUser = ctx.state.tokenUser;
|
||||||
|
const userId = tokenUser.id;
|
||||||
|
const amount = await AICost.getAmount(userId);
|
||||||
|
ctx.body = {
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
}).addTo(app)
|
||||||
|
|
||||||
|
app.route({
|
||||||
|
path: 'ai',
|
||||||
|
key: 'cost/get',
|
||||||
|
description: '检查AI调用余额是否足够',
|
||||||
|
middleware: ['auth-admin']
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const userId = ctx.query.userId || '';
|
||||||
|
if (!userId) {
|
||||||
|
ctx.throw(400, 'userId不能为空');
|
||||||
|
}
|
||||||
|
const amount = await AICost.getAmount(userId);
|
||||||
|
ctx.body = {
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
}).addTo(app)
|
||||||
|
|
||||||
|
|
||||||
|
app.route({
|
||||||
|
path: 'ai',
|
||||||
|
key: 'cost/add',
|
||||||
|
description: '增加AI调用余额, 需要传入userId和amount两个参数',
|
||||||
|
middleware: ['auth-admin']
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const userId = ctx.query.userId || '';
|
||||||
|
if (!userId) {
|
||||||
|
ctx.throw(400, 'userId不能为空');
|
||||||
|
}
|
||||||
|
const amount = ctx.query.amount || 0;
|
||||||
|
if (amount <= 0) {
|
||||||
|
ctx.throw(400, 'amount必须大于0');
|
||||||
|
}
|
||||||
|
await AICost.addAmount(userId, amount);
|
||||||
|
const newAmount = await AICost.getAmount(userId);
|
||||||
|
ctx.body = {
|
||||||
|
amount: newAmount
|
||||||
|
}
|
||||||
|
}).addTo(app)
|
||||||
|
|
||||||
|
app.route({
|
||||||
|
path: 'ai',
|
||||||
|
key: 'cost/set',
|
||||||
|
description: '设置AI调用余额, 需要传入userId和amount两个参数',
|
||||||
|
middleware: ['auth-admin']
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const userId = ctx.query.userId || '';
|
||||||
|
if (!userId) {
|
||||||
|
ctx.throw(400, 'userId不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount = ctx.query.amount || 0;
|
||||||
|
if (amount < 0) {
|
||||||
|
ctx.throw(400, 'amount必须大于等于0');
|
||||||
|
}
|
||||||
|
|
||||||
|
await AICost.setAmount(userId, amount);
|
||||||
|
const newAmount = await AICost.getAmount(userId);
|
||||||
|
ctx.body = {
|
||||||
|
amount: newAmount
|
||||||
|
}
|
||||||
|
}).addTo(app)
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
import './container/index.ts';
|
|
||||||
|
|
||||||
import './user/index.ts';
|
import './user/index.ts';
|
||||||
|
|
||||||
import './github/index.ts';
|
|
||||||
|
|
||||||
import './app-manager/index.ts';
|
import './app-manager/index.ts';
|
||||||
|
|
||||||
import './file/index.ts';
|
import './file/index.ts';
|
||||||
@@ -12,6 +8,7 @@ import './micro-app/index.ts';
|
|||||||
|
|
||||||
import './config/index.ts';
|
import './config/index.ts';
|
||||||
|
|
||||||
// import './mark/index.ts';
|
import './file-listener/index.ts';
|
||||||
|
|
||||||
import './file-listener/index.ts';
|
|
||||||
|
import './ai/index.ts';
|
||||||
Reference in New Issue
Block a user