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
JavaScript
/**
* 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);
}
}