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
270 lines
8.2 KiB
JavaScript
270 lines
8.2 KiB
JavaScript
/**
|
|
* ds-ai-chat-sidebar.js
|
|
* AI Chat Sidebar wrapper component
|
|
* Wraps ds-chat-panel with collapse/expand toggle and context binding
|
|
* MVP2: Right sidebar integrated with 3-column layout
|
|
*/
|
|
|
|
import contextStore from '../../stores/context-store.js';
|
|
import { useUserStore } from '../../stores/user-store.js';
|
|
|
|
class DSAiChatSidebar extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.userStore = useUserStore();
|
|
const preferences = this.userStore.getPreferences();
|
|
this.isCollapsed = preferences.chatCollapsedState !== false; // Default to collapsed
|
|
this.currentProject = null;
|
|
this.currentTeam = null;
|
|
this.currentPage = null;
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.render();
|
|
this.setupEventListeners();
|
|
this.initializeContextSubscriptions();
|
|
}
|
|
|
|
initializeContextSubscriptions() {
|
|
// Subscribe to context changes to update chat panel context
|
|
this.unsubscribe = contextStore.subscribe(({ state }) => {
|
|
this.currentProject = state.project;
|
|
this.currentTeam = state.team;
|
|
this.currentPage = state.page;
|
|
|
|
// Update chat panel with current context
|
|
const chatPanel = this.querySelector('ds-chat-panel');
|
|
if (chatPanel && chatPanel.setContext) {
|
|
chatPanel.setContext({
|
|
project: this.currentProject,
|
|
team: this.currentTeam,
|
|
page: this.currentPage
|
|
});
|
|
}
|
|
});
|
|
|
|
// Get initial context
|
|
const context = contextStore.getState();
|
|
if (context) {
|
|
this.currentProject = context.currentProject || context.project || null;
|
|
this.currentTeam = context.teamId || context.team || null;
|
|
this.currentPage = context.page || null;
|
|
}
|
|
}
|
|
|
|
render() {
|
|
const buttonClass = this.isCollapsed ? 'rotating' : '';
|
|
|
|
this.innerHTML = `
|
|
<div class="ai-chat-container" style="
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
background: var(--vscode-sidebar-background);
|
|
border-left: 1px solid var(--vscode-border);
|
|
" role="complementary" aria-label="AI Assistant sidebar">
|
|
<!-- Header with animated collapse button (shown when expanded) -->
|
|
<div style="
|
|
padding: 12px;
|
|
border-bottom: 1px solid var(--vscode-border);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
background: var(--vscode-bg);
|
|
${this.isCollapsed ? 'display: none;' : ''}
|
|
">
|
|
<div style="
|
|
font-weight: 500;
|
|
font-size: 13px;
|
|
color: var(--vscode-foreground);
|
|
">💬 AI Assistant</div>
|
|
<button
|
|
id="toggle-collapse-btn"
|
|
class="ai-chat-toggle-btn ${buttonClass}"
|
|
aria-label="Toggle chat sidebar"
|
|
aria-expanded="${!this.isCollapsed}"
|
|
style="
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--vscode-foreground);
|
|
cursor: pointer;
|
|
padding: 4px 8px;
|
|
font-size: 14px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
"
|
|
title="Toggle Chat Sidebar">
|
|
◀
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Chat content (collapsible) -->
|
|
<div class="chat-content" style="
|
|
flex: 1;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
${this.isCollapsed ? 'display: none;' : ''}
|
|
">
|
|
<!-- Chat panel will be hydrated here via component registry -->
|
|
<div id="chat-panel-container" style="
|
|
flex: 1;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
"></div>
|
|
</div>
|
|
|
|
<!-- Collapsed state indicator (shown when collapsed) -->
|
|
${this.isCollapsed ? `
|
|
<button
|
|
id="toggle-collapse-btn-collapsed"
|
|
class="ai-chat-toggle-btn ${buttonClass}"
|
|
aria-label="Expand chat sidebar"
|
|
aria-expanded="false"
|
|
style="
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--vscode-foreground);
|
|
cursor: pointer;
|
|
padding: 12px;
|
|
font-size: 16px;
|
|
text-align: center;
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
"
|
|
title="Expand Chat Sidebar">
|
|
💬
|
|
</button>
|
|
` : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
async setupEventListeners() {
|
|
// Handle both expanded and collapsed toggle buttons
|
|
const toggleBtn = this.querySelector('#toggle-collapse-btn');
|
|
const toggleBtnCollapsed = this.querySelector('#toggle-collapse-btn-collapsed');
|
|
|
|
const attachToggleListener = (btn) => {
|
|
if (btn) {
|
|
btn.addEventListener('click', () => {
|
|
this.toggleCollapse();
|
|
});
|
|
btn.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault();
|
|
btn.click();
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
attachToggleListener(toggleBtn);
|
|
attachToggleListener(toggleBtnCollapsed);
|
|
|
|
// Hydrate chat panel on first connection
|
|
const chatContainer = this.querySelector('#chat-panel-container');
|
|
if (chatContainer && chatContainer.children.length === 0) {
|
|
try {
|
|
// Import component registry to load chat panel
|
|
const { hydrateComponent } = await import('../../config/component-registry.js');
|
|
await hydrateComponent('ds-chat-panel', chatContainer);
|
|
console.log('[DSAiChatSidebar] Chat panel loaded');
|
|
|
|
// Set initial context on chat panel
|
|
const chatPanel = chatContainer.querySelector('ds-chat-panel');
|
|
if (chatPanel && chatPanel.setContext) {
|
|
chatPanel.setContext({
|
|
project: this.currentProject,
|
|
team: this.currentTeam,
|
|
page: this.currentPage
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('[DSAiChatSidebar] Failed to load chat panel:', error);
|
|
chatContainer.innerHTML = `
|
|
<div style="padding: 12px; color: var(--vscode-error); font-size: 12px;">
|
|
Failed to load chat panel
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
}
|
|
|
|
toggleCollapse() {
|
|
this.isCollapsed = !this.isCollapsed;
|
|
|
|
// Persist chat collapsed state to userStore
|
|
this.userStore.updatePreferences({ chatCollapsedState: this.isCollapsed });
|
|
|
|
// Update CSS class for smooth CSS transition (avoid re-render for better UX)
|
|
if (this.isCollapsed) {
|
|
this.classList.add('collapsed');
|
|
} else {
|
|
this.classList.remove('collapsed');
|
|
}
|
|
|
|
// Update button classes for rotation animation
|
|
const btns = this.querySelectorAll('.ai-chat-toggle-btn');
|
|
btns.forEach(btn => {
|
|
if (this.isCollapsed) {
|
|
btn.classList.add('rotating');
|
|
} else {
|
|
btn.classList.remove('rotating');
|
|
}
|
|
btn.setAttribute('aria-expanded', String(!this.isCollapsed));
|
|
});
|
|
|
|
// Update header and content visibility with inline styles
|
|
const header = this.querySelector('[style*="padding: 12px"]');
|
|
const content = this.querySelector('.chat-content');
|
|
|
|
if (header) {
|
|
if (this.isCollapsed) {
|
|
header.style.display = 'none';
|
|
} else {
|
|
header.style.display = 'flex';
|
|
}
|
|
}
|
|
|
|
if (content) {
|
|
if (this.isCollapsed) {
|
|
content.style.display = 'none';
|
|
} else {
|
|
content.style.display = 'flex';
|
|
}
|
|
}
|
|
|
|
// Toggle collapsed button visibility
|
|
let collapsedBtn = this.querySelector('#toggle-collapse-btn-collapsed');
|
|
if (!collapsedBtn && this.isCollapsed) {
|
|
// Create the collapsed button if needed
|
|
this.render();
|
|
this.setupEventListeners();
|
|
} else if (collapsedBtn && !this.isCollapsed) {
|
|
// Remove the collapsed button if needed
|
|
this.render();
|
|
this.setupEventListeners();
|
|
}
|
|
|
|
// Dispatch event for layout adjustment
|
|
this.dispatchEvent(new CustomEvent('chat-sidebar-toggled', {
|
|
detail: { isCollapsed: this.isCollapsed },
|
|
bubbles: true,
|
|
composed: true
|
|
}));
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
if (this.unsubscribe) {
|
|
this.unsubscribe();
|
|
}
|
|
}
|
|
}
|
|
|
|
customElements.define('ds-ai-chat-sidebar', DSAiChatSidebar);
|