Files
Momento/memento-note/lib/ai/factory.ts
Antigravity 724474cb49
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 5s
chore: remove dead code — 8 components, 5 libs, 4 API routes, 4 npm packages, 30+ scripts, dead CSS, dead exports
Removed unused components:
- brainstorm-canvas, brainstorm-create-dialog, invite-dialog, manual-idea-dialog
- note-inline-editor, profile-page-header, quota-paywall, label-management-dialog

Removed dead lib files:
- api-auth.ts, color-harmony-recommendation.ts, label-storage.ts, modern-color-options.ts
- hooks/use-card-size-mode.ts

Removed dead API routes:
- ai/test-chat, ai/test-embeddings, ai/test-tags, admin/randomize-labels

Removed unused npm packages:
- cmdk, novel, tippy.js, react-force-graph-2d

Cleaned dead CSS from globals.css:
- acrylic-*, win11-shadow-*, muuri-grid/item, ai-glass, ai-tab-indicator, ai-send-btn, sidebar-view-toggle, memento-sidebar-depth

Removed 29 orphan scripts and 3 root orphan files

Cleaned dead exports from 8 lib files:
- NOTE_TYPE_CONFIG, getPublishableKey, PROVIDER_DEFAULTS, useNotes/useNote/invalidateNote, etc.
2026-05-16 20:34:58 +00:00

278 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { OpenAIProvider } from './providers/openai';
import { OllamaProvider } from './providers/ollama';
import { CustomOpenAIProvider } from './providers/custom-openai';
import { AnthropicProvider } from './providers/anthropic';
import { GoogleProvider } from './providers/google';
import { AIProvider } from './types';
import { resolveAiRoute } from './router';
export type ProviderType =
| 'ollama'
| 'openai'
| 'google'
| 'minimax'
| 'glm'
| 'custom'
| 'deepseek'
| 'openrouter'
| 'mistral'
| 'zai'
| 'lmstudio'
| 'anthropic'
| 'anthropic_custom';
// --- Provider defaults ---
const PROVIDER_DEFAULTS: Record<string, { baseUrl: string; model: string; embeddingModel: string }> = {
deepseek: {
baseUrl: 'https://api.deepseek.com/v1',
model: 'deepseek-chat',
embeddingModel: '',
},
openrouter: {
baseUrl: 'https://openrouter.ai/api/v1',
model: 'openai/gpt-4o-mini',
embeddingModel: 'openai/text-embedding-3-small',
},
mistral: {
baseUrl: 'https://api.mistral.ai/v1',
model: 'mistral-small-latest',
embeddingModel: 'mistral-embed',
},
zai: {
baseUrl: 'https://api.zukijourney.com/v1',
model: 'gpt-4o-mini',
embeddingModel: 'text-embedding-3-small',
},
lmstudio: {
baseUrl: 'http://localhost:1234/v1',
model: '',
embeddingModel: '',
},
google: {
baseUrl: '',
model: 'gemini-1.5-flash',
embeddingModel: 'text-embedding-004',
},
minimax: {
baseUrl: 'https://api.minimax.io/v1',
model: 'abab6.5-chat',
embeddingModel: '',
},
glm: {
baseUrl: 'https://open.bigmodel.ai/api/paas/v4',
model: 'glm-4',
embeddingModel: 'embedding-2',
},
};
function createOllamaProvider(config: Record<string, string>, modelName: string, embeddingModelName: string, baseUrlOverride?: string): OllamaProvider {
let baseUrl = baseUrlOverride || config?.OLLAMA_BASE_URL || process.env.OLLAMA_BASE_URL
// Only use localhost as fallback for local development (not in Docker)
if (!baseUrl && process.env.NODE_ENV !== 'production') {
baseUrl = 'http://localhost:11434'
}
if (!baseUrl) {
throw new Error('OLLAMA_BASE_URL is required when using Ollama provider')
}
// Ensure baseUrl doesn't end with /api, we'll add it in OllamaProvider
if (baseUrl.endsWith('/api')) {
baseUrl = baseUrl.slice(0, -4);
}
return new OllamaProvider(baseUrl, modelName, embeddingModelName);
}
function createOpenAIProvider(config: Record<string, string>, modelName: string, embeddingModelName: string): OpenAIProvider {
const apiKey = config?.OPENAI_API_KEY || process.env.OPENAI_API_KEY || '';
if (!apiKey) {
throw new Error('OPENAI_API_KEY is required when using OpenAI provider');
}
return new OpenAIProvider(apiKey, modelName, embeddingModelName);
}
function createCustomOpenAIProvider(config: Record<string, string>, modelName: string, embeddingModelName: string): CustomOpenAIProvider {
const apiKey = config?.CUSTOM_OPENAI_API_KEY || process.env.CUSTOM_OPENAI_API_KEY || '';
const baseUrl = config?.CUSTOM_OPENAI_BASE_URL || process.env.CUSTOM_OPENAI_BASE_URL || '';
if (!apiKey) {
throw new Error('CUSTOM_OPENAI_API_KEY is required when using Custom OpenAI provider');
}
if (!baseUrl) {
throw new Error('CUSTOM_OPENAI_BASE_URL is required when using Custom OpenAI provider');
}
return new CustomOpenAIProvider(apiKey, baseUrl, modelName, embeddingModelName);
}
function createDeepSeekProvider(config: Record<string, string>, modelName: string, embeddingModelName: string): CustomOpenAIProvider {
const apiKey = config?.DEEPSEEK_API_KEY || process.env.DEEPSEEK_API_KEY || '';
if (!apiKey) throw new Error('DEEPSEEK_API_KEY is required when using DeepSeek provider');
const defaults = PROVIDER_DEFAULTS.deepseek;
return new CustomOpenAIProvider(apiKey, defaults.baseUrl, modelName || defaults.model, embeddingModelName || defaults.embeddingModel);
}
function createOpenRouterProvider(config: Record<string, string>, modelName: string, embeddingModelName: string): CustomOpenAIProvider {
const apiKey = config?.OPENROUTER_API_KEY || process.env.OPENROUTER_API_KEY || config?.CUSTOM_OPENAI_API_KEY || process.env.CUSTOM_OPENAI_API_KEY || '';
if (!apiKey) throw new Error('OPENROUTER_API_KEY is required when using OpenRouter provider');
const defaults = PROVIDER_DEFAULTS.openrouter;
return new CustomOpenAIProvider(apiKey, defaults.baseUrl, modelName || defaults.model, embeddingModelName || defaults.embeddingModel);
}
function createMistralProvider(config: Record<string, string>, modelName: string, embeddingModelName: string): CustomOpenAIProvider {
const apiKey = config?.MISTRAL_API_KEY || process.env.MISTRAL_API_KEY || '';
if (!apiKey) throw new Error('MISTRAL_API_KEY is required when using Mistral provider');
const defaults = PROVIDER_DEFAULTS.mistral;
return new CustomOpenAIProvider(apiKey, defaults.baseUrl, modelName || defaults.model, embeddingModelName || defaults.embeddingModel);
}
function createZAIProvider(config: Record<string, string>, modelName: string, embeddingModelName: string): CustomOpenAIProvider {
const apiKey = config?.ZAI_API_KEY || process.env.ZAI_API_KEY || '';
if (!apiKey) throw new Error('ZAI_API_KEY is required when using Z.AI provider');
const defaults = PROVIDER_DEFAULTS.zai;
return new CustomOpenAIProvider(apiKey, defaults.baseUrl, modelName || defaults.model, embeddingModelName || defaults.embeddingModel);
}
function createLMStudioProvider(config: Record<string, string>, modelName: string, embeddingModelName: string): CustomOpenAIProvider {
const baseUrl = config?.LMSTUDIO_BASE_URL || process.env.LMSTUDIO_BASE_URL || PROVIDER_DEFAULTS.lmstudio.baseUrl;
// LM Studio doesn't require an API key, but the CustomOpenAI provider needs one
// Use a dummy key if not provided
const apiKey = config?.LMSTUDIO_API_KEY || process.env.LMSTUDIO_API_KEY || 'lm-studio';
return new CustomOpenAIProvider(apiKey, baseUrl, modelName, embeddingModelName);
}
function createAnthropicProvider(config: Record<string, string>, modelName: string): AnthropicProvider {
const apiKey = config?.ANTHROPIC_API_KEY || process.env.ANTHROPIC_API_KEY || '';
if (!apiKey) {
throw new Error('ANTHROPIC_API_KEY is required when using Anthropic provider');
}
return new AnthropicProvider(apiKey, modelName || 'claude-sonnet-4-20250514');
}
/**
* Passerelles compatibles **Anthropic Messages API** (ex. MiniMax), pas OpenAI.
* Le SDK envoie les requêtes vers `{baseURL}/messages` avec len-tête `x-api-key`.
*/
function createAnthropicCustomProvider(config: Record<string, string>, modelName: string): AnthropicProvider {
const apiKey = config?.ANTHROPIC_CUSTOM_API_KEY || process.env.ANTHROPIC_CUSTOM_API_KEY || '';
const baseUrl = config?.ANTHROPIC_CUSTOM_BASE_URL || process.env.ANTHROPIC_CUSTOM_BASE_URL || '';
if (!apiKey) {
throw new Error('ANTHROPIC_CUSTOM_API_KEY is required when using Anthropic Custom provider');
}
if (!baseUrl) {
throw new Error('ANTHROPIC_CUSTOM_BASE_URL is required when using Anthropic Custom provider');
}
const resolvedModel =
modelName && modelName.trim() !== '' ? modelName.trim() : 'MiniMax-M2.7';
return new AnthropicProvider(apiKey, resolvedModel, baseUrl.trim());
}
function createGoogleProvider(config: Record<string, string>, modelName: string, embeddingModelName: string): GoogleProvider {
const apiKey = config?.GOOGLE_GENERATIVE_AI_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY || '';
if (!apiKey) throw new Error('GOOGLE_GENERATIVE_AI_API_KEY is required when using Google provider');
return new GoogleProvider(apiKey, modelName || PROVIDER_DEFAULTS.google.model, embeddingModelName || PROVIDER_DEFAULTS.google.embeddingModel);
}
function createMiniMaxProvider(config: Record<string, string>, modelName: string, embeddingModelName: string): CustomOpenAIProvider {
const apiKey = config?.MINIMAX_API_KEY || process.env.MINIMAX_API_KEY || '';
if (!apiKey) throw new Error('MINIMAX_API_KEY is required when using MiniMax provider');
const defaults = PROVIDER_DEFAULTS.minimax;
return new CustomOpenAIProvider(apiKey, defaults.baseUrl, modelName || defaults.model, embeddingModelName || defaults.embeddingModel);
}
function createGLMProvider(config: Record<string, string>, modelName: string, embeddingModelName: string): CustomOpenAIProvider {
const apiKey = config?.GLM_API_KEY || process.env.GLM_API_KEY || '';
if (!apiKey) throw new Error('GLM_API_KEY is required when using GLM provider');
const defaults = PROVIDER_DEFAULTS.glm;
return new CustomOpenAIProvider(apiKey, defaults.baseUrl, modelName || defaults.model, embeddingModelName || defaults.embeddingModel);
}
/** Exported for tests and Story 3.3+ orchestration; prefer `get*Provider` in production call sites. */
export function getProviderInstance(providerType: ProviderType, config: Record<string, string>, modelName: string, embeddingModelName: string, ollamaBaseUrl?: string): AIProvider {
switch (providerType) {
case 'ollama':
return createOllamaProvider(config, modelName, embeddingModelName, ollamaBaseUrl);
case 'openai':
return createOpenAIProvider(config, modelName, embeddingModelName);
case 'custom':
return createCustomOpenAIProvider(config, modelName, embeddingModelName);
case 'deepseek':
return createDeepSeekProvider(config, modelName, embeddingModelName);
case 'openrouter':
return createOpenRouterProvider(config, modelName, embeddingModelName);
case 'mistral':
return createMistralProvider(config, modelName, embeddingModelName);
case 'zai':
return createZAIProvider(config, modelName, embeddingModelName);
case 'lmstudio':
return createLMStudioProvider(config, modelName, embeddingModelName);
case 'anthropic':
return createAnthropicProvider(config, modelName);
case 'anthropic_custom':
return createAnthropicCustomProvider(config, modelName);
case 'google':
return createGoogleProvider(config, modelName, embeddingModelName);
case 'minimax':
return createMiniMaxProvider(config, modelName, embeddingModelName);
case 'glm':
return createGLMProvider(config, modelName, embeddingModelName);
default:
return createOllamaProvider(config, modelName, embeddingModelName, ollamaBaseUrl);
}
}
// Resolve the effective provider type and config keys for a given provider
// Returns { providerType, apiKeyConfigKey, baseUrlConfigKey }
function getProviderConfigKeys(providerType: string): { apiKeyConfigKey: string; baseUrlConfigKey: string } {
switch (providerType) {
case 'deepseek': return { apiKeyConfigKey: 'DEEPSEEK_API_KEY', baseUrlConfigKey: '' };
case 'openrouter': return { apiKeyConfigKey: 'OPENROUTER_API_KEY', baseUrlConfigKey: '' };
case 'mistral': return { apiKeyConfigKey: 'MISTRAL_API_KEY', baseUrlConfigKey: '' };
case 'zai': return { apiKeyConfigKey: 'ZAI_API_KEY', baseUrlConfigKey: '' };
case 'lmstudio': return { apiKeyConfigKey: 'LMSTUDIO_API_KEY', baseUrlConfigKey: 'LMSTUDIO_BASE_URL' };
case 'openai': return { apiKeyConfigKey: 'OPENAI_API_KEY', baseUrlConfigKey: '' };
case 'anthropic': return { apiKeyConfigKey: 'ANTHROPIC_API_KEY', baseUrlConfigKey: '' };
case 'anthropic_custom':
return { apiKeyConfigKey: 'ANTHROPIC_CUSTOM_API_KEY', baseUrlConfigKey: 'ANTHROPIC_CUSTOM_BASE_URL' };
case 'google': return { apiKeyConfigKey: 'GOOGLE_GENERATIVE_AI_API_KEY', baseUrlConfigKey: '' };
case 'minimax': return { apiKeyConfigKey: 'MINIMAX_API_KEY', baseUrlConfigKey: '' };
case 'glm': return { apiKeyConfigKey: 'GLM_API_KEY', baseUrlConfigKey: '' };
case 'custom': return { apiKeyConfigKey: 'CUSTOM_OPENAI_API_KEY', baseUrlConfigKey: 'CUSTOM_OPENAI_BASE_URL' };
default: return { apiKeyConfigKey: '', baseUrlConfigKey: 'OLLAMA_BASE_URL' };
}
}
export function getTagsProvider(config?: Record<string, string>): AIProvider {
const cfg = config || {};
const route = resolveAiRoute('tags', cfg);
return getProviderInstance(route.providerType as ProviderType, cfg, route.modelName, route.embeddingModelName, route.ollamaBaseUrl);
}
export function getEmbeddingsProvider(config?: Record<string, string>): AIProvider {
const cfg = config || {};
const route = resolveAiRoute('embedding', cfg);
return getProviderInstance(route.providerType as ProviderType, cfg, route.modelName, route.embeddingModelName, route.ollamaBaseUrl);
}
export function getAIProvider(config?: Record<string, string>): AIProvider {
return getEmbeddingsProvider(config);
}
export function getChatProvider(config?: Record<string, string>): AIProvider {
const cfg = config || {};
const route = resolveAiRoute('chat', cfg);
return getProviderInstance(route.providerType as ProviderType, cfg, route.modelName, route.embeddingModelName, route.ollamaBaseUrl);
}
// Export for use by admin settings form and deploy scripts
export { getProviderConfigKeys };