feat: add course AI agent backend
This commit is contained in:
@@ -30,6 +30,8 @@ import { UpdateInstructorCurriculumDto } from './dto/update-instructor-curriculu
|
||||
import { UpdateInstructorPlatformDto } from './dto/update-instructor-platform.dto';
|
||||
import { CreateInstructorCourseMapDto } from './dto/create-instructor-course-map.dto';
|
||||
import { NAV2_SYSTEM_MAPS } from '../curriculum/course-maps';
|
||||
import { AiCredentialService } from '../ai/ai-credential.service';
|
||||
import { UpdateCourseAiSettingDto } from '../ai/dto/update-course-ai-setting.dto';
|
||||
|
||||
const INSTRUCTOR_ROLES = new Set(['INSTRUCTOR', 'LMS_ADMIN']);
|
||||
const DEFAULT_SUBMISSION_BOT_EMAIL = 'lms-bot@futuredrive.net';
|
||||
@@ -95,6 +97,7 @@ export class InstructorService {
|
||||
private readonly logger = new Logger(InstructorService.name);
|
||||
|
||||
constructor(
|
||||
private readonly aiCredentialService: AiCredentialService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly chatGateway: ChatGateway,
|
||||
private readonly prisma: PrismaService,
|
||||
@@ -103,6 +106,64 @@ export class InstructorService {
|
||||
private readonly r2PublicFileService: R2PublicFileService,
|
||||
) {}
|
||||
|
||||
async getCourseAiSetting(accessToken: string | undefined, courseId: string) {
|
||||
const { authUser, instructor } = await this.getInstructor(accessToken);
|
||||
const organizationName = authUser.primaryAffiliation?.organizationName?.trim();
|
||||
if (!organizationName) throw new BadRequestException('Instructor organization is required');
|
||||
const course = await this.findInstructorCourse(organizationName, instructor.id, courseId);
|
||||
if (!course) throw new NotFoundException('Course not found');
|
||||
const setting = await this.prisma.courseAiSetting.findUnique({ where: { courseId: course.id } });
|
||||
return {
|
||||
courseId: course.id,
|
||||
enabled: setting?.enabled ?? false,
|
||||
provider: setting?.provider ?? 'OPENAI',
|
||||
model: setting?.model ?? 'gpt-4.1-mini',
|
||||
hasApiKey: Boolean(setting?.encryptedApiKey),
|
||||
updatedAt: setting?.updatedAt ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
async updateCourseAiSetting(
|
||||
accessToken: string | undefined,
|
||||
courseId: string,
|
||||
input: UpdateCourseAiSettingDto,
|
||||
) {
|
||||
const { authUser, instructor } = await this.getInstructor(accessToken);
|
||||
const organizationName = authUser.primaryAffiliation?.organizationName?.trim();
|
||||
if (!organizationName) throw new BadRequestException('Instructor organization is required');
|
||||
const course = await this.findInstructorCourse(organizationName, instructor.id, courseId);
|
||||
if (!course) throw new NotFoundException('Course not found');
|
||||
const existing = await this.prisma.courseAiSetting.findUnique({ where: { courseId: course.id } });
|
||||
const apiKey = input.apiKey?.trim();
|
||||
if (input.enabled && !apiKey && (!existing?.encryptedApiKey || existing.provider !== input.provider)) {
|
||||
throw new BadRequestException('AI 에이전트를 사용하거나 공급자를 변경하려면 해당 공급자의 API 키를 입력하세요.');
|
||||
}
|
||||
const setting = await this.prisma.courseAiSetting.upsert({
|
||||
where: { courseId: course.id },
|
||||
create: {
|
||||
courseId: course.id,
|
||||
enabled: input.enabled,
|
||||
provider: input.provider,
|
||||
model: input.model.trim(),
|
||||
encryptedApiKey: apiKey ? this.aiCredentialService.encrypt(apiKey) : null,
|
||||
},
|
||||
update: {
|
||||
enabled: input.enabled,
|
||||
provider: input.provider,
|
||||
model: input.model.trim(),
|
||||
...(apiKey ? { encryptedApiKey: this.aiCredentialService.encrypt(apiKey) } : {}),
|
||||
},
|
||||
});
|
||||
return {
|
||||
courseId: course.id,
|
||||
enabled: setting.enabled,
|
||||
provider: setting.provider,
|
||||
model: setting.model,
|
||||
hasApiKey: Boolean(setting.encryptedApiKey),
|
||||
updatedAt: setting.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
async listCourses(accessToken: string | undefined) {
|
||||
const { authUser, instructor } = await this.getInstructor(accessToken);
|
||||
const organizationName = authUser.primaryAffiliation?.organizationName?.trim();
|
||||
|
||||
Reference in New Issue
Block a user