codex-ai-platform / api /routes /external.ts
3v324v23's picture
chore: 彻底清理项目,符合 Hugging Face 部署规范
ae4ceef
import { Router, type Response } from 'express';
import { verifyApiKey } from '../middleware/api-auth.js';
import { AIService } from '../services/ai.service.js';
import { ConcurrencyService } from '../services/concurrency.service.js';
import db from '../lib/db.js';
const router = Router();
/**
* 外部 API: Chat Completion (类似 OpenAI 接口)
* POST /api/v1/chat/completions
*/
router.post('/chat/completions', verifyApiKey, async (req: any, res) => {
const { model, messages, stream = false } = req.body;
const userId = req.user.userId;
// 1. 参数校验
if (!messages || !Array.isArray(messages)) {
return res.status(400).json({ error: { message: "'messages' must be an array" } });
}
// 2. 检查配额
try {
const user = db.prepare('SELECT quota_remaining FROM users WHERE id = ?').get(userId) as any;
if (!user || user.quota_remaining <= 0) {
return res.status(402).json({
error: {
message: "You have exceeded your current quota.",
type: "insufficient_quota",
code: "quota_exceeded"
}
});
}
// 3. 执行 AI 调用 (仅提取最后一条用户消息)
const lastUserMsg = messages.reverse().find((m: any) => m.role === 'user');
const query = lastUserMsg?.content || '';
if (!query) {
return res.status(400).json({ error: { message: "No user message found" } });
}
// 4. 调用内部服务
const result = await AIService.chatWithKnowledge(userId, query);
// 5. 扣除额度 (简单起见,每次调用扣 1 点)
db.prepare('UPDATE users SET quota_remaining = quota_remaining - 1 WHERE id = ?').run(userId);
if (stream) {
// 流式响应 (SSE)
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
for await (const chunk of result.stream) {
const content = chunk.choices[0]?.delta?.content || '';
if (content) {
const sseData = JSON.stringify({
id: `chatcmpl-${Date.now()}`,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: model || 'codex-ai-v1',
choices: [{ delta: { content }, index: 0, finish_reason: null }]
});
res.write(`data: ${sseData}\n\n`);
}
}
res.write('data: [DONE]\n\n');
res.end();
} else {
// 非流式响应
let fullContent = '';
for await (const chunk of result.stream) {
fullContent += chunk.choices[0]?.delta?.content || '';
}
res.json({
id: `chatcmpl-${Date.now()}`,
object: 'chat.completion',
created: Math.floor(Date.now() / 1000),
model: model || 'codex-ai-v1',
usage: { prompt_tokens: query.length, completion_tokens: fullContent.length, total_tokens: query.length + fullContent.length },
choices: [{ message: { role: 'assistant', content: fullContent }, finish_reason: 'stop', index: 0 }]
});
}
} catch (err: any) {
res.status(500).json({ error: { message: err.message, type: "server_error" } });
}
});
/**
* 外部 API: 获取用户余额
* GET /api/v1/user/quota
*/
router.get('/user/quota', verifyApiKey, (req: any, res) => {
const userId = req.user.userId;
const user = db.prepare('SELECT email, plan, quota_remaining FROM users WHERE id = ?').get(userId);
res.json(user);
});
export default router;