This commit is contained in:
2025-12-29 23:09:06 +08:00
parent df6db9d88b
commit 7370f649c4
5 changed files with 184 additions and 8 deletions

View File

@@ -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) {

View File

@@ -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
View 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
View 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)

View File

@@ -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';