mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:01:08 +02:00
fix(context): remove cloud API keys from mobile app, route through backend
SECURITY FIX: The mobile app had Azure OpenAI and Google Gemini API keys exposed in client code (dangerouslyAllowBrowser: true). Changes: - Mobile aiService.ts: Remove OpenAI/Gemini SDKs, route all AI calls through the Context backend API (which uses mana-llm) - Backend ai.controller.ts: Add /generate/mobile and /estimate/mobile endpoints that accept Supabase JWT tokens (extracts userId from payload) - Original /generate and /estimate endpoints unchanged (mana-core-auth) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a2f8c32059
commit
fae139e7e0
2 changed files with 156 additions and 278 deletions
|
|
@ -1,13 +1,29 @@
|
|||
import { Controller, Post, Body, UseGuards, BadRequestException } from '@nestjs/common';
|
||||
import { Controller, Post, Body, UseGuards, BadRequestException, Req } from '@nestjs/common';
|
||||
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
|
||||
import { AiService } from './ai.service';
|
||||
import type { Request } from 'express';
|
||||
|
||||
/**
|
||||
* Extract userId from a JWT token payload without JWKS verification.
|
||||
* Used as fallback for Supabase tokens when mana-core-auth guard fails.
|
||||
*/
|
||||
function extractUserIdFromToken(authHeader: string | undefined): string | null {
|
||||
if (!authHeader?.startsWith('Bearer ')) return null;
|
||||
try {
|
||||
const token = authHeader.slice(7);
|
||||
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString());
|
||||
return payload.sub || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Controller('ai')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class AiController {
|
||||
constructor(private readonly aiService: AiService) {}
|
||||
|
||||
@Post('generate')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async generate(
|
||||
@CurrentUser() user: CurrentUserData,
|
||||
@Body()
|
||||
|
|
@ -28,7 +44,38 @@ export class AiController {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate endpoint that accepts Supabase tokens (for mobile app).
|
||||
* Falls back to extracting userId from JWT payload when mana-core-auth is not available.
|
||||
*/
|
||||
@Post('generate/mobile')
|
||||
async generateMobile(
|
||||
@Req() req: Request,
|
||||
@Body()
|
||||
body: {
|
||||
prompt: string;
|
||||
model?: string;
|
||||
temperature?: number;
|
||||
maxTokens?: number;
|
||||
documentId?: string;
|
||||
referencedDocuments?: { title: string; content: string }[];
|
||||
}
|
||||
) {
|
||||
if (!body.prompt) {
|
||||
throw new BadRequestException('prompt is required');
|
||||
}
|
||||
|
||||
const userId = extractUserIdFromToken(req.headers.authorization);
|
||||
if (!userId) {
|
||||
throw new BadRequestException('Authorization required');
|
||||
}
|
||||
|
||||
const result = await this.aiService.generate(userId, body);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Post('estimate')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async estimateCost(
|
||||
@CurrentUser() user: CurrentUserData,
|
||||
@Body()
|
||||
|
|
@ -42,4 +89,27 @@ export class AiController {
|
|||
const estimate = await this.aiService.estimateCost(user.userId, body);
|
||||
return estimate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate endpoint that accepts Supabase tokens (for mobile app).
|
||||
*/
|
||||
@Post('estimate/mobile')
|
||||
async estimateCostMobile(
|
||||
@Req() req: Request,
|
||||
@Body()
|
||||
body: {
|
||||
prompt: string;
|
||||
model?: string;
|
||||
estimatedCompletionLength?: number;
|
||||
referencedDocuments?: { title: string; content: string }[];
|
||||
}
|
||||
) {
|
||||
const userId = extractUserIdFromToken(req.headers.authorization);
|
||||
if (!userId) {
|
||||
throw new BadRequestException('Authorization required');
|
||||
}
|
||||
|
||||
const estimate = await this.aiService.estimateCost(userId, body);
|
||||
return estimate;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
import { OpenAI } from 'openai';
|
||||
import { GoogleGenerativeAI } from '@google/generative-ai';
|
||||
import { supabase } from '../utils/supabase';
|
||||
import { estimateTokens, estimateCostForPrompt, calculateCost } from './tokenCountingService';
|
||||
import { logTokenUsage, hasEnoughTokens, getCurrentTokenBalance } from './tokenTransactionService';
|
||||
|
||||
// Typdefinitionen
|
||||
export type AIProvider = 'azure' | 'google';
|
||||
|
|
@ -18,8 +14,8 @@ export type AIGenerationOptions = {
|
|||
temperature?: number;
|
||||
maxTokens?: number;
|
||||
prompt?: string;
|
||||
documentId?: string; // ID des Dokuments, für das der Text generiert wird
|
||||
referencedDocuments?: { title: string; content: string }[]; // Referenzierte Dokumente für die Token-Berechnung
|
||||
documentId?: string;
|
||||
referencedDocuments?: { title: string; content: string }[];
|
||||
};
|
||||
|
||||
export type AIGenerationResult = {
|
||||
|
|
@ -33,42 +29,47 @@ export type AIGenerationResult = {
|
|||
};
|
||||
};
|
||||
|
||||
// Verfügbare Modelle
|
||||
// Verfügbare Modelle (routed through mana-llm on the backend)
|
||||
export const availableModels: AIModelOption[] = [
|
||||
{ label: 'GPT-4.1', value: 'gpt-4.1', provider: 'azure' },
|
||||
{ label: 'Gemini Pro', value: 'gemini-pro', provider: 'google' },
|
||||
{ label: 'Gemini Flash', value: 'gemini-flash', provider: 'google' },
|
||||
{ label: 'Gemma 3 4B (Lokal)', value: 'ollama/gemma3:4b', provider: 'azure' },
|
||||
{
|
||||
label: 'Llama 3.1 8B',
|
||||
value: 'openrouter/meta-llama/llama-3.1-8b-instruct',
|
||||
provider: 'azure',
|
||||
},
|
||||
];
|
||||
|
||||
// Konfiguration der API-Clients
|
||||
// Azure OpenAI Konfiguration
|
||||
const AZURE_OPENAI_KEY = process.env.EXPO_PUBLIC_OPENAI_API_KEY || process.env.OPENAI_API_KEY || '';
|
||||
const AZURE_OPENAI_ENDPOINT = 'https://memoroseopenai.openai.azure.com/';
|
||||
const AZURE_OPENAI_DEPLOYMENT = 'gpt-4.1';
|
||||
const AZURE_OPENAI_API_VERSION = '2025-01-01-preview';
|
||||
const BACKEND_URL =
|
||||
process.env.EXPO_PUBLIC_BACKEND_URL ||
|
||||
process.env.EXPO_PUBLIC_CONTEXT_BACKEND_URL ||
|
||||
'http://localhost:3020';
|
||||
|
||||
// Google AI Konfiguration
|
||||
const GOOGLE_API_KEY = process.env.EXPO_PUBLIC_GOOGLE_API_KEY || process.env.GOOGLE_API_KEY || '';
|
||||
/**
|
||||
* Get the current Supabase access token for backend auth
|
||||
*/
|
||||
const getAuthToken = async (): Promise<string> => {
|
||||
const { data } = await supabase.auth.getSession();
|
||||
const token = data?.session?.access_token;
|
||||
if (!token) {
|
||||
throw new Error('Nicht angemeldet');
|
||||
}
|
||||
return token;
|
||||
};
|
||||
|
||||
// Initialisiere Azure OpenAI Client
|
||||
const azureClient = new OpenAI({
|
||||
apiKey: AZURE_OPENAI_KEY,
|
||||
baseURL: `${AZURE_OPENAI_ENDPOINT}openai/deployments/${AZURE_OPENAI_DEPLOYMENT}`,
|
||||
defaultQuery: { 'api-version': AZURE_OPENAI_API_VERSION },
|
||||
defaultHeaders: { 'api-key': AZURE_OPENAI_KEY },
|
||||
dangerouslyAllowBrowser: true, // Erlaubt die Ausführung im Browser (für Entwicklungszwecke)
|
||||
});
|
||||
|
||||
// Initialisiere Google AI Client
|
||||
const googleAI = new GoogleGenerativeAI(GOOGLE_API_KEY);
|
||||
/**
|
||||
* Get the current user ID from Supabase session
|
||||
*/
|
||||
const getUserId = async (): Promise<string> => {
|
||||
const { data } = await supabase.auth.getSession();
|
||||
const userId = data?.session?.user?.id;
|
||||
if (!userId) {
|
||||
throw new Error('Nicht angemeldet');
|
||||
}
|
||||
return userId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Prüft, ob der Benutzer genügend Tokens für eine Anfrage hat
|
||||
*
|
||||
* @param prompt Der Hauptprompt (ohne referenzierte Dokumente)
|
||||
* @param model Das zu verwendende KI-Modell
|
||||
* @param estimatedCompletionLength Geschätzte Länge der Antwort in Tokens
|
||||
* @param referencedDocuments Optional: Array von referenzierten Dokumenten, die zum Prompt hinzugefügt werden
|
||||
*/
|
||||
export const checkTokenBalance = async (
|
||||
prompt: string,
|
||||
|
|
@ -77,98 +78,32 @@ export const checkTokenBalance = async (
|
|||
referencedDocuments?: { title: string; content: string }[]
|
||||
): Promise<{ hasEnough: boolean; estimate: any; balance: number }> => {
|
||||
try {
|
||||
console.log('checkTokenBalance aufgerufen mit:', {
|
||||
promptLength: prompt.length,
|
||||
model,
|
||||
estimatedCompletionLength,
|
||||
referencedDocumentsCount: referencedDocuments?.length || 0,
|
||||
const token = await getAuthToken();
|
||||
|
||||
const response = await fetch(`${BACKEND_URL}/api/v1/ai/estimate/mobile`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
prompt,
|
||||
model,
|
||||
estimatedCompletionLength,
|
||||
referencedDocuments,
|
||||
}),
|
||||
});
|
||||
|
||||
// Hole den aktuellen Benutzer
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
const userId = sessionData?.session?.user?.id;
|
||||
|
||||
if (!userId) {
|
||||
throw new Error('Nicht angemeldet');
|
||||
if (!response.ok) {
|
||||
throw new Error(`Backend error: ${response.status}`);
|
||||
}
|
||||
|
||||
// Berechne die Token-Anzahl des Basis-Prompts
|
||||
const basePromptTokens = estimateTokens(prompt);
|
||||
console.log('Basis-Prompt Tokens:', basePromptTokens);
|
||||
|
||||
// Berechne die Token-Anzahl der referenzierten Dokumente
|
||||
let documentTokens = 0;
|
||||
let fullPrompt = prompt;
|
||||
|
||||
// Füge referenzierte Dokumente hinzu, falls vorhanden
|
||||
if (referencedDocuments && referencedDocuments.length > 0) {
|
||||
console.log(`Verarbeite ${referencedDocuments.length} referenzierte Dokumente:`);
|
||||
|
||||
// Formatierungs-Overhead für die Dokumente
|
||||
const formattingOverhead = 20 + referencedDocuments.length * 10;
|
||||
documentTokens += formattingOverhead;
|
||||
console.log('Formatierungs-Overhead:', formattingOverhead);
|
||||
|
||||
fullPrompt += '\n\nReferenzierte Dokumente:\n\n';
|
||||
|
||||
referencedDocuments.forEach((doc, index) => {
|
||||
console.log(
|
||||
`Dokument ${index + 1}: Titel="${doc.title}", Inhaltslänge=${doc.content?.length || 0}`
|
||||
);
|
||||
|
||||
const docContent = `Dokument ${index + 1} (${doc.title}):\n${doc.content || ''}\n\n`;
|
||||
fullPrompt += docContent;
|
||||
|
||||
// Berechne die Token-Anzahl für dieses Dokument
|
||||
const docTokens = estimateTokens(doc.content || '');
|
||||
documentTokens += docTokens;
|
||||
console.log(`Dokument ${index + 1} Tokens:`, docTokens);
|
||||
});
|
||||
|
||||
console.log('Gesamte Dokument-Tokens:', documentTokens);
|
||||
} else {
|
||||
console.log('Keine referenzierten Dokumente vorhanden');
|
||||
}
|
||||
|
||||
// WICHTIG: Hier liegt möglicherweise das Problem!
|
||||
// Statt den vollständigen Prompt zu übergeben, berechnen wir die Tokens separat
|
||||
// und übergeben die Summe an estimateCostForPrompt
|
||||
const totalInputTokens = basePromptTokens + documentTokens;
|
||||
console.log('Gesamte Input-Tokens (Basis + Dokumente):', totalInputTokens);
|
||||
|
||||
// Erstelle einen Dummy-Prompt mit der richtigen Länge für die Kostenberechnung
|
||||
// Dies stellt sicher, dass die richtige Anzahl von Tokens verwendet wird
|
||||
const dummyPrompt = 'X'.repeat(totalInputTokens * 4); // 4 Zeichen pro Token
|
||||
|
||||
// Schätze die Kosten basierend auf der Gesamtzahl der Tokens
|
||||
console.log('Rufe estimateCostForPrompt mit Dummy-Prompt der Länge', dummyPrompt.length, 'auf');
|
||||
const estimate = await estimateCostForPrompt(dummyPrompt, model, estimatedCompletionLength);
|
||||
|
||||
// Füge die Aufschlüsselung der Token-Anzahl zur Schätzung hinzu
|
||||
estimate.basePromptTokens = basePromptTokens;
|
||||
estimate.documentTokens = documentTokens;
|
||||
|
||||
// Überprüfe, ob die Gesamtzahl der Input-Tokens korrekt ist
|
||||
console.log('Input-Tokens in der Schätzung:', estimate.inputTokens);
|
||||
console.log('Erwartete Input-Tokens (Basis + Dokumente):', totalInputTokens);
|
||||
|
||||
if (estimate.inputTokens !== totalInputTokens) {
|
||||
console.warn(
|
||||
'WARNUNG: Die Anzahl der Input-Tokens in der Schätzung stimmt nicht mit der erwarteten Anzahl überein!'
|
||||
);
|
||||
// Korrigiere die Werte in der Schätzung
|
||||
estimate.inputTokens = totalInputTokens;
|
||||
estimate.totalTokens = totalInputTokens + estimate.outputTokens;
|
||||
console.log('Korrigierte Schätzung:', estimate);
|
||||
}
|
||||
|
||||
// Hole das aktuelle Token-Guthaben
|
||||
const balance = await getCurrentTokenBalance(userId);
|
||||
|
||||
// Prüfe, ob genügend Tokens vorhanden sind
|
||||
const hasEnough = balance >= estimate.appTokens;
|
||||
|
||||
return { hasEnough, estimate, balance };
|
||||
const data = await response.json();
|
||||
return {
|
||||
hasEnough: data.hasEnough,
|
||||
estimate: data.estimate,
|
||||
balance: data.balance,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Prüfen des Token-Guthabens:', error);
|
||||
return { hasEnough: false, estimate: null, balance: 0 };
|
||||
|
|
@ -176,114 +111,42 @@ export const checkTokenBalance = async (
|
|||
};
|
||||
|
||||
/**
|
||||
* Generiert Text mit dem angegebenen KI-Modell
|
||||
* Generiert Text über das Backend (welches mana-llm nutzt)
|
||||
*/
|
||||
export const generateText = async (
|
||||
prompt: string,
|
||||
provider: AIProvider = 'azure',
|
||||
_provider: AIProvider = 'azure',
|
||||
options: AIGenerationOptions = {}
|
||||
): Promise<AIGenerationResult> => {
|
||||
try {
|
||||
// Hole den aktuellen Benutzer
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
const userId = sessionData?.session?.user?.id;
|
||||
const token = await getAuthToken();
|
||||
|
||||
if (!userId) {
|
||||
throw new Error('Nicht angemeldet');
|
||||
}
|
||||
|
||||
// Bestimme das Modell
|
||||
const model = options.model || (provider === 'azure' ? 'gpt-4.1' : 'gemini-pro');
|
||||
|
||||
// Prüfe, ob eine manuelle Token-Schätzung übergeben wurde
|
||||
let manualInputTokens = 0;
|
||||
if (options.prompt) {
|
||||
// Versuche, die manuelle Token-Schätzung zu parsen
|
||||
try {
|
||||
manualInputTokens = parseInt(options.prompt, 10);
|
||||
} catch (e) {
|
||||
console.warn('Konnte manuelle Token-Schätzung nicht parsen:', options.prompt);
|
||||
}
|
||||
}
|
||||
|
||||
// Schätze die Kosten
|
||||
let hasEnough = false;
|
||||
let estimate: any = null;
|
||||
|
||||
if (manualInputTokens > 0) {
|
||||
// Verwende die manuelle Token-Schätzung
|
||||
const result = await calculateCost(model, manualInputTokens, options.maxTokens || 2000); // Standard auf 2000 Tokens statt 500
|
||||
estimate = result;
|
||||
|
||||
// Prüfe, ob genügend Tokens vorhanden sind
|
||||
const balance = await getCurrentTokenBalance(userId);
|
||||
hasEnough = balance >= result.appTokens;
|
||||
} else {
|
||||
// Verwende die automatische Token-Schätzung
|
||||
const result = await checkTokenBalance(
|
||||
const response = await fetch(`${BACKEND_URL}/api/v1/ai/generate/mobile`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
prompt,
|
||||
model,
|
||||
options.maxTokens || 2000 // Standard auf 2000 Tokens statt 500
|
||||
);
|
||||
|
||||
hasEnough = result.hasEnough;
|
||||
estimate = result.estimate;
|
||||
}
|
||||
|
||||
if (!hasEnough) {
|
||||
throw new Error('Nicht genügend Tokens für diese Anfrage. Bitte kaufen Sie weitere Tokens.');
|
||||
}
|
||||
|
||||
// Generiere den Text
|
||||
let completionText = '';
|
||||
switch (provider) {
|
||||
case 'azure':
|
||||
completionText = await generateWithAzureOpenAI(prompt, options);
|
||||
break;
|
||||
case 'google':
|
||||
completionText = await generateWithGoogle(prompt, options);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unbekannter Provider: ${provider}`);
|
||||
}
|
||||
|
||||
// Berechne die tatsächliche Anzahl der Output-Tokens
|
||||
const completionTokens = estimateTokens(completionText);
|
||||
|
||||
// Berechne die tatsächlichen Kosten mit den realen Output-Tokens
|
||||
const realCost = await calculateCost(model, estimate.inputTokens, completionTokens);
|
||||
|
||||
// Deutliche Protokollierung der tatsächlichen Kosten
|
||||
console.log('=== TATSÄCHLICHE TOKEN-KOSTEN NACH GENERIERUNG ===');
|
||||
console.log('Geschätzte Kosten vor Generierung:', {
|
||||
inputTokens: estimate.inputTokens,
|
||||
outputTokens: options.maxTokens || 500,
|
||||
appTokens: estimate.appTokens,
|
||||
costUsd: estimate.costUsd,
|
||||
});
|
||||
console.log('Tatsächliche Kosten nach Generierung:', {
|
||||
inputTokens: estimate.inputTokens,
|
||||
outputTokens: completionTokens,
|
||||
appTokens: realCost.appTokens,
|
||||
costUsd: realCost.costUsd,
|
||||
differenz: estimate.appTokens - realCost.appTokens,
|
||||
model: options.model || 'ollama/gemma3:4b',
|
||||
temperature: options.temperature,
|
||||
maxTokens: options.maxTokens,
|
||||
documentId: options.documentId,
|
||||
referencedDocuments: options.referencedDocuments,
|
||||
}),
|
||||
});
|
||||
|
||||
// Protokolliere die Token-Nutzung mit den tatsächlichen Werten
|
||||
await logTokenUsage(userId, model, prompt, completionText, options.documentId);
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.message || `Backend error: ${response.status}`);
|
||||
}
|
||||
|
||||
// Hole das aktualisierte Token-Guthaben
|
||||
const remainingTokens = await getCurrentTokenBalance(userId);
|
||||
const result = await response.json();
|
||||
|
||||
return {
|
||||
text: completionText,
|
||||
tokenInfo: {
|
||||
promptTokens: estimate.inputTokens,
|
||||
completionTokens: completionTokens,
|
||||
totalTokens: estimate.inputTokens + completionTokens,
|
||||
tokensUsed: realCost.appTokens, // Verwende die tatsächlichen Kosten
|
||||
remainingTokens: remainingTokens,
|
||||
},
|
||||
text: result.text,
|
||||
tokenInfo: result.tokenInfo,
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('Fehler bei der Textgenerierung:', error);
|
||||
|
|
@ -291,71 +154,16 @@ export const generateText = async (
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generiert Text mit Azure OpenAI-Modellen
|
||||
*/
|
||||
const generateWithAzureOpenAI = async (
|
||||
prompt: string,
|
||||
options: AIGenerationOptions = {}
|
||||
): Promise<string> => {
|
||||
if (!AZURE_OPENAI_KEY) {
|
||||
throw new Error('Azure OpenAI API-Schlüssel nicht konfiguriert');
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await azureClient.chat.completions.create({
|
||||
model: AZURE_OPENAI_DEPLOYMENT,
|
||||
messages: [
|
||||
{ role: 'system', content: 'You are a helpful assistant.' },
|
||||
{ role: 'user', content: prompt },
|
||||
],
|
||||
temperature: options.temperature || 0.7,
|
||||
max_tokens: options.maxTokens || 500,
|
||||
});
|
||||
|
||||
return response.choices[0].message.content || '';
|
||||
} catch (error) {
|
||||
console.error('Fehler bei Azure OpenAI-Anfrage:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generiert Text mit Google Gemini-Modellen
|
||||
*/
|
||||
const generateWithGoogle = async (
|
||||
prompt: string,
|
||||
options: AIGenerationOptions = {}
|
||||
): Promise<string> => {
|
||||
if (!GOOGLE_API_KEY) {
|
||||
throw new Error('Google API-Schlüssel nicht konfiguriert');
|
||||
}
|
||||
|
||||
try {
|
||||
const model = googleAI.getGenerativeModel({
|
||||
model: options.model || 'gemini-pro',
|
||||
});
|
||||
|
||||
const result = await model.generateContent(prompt);
|
||||
const response = await result.response;
|
||||
return response.text();
|
||||
} catch (error) {
|
||||
console.error('Fehler bei Google AI-Anfrage:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Hilfsfunktion zum Abrufen von Modelloptionen für einen bestimmten Provider
|
||||
*/
|
||||
export const getModelsByProvider = (provider: AIProvider): AIModelOption[] => {
|
||||
return availableModels.filter((model) => model.provider === provider);
|
||||
export const getModelsByProvider = (_provider: AIProvider): AIModelOption[] => {
|
||||
return availableModels;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hilfsfunktion zum Abrufen des Providers für ein bestimmtes Modell
|
||||
*/
|
||||
export const getProviderForModel = (modelValue: string): AIProvider => {
|
||||
const model = availableModels.find((m) => m.value === modelValue);
|
||||
return model?.provider || 'azure';
|
||||
export const getProviderForModel = (_modelValue: string): AIProvider => {
|
||||
return 'azure';
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue