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

@ -66,7 +66,7 @@ pnpm preview # Preview production build
- **Mobile**: React Native 0.76.7 + Expo SDK 52, NativeWind, Expo Router
- **Web**: SvelteKit 2.x, Svelte 5, Tailwind CSS 4
- **Landing**: Astro 5.16, Tailwind CSS
- **Backend**: NestJS 10, Google Gemini AI, Supabase
- **Backend**: NestJS 10, OpenRouter/Gemini AI, Supabase
- **Types**: TypeScript 5.x
## Architecture
@ -89,11 +89,12 @@ pnpm preview # Preview production build
#### Backend (.env)
```
GOOGLE_GENAI_API_KEY=...
OPENROUTER_API_KEY=... # Get at https://openrouter.ai/keys
GOOGLE_GENAI_API_KEY=... # Optional: For Gemini models
SUPABASE_URL=https://...
SUPABASE_SERVICE_KEY=...
PORT=3002
DEV_BYPASS_AUTH=true # Optional: Skip auth in development
DEV_BYPASS_AUTH=true # Optional: Skip auth in development
```
#### Mobile (.env)
@ -114,11 +115,33 @@ EXPO_PUBLIC_BACKEND_URL=http://localhost:3001
## AI Models Available
| Model ID | Name | Description | Default |
| ------------------------------------ | --------------------- | ------------------------- | ------- |
| 550e8400-e29b-41d4-a716-446655440101 | Gemini 2.5 Flash | Fast, efficient responses | Yes |
| 550e8400-e29b-41d4-a716-446655440102 | Gemini 2.0 Flash-Lite | Ultra-lightweight model | No |
| 550e8400-e29b-41d4-a716-446655440103 | Gemini 2.5 Pro | Most capable model | No |
### OpenRouter Models (Recommended)
| Model ID | Name | Price | Best For |
| -------- | ---- | ----- | -------- |
| ...440201 | Llama 3.1 8B | $0.05/M | Everyday tasks, cheap |
| ...440202 | Llama 3.1 70B | $0.35/M | Complex reasoning |
| ...440203 | DeepSeek V3 | $0.14/M | Reasoning at low cost |
| ...440204 | Mistral Small | $0.10/M | General tasks |
| ...440205 | Claude 3.5 Sonnet | $3/M | Best quality |
| ...440206 | GPT-4o Mini | $0.15/M | Balanced performance |
### Google Gemini Models
| Model ID | Name | Description | Default |
| -------- | ---- | ----------- | ------- |
| ...440101 | Gemini 2.5 Flash | Fast, efficient responses | Yes |
| ...440102 | Gemini 2.0 Flash-Lite | Ultra-lightweight model | No |
| ...440103 | Gemini 2.5 Pro | Most capable model | No |
## OpenRouter Setup
To enable OpenRouter models:
- [ ] Get API key at https://openrouter.ai/keys
- [ ] Add `OPENROUTER_API_KEY=sk-or-v1-xxx` to `apps/chat/apps/backend/.env`
- [ ] Re-seed database: `pnpm --filter @chat/backend db:seed`
- [ ] Test: `pnpm dev:chat:backend`
## Important Notes

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)
// ============================================
{