import type { AIProvider } from "@cv/ai-provider"; import { AI_PROVIDER, registeredProviderTypes } from "@cv/ai-provider"; import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; import { User as DomainUser } from "@cv/core"; import { Inject, Optional, UseGuards } from "@nestjs/common"; import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; import { AiPreference } from "@prisma/client"; import { CurrentUser } from "@/modules/current-user/current-user.decorator"; import { UserAiSettingsService } from "./user-ai-settings.service"; import { AiSettings, PlatformAiStatus, PlatformCapabilities, RemoveAiProviderResult, UserAiProviderType, } from "./user-ai-settings.type"; @Resolver() @UseGuards(JwtAuthGuard, VerifiedScopeGuard) export class UserAiSettingsResolver { constructor( private readonly settingsService: UserAiSettingsService, @Inject(AI_PROVIDER) @Optional() private readonly globalProvider?: AIProvider, ) {} @Query(() => AiSettings) async myAiSettings(@CurrentUser() user: DomainUser): Promise { return AiSettings.fromDomain( await this.settingsService.getOrCreateSettings(user), ); } @Query(() => [UserAiProviderType]) async myAiProviders( @CurrentUser() user: DomainUser, ): Promise { const providers = await this.settingsService.listProviders(user); return providers.map(UserAiProviderType.fromDomain); } @Query(() => PlatformAiStatus) async platformAiAvailable(): Promise { if (!this.globalProvider) { return new PlatformAiStatus({ available: false }); } try { const healthy = await this.globalProvider.isHealthy(); return new PlatformAiStatus({ available: healthy }); } catch { return new PlatformAiStatus({ available: false }); } } @Query(() => PlatformCapabilities) async platformCapabilities(): Promise { const aiParsing = this.globalProvider ? await this.globalProvider.isHealthy().catch(() => false) : false; return new PlatformCapabilities({ availableProviderTypes: registeredProviderTypes(), aiParsing, storyMode: false, }); } @Mutation(() => AiSettings) async updateAiPreference( @CurrentUser() user: DomainUser, @Args("preference", { type: () => AiPreference }) preference: AiPreference, ): Promise { return AiSettings.fromDomain( await this.settingsService.updatePreference(user, preference), ); } @Mutation(() => UserAiProviderType) async addAiProvider( @CurrentUser() user: DomainUser, @Args("label") label: string, @Args("providerType") providerType: string, @Args("apiKey") apiKey: string, @Args("model", { nullable: true }) model?: string, @Args("baseUrl", { nullable: true }) baseUrl?: string, @Args("setActive", { nullable: true, defaultValue: false }) setActive?: boolean, ): Promise { const provider = await this.settingsService.addProvider(user, { label, providerType, apiKey, setActive: setActive ?? false, ...(model !== undefined && { model }), ...(baseUrl !== undefined && { baseUrl }), }); return UserAiProviderType.fromDomain(provider); } @Mutation(() => RemoveAiProviderResult) async removeAiProvider( @CurrentUser() user: DomainUser, @Args("providerId") providerId: string, ): Promise { const { wasActive } = await this.settingsService.removeProvider( user, providerId, ); const settings = await this.settingsService.getOrCreateSettings(user); return new RemoveAiProviderResult({ success: true, wasActive, newPreference: settings.aiPreference, }); } @Mutation(() => UserAiProviderType) async replaceAiProviderKey( @CurrentUser() user: DomainUser, @Args("providerId") providerId: string, @Args("newApiKey") newApiKey: string, ): Promise { const provider = await this.settingsService.replaceProviderKey( user, providerId, newApiKey, ); return UserAiProviderType.fromDomain(provider); } @Mutation(() => UserAiProviderType) async updateAiProvider( @CurrentUser() user: DomainUser, @Args("providerId") providerId: string, @Args("label", { nullable: true }) label?: string, @Args("model", { nullable: true }) model?: string, @Args("baseUrl", { nullable: true }) baseUrl?: string, ): Promise { const provider = await this.settingsService.updateProviderMeta( user, providerId, { ...(label !== undefined && { label }), ...(model !== undefined && { model }), ...(baseUrl !== undefined && { baseUrl }), }, ); return UserAiProviderType.fromDomain(provider); } @Mutation(() => AiSettings) async setActiveAiProvider( @CurrentUser() user: DomainUser, @Args("providerId") providerId: string, ): Promise { return AiSettings.fromDomain( await this.settingsService.setActiveProvider(user, providerId), ); } }