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:
269
admin-ui/js/components/layout/ds-ai-chat-sidebar.js
Normal file
269
admin-ui/js/components/layout/ds-ai-chat-sidebar.js
Normal file
@@ -0,0 +1,269 @@
|
||||
/**
|
||||
* 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);
|
||||
Reference in New Issue
Block a user