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:
Till-JS 2025-11-25 13:48:24 +01:00
parent fcf3a344b1
commit c638a7ffee
155 changed files with 22622 additions and 348 deletions

View 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;
}

View 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;
}
}

View 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,
},
});