Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
356 lines
11 KiB
JavaScript
356 lines
11 KiB
JavaScript
/**
|
||
* ds-chat-panel.js
|
||
* AI chatbot panel with team+project context
|
||
* MVP1: Integrates claude-service with ContextStore for team-aware assistance
|
||
*/
|
||
|
||
import claudeService from '../../services/claude-service.js';
|
||
import contextStore from '../../stores/context-store.js';
|
||
import { ComponentHelpers } from '../../utils/component-helpers.js';
|
||
|
||
class DSChatPanel extends HTMLElement {
|
||
constructor() {
|
||
super();
|
||
this.messages = [];
|
||
this.isLoading = false;
|
||
}
|
||
|
||
async connectedCallback() {
|
||
// Sync claude-service with ContextStore
|
||
const context = contextStore.getMCPContext();
|
||
if (context.project_id) {
|
||
claudeService.setProject(context.project_id);
|
||
}
|
||
|
||
// Subscribe to project changes
|
||
this.unsubscribe = contextStore.subscribeToKey('projectId', (newProjectId) => {
|
||
if (newProjectId) {
|
||
claudeService.setProject(newProjectId);
|
||
this.showSystemMessage(`Switched to project: ${newProjectId}`);
|
||
}
|
||
});
|
||
|
||
// Initialize MCP tools in background
|
||
this.initializeMcpTools();
|
||
|
||
this.render();
|
||
this.setupEventListeners();
|
||
this.loadHistory();
|
||
this.showWelcomeMessage();
|
||
}
|
||
|
||
/**
|
||
* Initialize MCP tools to enable AI tool awareness
|
||
*/
|
||
async initializeMcpTools() {
|
||
try {
|
||
console.log('[DSChatPanel] Initializing MCP tools...');
|
||
await claudeService.getMcpTools();
|
||
console.log('[DSChatPanel] MCP tools initialized successfully');
|
||
} catch (error) {
|
||
console.warn('[DSChatPanel] Failed to load MCP tools (non-blocking):', error.message);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Set context from parent component (ds-ai-chat-sidebar)
|
||
* @param {Object} context - Context object with project, team, page
|
||
*/
|
||
setContext(context) {
|
||
if (!context) return;
|
||
|
||
// Handle project context (could be object with id or string id)
|
||
if (context.project) {
|
||
const projectId = typeof context.project === 'object'
|
||
? context.project.id
|
||
: context.project;
|
||
|
||
if (projectId && projectId !== claudeService.currentProjectId) {
|
||
claudeService.setProject(projectId);
|
||
console.log('[DSChatPanel] Context updated via setContext:', { projectId });
|
||
}
|
||
}
|
||
|
||
// Store team and page context for reference
|
||
if (context.team) {
|
||
this.currentTeam = context.team;
|
||
}
|
||
if (context.page) {
|
||
this.currentPage = context.page;
|
||
}
|
||
}
|
||
|
||
disconnectedCallback() {
|
||
if (this.unsubscribe) {
|
||
this.unsubscribe();
|
||
}
|
||
}
|
||
|
||
setupEventListeners() {
|
||
const input = this.querySelector('#chat-input');
|
||
const sendBtn = this.querySelector('#chat-send-btn');
|
||
const clearBtn = this.querySelector('#chat-clear-btn');
|
||
const exportBtn = this.querySelector('#chat-export-btn');
|
||
|
||
if (sendBtn && input) {
|
||
sendBtn.addEventListener('click', () => this.sendMessage());
|
||
input.addEventListener('keypress', (e) => {
|
||
if (e.key === 'Enter' && !e.shiftKey) {
|
||
e.preventDefault();
|
||
this.sendMessage();
|
||
}
|
||
});
|
||
}
|
||
|
||
if (clearBtn) {
|
||
clearBtn.addEventListener('click', () => this.clearChat());
|
||
}
|
||
|
||
if (exportBtn) {
|
||
exportBtn.addEventListener('click', () => this.exportChat());
|
||
}
|
||
}
|
||
|
||
loadHistory() {
|
||
const history = claudeService.getHistory();
|
||
if (history && history.length > 0) {
|
||
this.messages = history;
|
||
this.renderMessages();
|
||
}
|
||
}
|
||
|
||
showWelcomeMessage() {
|
||
if (this.messages.length === 0) {
|
||
const context = contextStore.getMCPContext();
|
||
const teamId = context.team_id || 'ui';
|
||
|
||
const teamGreetings = {
|
||
ui: 'I can help with token extraction, component audits, Storybook comparisons, and quick wins analysis.',
|
||
ux: 'I can assist with Figma syncing, design tokens, asset management, and navigation flows.',
|
||
qa: 'I can help with visual regression testing, accessibility audits, and ESRE validation.',
|
||
admin: 'I can help manage projects, configure integrations, and oversee the design system.'
|
||
};
|
||
|
||
const greeting = teamGreetings[teamId] || teamGreetings.admin;
|
||
|
||
this.showSystemMessage(
|
||
`👋 Welcome to the ${teamId.toUpperCase()} team workspace!\n\n${greeting}\n\nI have access to all MCP tools for the active project.`
|
||
);
|
||
}
|
||
}
|
||
|
||
showSystemMessage(text) {
|
||
this.messages.push({
|
||
role: 'system',
|
||
content: text,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
this.renderMessages();
|
||
}
|
||
|
||
async sendMessage() {
|
||
const input = this.querySelector('#chat-input');
|
||
const message = input?.value.trim();
|
||
|
||
if (!message || this.isLoading) return;
|
||
|
||
// Check project context
|
||
const context = contextStore.getMCPContext();
|
||
if (!context.project_id) {
|
||
ComponentHelpers.showToast?.('Please select a project before chatting', 'error');
|
||
return;
|
||
}
|
||
|
||
// Add user message
|
||
this.messages.push({
|
||
role: 'user',
|
||
content: message,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
|
||
// Clear input
|
||
input.value = '';
|
||
|
||
// Render and scroll
|
||
this.renderMessages();
|
||
this.scrollToBottom();
|
||
|
||
// Show loading
|
||
this.isLoading = true;
|
||
this.updateLoadingState();
|
||
|
||
try {
|
||
// Add team context to the request
|
||
const teamContext = {
|
||
projectId: context.project_id,
|
||
teamId: context.team_id,
|
||
userId: context.user_id,
|
||
page: 'workdesk',
|
||
capabilities: context.capabilities
|
||
};
|
||
|
||
// Send to Claude with team+project context
|
||
const response = await claudeService.chat(message, teamContext);
|
||
|
||
// Add assistant response
|
||
this.messages.push({
|
||
role: 'assistant',
|
||
content: response,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
|
||
this.renderMessages();
|
||
this.scrollToBottom();
|
||
} catch (error) {
|
||
console.error('[DSChatPanel] Failed to send message:', error);
|
||
ComponentHelpers.showToast?.(`Chat error: ${error.message}`, 'error');
|
||
|
||
this.messages.push({
|
||
role: 'system',
|
||
content: `❌ Error: ${error.message}\n\nPlease try again or check your connection.`,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
|
||
this.renderMessages();
|
||
} finally {
|
||
this.isLoading = false;
|
||
this.updateLoadingState();
|
||
}
|
||
}
|
||
|
||
clearChat() {
|
||
if (!confirm('Clear all chat history?')) return;
|
||
|
||
claudeService.clearHistory();
|
||
this.messages = [];
|
||
this.renderMessages();
|
||
this.showWelcomeMessage();
|
||
ComponentHelpers.showToast?.('Chat history cleared', 'success');
|
||
}
|
||
|
||
exportChat() {
|
||
claudeService.exportConversation();
|
||
ComponentHelpers.showToast?.('Chat exported successfully', 'success');
|
||
}
|
||
|
||
updateLoadingState() {
|
||
const sendBtn = this.querySelector('#chat-send-btn');
|
||
const input = this.querySelector('#chat-input');
|
||
|
||
if (sendBtn) {
|
||
sendBtn.disabled = this.isLoading;
|
||
sendBtn.textContent = this.isLoading ? '⏳ Sending...' : '📤 Send';
|
||
}
|
||
|
||
if (input) {
|
||
input.disabled = this.isLoading;
|
||
}
|
||
}
|
||
|
||
scrollToBottom() {
|
||
const messagesContainer = this.querySelector('#chat-messages');
|
||
if (messagesContainer) {
|
||
setTimeout(() => {
|
||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||
}, 100);
|
||
}
|
||
}
|
||
|
||
renderMessages() {
|
||
const messagesContainer = this.querySelector('#chat-messages');
|
||
if (!messagesContainer) return;
|
||
|
||
if (this.messages.length === 0) {
|
||
messagesContainer.innerHTML = `
|
||
<div style="text-align: center; padding: 48px; color: var(--vscode-text-dim);">
|
||
<div style="font-size: 48px; margin-bottom: 16px;">💬</div>
|
||
<h3 style="font-size: 14px; font-weight: 600; margin-bottom: 8px;">No messages yet</h3>
|
||
<p style="font-size: 12px;">Start a conversation to get help with your design system.</p>
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
messagesContainer.innerHTML = this.messages.map(msg => {
|
||
const isUser = msg.role === 'user';
|
||
const isSystem = msg.role === 'system';
|
||
|
||
const alignStyle = isUser ? 'flex-end' : 'flex-start';
|
||
const bgColor = isUser
|
||
? 'var(--vscode-button-background)'
|
||
: isSystem
|
||
? 'rgba(255, 191, 0, 0.1)'
|
||
: 'var(--vscode-sidebar)';
|
||
const textColor = isUser ? 'var(--vscode-button-foreground)' : 'var(--vscode-text)';
|
||
const maxWidth = isSystem ? '100%' : '80%';
|
||
const icon = isUser ? '👤' : isSystem ? 'ℹ️' : '🤖';
|
||
|
||
return `
|
||
<div style="display: flex; justify-content: ${alignStyle}; margin-bottom: 16px;">
|
||
<div style="max-width: ${maxWidth}; background: ${bgColor}; padding: 12px; border-radius: 8px; color: ${textColor};">
|
||
<div style="font-size: 10px; color: var(--vscode-text-dim); margin-bottom: 6px; display: flex; align-items: center; gap: 6px;">
|
||
<span>${icon}</span>
|
||
<span>${isUser ? 'You' : isSystem ? 'System' : 'AI Assistant'}</span>
|
||
<span>•</span>
|
||
<span>${ComponentHelpers.formatRelativeTime(new Date(msg.timestamp))}</span>
|
||
</div>
|
||
<div style="font-size: 12px; white-space: pre-wrap; word-wrap: break-word;">
|
||
${ComponentHelpers.escapeHtml(msg.content)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
render() {
|
||
this.innerHTML = `
|
||
<div style="height: 100%; display: flex; flex-direction: column; background: var(--vscode-bg);">
|
||
<!-- Header -->
|
||
<div style="padding: 12px 16px; border-bottom: 1px solid var(--vscode-border); display: flex; justify-content: space-between; align-items: center;">
|
||
<div>
|
||
<h3 style="font-size: 12px; font-weight: 600; margin-bottom: 4px;">AI Assistant</h3>
|
||
<div style="font-size: 10px; color: var(--vscode-text-dim);">Team-contextualized help with MCP tools</div>
|
||
</div>
|
||
<div style="display: flex; gap: 8px;">
|
||
<button id="chat-export-btn" class="button" style="padding: 4px 8px; font-size: 10px;">
|
||
📥 Export
|
||
</button>
|
||
<button id="chat-clear-btn" class="button" style="padding: 4px 8px; font-size: 10px;">
|
||
🗑️ Clear
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Messages -->
|
||
<div id="chat-messages" style="flex: 1; overflow-y: auto; padding: 16px;">
|
||
${ComponentHelpers.renderLoading('Loading chat...')}
|
||
</div>
|
||
|
||
<!-- Input -->
|
||
<div style="padding: 16px; border-top: 1px solid var(--vscode-border);">
|
||
<div style="display: flex; gap: 8px;">
|
||
<textarea
|
||
id="chat-input"
|
||
placeholder="Ask me anything about your design system..."
|
||
class="input"
|
||
style="flex: 1; min-height: 60px; resize: vertical; font-size: 12px;"
|
||
rows="2"
|
||
></textarea>
|
||
<button id="chat-send-btn" class="button" style="padding: 8px 16px; font-size: 12px; height: 60px;">
|
||
📤 Send
|
||
</button>
|
||
</div>
|
||
<div style="font-size: 10px; color: var(--vscode-text-dim); margin-top: 8px;">
|
||
Press Enter to send • Shift+Enter for new line
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
customElements.define('ds-chat-panel', DSChatPanel);
|
||
|
||
export default DSChatPanel;
|