mirror of
https://git.meshkee.com/Meshkee/backend.git
synced 2026-08-12 06:40:58 +04:30
Initial commit: Meshkee CMS API
NestJS backend with Prisma, Docker Compose for Postgres/Redis, and deploy docs for the production VM.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { ServiceUnavailableException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
export type AiProviderConfig = {
|
||||
apiKey: string;
|
||||
baseUrl: string;
|
||||
model: string;
|
||||
};
|
||||
|
||||
export function resolveAiProvider(config: ConfigService): AiProviderConfig {
|
||||
const configured = config.get<string>('AI_PROVIDER')?.trim().toLowerCase();
|
||||
const groqKey = config.get<string>('GROQ_API_KEY')?.trim();
|
||||
const openAiKey = config.get<string>('OPENAI_API_KEY')?.trim();
|
||||
|
||||
const useGroq =
|
||||
configured === 'groq' || (!configured && !!groqKey) || (!openAiKey && !!groqKey);
|
||||
|
||||
if (useGroq) {
|
||||
if (!groqKey) {
|
||||
throw new ServiceUnavailableException(
|
||||
'AI is not configured. Set GROQ_API_KEY on the server.',
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
apiKey: groqKey,
|
||||
baseUrl: 'https://api.groq.com/openai/v1',
|
||||
model: config.get<string>('GROQ_MODEL')?.trim() || 'llama-3.3-70b-versatile',
|
||||
};
|
||||
}
|
||||
|
||||
if (!openAiKey) {
|
||||
throw new ServiceUnavailableException(
|
||||
'AI is not configured. Set GROQ_API_KEY or OPENAI_API_KEY on the server.',
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
apiKey: openAiKey,
|
||||
baseUrl: 'https://api.openai.com/v1',
|
||||
model: config.get<string>('OPENAI_MODEL')?.trim() || 'gpt-4o-mini',
|
||||
};
|
||||
}
|
||||
|
||||
export async function requestAiJsonCompletion(
|
||||
provider: AiProviderConfig,
|
||||
systemPrompt: string,
|
||||
userPrompt: string,
|
||||
temperature = 0.4,
|
||||
): Promise<string> {
|
||||
const response = await fetch(`${provider.baseUrl}/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${provider.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: provider.model,
|
||||
temperature,
|
||||
response_format: { type: 'json_object' },
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: userPrompt },
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const detail = await response.text();
|
||||
throw new Error(`AI provider error (${response.status}): ${detail.slice(0, 240)}`);
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as {
|
||||
choices?: { message?: { content?: string } }[];
|
||||
};
|
||||
|
||||
const content = payload.choices?.[0]?.message?.content;
|
||||
if (!content) {
|
||||
throw new Error('AI returned an empty response');
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
import { Observable, map } from 'rxjs';
|
||||
|
||||
function serializeBigInt(value: unknown): unknown {
|
||||
if (typeof value === 'bigint') {
|
||||
return Number(value);
|
||||
}
|
||||
|
||||
if (value instanceof Date) {
|
||||
return value.toISOString();
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(serializeBigInt);
|
||||
}
|
||||
|
||||
if (value !== null && typeof value === 'object') {
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([key, val]) => [key, serializeBigInt(val)]),
|
||||
);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class BigIntSerializerInterceptor implements NestInterceptor {
|
||||
intercept(_context: ExecutionContext, next: CallHandler): Observable<unknown> {
|
||||
return next.handle().pipe(map((data) => serializeBigInt(data)));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user