/** * Claude Service - Chat interface with Claude AI and MCP Integration * * Provides: * - Direct Claude chat * - MCP tool execution * - Project context integration * - Integration management (Figma, Jira, Confluence) */ import api from '../core/api.js'; import logger from '../core/logger.js'; class ClaudeService { constructor() { this.conversationHistory = []; this.maxHistory = 50; this.currentProjectId = null; this.userId = 1; // Default user ID (TODO: implement proper auth) this.mcpTools = null; this.mcpStatus = null; // Load persisted chat history this.loadHistory(); } /** * Get storage key for current user * @private */ _getStorageKey() { return `ds-chat-history-user-${this.userId}`; } /** * Load chat history from localStorage * @private */ loadHistory() { const key = this._getStorageKey(); try { const stored = localStorage.getItem(key); if (stored) { this.conversationHistory = JSON.parse(stored); logger.info('Claude', `Loaded ${this.conversationHistory.length} messages from history`); } } catch (error) { logger.warn('Claude', 'Failed to load chat history, resetting', error); localStorage.removeItem(key); this.conversationHistory = []; } } /** * Save chat history to localStorage with quota management * @private */ saveHistory() { const key = this._getStorageKey(); try { // Keep only last 50 messages to respect localStorage limits const historyToSave = this.conversationHistory.slice(-50); localStorage.setItem(key, JSON.stringify(historyToSave)); } catch (error) { logger.warn('Claude', 'Failed to save chat history', error); if (error.name === 'QuotaExceededError') { // Emergency trim: cut in half and retry this.conversationHistory = this.conversationHistory.slice(-25); try { localStorage.setItem(key, JSON.stringify(this.conversationHistory)); logger.info('Claude', 'History trimmed to 25 messages due to quota'); } catch (retryError) { logger.error('Claude', 'Failed to save history even after trim', retryError); } } } } /** * Set current project context */ setProject(projectId) { this.currentProjectId = projectId; logger.info('Claude', 'Project context set', { projectId }); } /** * Send message to Claude with MCP context */ async chat(message, context = {}) { logger.info('Claude', 'Sending message', { message: message.substring(0, 50) + '...' }); // Add project context if available if (this.currentProjectId && !context.projectId) { context.projectId = this.currentProjectId; } // Add to history this.conversationHistory.push({ role: 'user', content: message, timestamp: new Date().toISOString() }); try { // Call backend Claude endpoint with MCP context const response = await api.post('/claude/chat', { message, context: { ...context, mcp_enabled: true, available_tools: this.mcpTools ? Object.keys(this.mcpTools).flatMap(k => this.mcpTools[k].map(t => t.name)) : [] }, history: this.conversationHistory.slice(-10) // Send last 10 messages }); // Add response to history this.conversationHistory.push({ role: 'assistant', content: response.response, timestamp: new Date().toISOString(), tools_used: response.tools_used || [] }); // Keep history manageable if (this.conversationHistory.length > this.maxHistory) { this.conversationHistory = this.conversationHistory.slice(-this.maxHistory); } // Persist updated history this.saveHistory(); logger.info('Claude', 'Received response', { length: response.response.length }); return response.response; } catch (error) { logger.error('Claude', 'Chat failed', error); throw error; } } /** * Get conversation history */ getHistory() { return this.conversationHistory; } /** * Clear history */ clearHistory() { this.conversationHistory = []; this.saveHistory(); // Persist empty state logger.info('Claude', 'Conversation history cleared'); } /** * Export conversation */ exportConversation() { const data = JSON.stringify(this.conversationHistory, null, 2); const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(data); const exportFileDefaultName = `claude-conversation-${Date.now()}.json`; const linkElement = document.createElement('a'); linkElement.setAttribute('href', dataUri); linkElement.setAttribute('download', exportFileDefaultName); linkElement.click(); } // === MCP Integration Methods === /** * Get MCP server status */ async getMcpStatus() { try { const status = await api.get('/mcp/status'); this.mcpStatus = status; logger.info('Claude', 'MCP status fetched', status); return status; } catch (error) { logger.error('Claude', 'Failed to fetch MCP status', error); throw error; } } /** * Get all available MCP tools */ async getMcpTools() { try { const tools = await api.get('/mcp/tools'); this.mcpTools = tools.tools; logger.info('Claude', 'MCP tools fetched', { count: tools.total_count }); return tools; } catch (error) { logger.error('Claude', 'Failed to fetch MCP tools', error); throw error; } } /** * Execute an MCP tool directly */ async executeMcpTool(toolName, args = {}) { if (this.currentProjectId && !args.project_id) { args.project_id = this.currentProjectId; } try { logger.info('Claude', 'Executing MCP tool', { toolName, args }); const result = await api.post(`/mcp/tools/${toolName}/execute?user_id=${this.userId}`, args); logger.info('Claude', 'MCP tool executed', { toolName, success: !result.error }); return result; } catch (error) { logger.error('Claude', 'MCP tool execution failed', error); throw error; } } // === Integration Management === /** * Get all integration types and their health status */ async getIntegrations() { try { const integrations = await api.get('/mcp/integrations'); logger.info('Claude', 'Integrations fetched', { count: integrations.integrations.length }); return integrations.integrations; } catch (error) { logger.error('Claude', 'Failed to fetch integrations', error); throw error; } } /** * Get project-specific integrations */ async getProjectIntegrations(projectId = null) { const pid = projectId || this.currentProjectId; if (!pid) { throw new Error('No project ID specified'); } try { const integrations = await api.get(`/projects/${pid}/integrations?user_id=${this.userId}`); return integrations.integrations; } catch (error) { logger.error('Claude', 'Failed to fetch project integrations', error); throw error; } } /** * Configure an integration for a project */ async configureIntegration(integrationType, config, projectId = null) { const pid = projectId || this.currentProjectId; if (!pid) { throw new Error('No project ID specified'); } try { const result = await api.post(`/projects/${pid}/integrations?user_id=${this.userId}`, { integration_type: integrationType, config, enabled: true }); logger.info('Claude', 'Integration configured', { integrationType, projectId: pid }); return result; } catch (error) { logger.error('Claude', 'Failed to configure integration', error); throw error; } } /** * Update an integration */ async updateIntegration(integrationType, updates, projectId = null) { const pid = projectId || this.currentProjectId; if (!pid) { throw new Error('No project ID specified'); } try { const result = await api.put( `/projects/${pid}/integrations/${integrationType}?user_id=${this.userId}`, updates ); logger.info('Claude', 'Integration updated', { integrationType, projectId: pid }); return result; } catch (error) { logger.error('Claude', 'Failed to update integration', error); throw error; } } /** * Delete an integration */ async deleteIntegration(integrationType, projectId = null) { const pid = projectId || this.currentProjectId; if (!pid) { throw new Error('No project ID specified'); } try { const result = await api.delete( `/projects/${pid}/integrations/${integrationType}?user_id=${this.userId}` ); logger.info('Claude', 'Integration deleted', { integrationType, projectId: pid }); return result; } catch (error) { logger.error('Claude', 'Failed to delete integration', error); throw error; } } /** * Toggle integration enabled/disabled */ async toggleIntegration(integrationType, enabled, projectId = null) { return this.updateIntegration(integrationType, { enabled }, projectId); } // === Project Context Tools === /** * Get project summary via MCP */ async getProjectSummary(projectId = null) { return this.executeMcpTool('dss_get_project_summary', { project_id: projectId || this.currentProjectId, include_components: false }); } /** * Get project health via MCP */ async getProjectHealth(projectId = null) { return this.executeMcpTool('dss_get_project_health', { project_id: projectId || this.currentProjectId }); } /** * List project components via MCP */ async listComponents(projectId = null, filter = null) { const args = { project_id: projectId || this.currentProjectId }; if (filter) { args.filter_name = filter; } return this.executeMcpTool('dss_list_components', args); } /** * Get design tokens via MCP */ async getDesignTokens(projectId = null, category = null) { const args = { project_id: projectId || this.currentProjectId }; if (category) { args.token_category = category; } return this.executeMcpTool('dss_get_design_tokens', args); } } // Singleton instance const claudeService = new ClaudeService(); export default claudeService; export { ClaudeService };