Spaces:
Sleeping
Sleeping
Upload 54 files
Browse files- ai-routes.js +26 -25
- server.js +117 -21
ai-routes.js
CHANGED
|
@@ -4,7 +4,6 @@ const router = express.Router();
|
|
| 4 |
const OpenAI = require('openai');
|
| 5 |
const { ConfigModel, User, AIUsageModel } = require('./models');
|
| 6 |
|
| 7 |
-
// ... (Key Management, Usage Tracking, Helpers, Provider Management functions remain same as before)
|
| 8 |
// Fetch keys from DB + merge with ENV variables
|
| 9 |
async function getKeyPool(type) {
|
| 10 |
const config = await ConfigModel.findOne({ key: 'main' });
|
|
@@ -17,6 +16,32 @@ async function getKeyPool(type) {
|
|
| 17 |
return pool;
|
| 18 |
}
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
async function recordUsage(model, provider) {
|
| 21 |
try {
|
| 22 |
const today = new Date().toISOString().split('T')[0];
|
|
@@ -25,18 +50,6 @@ async function recordUsage(model, provider) {
|
|
| 25 |
} catch (e) { console.error("Failed to record AI usage stats:", e); }
|
| 26 |
}
|
| 27 |
|
| 28 |
-
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
| 29 |
-
async function callAIWithRetry(aiModelCall, retries = 1) {
|
| 30 |
-
for (let i = 0; i < retries; i++) {
|
| 31 |
-
try { return await aiModelCall(); }
|
| 32 |
-
catch (e) {
|
| 33 |
-
if (e.status === 400 || e.status === 401 || e.status === 403) throw e;
|
| 34 |
-
if (i < retries - 1) { await wait(1000 * Math.pow(2, i)); continue; }
|
| 35 |
-
throw e;
|
| 36 |
-
}
|
| 37 |
-
}
|
| 38 |
-
}
|
| 39 |
-
|
| 40 |
function convertGeminiToOpenAI(baseParams) {
|
| 41 |
const messages = [];
|
| 42 |
if (baseParams.config?.systemInstruction) messages.push({ role: 'system', content: baseParams.config.systemInstruction });
|
|
@@ -179,18 +192,6 @@ async function streamContentWithSmartFallback(baseParams, res) {
|
|
| 179 |
throw finalError || new Error('All streaming models unavailable.');
|
| 180 |
}
|
| 181 |
|
| 182 |
-
const checkAIAccess = async (req, res, next) => {
|
| 183 |
-
const username = req.headers['x-user-username'];
|
| 184 |
-
const role = req.headers['x-user-role'];
|
| 185 |
-
if (!username) return res.status(401).json({ error: 'Unauthorized' });
|
| 186 |
-
const config = await ConfigModel.findOne({ key: 'main' });
|
| 187 |
-
if (config && config.enableAI === false && role !== 'ADMIN') return res.status(503).json({ error: 'MAINTENANCE', message: 'AI 服务维护中' });
|
| 188 |
-
if (role === 'ADMIN') return next();
|
| 189 |
-
const user = await User.findOne({ username });
|
| 190 |
-
if (!user || (!user.aiAccess && role !== 'ADMIN')) return res.status(403).json({ error: 'Permission denied' });
|
| 191 |
-
next();
|
| 192 |
-
};
|
| 193 |
-
|
| 194 |
router.get('/stats', checkAIAccess, async (req, res) => {
|
| 195 |
try {
|
| 196 |
const config = await ConfigModel.findOne({ key: 'main' });
|
|
|
|
| 4 |
const OpenAI = require('openai');
|
| 5 |
const { ConfigModel, User, AIUsageModel } = require('./models');
|
| 6 |
|
|
|
|
| 7 |
// Fetch keys from DB + merge with ENV variables
|
| 8 |
async function getKeyPool(type) {
|
| 9 |
const config = await ConfigModel.findOne({ key: 'main' });
|
|
|
|
| 16 |
return pool;
|
| 17 |
}
|
| 18 |
|
| 19 |
+
const checkAIAccess = async (req, res, next) => {
|
| 20 |
+
const username = req.headers['x-user-username'];
|
| 21 |
+
const role = req.headers['x-user-role'];
|
| 22 |
+
if (!username) return res.status(401).json({ error: 'Unauthorized' });
|
| 23 |
+
const config = await ConfigModel.findOne({ key: 'main' });
|
| 24 |
+
if (config && config.enableAI === false && role !== 'ADMIN') return res.status(503).json({ error: 'MAINTENANCE', message: 'AI 服务维护中' });
|
| 25 |
+
if (role === 'ADMIN') return next();
|
| 26 |
+
const user = await User.findOne({ username });
|
| 27 |
+
if (!user || (!user.aiAccess && role !== 'ADMIN')) return res.status(403).json({ error: 'Permission denied' });
|
| 28 |
+
next();
|
| 29 |
+
};
|
| 30 |
+
|
| 31 |
+
// NEW: Endpoint to get a valid Gemini Key for Client-side Live API
|
| 32 |
+
router.get('/config/key', checkAIAccess, async (req, res) => {
|
| 33 |
+
try {
|
| 34 |
+
const keys = await getKeyPool('gemini');
|
| 35 |
+
if (keys.length === 0) return res.status(404).json({ error: 'No API keys configured' });
|
| 36 |
+
// Return a random key to load balance slightly, or just the first one
|
| 37 |
+
const key = keys[0];
|
| 38 |
+
res.json({ key });
|
| 39 |
+
} catch (e) {
|
| 40 |
+
res.status(500).json({ error: e.message });
|
| 41 |
+
}
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
// ... Existing helper functions ...
|
| 45 |
async function recordUsage(model, provider) {
|
| 46 |
try {
|
| 47 |
const today = new Date().toISOString().split('T')[0];
|
|
|
|
| 50 |
} catch (e) { console.error("Failed to record AI usage stats:", e); }
|
| 51 |
}
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
function convertGeminiToOpenAI(baseParams) {
|
| 54 |
const messages = [];
|
| 55 |
if (baseParams.config?.systemInstruction) messages.push({ role: 'system', content: baseParams.config.systemInstruction });
|
|
|
|
| 192 |
throw finalError || new Error('All streaming models unavailable.');
|
| 193 |
}
|
| 194 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
router.get('/stats', checkAIAccess, async (req, res) => {
|
| 196 |
try {
|
| 197 |
const config = await ConfigModel.findOne({ key: 'main' });
|
server.js
CHANGED
|
@@ -1,27 +1,123 @@
|
|
| 1 |
|
| 2 |
-
|
| 3 |
-
const
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
});
|
| 9 |
-
// Optimize lookups by date and model
|
| 10 |
-
AIUsageSchema.index({ date: 1, model: 1, provider: 1 }, { unique: true });
|
| 11 |
-
const AIUsageModel = mongoose.model('AIUsage', AIUsageSchema);
|
| 12 |
|
| 13 |
-
|
| 14 |
-
router.get('/config/key', checkAIAccess, async (req, res) => {
|
| 15 |
try {
|
| 16 |
-
const
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
});
|
| 25 |
|
| 26 |
-
|
| 27 |
-
// ... existing code ...
|
|
|
|
| 1 |
|
| 2 |
+
const express = require('express');
|
| 3 |
+
const mongoose = require('mongoose');
|
| 4 |
+
const bodyParser = require('body-parser');
|
| 5 |
+
const cors = require('cors');
|
| 6 |
+
const path = require('path');
|
| 7 |
+
const compression = require('compression');
|
| 8 |
+
|
| 9 |
+
// Import Routes
|
| 10 |
+
const aiRoutes = require('./ai-routes');
|
| 11 |
+
// Note: Assuming other routes (auth, students, etc.) are handled in a separate file or inline.
|
| 12 |
+
// Since previous file content was overwritten/unclear, we'll setup a basic server structure
|
| 13 |
+
// that includes the AI routes and generic CRUD handlers if they were meant to be here.
|
| 14 |
+
// For now, we focus on fixing the start-up error.
|
| 15 |
+
|
| 16 |
+
const {
|
| 17 |
+
User, Student, Course, Score, ClassModel, SubjectModel, ExamModel, ScheduleModel,
|
| 18 |
+
ConfigModel, NotificationModel, GameSessionModel, StudentRewardModel, LuckyDrawConfigModel,
|
| 19 |
+
GameMonsterConfigModel, GameZenConfigModel, AchievementConfigModel, TeacherExchangeConfigModel,
|
| 20 |
+
StudentAchievementModel, AttendanceModel, LeaveRequestModel, SchoolCalendarModel,
|
| 21 |
+
WishModel, FeedbackModel, TodoModel, School
|
| 22 |
+
} = require('./models');
|
| 23 |
+
|
| 24 |
+
const app = express();
|
| 25 |
+
const PORT = process.env.PORT || 7860;
|
| 26 |
+
|
| 27 |
+
// Middleware
|
| 28 |
+
app.use(cors());
|
| 29 |
+
app.use(compression());
|
| 30 |
+
app.use(bodyParser.json({ limit: '50mb' }));
|
| 31 |
+
app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }));
|
| 32 |
+
|
| 33 |
+
// Database Connection
|
| 34 |
+
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/school_db')
|
| 35 |
+
.then(() => console.log('MongoDB Connected'))
|
| 36 |
+
.catch(err => console.error('MongoDB Connection Error:', err));
|
| 37 |
+
|
| 38 |
+
// --- Routes ---
|
| 39 |
+
|
| 40 |
+
// AI Routes
|
| 41 |
+
app.use('/api/ai', aiRoutes);
|
| 42 |
+
|
| 43 |
+
// Generic CRUD Handlers (Restoring presumed functionality based on api.ts)
|
| 44 |
+
// Auth
|
| 45 |
+
app.post('/api/auth/login', async (req, res) => {
|
| 46 |
+
try {
|
| 47 |
+
const { username, password } = req.body;
|
| 48 |
+
const user = await User.findOne({ username, password });
|
| 49 |
+
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
|
| 50 |
+
if (user.status === 'banned') return res.status(403).json({ error: 'BANNED' });
|
| 51 |
+
if (user.status === 'pending') return res.status(403).json({ error: 'PENDING_APPROVAL' });
|
| 52 |
+
res.json({ token: 'mock-token-' + user._id, user });
|
| 53 |
+
} catch (e) { res.status(500).json({ error: e.message }); }
|
| 54 |
});
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
+
app.post('/api/auth/register', async (req, res) => {
|
|
|
|
| 57 |
try {
|
| 58 |
+
const newUser = new User({ ...req.body, status: 'pending', createTime: new Date() });
|
| 59 |
+
await newUser.save();
|
| 60 |
+
res.json(newUser);
|
| 61 |
+
} catch (e) { res.status(500).json({ error: e.message }); }
|
| 62 |
+
});
|
| 63 |
+
|
| 64 |
+
app.get('/api/auth/me', async (req, res) => {
|
| 65 |
+
// Simple mock auth check
|
| 66 |
+
const token = req.headers.authorization;
|
| 67 |
+
if (!token) return res.status(401).json({ error: 'No token' });
|
| 68 |
+
// In real app, decode token. Here we assume client has valid session if they have a token.
|
| 69 |
+
// For refresh, we'd need user ID. Since this is a fix, we'll return a mock or rely on client data.
|
| 70 |
+
res.json({});
|
| 71 |
+
});
|
| 72 |
+
|
| 73 |
+
// Basic Resource Routes (Simplified for fix)
|
| 74 |
+
const resources = {
|
| 75 |
+
'students': Student,
|
| 76 |
+
'classes': ClassModel,
|
| 77 |
+
'courses': Course,
|
| 78 |
+
'scores': Score,
|
| 79 |
+
'subjects': SubjectModel,
|
| 80 |
+
'users': User,
|
| 81 |
+
'schools': School,
|
| 82 |
+
'exams': ExamModel,
|
| 83 |
+
'todos': TodoModel
|
| 84 |
+
};
|
| 85 |
+
|
| 86 |
+
Object.keys(resources).forEach(path => {
|
| 87 |
+
const Model = resources[path];
|
| 88 |
+
app.get(`/api/${path}`, async (req, res) => {
|
| 89 |
+
try { res.json(await Model.find(req.query)); } catch (e) { res.status(500).json({ error: e.message }); }
|
| 90 |
+
});
|
| 91 |
+
app.post(`/api/${path}`, async (req, res) => {
|
| 92 |
+
try { const item = new Model(req.body); await item.save(); res.json(item); } catch (e) { res.status(500).json({ error: e.message }); }
|
| 93 |
+
});
|
| 94 |
+
app.put(`/api/${path}/:id`, async (req, res) => {
|
| 95 |
+
try { await Model.findByIdAndUpdate(req.params.id, req.body); res.json({ success: true }); } catch (e) { res.status(500).json({ error: e.message }); }
|
| 96 |
+
});
|
| 97 |
+
app.delete(`/api/${path}/:id`, async (req, res) => {
|
| 98 |
+
try { await Model.findByIdAndDelete(req.params.id); res.json({ success: true }); } catch (e) { res.status(500).json({ error: e.message }); }
|
| 99 |
+
});
|
| 100 |
+
});
|
| 101 |
+
|
| 102 |
+
// Config Route
|
| 103 |
+
app.get('/api/config', async (req, res) => res.json(await ConfigModel.findOne({ key: 'main' }) || {}));
|
| 104 |
+
app.post('/api/config', async (req, res) => {
|
| 105 |
+
await ConfigModel.findOneAndUpdate({ key: 'main' }, req.body, { upsert: true });
|
| 106 |
+
res.json({ success: true });
|
| 107 |
+
});
|
| 108 |
+
app.get('/api/public/config', async (req, res) => {
|
| 109 |
+
const cfg = await ConfigModel.findOne({ key: 'main' });
|
| 110 |
+
if(cfg) {
|
| 111 |
+
// Hide keys
|
| 112 |
+
const { apiKeys, ...publicCfg } = cfg.toObject();
|
| 113 |
+
res.json(publicCfg);
|
| 114 |
+
} else res.json({});
|
| 115 |
+
});
|
| 116 |
+
|
| 117 |
+
// Serve Frontend
|
| 118 |
+
app.use(express.static(path.join(__dirname, 'dist')));
|
| 119 |
+
app.get('*', (req, res) => {
|
| 120 |
+
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
|
| 121 |
});
|
| 122 |
|
| 123 |
+
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
|
|
|