mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 18:26:42 +02:00
feat(chat): integrate chat project into monorepo with full app structure
- Restructure chat as apps/mobile, apps/web, apps/landing, backend - Add NestJS backend for secure Azure OpenAI API calls - Remove exposed API key from mobile app (security fix) - Add shared chat-types package - Create SvelteKit web app scaffold - Create Astro landing page scaffold - Update pnpm workspace configuration - Add project-level CLAUDE.md documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
fcf3a344b1
commit
c638a7ffee
155 changed files with 22622 additions and 348 deletions
87
chat/apps/mobile/utils/api.ts
Normal file
87
chat/apps/mobile/utils/api.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* API-Hilfsmodul für Chat-Anfragen
|
||||
* Verwendet das Backend für sichere AI API Aufrufe
|
||||
*/
|
||||
import { ChatMessage } from '../services/openai';
|
||||
import { sendChatCompletion, ChatCompletionResponse, TokenUsage } from './backendApi';
|
||||
|
||||
// Re-export types for backward compatibility
|
||||
export type { TokenUsage };
|
||||
|
||||
// Rückgabetyp für die Chat-Anfrage
|
||||
export type ChatRequestResult = {
|
||||
content: string;
|
||||
usage: TokenUsage;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sendet eine Chat-Anfrage über das Backend
|
||||
* @param messages Array von Nachrichten
|
||||
* @param temperature Kreativität (0.0 - 1.0)
|
||||
* @param maxTokens Maximale Token in der Antwort
|
||||
* @param config Model-Konfiguration (für modelId Extraktion)
|
||||
* @returns Die Antwort des Modells als Text mit Tokennutzung
|
||||
*/
|
||||
export async function sendChatRequest(
|
||||
messages: ChatMessage[],
|
||||
temperature: number = 0.7,
|
||||
maxTokens: number = 800,
|
||||
config: {
|
||||
endpoint?: string;
|
||||
apiKey?: string;
|
||||
deployment: string;
|
||||
apiVersion?: string;
|
||||
}
|
||||
): Promise<string | ChatRequestResult> {
|
||||
console.log('sendChatRequest via Backend:', {
|
||||
messagesCount: messages.length,
|
||||
deployment: config.deployment,
|
||||
temperature,
|
||||
maxTokens,
|
||||
});
|
||||
|
||||
try {
|
||||
// Map deployment name to model ID
|
||||
const modelId = getModelIdFromDeployment(config.deployment);
|
||||
|
||||
// Send request through backend
|
||||
const result = await sendChatCompletion(
|
||||
messages,
|
||||
modelId,
|
||||
temperature,
|
||||
maxTokens
|
||||
);
|
||||
|
||||
return {
|
||||
content: result.content,
|
||||
usage: result.usage,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Backend API-Aufruf:', error);
|
||||
|
||||
// Return user-friendly error message
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||||
return `Es konnte keine Antwort generiert werden. Bitte stelle sicher, dass das Backend läuft. Fehler: ${errorMessage}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps deployment names to model IDs
|
||||
* This ensures backward compatibility with existing code
|
||||
*/
|
||||
function getModelIdFromDeployment(deployment: string): string {
|
||||
const deploymentToModelId: Record<string, string> = {
|
||||
'gpt-o3-mini-se': '550e8400-e29b-41d4-a716-446655440000',
|
||||
'gpt-4o-mini-se': '550e8400-e29b-41d4-a716-446655440004',
|
||||
'gpt-4o-se': '550e8400-e29b-41d4-a716-446655440005',
|
||||
};
|
||||
|
||||
const modelId = deploymentToModelId[deployment];
|
||||
|
||||
if (!modelId) {
|
||||
console.warn(`Unknown deployment: ${deployment}, using default model`);
|
||||
return '550e8400-e29b-41d4-a716-446655440000'; // Default to GPT-O3-Mini
|
||||
}
|
||||
|
||||
return modelId;
|
||||
}
|
||||
132
chat/apps/mobile/utils/backendApi.ts
Normal file
132
chat/apps/mobile/utils/backendApi.ts
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/**
|
||||
* Backend API Client
|
||||
* Handles all communication with the NestJS backend
|
||||
* API keys are stored securely on the backend - not in the mobile app
|
||||
*/
|
||||
|
||||
import { ChatMessage } from '../services/openai';
|
||||
|
||||
const BACKEND_URL = process.env.EXPO_PUBLIC_BACKEND_URL || 'http://localhost:3001';
|
||||
|
||||
// Token usage type
|
||||
export type TokenUsage = {
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
total_tokens: number;
|
||||
};
|
||||
|
||||
// Chat completion response
|
||||
export type ChatCompletionResponse = {
|
||||
content: string;
|
||||
usage: TokenUsage;
|
||||
};
|
||||
|
||||
// AI Model type from backend
|
||||
export type AIModel = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: {
|
||||
temperature: number;
|
||||
max_tokens: number;
|
||||
provider: string;
|
||||
deployment: string;
|
||||
endpoint: string;
|
||||
api_version: string;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches available AI models from the backend
|
||||
*/
|
||||
export async function getAvailableModels(): Promise<AIModel[]> {
|
||||
try {
|
||||
const response = await fetch(`${BACKEND_URL}/api/chat/models`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch models: ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error fetching models from backend:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a chat completion request to the backend
|
||||
* The backend securely handles the Azure OpenAI API call
|
||||
*/
|
||||
export async function sendChatCompletion(
|
||||
messages: ChatMessage[],
|
||||
modelId: string,
|
||||
temperature?: number,
|
||||
maxTokens?: number
|
||||
): Promise<ChatCompletionResponse> {
|
||||
console.log('Sending chat request to backend:', {
|
||||
messagesCount: messages.length,
|
||||
modelId,
|
||||
temperature,
|
||||
maxTokens,
|
||||
backendUrl: BACKEND_URL,
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch(`${BACKEND_URL}/api/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
messages: messages.map((msg) => ({
|
||||
role: msg.role,
|
||||
content: msg.content,
|
||||
})),
|
||||
modelId,
|
||||
temperature,
|
||||
maxTokens,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('Backend API error:', response.status, errorText);
|
||||
throw new Error(`Backend API error: ${response.status} - ${errorText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Backend response received:', {
|
||||
contentLength: data.content?.length,
|
||||
usage: data.usage,
|
||||
});
|
||||
|
||||
return {
|
||||
content: data.content,
|
||||
usage: data.usage,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error sending chat request to backend:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Health check for the backend
|
||||
*/
|
||||
export async function checkBackendHealth(): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(`${BACKEND_URL}/api/health`, {
|
||||
method: 'GET',
|
||||
});
|
||||
return response.ok;
|
||||
} catch (error) {
|
||||
console.error('Backend health check failed:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
35
chat/apps/mobile/utils/supabase.ts
Normal file
35
chat/apps/mobile/utils/supabase.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { createClient } from '@supabase/supabase-js';
|
||||
|
||||
const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL || '';
|
||||
const supabaseAnonKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY || '';
|
||||
|
||||
// Überprüfe, ob wir in einer Browser-Umgebung sind
|
||||
const isBrowser = typeof window !== 'undefined';
|
||||
|
||||
// Importiere AsyncStorage nur, wenn wir in einer Browser-Umgebung sind
|
||||
let AsyncStorage;
|
||||
if (isBrowser) {
|
||||
AsyncStorage = require('@react-native-async-storage/async-storage').default;
|
||||
}
|
||||
|
||||
// Erstelle Supabase-Client mit unterschiedlichen Konfigurationen je nach Umgebung
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
|
||||
auth: isBrowser
|
||||
? {
|
||||
storage: AsyncStorage,
|
||||
autoRefreshToken: true,
|
||||
persistSession: true,
|
||||
detectSessionInUrl: false,
|
||||
}
|
||||
: {
|
||||
// Dummy-Storage für serverseitiges Rendering
|
||||
storage: {
|
||||
getItem: () => Promise.resolve(null),
|
||||
setItem: () => Promise.resolve(),
|
||||
removeItem: () => Promise.resolve(),
|
||||
},
|
||||
autoRefreshToken: false,
|
||||
persistSession: false,
|
||||
detectSessionInUrl: false,
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue