| | import axios from 'axios'; |
| | import { config } from '../utils/config.js'; |
| | import { logger } from '../utils/logger.js'; |
| | import type { CVResult } from '../types/index.js'; |
| |
|
| | export class CVService { |
| | |
| | |
| | |
| | |
| | private async callCVAPI(imageUrl: string, modelType: 'dermnet' | 'teeth' | 'nail', topK: number = 3): Promise<CVResult> { |
| | try { |
| | logger.info('='.repeat(80)); |
| | logger.info('[MCP CV] INPUT:'); |
| | logger.info(` Image URL: ${imageUrl}`); |
| | logger.info(` Model: ${modelType}`); |
| | logger.info(` Top K: ${topK}`); |
| |
|
| | if (!config.cvModels.endpoint) { |
| | logger.warn('[MCP CV] CV_ENDPOINT not configured'); |
| | logger.info('='.repeat(80)); |
| | return { top_conditions: [] }; |
| | } |
| |
|
| | |
| | const endpoint = `${config.cvModels.endpoint.replace(/\/$/, '')}/run/handle_prediction`; |
| | |
| | logger.info(`[MCP CV] Calling CV API: ${endpoint}`); |
| |
|
| | |
| | const response = await axios.post( |
| | endpoint, |
| | { |
| | data: [ |
| | imageUrl, |
| | modelType, |
| | topK |
| | ] |
| | }, |
| | { |
| | headers: { 'Content-Type': 'application/json' }, |
| | timeout: 30000 |
| | } |
| | ); |
| |
|
| | logger.info('[MCP CV] Response received'); |
| |
|
| | |
| | if (response.data && response.data.data && response.data.data.length > 0) { |
| | const jsonResultString = response.data.data[0]; |
| | |
| | |
| | const jsonResult = typeof jsonResultString === 'string' |
| | ? JSON.parse(jsonResultString) |
| | : jsonResultString; |
| |
|
| | logger.info('[MCP CV] OUTPUT:'); |
| | logger.info(` Success: ${jsonResult.success}`); |
| | logger.info(` Model: ${jsonResult.model || 'N/A'}`); |
| | |
| | if (jsonResult.success && jsonResult.predictions) { |
| | const top_conditions = jsonResult.predictions.map((pred: any) => ({ |
| | name: pred.class, |
| | prob: pred.confidence |
| | })); |
| |
|
| | logger.info(` Predictions: ${top_conditions.length}`); |
| | top_conditions.forEach((cond: any, idx: number) => { |
| | logger.info(` ${idx + 1}. ${cond.name}: ${(cond.prob * 100).toFixed(1)}%`); |
| | }); |
| | logger.info('='.repeat(80)); |
| |
|
| | return { top_conditions }; |
| | } else { |
| | logger.error(`[MCP CV] ERROR: ${jsonResult.error || 'Unknown error'}`); |
| | logger.info('='.repeat(80)); |
| | return { top_conditions: [] }; |
| | } |
| | } |
| |
|
| | logger.error('[MCP CV] Unexpected response format'); |
| | logger.info('='.repeat(80)); |
| | return { top_conditions: [] }; |
| |
|
| | } catch (error: any) { |
| | logger.error('[MCP CV] ERROR:'); |
| | logger.error(` Message: ${error.message}`); |
| | if (error.response) { |
| | logger.error(` Status: ${error.response.status}`); |
| | logger.error(` Data: ${JSON.stringify(error.response.data)}`); |
| | } |
| | logger.info('='.repeat(80)); |
| | return { top_conditions: [] }; |
| | } |
| | } |
| |
|
| | async callDermCV(imageUrl: string): Promise<CVResult> { |
| | try { |
| | logger.info('[MCP CV] Calling Dermatology CV model...'); |
| | return await this.callCVAPI(imageUrl, 'dermnet', 3); |
| | } catch (error) { |
| | logger.error({ error }, '[MCP CV] Derm CV error'); |
| | return { top_conditions: [] }; |
| | } |
| | } |
| |
|
| | async callEyeCV(imageUrl: string): Promise<CVResult> { |
| | try { |
| | logger.info('[MCP CV] Calling Eye CV model...'); |
| | |
| | return await this.callCVAPI(imageUrl, 'dermnet', 3); |
| | } catch (error) { |
| | logger.error({ error }, '[MCP CV] Eye CV error'); |
| | return { top_conditions: [] }; |
| | } |
| | } |
| |
|
| | async callWoundCV(imageUrl: string): Promise<CVResult> { |
| | try { |
| | logger.info('[MCP CV] Calling Wound CV model...'); |
| | |
| | return await this.callCVAPI(imageUrl, 'dermnet', 3); |
| | } catch (error) { |
| | logger.error({ error }, '[MCP CV] Wound CV error'); |
| | return { top_conditions: [] }; |
| | } |
| | } |
| |
|
| | async analyzeImage(imageUrl: string, type?: 'derm' | 'eye' | 'wound'): Promise<CVResult> { |
| | |
| | if (type === 'derm') return this.callDermCV(imageUrl); |
| | if (type === 'eye') return this.callEyeCV(imageUrl); |
| | if (type === 'wound') return this.callWoundCV(imageUrl); |
| |
|
| | |
| | return { top_conditions: [] }; |
| | } |
| | } |
| |
|
| |
|