feat: add AI grading test endpoint and update connection logic
Test / backend (push) Successful in 13s
Test / frontend (push) Successful in 24s
Release Pipeline / build-and-release (push) Successful in 5m26s

This commit is contained in:
LaurensBartelds committed 2026-05-02 13:46:49 +02:00
1 parent 993265318b
commit 201d21a678
4 files changed
+52 -16

No files matched your search

+26
View File
@@ -1,4 +1,5 @@
import { Router } from 'express'
import { z } from 'zod'
import { validateBody, validateQuery, validateParams } from '../middleware/validate.js'
import { asyncHandler } from '../utils/asyncHandler.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 settingsRepo from '../repositories/settingsRepo.js'
import { updateUserRoleSchema, updateSettingSchema, updateSettingsBatchSchema, listUsersQuerySchema, userIdParamSchema } from '../validators/adminSchemas.js'
import { gradeQuestionWithAI } from '../services/aiGrading.js'
import type { Request } from 'express'
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
+10 -7
View File
@@ -14,12 +14,12 @@ interface GradeRequest {
language: string
}
function getClient(): OpenAI | null {
const provider = getSetting('ai_provider')
const apiKey = getSetting('ai_api_key')
function getClient(override?: { provider?: string; apiKey?: string; baseURL?: string }): OpenAI | null {
const provider = override?.provider ?? getSetting('ai_provider')
const apiKey = override?.apiKey ?? getSetting('ai_api_key')
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)
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> {
const client = getClient()
export async function gradeQuestionWithAI(
req: GradeRequest,
override?: { provider?: string; apiKey?: string; baseURL?: string; model?: string },
): Promise<GradingResult> {
const client = getClient(override)
if (!client) {
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 completion = await client.chat.completions.create({
+10 -8
View File
@@ -96,14 +96,16 @@ export function sendTestEmail(to: string | undefined, smtp: SmtpConfig): Promise
})
}
export function testAiConnection(): Promise<GradingResult> {
return apiFetch('/api/grade', {
export interface AiTestPayload {
provider: string
apiKey: string
model: string
baseURL?: string
}
export function testAiConnection(payload: AiTestPayload): Promise<GradingResult> {
return apiFetch('/api/admin/test-ai', {
method: 'POST',
body: JSON.stringify({
type: 'short-answer',
question: { prompt: 'What is 2+2?', answer: '4' },
answer: '4',
language: 'en',
}),
body: JSON.stringify(payload),
})
}
+6 -1
View File
@@ -135,7 +135,12 @@ export default function AdminDashboard({ onExit }: Readonly<Props>) {
setTestAiSending(true);
setTestAiMsg(null);
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") });
} catch (e) {
setTestAiMsg({ ok: false, text: (e as Error).message });