Initial commit: Clean DSS implementation
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
This commit is contained in:
355
admin-ui/js/components/tools/ds-chat-panel.js
Normal file
355
admin-ui/js/components/tools/ds-chat-panel.js
Normal file
@@ -0,0 +1,355 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user