import { useState, useRef, useEffect } from 'preact/hooks'; import { chatSidebarOpen } from '../../state/app'; import { currentProject } from '../../state/project'; import { activeTeam } from '../../state/team'; import { endpoints } from '../../api/client'; import { Button } from '../base/Button'; import { Badge } from '../base/Badge'; import { Spinner } from '../base/Spinner'; import type { ToolCall, ToolResult } from '../../api/types'; import './ChatSidebar.css'; interface Message { id: string; role: 'user' | 'assistant' | 'system'; content: string; timestamp: number; toolCalls?: ToolCall[]; toolResults?: ToolResult[]; } export function ChatSidebar() { const [messages, setMessages] = useState([ { id: '1', role: 'assistant', content: 'Hello! I\'m your DSS AI assistant. I can help you with:\n\n- Extracting design tokens from Figma\n- Generating component code\n- Analyzing token drift\n- Managing ESRE definitions\n- Project configuration\n\nHow can I help you today?', timestamp: Date.now() } ]); const [input, setInput] = useState(''); const [isLoading, setIsLoading] = useState(false); const [mcpTools, setMcpTools] = useState([]); const messagesEndRef = useRef(null); const inputRef = useRef(null); // Load available MCP tools useEffect(() => { endpoints.mcp.tools() .then(tools => setMcpTools(tools.map(t => t.name))) .catch(() => setMcpTools([])); }, []); // Auto-scroll to bottom on new messages useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); // Focus input on open useEffect(() => { if (chatSidebarOpen.value) { inputRef.current?.focus(); } }, []); const handleSubmit = async (e: Event) => { e.preventDefault(); if (!input.trim() || isLoading) return; const userMessage: Message = { id: `user-${Date.now()}`, role: 'user', content: input.trim(), timestamp: Date.now() }; setMessages(prev => [...prev, userMessage]); setInput(''); setIsLoading(true); try { // Build context from current state const context = { team: activeTeam.value, project: currentProject.value ? { id: currentProject.value.id, name: currentProject.value.name, path: currentProject.value.path } : null, currentView: window.location.hash }; const response = await endpoints.claude.chat( userMessage.content, currentProject.value?.id, context ); const assistantMessage: Message = { id: `assistant-${Date.now()}`, role: 'assistant', content: response.message?.content || 'Sorry, I couldn\'t process that request.', timestamp: Date.now(), toolCalls: response.tool_calls, toolResults: response.tool_results }; setMessages(prev => [...prev, assistantMessage]); } catch (error) { console.error('Chat error:', error); const errorMessage: Message = { id: `error-${Date.now()}`, role: 'assistant', content: 'Sorry, I encountered an error. Please make sure the DSS server is running.', timestamp: Date.now() }; setMessages(prev => [...prev, errorMessage]); } finally { setIsLoading(false); } }; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSubmit(e); } }; const handleClearChat = () => { setMessages([{ id: `welcome-${Date.now()}`, role: 'assistant', content: 'Chat cleared. How can I help you?', timestamp: Date.now() }]); }; const quickActions = [ { label: 'Extract tokens', prompt: 'Extract design tokens from Figma' }, { label: 'Find drift', prompt: 'Check for token drift in my project' }, { label: 'Quick wins', prompt: 'Find quick wins for design system adoption' }, { label: 'Help', prompt: 'What can you help me with?' } ]; const handleQuickAction = (prompt: string) => { setInput(prompt); inputRef.current?.focus(); }; return (