🎉 feat: initialize 宇之然AI维度 project
- uni-app Vue3 frontend with dark glassmorphism theme - 5 AI dimensions: Origin / Development / Current / Learning / Trend - AI Chat system prompt updated from geometry to AI - 23 AI knowledge articles initialized in DB - Trend API + cron job for daily news - Product pricing (Pro ¥19.9, VIP ¥39.9) - Pinia stores + API utilities - AGENTS.md documentation
This commit is contained in:
Executable
+351
@@ -0,0 +1,351 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const { AIModel } = require('../models')
|
||||
const { authMiddleware, requirePermission } = require('../middleware/auth')
|
||||
const ApiResponse = require('../utils/response')
|
||||
const logger = require('../utils/logger')
|
||||
|
||||
// 所有路由都需要认证
|
||||
router.use(authMiddleware)
|
||||
|
||||
/**
|
||||
* 获取当前使用的模型(公开接口,用于AI问答)
|
||||
* GET /api/ai-model/current
|
||||
*/
|
||||
router.get('/current', async (req, res) => {
|
||||
try {
|
||||
const model = await AIModel.getCurrentModel()
|
||||
|
||||
if (!model) {
|
||||
// 如果没有配置模型,返回环境变量中的默认配置
|
||||
return ApiResponse.success(res, {
|
||||
name: '默认模型',
|
||||
modelId: process.env.OPENAI_MODEL || 'gpt-3.5-turbo',
|
||||
apiUrl: process.env.OPENAI_API_URL || 'https://api.openai.com/v1/chat/completions',
|
||||
apiKey: process.env.OPENAI_API_KEY || '',
|
||||
config: {
|
||||
temperature: 0.7,
|
||||
maxTokens: 800,
|
||||
topP: 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 返回模型信息(不返回API Key)
|
||||
return ApiResponse.success(res, {
|
||||
name: model.name,
|
||||
modelId: model.modelId,
|
||||
config: model.config
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
logger.error('获取当前模型失败:', error)
|
||||
return ApiResponse.serverError(res, error.message)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取模型列表(管理后台)
|
||||
* GET /api/ai-model/list
|
||||
*/
|
||||
router.get('/list', requirePermission('dashboard:read'), async (req, res) => {
|
||||
try {
|
||||
const { page = 1, pageSize = 10, keyword = '' } = req.query
|
||||
|
||||
const query = keyword
|
||||
? { $or: [
|
||||
{ name: { $regex: keyword, $options: 'i' } },
|
||||
{ modelId: { $regex: keyword, $options: 'i' } }
|
||||
]}
|
||||
: {}
|
||||
|
||||
const [list, total] = await Promise.all([
|
||||
AIModel.find(query)
|
||||
.sort({ isDefault: -1, priority: 1, createdAt: -1 })
|
||||
.skip((page - 1) * pageSize)
|
||||
.limit(parseInt(pageSize)),
|
||||
AIModel.countDocuments(query)
|
||||
])
|
||||
|
||||
return ApiResponse.success(res, {
|
||||
list,
|
||||
total,
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize)
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
logger.error('获取模型列表失败:', error)
|
||||
return ApiResponse.serverError(res, error.message)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取所有启用的模型(下拉选择用)
|
||||
* GET /api/ai-model/active
|
||||
*/
|
||||
router.get('/active', requirePermission('dashboard:read'), async (req, res) => {
|
||||
try {
|
||||
const models = await AIModel.getActiveModels()
|
||||
|
||||
return ApiResponse.success(res, {
|
||||
list: models.map(m => ({
|
||||
_id: m._id,
|
||||
name: m.name,
|
||||
modelId: m.modelId,
|
||||
isDefault: m.isDefault
|
||||
}))
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
logger.error('获取启用模型失败:', error)
|
||||
return ApiResponse.serverError(res, error.message)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取模型详情
|
||||
* GET /api/ai-model/:id
|
||||
*/
|
||||
router.get('/:id', requirePermission('dashboard:read'), async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
|
||||
const model = await AIModel.findById(id)
|
||||
|
||||
if (!model) {
|
||||
return ApiResponse.notFound(res, '模型不存在')
|
||||
}
|
||||
|
||||
return ApiResponse.success(res, { model })
|
||||
|
||||
} catch (error) {
|
||||
logger.error('获取模型详情失败:', error)
|
||||
return ApiResponse.serverError(res, error.message)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 创建模型
|
||||
* POST /api/ai-model
|
||||
*/
|
||||
router.post('/', requirePermission('settings:write'), async (req, res) => {
|
||||
try {
|
||||
const { name, modelId, apiUrl, apiKey, description, config, priority } = req.body
|
||||
|
||||
// 验证必填字段
|
||||
if (!name || !modelId || !apiUrl || !apiKey) {
|
||||
return ApiResponse.error(res, '请填写所有必填字段', 400)
|
||||
}
|
||||
|
||||
// 检查modelId是否已存在
|
||||
const existingModel = await AIModel.findOne({ modelId })
|
||||
if (existingModel) {
|
||||
return ApiResponse.error(res, '该模型ID已存在', 400)
|
||||
}
|
||||
|
||||
const model = new AIModel({
|
||||
name,
|
||||
modelId,
|
||||
apiUrl,
|
||||
apiKey,
|
||||
description,
|
||||
config,
|
||||
priority
|
||||
})
|
||||
|
||||
await model.save()
|
||||
|
||||
logger.info('创建AI模型:', { name, modelId, admin: req.admin.username })
|
||||
|
||||
return ApiResponse.success(res, {
|
||||
message: '创建成功',
|
||||
model: {
|
||||
_id: model._id,
|
||||
name: model.name,
|
||||
modelId: model.modelId,
|
||||
isDefault: model.isDefault,
|
||||
isActive: model.isActive
|
||||
}
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
logger.error('创建模型失败:', error)
|
||||
return ApiResponse.serverError(res, error.message)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 更新模型
|
||||
* PUT /api/ai-model/:id
|
||||
*/
|
||||
router.put('/:id', requirePermission('settings:write'), async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const { name, modelId, apiUrl, apiKey, description, isActive, isDefault, config, priority } = req.body
|
||||
|
||||
const model = await AIModel.findById(id)
|
||||
|
||||
if (!model) {
|
||||
return ApiResponse.notFound(res, '模型不存在')
|
||||
}
|
||||
|
||||
// 如果修改了modelId,检查是否与其他模型冲突
|
||||
if (modelId && modelId !== model.modelId) {
|
||||
const existingModel = await AIModel.findOne({ modelId, _id: { $ne: id } })
|
||||
if (existingModel) {
|
||||
return ApiResponse.error(res, '该模型ID已存在', 400)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
if (name) model.name = name
|
||||
if (modelId) model.modelId = modelId
|
||||
if (apiUrl) model.apiUrl = apiUrl
|
||||
if (apiKey) model.apiKey = apiKey
|
||||
if (description !== undefined) model.description = description
|
||||
if (isActive !== undefined) model.isActive = isActive
|
||||
if (isDefault !== undefined) model.isDefault = isDefault
|
||||
if (config) model.config = { ...model.config, ...config }
|
||||
if (priority !== undefined) model.priority = priority
|
||||
|
||||
await model.save()
|
||||
|
||||
logger.info('更新AI模型:', { id, name: model.name, admin: req.admin.username })
|
||||
|
||||
return ApiResponse.success(res, {
|
||||
message: '更新成功',
|
||||
model: {
|
||||
_id: model._id,
|
||||
name: model.name,
|
||||
modelId: model.modelId,
|
||||
isDefault: model.isDefault,
|
||||
isActive: model.isActive
|
||||
}
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
logger.error('更新模型失败:', error)
|
||||
return ApiResponse.serverError(res, error.message)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 删除模型
|
||||
* DELETE /api/ai-model/:id
|
||||
*/
|
||||
router.delete('/:id', requirePermission('settings:write'), async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
|
||||
const model = await AIModel.findById(id)
|
||||
|
||||
if (!model) {
|
||||
return ApiResponse.notFound(res, '模型不存在')
|
||||
}
|
||||
|
||||
// 检查是否是唯一的默认模型
|
||||
if (model.isDefault) {
|
||||
const defaultCount = await AIModel.countDocuments({ isDefault: true })
|
||||
if (defaultCount <= 1) {
|
||||
return ApiResponse.error(res, '不能删除唯一的默认模型,请先设置其他默认模型', 400)
|
||||
}
|
||||
}
|
||||
|
||||
await AIModel.findByIdAndDelete(id)
|
||||
|
||||
logger.info('删除AI模型:', { id, name: model.name, admin: req.admin.username })
|
||||
|
||||
return ApiResponse.success(res, { message: '删除成功' })
|
||||
|
||||
} catch (error) {
|
||||
logger.error('删除模型失败:', error)
|
||||
return ApiResponse.serverError(res, error.message)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 设置默认模型
|
||||
* POST /api/ai-model/:id/set-default
|
||||
*/
|
||||
router.post('/:id/set-default', requirePermission('settings:write'), async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
|
||||
const model = await AIModel.findById(id)
|
||||
|
||||
if (!model) {
|
||||
return ApiResponse.notFound(res, '模型不存在')
|
||||
}
|
||||
|
||||
if (!model.isActive) {
|
||||
return ApiResponse.error(res, '不能将禁用模型设为默认', 400)
|
||||
}
|
||||
|
||||
model.isDefault = true
|
||||
await model.save()
|
||||
|
||||
logger.info('设置默认AI模型:', { id, name: model.name, admin: req.admin.username })
|
||||
|
||||
return ApiResponse.success(res, {
|
||||
message: '设置默认模型成功',
|
||||
model: {
|
||||
_id: model._id,
|
||||
name: model.name,
|
||||
modelId: model.modelId,
|
||||
isDefault: true
|
||||
}
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
logger.error('设置默认模型失败:', error)
|
||||
return ApiResponse.serverError(res, error.message)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 初始化默认模型(从环境变量)
|
||||
* POST /api/ai-model/init-default
|
||||
*/
|
||||
router.post('/init-default', requirePermission('settings:write'), async (req, res) => {
|
||||
try {
|
||||
// 检查是否已有模型
|
||||
const existingCount = await AIModel.countDocuments()
|
||||
|
||||
if (existingCount > 0) {
|
||||
return ApiResponse.error(res, '已有模型配置,如需重新初始化请先删除现有模型', 400)
|
||||
}
|
||||
|
||||
// 从环境变量创建默认模型
|
||||
const defaultModel = new AIModel({
|
||||
name: '默认模型',
|
||||
modelId: process.env.OPENAI_MODEL || 'gpt-3.5-turbo',
|
||||
apiUrl: process.env.OPENAI_API_URL || 'https://api.openai.com/v1/chat/completions',
|
||||
apiKey: process.env.OPENAI_API_KEY || '',
|
||||
description: '从环境变量初始化的默认模型',
|
||||
isDefault: true,
|
||||
isActive: true,
|
||||
priority: 0
|
||||
})
|
||||
|
||||
await defaultModel.save()
|
||||
|
||||
logger.info('初始化默认AI模型:', { admin: req.admin.username })
|
||||
|
||||
return ApiResponse.success(res, {
|
||||
message: '初始化默认模型成功',
|
||||
model: {
|
||||
_id: defaultModel._id,
|
||||
name: defaultModel.name,
|
||||
modelId: defaultModel.modelId,
|
||||
isDefault: true
|
||||
}
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
logger.error('初始化默认模型失败:', error)
|
||||
return ApiResponse.serverError(res, error.message)
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
Reference in New Issue
Block a user