import { z } from 'zod'; import { BaseService } from '@/lib/service-base'; import { reportError } from '@/lib/error-handling'; import { SERVICE_CONFIGS } from '@/lib/service-config'; import type { HistoricalLandmark, OrganizationFormData, WebIntelligenceResult } from '@/types'; /** * Backend AI API Client * All AI/LLM operations are handled by the backend */ // Request/Response schemas const extractDataFromTextRequestSchema = z.object({ text: z.string().min(1, 'Text cannot be empty'), }); const extractDataFromFileRequestSchema = z.object({ file: z.instanceof(File), }); const analyzeSymbiosisRequestSchema = z.object({ organization_id: z.string().min(1, 'Organization ID is required'), }); const analyzeSymbiosisResponseSchema = z.object({ matches: z.array(z.object({ partner_id: z.string(), partner_name: z.string(), reason: z.string(), score: z.number().min(0).max(1), })), }); const getWebIntelligenceRequestSchema = z.object({ organization_name: z.string().min(1, 'Organization name is required'), }); const getSearchSuggestionsRequestSchema = z.object({ query: z.string().min(1, 'Query cannot be empty'), }); const generateOrganizationDescriptionRequestSchema = z.object({ name: z.string().min(1, 'Name is required'), sector_key: z.string().min(1, 'Sector key is required'), keywords: z.string().min(1, 'Keywords are required'), }); const generateHistoricalContextRequestSchema = z.object({ landmark: z.object({ id: z.string(), name: z.string(), period: z.string(), originalPurpose: z.string(), currentStatus: z.string(), }), }); const sendMessageRequestSchema = z.object({ message: z.string().min(1, 'Message cannot be empty'), system_instruction: z.string().optional(), }); // Type exports (keeping for backward compatibility) export interface ExtractDataFromTextRequest { text: string; } export interface ExtractDataFromFileRequest { file: File; } export interface AnalyzeSymbiosisRequest { organization_id: string; } export interface GetWebIntelligenceRequest { organization_name: string; } export interface GetSearchSuggestionsRequest { query: string; } export interface GenerateOrganizationDescriptionRequest { name: string; sector_key: string; keywords: string; } export interface GenerateHistoricalContextRequest { landmark: HistoricalLandmark; } export interface SendMessageRequest { message: string; system_instruction?: string; } /** * AI Service * Handles all AI-related API operations with improved reliability and type safety */ class AIService extends BaseService { constructor() { super(SERVICE_CONFIGS.AI); } /** * Extract structured organization data from text */ async extractDataFromText(request: ExtractDataFromTextRequest): Promise> { const validatedRequest = this.validateStrict(extractDataFromTextRequestSchema, request, 'extractDataFromText'); const formData = new FormData(); formData.append('text', validatedRequest.text); const result = await this.post('extract/text', formData, z.any(), { context: 'extractDataFromText', isFormData: true, }); if (!result.success) { const error = reportError( new Error(result.error?.message || 'Failed to extract data from text'), { operation: 'extractDataFromText', textLength: validatedRequest.text.length } ); throw error; } return result.data; } /** * Extract structured organization data from file */ async extractDataFromFile(request: ExtractDataFromFileRequest): Promise> { const validatedRequest = this.validateStrict(extractDataFromFileRequestSchema, request, 'extractDataFromFile'); const formData = new FormData(); formData.append('file', validatedRequest.file); const result = await this.post('extract/file', formData, z.any(), { context: 'extractDataFromFile', isFormData: true, }); if (!result.success) { const error = reportError( new Error(result.error?.message || 'Failed to extract data from file'), { operation: 'extractDataFromFile', fileName: validatedRequest.file.name, fileSize: validatedRequest.file.size } ); throw error; } return result.data; } /** * Analyze symbiotic relationships */ async analyzeSymbiosis(request: AnalyzeSymbiosisRequest) { const validatedRequest = this.validateStrict(analyzeSymbiosisRequestSchema, request, 'analyzeSymbiosis'); const result = await this.post('analyze/symbiosis', validatedRequest, analyzeSymbiosisResponseSchema, { context: 'analyzeSymbiosis', }); if (!result.success) { const error = reportError( new Error(result.error?.message || 'Failed to analyze symbiosis'), { operation: 'analyzeSymbiosis', organizationId: validatedRequest.organization_id } ); throw error; } return result.data; } /** * Get web intelligence */ async getWebIntelligence(request: GetWebIntelligenceRequest): Promise { const validatedRequest = this.validateStrict(getWebIntelligenceRequestSchema, request, 'getWebIntelligence'); const result = await this.post('web-intelligence', validatedRequest, z.any(), { context: 'getWebIntelligence', }); if (!result.success) { const error = reportError( new Error(result.error?.message || 'Failed to get web intelligence'), { operation: 'getWebIntelligence', organizationName: validatedRequest.organization_name } ); throw error; } return result.data; } /** * Get search suggestions */ async getSearchSuggestions(request: GetSearchSuggestionsRequest): Promise { const validatedRequest = this.validateStrict(getSearchSuggestionsRequestSchema, request, 'getSearchSuggestions'); const result = await this.post('search-suggestions', validatedRequest, z.array(z.string()), { context: 'getSearchSuggestions', }); if (!result.success) { const error = reportError( new Error(result.error?.message || 'Failed to get search suggestions'), { operation: 'getSearchSuggestions', query: validatedRequest.query } ); throw error; } return result.data; } /** * Generate organization description */ async generateOrganizationDescription(request: GenerateOrganizationDescriptionRequest): Promise { const validatedRequest = this.validateStrict(generateOrganizationDescriptionRequestSchema, request, 'generateOrganizationDescription'); const result = await this.post('generate/description', validatedRequest, z.string(), { context: 'generateOrganizationDescription', }); if (!result.success) { const error = reportError( new Error(result.error?.message || 'Failed to generate description'), { operation: 'generateOrganizationDescription', name: validatedRequest.name } ); throw error; } return result.data; } /** * Generate historical context */ async generateHistoricalContext(request: GenerateHistoricalContextRequest): Promise { const validatedRequest = this.validateStrict(generateHistoricalContextRequestSchema, request, 'generateHistoricalContext'); const result = await this.post('generate/historical-context', validatedRequest, z.string(), { context: 'generateHistoricalContext', }); if (!result.success) { const error = reportError( new Error(result.error?.message || 'Failed to generate historical context'), { operation: 'generateHistoricalContext', landmarkId: validatedRequest.landmark.id } ); throw error; } return result.data; } /** * Send message to AI */ async sendMessage(request: SendMessageRequest): Promise { const validatedRequest = this.validateStrict(sendMessageRequestSchema, request, 'sendMessage'); const result = await this.post('chat', validatedRequest, z.string(), { context: 'sendMessage', }); if (!result.success) { const error = reportError( new Error(result.error?.message || 'Failed to send message'), { operation: 'sendMessage', messageLength: validatedRequest.message.length, hasSystemInstruction: !!validatedRequest.system_instruction } ); throw error; } return result.data; } } // Create and export service instance const aiService = new AIService(); // Export service instance for direct usage export { aiService }; // Export service methods directly for cleaner imports export const extractDataFromText = aiService.extractDataFromText.bind(aiService); export const extractDataFromFile = aiService.extractDataFromFile.bind(aiService); export const analyzeSymbiosis = aiService.analyzeSymbiosis.bind(aiService); export const getWebIntelligence = aiService.getWebIntelligence.bind(aiService); export const getSearchSuggestions = aiService.getSearchSuggestions.bind(aiService); export const generateOrganizationDescription = aiService.generateOrganizationDescription.bind(aiService); export const generateHistoricalContext = aiService.generateHistoricalContext.bind(aiService); export const sendMessage = aiService.sendMessage.bind(aiService);