Public Access
feat: add AI grading test endpoint and update connection logic
This commit is contained in:
1 parent
993265318b
commit
201d21a678
4 files changed
+52
-16
No files matched your search
@@ -1,4 +1,5 @@
|
|||||||
import { Router } from 'express'
|
import { Router } from 'express'
|
||||||
|
import { z } from 'zod'
|
||||||
import { validateBody, validateQuery, validateParams } from '../middleware/validate.js'
|
import { validateBody, validateQuery, validateParams } from '../middleware/validate.js'
|
||||||
import { asyncHandler } from '../utils/asyncHandler.js'
|
import { asyncHandler } from '../utils/asyncHandler.js'
|
||||||
import { requireAuth, requireAdmin } from '../middleware/auth.js'
|
import { requireAuth, requireAdmin } from '../middleware/auth.js'
|
||||||
@@ -7,6 +8,7 @@ import * as userRepo from '../repositories/userRepo.js'
|
|||||||
import * as resultRepo from '../repositories/resultRepo.js'
|
import * as resultRepo from '../repositories/resultRepo.js'
|
||||||
import * as settingsRepo from '../repositories/settingsRepo.js'
|
import * as settingsRepo from '../repositories/settingsRepo.js'
|
||||||
import { updateUserRoleSchema, updateSettingSchema, updateSettingsBatchSchema, listUsersQuerySchema, userIdParamSchema } from '../validators/adminSchemas.js'
|
import { updateUserRoleSchema, updateSettingSchema, updateSettingsBatchSchema, listUsersQuerySchema, userIdParamSchema } from '../validators/adminSchemas.js'
|
||||||
|
import { gradeQuestionWithAI } from '../services/aiGrading.js'
|
||||||
import type { Request } from 'express'
|
import type { Request } from 'express'
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
@@ -140,4 +142,28 @@ router.put(
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// POST /api/admin/test-ai
|
||||||
|
router.post(
|
||||||
|
'/test-ai',
|
||||||
|
validateBody(z.object({
|
||||||
|
provider: z.string().min(1),
|
||||||
|
apiKey: z.string().min(1),
|
||||||
|
model: z.string().min(1),
|
||||||
|
baseURL: z.string().optional(),
|
||||||
|
})),
|
||||||
|
asyncHandler(async (req: Request, res) => {
|
||||||
|
const { provider, apiKey, model, baseURL } = req.body
|
||||||
|
const result = await gradeQuestionWithAI(
|
||||||
|
{
|
||||||
|
type: 'short-answer',
|
||||||
|
question: { prompt: 'What is 2+2?', answer: '4' },
|
||||||
|
answer: '4',
|
||||||
|
language: 'en',
|
||||||
|
},
|
||||||
|
{ provider, apiKey, model, baseURL },
|
||||||
|
)
|
||||||
|
res.json(result)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
@@ -14,12 +14,12 @@ interface GradeRequest {
|
|||||||
language: string
|
language: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function getClient(): OpenAI | null {
|
function getClient(override?: { provider?: string; apiKey?: string; baseURL?: string }): OpenAI | null {
|
||||||
const provider = getSetting('ai_provider')
|
const provider = override?.provider ?? getSetting('ai_provider')
|
||||||
const apiKey = getSetting('ai_api_key')
|
const apiKey = override?.apiKey ?? getSetting('ai_api_key')
|
||||||
if (!provider || !apiKey) return null
|
if (!provider || !apiKey) return null
|
||||||
|
|
||||||
const customBase = getSetting('ai_base_url')
|
const customBase = override?.baseURL ?? getSetting('ai_base_url')
|
||||||
const baseURL = provider === 'openai' ? undefined : (customBase || undefined)
|
const baseURL = provider === 'openai' ? undefined : (customBase || undefined)
|
||||||
return new OpenAI({ apiKey, baseURL })
|
return new OpenAI({ apiKey, baseURL })
|
||||||
}
|
}
|
||||||
@@ -141,13 +141,16 @@ Use the human-readable text labels in the arrays.`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function gradeQuestionWithAI(req: GradeRequest): Promise<GradingResult> {
|
export async function gradeQuestionWithAI(
|
||||||
const client = getClient()
|
req: GradeRequest,
|
||||||
|
override?: { provider?: string; apiKey?: string; baseURL?: string; model?: string },
|
||||||
|
): Promise<GradingResult> {
|
||||||
|
const client = getClient(override)
|
||||||
if (!client) {
|
if (!client) {
|
||||||
throw new Error('AI grading is not configured. Please set provider and API key in admin settings.')
|
throw new Error('AI grading is not configured. Please set provider and API key in admin settings.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const model = getSetting('ai_model') || 'gpt-4o-mini'
|
const model = (override?.model ?? getSetting('ai_model')) || 'gpt-4o-mini'
|
||||||
const prompt = buildPrompt(req)
|
const prompt = buildPrompt(req)
|
||||||
|
|
||||||
const completion = await client.chat.completions.create({
|
const completion = await client.chat.completions.create({
|
||||||
|
|||||||
@@ -96,14 +96,16 @@ export function sendTestEmail(to: string | undefined, smtp: SmtpConfig): Promise
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function testAiConnection(): Promise<GradingResult> {
|
export interface AiTestPayload {
|
||||||
return apiFetch('/api/grade', {
|
provider: string
|
||||||
|
apiKey: string
|
||||||
|
model: string
|
||||||
|
baseURL?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function testAiConnection(payload: AiTestPayload): Promise<GradingResult> {
|
||||||
|
return apiFetch('/api/admin/test-ai', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(payload),
|
||||||
type: 'short-answer',
|
|
||||||
question: { prompt: 'What is 2+2?', answer: '4' },
|
|
||||||
answer: '4',
|
|
||||||
language: 'en',
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -135,7 +135,12 @@ export default function AdminDashboard({ onExit }: Readonly<Props>) {
|
|||||||
setTestAiSending(true);
|
setTestAiSending(true);
|
||||||
setTestAiMsg(null);
|
setTestAiMsg(null);
|
||||||
try {
|
try {
|
||||||
await adminApi.testAiConnection();
|
await adminApi.testAiConnection({
|
||||||
|
provider: settingsDraft.ai_provider,
|
||||||
|
apiKey: settingsDraft.ai_api_key,
|
||||||
|
model: settingsDraft.ai_model || 'gpt-4o-mini',
|
||||||
|
baseURL: settingsDraft.ai_base_url || undefined,
|
||||||
|
});
|
||||||
setTestAiMsg({ ok: true, text: t("admin.aiGrading.testSuccess") });
|
setTestAiMsg({ ok: true, text: t("admin.aiGrading.testSuccess") });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setTestAiMsg({ ok: false, text: (e as Error).message });
|
setTestAiMsg({ ok: false, text: (e as Error).message });
|
||||||
|
|||||||
Reference in new issue
Block a user