UNPKG

claritykit-svelte

Version:

A comprehensive Svelte component library focused on accessibility, ADHD-optimized design, developer experience, and full SSR compatibility

287 lines (286 loc) 10.6 kB
/** * AI Analysis Service * * Abstract class representing an AI service for text analysis. */ export class AIAnalysisService { constructor(config) { Object.defineProperty(this, "endpoint", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "apiKey", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "provider", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "allowTelemetry", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.provider = config.provider; this.endpoint = config.endpoint; this.apiKey = config.apiKey; this.allowTelemetry = config.allowTelemetry; } /** * Destroy the AI analysis service and release resources. */ destroy() { // Implement cleanup if needed by specific provider } } /** * Implementation of MCP-based AI Analysis Service */ export class MCPAIService extends AIAnalysisService { constructor(config) { super(config); Object.defineProperty(this, "cache", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "cacheTimeout", { enumerable: true, configurable: true, writable: true, value: 5 * 60 * 1000 }); // 5 minutes // Initialize with MCP server settings if (!this.endpoint) { console.warn('MCP server endpoint not provided - using mock analysis'); } } async analyzeText(text, context) { if (!text.trim()) return []; // Check cache first const cacheKey = this.getCacheKey(text, context); const cached = this.cache.get(cacheKey); if (cached && Date.now() - cached.timestamp < this.cacheTimeout) { return cached.result; } try { let results; if (this.endpoint) { // Make MCP server call for AI analysis results = await this.callMCPServer(text, context); } else { // Use mock analysis for development/testing results = await this.mockAnalysis(text, context); } // Cache the results this.cache.set(cacheKey, { result: results, timestamp: Date.now() }); return results; } catch (error) { console.error('MCP AI analysis failed:', error); // Fall back to mock analysis return await this.mockAnalysis(text, context); } } async callMCPServer(text, context) { // Call MCP server for writing analysis const response = await fetch(`${this.endpoint}/tools/call`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(this.apiKey && { 'Authorization': `Bearer ${this.apiKey}` }) }, body: JSON.stringify({ name: 'analyze_writing', arguments: { text, context: this.formatMCPContext(context), enabled_types: context.enabledTypes || ['grammar', 'tone', 'clarity', 'style'], max_suggestions: 10 } }) }); if (!response.ok) { throw new Error(`MCP server error: ${response.status} ${response.statusText}`); } const data = await response.json(); return this.parseMCPResponse(data, text); } formatMCPContext(context) { return { writing_style: context.writingStyle, tone_preference: context.tonePreference, conversation_type: context.conversationType, custom_instructions: context.customInstructions, document_type: context.documentType, language_level: context.languageLevel, recent_messages: context.recentMessages?.slice(-3), // Last 3 messages for context relevant_concepts: context.relevantConcepts?.slice(0, 5) // Top 5 relevant concepts }; } parseMCPResponse(response, originalText) { try { // MCP response format: { content: [...], isError: false } if (response.isError) { console.error('MCP server returned error:', response.content); return []; } const content = response.content || []; const suggestions = Array.isArray(content) ? content : [content]; return suggestions.map((s, index) => ({ id: `mcp-${Date.now()}-${index}`, type: s.type || 'style', message: s.message || s.description || 'AI suggestion', range: s.range || { from: 0, to: originalText.length }, severity: s.severity || 'info', confidence: s.confidence || 'medium', replacement: s.replacement || s.suggested_text, explanation: s.explanation || s.reasoning, source: 'mcp-server', category: s.category, metadata: s.metadata, created: new Date() })); } catch (error) { console.error('Failed to parse MCP response:', error); return []; } } async mockAnalysis(text, context) { // Simulate API delay await new Promise(resolve => setTimeout(resolve, 200)); const results = []; // Mock grammar suggestions const grammarIssues = this.findGrammarIssues(text); results.push(...grammarIssues); // Mock tone suggestions const toneIssues = this.findToneIssues(text, context); results.push(...toneIssues); // Mock clarity suggestions const clarityIssues = this.findClarityIssues(text); results.push(...clarityIssues); return results.slice(0, 5); // Limit to 5 suggestions } findGrammarIssues(text) { const issues = []; // Simple grammar checks const patterns = [ { pattern: /\b(their|there|they're)\b/gi, message: 'Check their/there/they\'re usage' }, { pattern: /\b(your|you're)\b/gi, message: 'Check your/you\'re usage' }, { pattern: /\b(its|it's)\b/gi, message: 'Check its/it\'s usage' }, { pattern: /\s{2,}/g, message: 'Multiple spaces found' } ]; patterns.forEach((p, index) => { let match; while ((match = p.pattern.exec(text)) !== null) { issues.push({ id: `grammar-${Date.now()}-${index}`, type: 'grammar', message: p.message, range: { from: match.index, to: match.index + match[0].length }, severity: 'warning', confidence: 'medium', source: 'mock', created: new Date() }); } }); return issues; } findToneIssues(text, context) { const issues = []; // Check for overly casual language in formal contexts if (context.writingStyle === 'formal') { const casualWords = ['gonna', 'kinda', 'sorta', 'yeah', 'nah']; casualWords.forEach(word => { const regex = new RegExp(`\\b${word}\\b`, 'gi'); let match; while ((match = regex.exec(text)) !== null) { issues.push({ id: `tone-${Date.now()}-${word}`, type: 'tone', message: `"${word}" may be too casual for formal writing`, range: { from: match.index, to: match.index + match[0].length }, severity: 'info', confidence: 'medium', replacement: word === 'gonna' ? 'going to' : undefined, source: 'mock', created: new Date() }); } }); } return issues; } findClarityIssues(text) { const issues = []; // Check for overly long sentences const sentences = text.split(/[.!?]+/); sentences.forEach((sentence, index) => { if (sentence.trim().length > 100) { const start = text.indexOf(sentence.trim()); if (start !== -1) { issues.push({ id: `clarity-${Date.now()}-${index}`, type: 'clarity', message: 'Consider breaking this long sentence into shorter ones', range: { from: start, to: start + sentence.trim().length }, severity: 'info', confidence: 'medium', explanation: 'Long sentences can be harder to read and understand', source: 'mock', created: new Date() }); } } }); return issues; } getCacheKey(text, context) { return `${text.slice(0, 100)}-${JSON.stringify(context)}`.replace(/\s+/g, ''); } } /** * Implementation of OpenAI GPT Service */ export class OpenAIService extends AIAnalysisService { constructor(config) { super(config); // Initialize with OpenAI-specific settings } async analyzeText(text, context) { console.log('OpenAI analysis not implemented. Text:', text); // TODO: Implement actual API call to OpenAI service return []; } } /** * Factory function for creating an AI analysis service. */ export function createAIAnalysisService(config) { switch (config.provider) { case 'mcp': case 'agent': case 'local': return new MCPAIService(config); case 'openai': return new OpenAIService(config); // Legacy support case 'claude': case 'custom': default: return new MCPAIService(config); } }