import { IsString, IsBoolean, IsOptional, IsInt, Min, Max, IsIn, IsNotEmpty, ValidateIf, } from 'class-validator'; import { AiProvider } from '../ai-config.entity'; const PROVIDERS = [AiProvider.OPENAI, AiProvider.DEEPSEEK, AiProvider.OPENAI_COMPATIBLE] as const; const DEFAULT_BASE_URLS: Record = { [AiProvider.OPENAI]: 'https://api.openai.com/v1', [AiProvider.DEEPSEEK]: 'https://api.deepseek.com', [AiProvider.OPENAI_COMPATIBLE]: '', }; /** DTO for PUT /api/ai/config — all fields required or validated */ export class SaveAiConfigDto { @IsIn(PROVIDERS) provider!: AiProvider; @ValidateIf((o: SaveAiConfigDto) => o.provider === AiProvider.OPENAI_COMPATIBLE || (o.baseUrl !== undefined && o.baseUrl !== '')) @IsNotEmpty({ message: 'Base URL 不能为空' }) @IsString() baseUrl?: string; /** Raw API key — never returned by GET; empty / undefined = keep existing */ @IsOptional() @IsString() apiKey?: string; @IsOptional() @IsString() defaultModel?: string; @IsOptional() @IsBoolean() enabled?: boolean; @IsOptional() @IsInt() @Min(1000) @Max(120000) timeoutMs?: number; } /** DTO for POST /api/ai/config/test — all fields optional, validate only when provided */ export class TestAiConfigDto { @IsOptional() @IsIn(PROVIDERS) provider?: AiProvider; @IsOptional() @IsString() baseUrl?: string; @IsOptional() @IsString() apiKey?: string; @IsOptional() @IsString() defaultModel?: string; @IsOptional() @IsInt() @Min(1000) @Max(120000) timeoutMs?: number; } /** Response shape for GET /api/ai/config — NEVER includes plaintext key */ export interface AiConfigResponseDto { id: number; provider: AiProvider; baseUrl: string; hasApiKey: boolean; hasDatabaseKey: boolean; maskedApiKey: string | null; keySource: 'database' | 'environment' | 'none'; defaultModel: string | null; enabled: boolean; timeoutMs: number; verified: boolean; lastTestedAt: string | null; lastTestLatencyMs: number | null; createdAt: string; updatedAt: string; } /** Response shape for POST /api/ai/config/test */ export interface AiConfigTestResultDto { success: boolean; latencyMs: number | null; modelCount: number | null; modelAvailable: boolean; testedAt: string; message: string; } /** Server-only runtime config — NEVER exported via controller DTO */ export interface AiRuntimeConfig { provider: AiProvider; baseUrl: string; apiKey: string; defaultModel: string; timeoutMs: number; enabled: boolean; } /** DTO for POST /api/ai/config/models — fetch available model list from provider */ export class FetchModelsDto { @IsOptional() @IsIn(PROVIDERS) provider?: AiProvider; @IsOptional() @IsString() baseUrl?: string; @IsOptional() @IsString() apiKey?: string; @IsOptional() @IsInt() @Min(1000) @Max(120000) timeoutMs?: number; } /** Response shape for POST /api/ai/config/models */ export interface FetchModelsResultDto { success: boolean; models: Array<{ id: string }>; message?: string; } export { DEFAULT_BASE_URLS };