feat(chat,picture): add OpenRouter integration and credit system

Chat:
- Add OpenRouter as primary AI provider with multiple models
- Update chat service with new model configurations
- Add model seed data for Llama, DeepSeek, Mistral, Claude, GPT-4o

Picture:
- Integrate @mana-core/nestjs-integration for credit system
- Implement freemium model (3 free generations, then 10 credits)
- Migrate storage to @manacore/shared-storage
- Add comprehensive project documentation
This commit is contained in:
Wuesteon 2025-12-10 20:46:33 +01:00
parent 422fcd6b34
commit 6f74e1d9a6
12 changed files with 878 additions and 468 deletions

View file

@ -1,4 +1,11 @@
# Azure OpenAI Configuration
# OpenRouter Configuration (Recommended - multi-model access)
# Get your API key at https://openrouter.ai/keys
OPENROUTER_API_KEY=your-openrouter-api-key
# Google Gemini Configuration
GOOGLE_GENAI_API_KEY=your-google-api-key
# Azure OpenAI Configuration (Optional)
AZURE_OPENAI_ENDPOINT=https://your-azure-openai-endpoint.openai.azure.com
AZURE_OPENAI_API_KEY=your-api-key-here
AZURE_OPENAI_API_VERSION=2024-12-01-preview

View file

@ -3,6 +3,7 @@ import { ConfigService } from '@nestjs/config';
import { eq } from 'drizzle-orm';
import { AsyncResult, ok, err, ValidationError, ServiceError } from '@manacore/shared-errors';
import { GoogleGenerativeAI } from '@google/generative-ai';
import OpenAI from 'openai';
import { DATABASE_CONNECTION } from '../db/database.module';
import { Database } from '../db/connection';
import { models } from '../db/schema/models.schema';
@ -19,6 +20,8 @@ export class ChatService {
private readonly azureApiVersion: string;
// Google Gemini config
private readonly geminiClient: GoogleGenerativeAI | null = null;
// OpenRouter config
private readonly openRouterClient: OpenAI | null = null;
constructor(
private configService: ConfigService,
@ -41,6 +44,22 @@ export class ChatService {
this.logger.warn('GOOGLE_GENAI_API_KEY is not set - Gemini models unavailable');
}
// OpenRouter setup
const openRouterApiKey = this.configService.get<string>('OPENROUTER_API_KEY');
if (openRouterApiKey) {
this.openRouterClient = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: openRouterApiKey,
defaultHeaders: {
'HTTP-Referer': this.configService.get<string>('APP_URL') || 'http://localhost:3002',
'X-Title': 'Mana Chat',
},
});
this.logger.log('OpenRouter client initialized');
} else {
this.logger.warn('OPENROUTER_API_KEY is not set - OpenRouter models unavailable');
}
if (!this.azureApiKey) {
this.logger.warn('AZURE_OPENAI_API_KEY is not set - Azure models unavailable');
}
@ -84,6 +103,8 @@ export class ChatService {
// Route to appropriate provider
if (model.provider === 'gemini') {
return this.createGeminiCompletion(model, dto);
} else if (model.provider === 'openrouter') {
return this.createOpenRouterCompletion(model, dto);
} else {
return this.createAzureCompletion(model, dto);
}
@ -250,4 +271,62 @@ export class ChatService {
);
}
}
private async createOpenRouterCompletion(
model: Model,
dto: ChatCompletionDto
): AsyncResult<ChatCompletionResponseDto> {
if (!this.openRouterClient) {
return err(ServiceError.externalError('OpenRouter', 'OpenRouter client not configured'));
}
const params = model.parameters as {
model?: string;
temperature?: number;
max_tokens?: number;
} | null;
const modelName = params?.model || 'meta-llama/llama-3.1-8b-instruct';
const temperature = dto.temperature ?? params?.temperature ?? 0.7;
const maxTokens = dto.maxTokens ?? params?.max_tokens ?? 4096;
this.logger.log(`Sending request to OpenRouter model: ${modelName}`);
try {
const response = await this.openRouterClient.chat.completions.create({
model: modelName,
messages: dto.messages.map((msg) => ({
role: msg.role as 'system' | 'user' | 'assistant',
content: msg.content,
})),
temperature,
max_tokens: maxTokens,
});
const messageContent = response.choices?.[0]?.message?.content;
if (!messageContent) {
this.logger.warn('No message content in OpenRouter response');
return err(ServiceError.generationFailed('OpenRouter', 'No response generated'));
}
return ok({
content: messageContent,
usage: {
prompt_tokens: response.usage?.prompt_tokens || 0,
completion_tokens: response.usage?.completion_tokens || 0,
total_tokens: response.usage?.total_tokens || 0,
},
});
} catch (error) {
this.logger.error('Error calling OpenRouter API', error);
return err(
ServiceError.generationFailed(
'OpenRouter',
error instanceof Error ? error.message : 'Unknown error',
error instanceof Error ? error : undefined
)
);
}
}
}

View file

@ -75,6 +75,87 @@ async function seed() {
isDefault: false,
},
// ============================================
// OpenRouter Models (Multi-provider, cost-effective)
// ============================================
{
id: '550e8400-e29b-41d4-a716-446655440201',
name: 'Llama 3.1 8B',
description: 'Fast & cheap - great for everyday tasks ($0.05/M tokens)',
provider: 'openrouter',
parameters: {
model: 'meta-llama/llama-3.1-8b-instruct',
temperature: 0.7,
max_tokens: 4096,
},
isActive: true,
isDefault: false,
},
{
id: '550e8400-e29b-41d4-a716-446655440202',
name: 'Llama 3.1 70B',
description: 'Powerful open model - complex reasoning ($0.35/M tokens)',
provider: 'openrouter',
parameters: {
model: 'meta-llama/llama-3.1-70b-instruct',
temperature: 0.7,
max_tokens: 8192,
},
isActive: true,
isDefault: false,
},
{
id: '550e8400-e29b-41d4-a716-446655440203',
name: 'DeepSeek V3',
description: 'Excellent reasoning at low cost ($0.14/M tokens)',
provider: 'openrouter',
parameters: {
model: 'deepseek/deepseek-chat',
temperature: 0.7,
max_tokens: 8192,
},
isActive: true,
isDefault: false,
},
{
id: '550e8400-e29b-41d4-a716-446655440204',
name: 'Mistral Small',
description: 'Fast European model - good for general tasks ($0.10/M tokens)',
provider: 'openrouter',
parameters: {
model: 'mistralai/mistral-small-24b-instruct-2501',
temperature: 0.7,
max_tokens: 4096,
},
isActive: true,
isDefault: false,
},
{
id: '550e8400-e29b-41d4-a716-446655440205',
name: 'Claude 3.5 Sonnet',
description: 'Best overall quality - coding & analysis ($3/M tokens)',
provider: 'openrouter',
parameters: {
model: 'anthropic/claude-3.5-sonnet',
temperature: 0.7,
max_tokens: 8192,
},
isActive: true,
isDefault: false,
},
{
id: '550e8400-e29b-41d4-a716-446655440206',
name: 'GPT-4o Mini',
description: 'OpenAI fast model - balanced performance ($0.15/M tokens)',
provider: 'openrouter',
parameters: {
model: 'openai/gpt-4o-mini',
temperature: 0.7,
max_tokens: 4096,
},
isActive: true,
isDefault: false,
},
// ============================================
// Azure OpenAI GPT-5 Family (Inactive - no deployment)
// ============================================
{