feat: OpenRouter-ready AI service — all 11 methods implemented

IAIService:
+ generateMatchExplanation, summarizeTradeOffs, generateDataQualitySummary,
  classifyMarketSignal (4 new methods covering all documented AI output types)

OpenRouterAIService:
- All 11 methods now make real API calls via withFallback() pattern
- Every fallback is explicit (console.warn/error) — no silent mock bleed-through
- Proper JSON extraction with type-safe parsers, no any casts
- parseNeed: AI JSON → ParseNeedResult mapping (no TODO stubs)
- generateFollowUpQuestions, generateMatchExplanation, summarizeTradeOffs,
  generateDataQualitySummary, classifyMarketSignal, generateOfferEmail,
  extractCriteria, generateFollowUp: fully implemented

Prompts: +followUpQuestionsPrompt, +tradeOffPrompt, +dataQualityPrompt, +marketSignalPrompt

Factory (index.ts):
- VITE_AI_PROVIDER=mock|openrouter (new, takes priority)
- VITE_USE_REAL_AI=true still supported (legacy compat)
- Missing API key → explicit console.warn + MockAIService fallback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-24 12:43:51 +02:00
parent 723f553939
commit c972392b78
9 changed files with 755 additions and 88 deletions
+31 -7
View File
@@ -1,20 +1,44 @@
/**
* AI Service Factory
*
* Selects the active implementation based on the VITE_USE_REAL_AI feature flag.
* Provider selection (priority order):
* 1. VITE_AI_PROVIDER=openrouter → OpenRouterAIService (requires VITE_OPENROUTER_API_KEY)
* 2. VITE_AI_PROVIDER=mock → MockAIService (deterministic, no API key required)
* 3. VITE_USE_REAL_AI=true → OpenRouterAIService (legacy flag, requires VITE_OPENROUTER_API_KEY)
* 4. (default) → MockAIService
*
* Mock mode (default): no API key required, all responses are deterministic.
* Real mode: set VITE_USE_REAL_AI=true + VITE_OPENROUTER_API_KEY in .env
* If VITE_AI_PROVIDER=openrouter but VITE_OPENROUTER_API_KEY is missing, the factory
* logs a warning and falls back to MockAIService — never silently fails.
*
* Optional: VITE_OPENROUTER_MODEL controls which model OpenRouter uses.
* Default: anthropic/claude-3-5-haiku
*/
import { MockAIService } from './mock/MockAIService'
import { OpenRouterAIService } from './openrouter/OpenRouterAIService'
import type { IAIService } from './IAIService'
const useRealAI = import.meta.env.VITE_USE_REAL_AI === 'true'
&& !!import.meta.env.VITE_OPENROUTER_API_KEY
function resolveProvider(): IAIService {
const provider = import.meta.env.VITE_AI_PROVIDER as string | undefined
const legacyRealAI = import.meta.env.VITE_USE_REAL_AI === 'true'
const apiKey = import.meta.env.VITE_OPENROUTER_API_KEY as string | undefined
export const aiService: IAIService = useRealAI ? OpenRouterAIService : MockAIService
const wantsOpenRouter = provider === 'openrouter' || (legacyRealAI && !provider)
if (wantsOpenRouter) {
if (!apiKey) {
console.warn(
'[aiService] OpenRouter selected but VITE_OPENROUTER_API_KEY is missing — falling back to MockAIService.',
'Set VITE_AI_PROVIDER=mock to suppress this warning.',
)
return MockAIService
}
return OpenRouterAIService
}
return MockAIService
}
export const aiService: IAIService = resolveProvider()
// Named export so callers can reach the concrete impl when needed
export { MockAIService, OpenRouterAIService }
export type { IAIService }