From ac8178829ce0bb9eae4409b21445b968595ab1f1 Mon Sep 17 00:00:00 2001 From: QTom <22166516+DaydreamCoding@users.noreply.github.com> Date: Mon, 8 Sep 2025 05:40:59 +0000 Subject: [PATCH] Add support for VS Code LM API, including model selection and error handling, and update related descriptions and configuration options. --- package-lock.json | 4 +- package.json | 6 +- src/providers/chatSidebarProvider.ts | 62 +- src/services/chatMessageService.ts | 11 +- src/services/customAgentService.ts | 110 +- src/webview/components/Chat/ChatInterface.css | 3806 +++++++---------- src/webview/components/Chat/ModelSelector.tsx | 52 +- 7 files changed, 1740 insertions(+), 2311 deletions(-) diff --git a/package-lock.json b/package-lock.json index 399f4734..42f44ef1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "superdesign", - "version": "0.0.10", + "version": "0.0.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "superdesign", - "version": "0.0.10", + "version": "0.0.11", "dependencies": { "@ai-sdk/anthropic": "^1.2.12", "@ai-sdk/google": "^1.2.19", diff --git a/package.json b/package.json index baaf7f45..e236cd59 100644 --- a/package.json +++ b/package.json @@ -158,10 +158,11 @@ "enum": [ "openai", "anthropic", - "openrouter" + "openrouter", + "vscodelm" ], "default": "anthropic", - "description": "AI model provider for custom agent (OpenAI, Anthropic, or OpenRouter)", + "description": "AI model provider for custom agent (OpenAI, Anthropic, OpenRouter, or VS Code LM)", "scope": "application" }, "superdesign.aiModel": { @@ -179,6 +180,7 @@ "watch:esbuild": "node esbuild.js --watch", "watch:tsc": "tsc --noEmit --watch --project tsconfig.json", "package": "npm run check-types && npm run lint && node esbuild.js --production", + "build-vsix": "npm run package && vsce package", "compile-tests": "tsc -p . --outDir out", "watch-tests": "tsc -p . -w --outDir out", "pretest": "npm run compile-tests && npm run compile && npm run lint", diff --git a/src/providers/chatSidebarProvider.ts b/src/providers/chatSidebarProvider.ts index 325996d6..28c0ca8e 100644 --- a/src/providers/chatSidebarProvider.ts +++ b/src/providers/chatSidebarProvider.ts @@ -71,6 +71,9 @@ export class ChatSidebarProvider implements vscode.WebviewViewProvider { case 'stopChat': await this.messageHandler.stopCurrentChat(webviewView.webview); break; + case 'getVsCodeLMModels': + await this.handleGetVsCodeLMModels(webviewView.webview); + break; case 'executeAction': // Execute command from error action buttons console.log('Executing action:', message.actionCommand, message.actionArgs); @@ -95,6 +98,28 @@ export class ChatSidebarProvider implements vscode.WebviewViewProvider { ); } + private async handleGetVsCodeLMModels(webview: vscode.Webview) { + try { + const lm: any = (vscode as any).lm; + if (!lm || typeof lm.selectChatModels !== 'function') { + throw new Error('VS Code LM API 不可用'); + } + const models: any[] = await lm.selectChatModels({} as any); + const items = (models || []).map((m: any) => { + const vendor = m.vendor || 'unknown'; + const family = m.family || ''; + const name = m.name || ''; + const idPart = family || name || 'model'; + const id = `vscodelm/${vendor}/${idPart}`; + const label = `${vendor}${family ? ' / ' + family : (name ? ' / ' + name : '')}`; + return { id, label, vendor, family, name }; + }); + webview.postMessage({ command: 'vsCodeLmModels', models: items }); + } catch (error) { + webview.postMessage({ command: 'vsCodeLmModels', models: [], error: String(error) }); + } + } + private async handleGetCurrentProvider(webview: vscode.Webview) { const config = vscode.workspace.getConfiguration('superdesign'); const currentProvider = config.get('aiModelProvider', 'anthropic'); @@ -106,6 +131,9 @@ export class ChatSidebarProvider implements vscode.WebviewViewProvider { case 'openai': defaultModel = 'gpt-4o'; break; + case 'vscodelm': + defaultModel = 'vscodelm/auto'; + break; case 'openrouter': defaultModel = 'anthropic/claude-3-7-sonnet-20250219'; break; @@ -132,7 +160,12 @@ export class ChatSidebarProvider implements vscode.WebviewViewProvider { let configureCommand: string; let displayName: string; - if (model.includes('/')) { + if (model === 'vscodelm' || model.startsWith('vscodelm')) { + provider = 'vscodelm'; + apiKeyKey = ''; + configureCommand = ''; + displayName = `VS Code LM API (${this.getModelDisplayName(model)})`; + } else if (model.includes('/')) { // OpenRouter model (contains slash like "openai/gpt-4o") provider = 'openrouter'; apiKeyKey = 'openrouterApiKey'; @@ -154,18 +187,19 @@ export class ChatSidebarProvider implements vscode.WebviewViewProvider { await config.update('aiModelProvider', provider, vscode.ConfigurationTarget.Global); await config.update('aiModel', model, vscode.ConfigurationTarget.Global); - // Check if the API key is configured for the selected provider - const apiKey = config.get(apiKeyKey); - - if (!apiKey) { - const result = await vscode.window.showWarningMessage( - `${displayName} selected, but API key is not configured. Would you like to configure it now?`, - 'Configure API Key', - 'Later' - ); - - if (result === 'Configure API Key') { - await vscode.commands.executeCommand(configureCommand); + // Check if the API key is configured for the selected provider (skip for vscodelm) + if (provider !== 'vscodelm') { + const apiKey = apiKeyKey ? config.get(apiKeyKey) : undefined; + if (!apiKey) { + const result = await vscode.window.showWarningMessage( + `${displayName} selected, but API key is not configured. Would you like to configure it now?`, + 'Configure API Key', + 'Later' + ); + + if (result === 'Configure API Key' && configureCommand) { + await vscode.commands.executeCommand(configureCommand); + } } } @@ -183,6 +217,8 @@ export class ChatSidebarProvider implements vscode.WebviewViewProvider { private getModelDisplayName(model: string): string { const modelNames: { [key: string]: string } = { + // VS Code LM API + 'vscodelm/auto': 'VS Code LM (Auto)', // OpenAI models 'gpt-4.1': 'GPT-4.1', 'gpt-4.1-mini': 'GPT-4.1 Mini', diff --git a/src/services/chatMessageService.ts b/src/services/chatMessageService.ts index 8f9f605a..bacf5344 100644 --- a/src/services/chatMessageService.ts +++ b/src/services/chatMessageService.ts @@ -138,7 +138,9 @@ export class ChatMessageService { let configureCommand = 'superdesign.configureApiKey'; if (specificModel) { - if (specificModel.includes('/')) { + if (specificModel === 'vscodelm' || specificModel.startsWith('vscodelm')) { + effectiveProvider = 'vscodelm'; + } else if (specificModel.includes('/')) { effectiveProvider = 'openrouter'; } else if (specificModel.startsWith('claude-')) { effectiveProvider = 'anthropic'; @@ -148,6 +150,13 @@ export class ChatMessageService { } switch (effectiveProvider) { + case 'vscodelm': + // VS Code LM does not require an API Key. Show an English error message and return. + webview.postMessage({ + command: 'chatError', + error: 'VS Code LM is unavailable (no accessible language model or permission detected). Please ensure you have installed and signed in to the relevant provider extension (e.g., GitHub Copilot) in VS Code.' + }); + return; case 'openrouter': providerName = 'OpenRouter'; configureCommand = 'superdesign.configureOpenRouterApiKey'; diff --git a/src/services/customAgentService.ts b/src/services/customAgentService.ts index b06b7c0e..68042e2f 100644 --- a/src/services/customAgentService.ts +++ b/src/services/customAgentService.ts @@ -90,7 +90,9 @@ export class CustomAgentService implements AgentService { // Determine provider from model name if specific model is set let effectiveProvider = provider; if (specificModel) { - if (specificModel.includes('/')) { + if (specificModel === 'vscodelm' || specificModel.startsWith('vscodelm')) { + effectiveProvider = 'vscodelm'; + } else if (specificModel.includes('/')) { effectiveProvider = 'openrouter'; } else if (specificModel.startsWith('claude-')) { effectiveProvider = 'anthropic'; @@ -100,6 +102,10 @@ export class CustomAgentService implements AgentService { } switch (effectiveProvider) { + case 'vscodelm': + // For VS Code LM, we do not return an AI SDK model; handled in query() + // Return a sentinel value to indicate vscodelm flow + return { _provider: 'vscodelm', _model: specificModel || 'vscodelm/auto' } as any; case 'openrouter': const openrouterKey = config.get('openrouterApiKey'); if (!openrouterKey) { @@ -621,14 +627,31 @@ I've created the html design, please reveiw and let me know if you need any chan generateTheme: createThemeTool(executionContext) }; + // Prepare model/provider + const selectedModel = this.getModel(); + + // VS Code LM API branch + if ((selectedModel as any)?._provider === 'vscodelm') { + this.outputChannel.appendLine('Using VS Code LM API provider flow'); + await this.queryViaVsCodeLM( + usingConversationHistory ? undefined : prompt, + usingConversationHistory ? conversationHistory : undefined, + abortController, + onMessage + ); + this.outputChannel.appendLine(`Query completed successfully. Total messages: ${responseMessages.length}`); + this.outputChannel.appendLine(`Complete response: "${messageBuffer}"`); + return responseMessages; + } + // Prepare AI SDK input based on available data const streamTextConfig: any = { - model: this.getModel(), + model: selectedModel, system: this.getSystemPrompt(), tools: tools, toolCallStreaming: true, - maxSteps: 10, // Enable multi-step reasoning with tools - maxTokens: 8192 // Increase token limit to prevent truncation + maxSteps: 10, + maxTokens: 8192 }; if (usingConversationHistory) { @@ -868,6 +891,85 @@ I've created the html design, please reveiw and let me know if you need any chan } } + private async queryViaVsCodeLM( + prompt?: string, + conversationHistory?: CoreMessage[], + abortController?: AbortController, + onMessage?: (message: any) => void + ): Promise { + // Minimal integration per VSCodeLMAPI.md guidance + // Note: VS Code LM API is experimental and available only in VS Code runtime + try { + const selector: any = {} as any; // use default selection + const models = await (vscode as any).lm.selectChatModels(selector); + if (!models || models.length === 0) { + throw new Error('No available VS Code language models found (please ensure you have installed and signed in to a provider extension).'); + } + const client = models[0]; + + // Build messages: put system as Assistant (compatible approach), then history/prompt + const messages: any[] = []; + const systemPrompt = this.getSystemPrompt(); + messages.push((vscode as any).LanguageModelChatMessage.Assistant(systemPrompt)); + + if (conversationHistory && conversationHistory.length > 0) { + for (const msg of conversationHistory) { + if (msg.role === 'user') { + if (typeof msg.content === 'string') { + messages.push((vscode as any).LanguageModelChatMessage.User(msg.content)); + } else if (Array.isArray(msg.content)) { + const textParts = msg.content + .filter((p: any) => p.type === 'text' && typeof p.text === 'string') + .map((p: any) => new (vscode as any).LanguageModelTextPart(p.text)); + if (textParts.length > 0) { + messages.push((vscode as any).LanguageModelChatMessage.User(textParts)); + } + } + } else if (msg.role === 'assistant') { + if (typeof msg.content === 'string') { + messages.push((vscode as any).LanguageModelChatMessage.Assistant(msg.content)); + } else if (Array.isArray(msg.content)) { + const textParts = msg.content + .filter((p: any) => p.type === 'text' && typeof p.text === 'string') + .map((p: any) => new (vscode as any).LanguageModelTextPart(p.text)); + if (textParts.length > 0) { + messages.push((vscode as any).LanguageModelChatMessage.Assistant(textParts)); + } + } + } + } + } else if (prompt) { + messages.push((vscode as any).LanguageModelChatMessage.User(prompt)); + } + + const token = (abortController as any)?.token; + const response = await client.sendRequest(messages, { justification: 'Superdesign wants to use VS Code LM model' }, token); + + for await (const chunk of response.stream) { + if (chunk instanceof (vscode as any).LanguageModelTextPart) { + const text = chunk.value; + const textMessage: CoreMessage = { role: 'assistant', content: text }; + onMessage?.(textMessage); + } else if (chunk instanceof (vscode as any).LanguageModelToolCallPart) { + const toolCallMessage: CoreMessage = { + role: 'assistant', + content: [{ + type: 'tool-call', + toolCallId: chunk.callId, + toolName: chunk.name, + args: chunk.input || {} + }] + } as any; + onMessage?.(toolCallMessage); + } + } + + } catch (err) { + this.outputChannel.appendLine(`VS Code LM flow error: ${err}`); + throw err; + } + } + get isReady(): boolean { return this.isInitialized; } diff --git a/src/webview/components/Chat/ChatInterface.css b/src/webview/components/Chat/ChatInterface.css index 47c27d93..1f0b9c87 100644 --- a/src/webview/components/Chat/ChatInterface.css +++ b/src/webview/components/Chat/ChatInterface.css @@ -1,2285 +1,1521 @@ -/* Base chat interface styles */ -.chat-interface { - display: flex; - flex-direction: column; - height: 100vh; - width: 100%; - background: var(--vscode-sideBar-background); - color: var(--vscode-sideBar-foreground); - position: relative; - overflow: hidden; -} - -.chat-interface--panel { - max-width: 100%; - background: var(--vscode-panel-background); - color: var(--vscode-panel-foreground); -} - -.chat-interface--sidebar { - font-size: 12px; - background: var(--vscode-sideBar-background); - color: var(--vscode-sideBar-foreground); -} - -/* Header styles (panel only) */ -.chat-header { - padding: 0; - border-bottom: 1px solid var(--vscode-panel-border); - margin-bottom: 0; - position: relative; -} - -.chat-header h2 { - margin: 0 0 8px 0; - font-size: 18px; - font-weight: 600; -} - -.chat-header p { - margin: 0; - color: var(--vscode-descriptionForeground); - font-size: 14px; -} - -/* New Conversation Button */ -.new-conversation-btn { - position: absolute; - top: 0; - right: 0; - background: transparent; - border: none; - color: var(--vscode-descriptionForeground); - cursor: pointer; - padding: 8px; - border-radius: 4px; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s ease; - opacity: 0.7; -} - -.new-conversation-btn:hover { - background: var(--vscode-list-hoverBackground); - color: var(--vscode-foreground); - opacity: 1; -} - -.new-conversation-btn:active { - background: var(--vscode-list-activeSelectionBackground); - transform: scale(0.95); -} - -.new-conversation-btn:disabled { - opacity: 0.4; - cursor: not-allowed; - background: transparent; - transform: none; -} - -/* Chat container */ -.chat-container { - flex: 1; - display: flex; - flex-direction: column; - overflow: hidden; - position: relative; -} - -.chat-history { - flex: 1; - overflow-y: auto; - padding: 0 12px 8px 12px; - margin-bottom: 0; - scroll-behavior: smooth; -} - -.chat-history::-webkit-scrollbar { - width: 6px; -} - -.chat-history::-webkit-scrollbar-track { - background: transparent; -} - -.chat-history::-webkit-scrollbar-thumb { - background: var(--vscode-scrollbarSlider-background); - border-radius: 3px; -} - -.chat-history::-webkit-scrollbar-thumb:hover { - background: var(--vscode-scrollbarSlider-hoverBackground); -} - -/* Chat messages */ -.chat-message { - margin-bottom: 16px; - padding: 14px 16px; - border-radius: 12px; - line-height: 1.5; - border: 1px solid transparent; - transition: all 0.2s ease; - position: relative; -} - -.chat-message--panel { - padding: 18px 20px; - margin-bottom: 20px; - font-size: 14px; - border-radius: 16px; -} - -.chat-message--sidebar { - padding: 10px 12px; - margin-bottom: 12px; - font-size: 12px; - border-radius: 10px; -} - -/* User message specific overrides for compact styling */ -.chat-message--user.chat-message--panel { - padding: 12px 14px; - margin-bottom: 16px; - border-radius: 8px; -} - -.chat-message--user.chat-message--sidebar { - padding: 8px 10px; - margin-bottom: 10px; - border-radius: 6px; -} - -.chat-message--user { - background: var(--vscode-input-background); - border: 1px solid var(--vscode-input-border); - margin-left: 0; - margin-right: 0; - max-width: 100%; - padding: 10px 12px; - border-radius: 8px; - margin-bottom: 12px; -} - -/* SDK User messages (from Claude Code SDK) */ -.chat-message--user-sdk { - background: var(--vscode-input-background); - border: 1px solid var(--vscode-textLink-foreground); - border-left: 3px solid var(--vscode-textLink-foreground); - margin-left: 0; - margin-right: 0; - max-width: 100%; - padding: 10px 12px; - border-radius: 8px; - margin-bottom: 12px; - opacity: 0.9; -} - -.chat-message--assistant { - background: transparent; - border: none; - border-left: none; - margin-left: 0; - margin-right: 0; - max-width: 100%; - padding-left: 0; - border-radius: 0; -} - -/* Result messages */ -.chat-message--result { - background: var(--vscode-editorWidget-background); - border: 1px solid var(--vscode-editorWidget-border); - border-left: 3px solid var(--vscode-charts-green); - margin-left: 0; - margin-right: 0; - max-width: 100%; - padding: 10px 12px; - border-radius: 8px; - margin-bottom: 12px; - font-family: var(--vscode-editor-font-family); - font-size: 13px; -} - -/* Error result messages */ -.chat-message--result-error { - background: var(--vscode-inputValidation-errorBackground); - border: 1px solid var(--vscode-inputValidation-errorBorder); - border-left: 3px solid var(--vscode-errorForeground); - margin-left: 0; - margin-right: 0; - max-width: 100%; - padding: 10px 12px; - border-radius: 8px; - margin-bottom: 12px; - font-family: var(--vscode-editor-font-family); - font-size: 13px; -} - -.chat-message__header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - margin-bottom: 8px; - font-weight: 600; - opacity: 0.8; - font-size: 11px; -} - -.chat-message--sidebar .chat-message__header { - margin-bottom: 6px; - font-size: 10px; -} - -.chat-message__label { - font-size: 11px; - color: var(--vscode-descriptionForeground); - font-weight: 500; -} - -/* Metadata display */ -.chat-message__metadata { - display: flex; - align-items: center; - gap: 8px; - font-size: 10px; - color: var(--vscode-descriptionForeground); - opacity: 0.7; -} - -.metadata-item { - background: var(--vscode-badge-background); - color: var(--vscode-badge-foreground); - padding: 2px 4px; - border-radius: 3px; - font-size: 9px; - font-weight: 500; -} - -.chat-message__content { - white-space: pre-line; - word-wrap: break-word; - line-height: 1.3; - color: var(--vscode-editor-foreground); -} - -/* Override white-space for markdown content to prevent excessive spacing */ -.chat-message__content .markdown-content { - white-space: normal; -} - -.chat-message--user .chat-message__content { - line-height: 1.3; - font-size: 13px; -} - -.chat-message--user:hover { - border-color: var(--vscode-input-border); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); -} - -/* Typing indicator */ -.typing .typing-indicator { - animation: typing 1.4s infinite ease-in-out; - color: var(--vscode-descriptionForeground); - font-style: italic; -} - -.typing .typing-indicator::before { - content: ''; - display: inline-block; - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--vscode-textLink-foreground); - margin-right: 8px; - animation: pulse 1.5s infinite ease-in-out; -} - -@keyframes typing { - 0%, 80%, 100% { opacity: 0.3; } - 40% { opacity: 1; } -} - -@keyframes pulse { - 0%, 100% { opacity: 0.4; transform: scale(0.8); } - 50% { opacity: 1; transform: scale(1.1); } -} - -/* Generating content layout */ -.generating-content { - display: flex; - align-items: center; - justify-content: space-between; - gap: 6px; - line-height: 1.3; - margin-top: 4px; - padding: 4px 0; - margin-left: 0; - margin-right: 0; - padding-left: 0; - padding-right: 0; -} - -.generating-text { - color: var(--vscode-descriptionForeground); - font-size: 11px; - flex: 1; - animation: generating-pulse 1.8s ease-in-out infinite; - opacity: 0.8; -} - -.generating-text::after { - content: ''; - animation: generating-dots 1.5s ease-in-out infinite; - margin-left: 2px; -} - -@keyframes generating-pulse { - 0%, 100% { opacity: 0.6; } - 50% { opacity: 1; } -} - -@keyframes generating-dots { - 0%, 20% { content: ''; } - 25%, 45% { content: '.'; } - 50%, 70% { content: '..'; } - 75%, 100% { content: '...'; } -} - -.generating-stop-btn { - background: transparent; - border: 1px solid var(--vscode-input-border); - color: var(--vscode-descriptionForeground); - font-size: 10px; - cursor: pointer; - padding: 2px 6px; - font-family: inherit; - transition: all 0.2s ease; - border-radius: 4px; - font-weight: normal; -} - -.generating-stop-btn:hover { - background: var(--vscode-list-hoverBackground); - color: var(--vscode-foreground); - border-color: var(--vscode-focusBorder); -} - -/* Responsive generating content for different layouts - simplified */ -.chat-message--panel .generating-content, -.chat-message--sidebar .generating-content { - margin-left: 0; - margin-right: 0; - padding-left: 0; - padding-right: 0; -} - - - -/* Chat placeholder */ -.chat-placeholder { - text-align: center; - color: var(--vscode-descriptionForeground); - padding: 0 20px 20px 20px; -} - -.chat-placeholder--panel { - padding: 0 20px 32px 20px; - font-size: 14px; -} - -.chat-placeholder--sidebar { - padding: 0 8px 16px 8px; - font-size: 11px; -} - -.chat-placeholder__features { - margin-top: 16px; -} - -.chat-placeholder__features ul { - text-align: left; - max-width: 300px; - margin: 12px auto; -} - -.chat-placeholder__features li { - margin: 8px 0; -} - -/* Empty state styles */ -.chat-placeholder__content { - margin-top: 20px; - max-width: 500px; - margin-left: auto; - margin-right: auto; -} - -.empty-state-message { - display: flex; - flex-direction: column; - gap: 16px; - align-items: center; - text-align: center; -} - -.empty-state-message p { - margin: 0; - line-height: 1.5; - color: var(--vscode-foreground); -} - -.empty-state-message strong { - color: var(--vscode-textLink-foreground); - font-weight: 600; -} - -.empty-state-message kbd { - background: var(--vscode-keybindingLabel-background); - border: 1px solid var(--vscode-keybindingLabel-border); - border-bottom: 2px solid var(--vscode-keybindingLabel-bottomBorder); - color: var(--vscode-keybindingLabel-foreground); - padding: 2px 6px; - border-radius: 3px; - font-size: 11px; - font-family: var(--vscode-editor-font-family); - white-space: nowrap; -} - -.empty-state-message code { - background: var(--vscode-textCodeBlock-background); - color: var(--vscode-textPreformat-foreground); - padding: 2px 4px; - border-radius: 3px; - font-size: 12px; - font-family: var(--vscode-editor-font-family); -} - -.empty-state-divider { - color: var(--vscode-descriptionForeground); - font-weight: 500; - font-size: 13px; - margin: 8px 0; - position: relative; -} - -.empty-state-divider::before, -.empty-state-divider::after { - content: ''; - position: absolute; - top: 50%; - width: 40px; - height: 1px; - background: var(--vscode-separator-foreground); - opacity: 0.3; -} - -.empty-state-divider::before { - right: calc(100% + 10px); -} - -.empty-state-divider::after { - left: calc(100% + 10px); -} - -.empty-state-message em { - color: var(--vscode-descriptionForeground); - font-style: italic; -} - -/* Sidebar layout adjustments */ -.chat-placeholder--sidebar .chat-placeholder__content { - margin-top: 16px; - max-width: 100%; - padding: 0 8px; -} - -.chat-placeholder--sidebar .empty-state-message { - gap: 12px; -} - -.chat-placeholder--sidebar .empty-state-message p { - font-size: 12px; - line-height: 1.4; -} - -.chat-placeholder--sidebar .empty-state-message kbd { - font-size: 10px; - padding: 1px 4px; -} - -.chat-placeholder--sidebar .empty-state-message code { - font-size: 10px; - padding: 1px 3px; -} - -.chat-placeholder--sidebar .empty-state-divider { - font-size: 11px; -} - -.chat-placeholder--sidebar .empty-state-divider::before, -.chat-placeholder--sidebar .empty-state-divider::after { - width: 20px; -} - -/* Responsive adjustments for empty state */ -@media (max-width: 600px) { - .chat-placeholder__content { - max-width: 90%; - margin-top: 16px; - } - - .empty-state-message { - gap: 12px; - } - - .empty-state-message p { - font-size: 13px; - } - - .empty-state-divider::before, - .empty-state-divider::after { - width: 20px; - } -} - -/* Input container - Cursor style - REMOVED */ - -/* Add Context Button */ -.add-context-btn { - display: flex; - align-items: center; - justify-content: flex-start; - gap: 4px; - background: transparent; - color: var(--vscode-foreground); - border: 1px solid var(--vscode-input-border); - border-radius: 6px; - padding: 4px 4px; - margin: 6px 6px 6px 6px; - cursor: pointer; - font-size: 11px; - font-family: inherit; - transition: all 0.2s ease; - width: fit-content; - line-height: 1; -} - -.add-context-btn:hover:not(:disabled) { - background: var(--vscode-button-hoverBackground); - border-color: var(--vscode-focusBorder); -} - -.add-context-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.add-context-icon { - font-weight: normal; - font-size: 11px; - line-height: 1; - display: inline-block; -} - -/* Message context display in user bubbles */ -.message-context-display { - display: inline-flex; - align-items: center; - justify-content: flex-start; - gap: 4px; - background: transparent; - color: var(--vscode-foreground); - border: 1px solid var(--vscode-input-border); - border-radius: 4px; - padding: 4px 8px; - margin-bottom: 8px; - font-size: 11px; - font-family: inherit; - width: fit-content; - line-height: 1; -} - -.message-context-display .context-icon { - font-weight: normal; - font-size: 11px; - line-height: 1; - display: inline-block; -} - -.message-context-display .context-text { - font-size: 11px; - line-height: 1; - color: var(--vscode-foreground); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 200px; -} - -.message-text { - margin: 0; -} - -/* Main Input Wrapper */ -.chat-input-wrapper { - background: var(--vscode-input-background); - border: 1px solid var(--vscode-input-border); - border-radius: 12px; - overflow: hidden; - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); - transition: all 0.2s ease; - margin: 6px 6px 12px 6px; - margin-top: auto; - flex-shrink: 0; -} - -/* Remove focus highlight from chat input wrapper */ - -/* Input Controls */ -.input-controls { - display: flex; - align-items: center; - justify-content: space-between; - padding: 4px 8px; - gap: 6px; -} - -.selectors-group { - display: flex; - align-items: center; - gap: 4px; - flex-shrink: 1; - min-width: 0; -} - -.selector-wrapper { - position: relative; - display: inline-block; -} - -/* Selector icon styles removed - handled by ModelSelector component */ - -.agent-selector { - background: transparent; - color: var(--vscode-foreground); - border: none; - outline: none; - font-size: 11px; - font-family: inherit; - cursor: pointer; - padding: 2px 8px 2px 18px; - border-radius: 4px; - transition: background-color 0.2s ease; - appearance: none; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 12 4-4 4 4'/%3E%3C/svg%3E"); - background-position: right 6px center; - background-repeat: no-repeat; - background-size: 12px; - min-width: 120px; - white-space: nowrap; - font-weight: 500; -} - -.agent-selector:hover { - background: var(--vscode-list-hoverBackground); -} - -/* Chat input */ -.chat-input { - padding: 6px 8px 4px 8px; - position: relative; -} - -.message-input { - width: 100%; - padding: 0; - background: transparent; - color: var(--vscode-input-foreground); - border: none; - outline: none; - font-size: 14px; - font-family: inherit; - resize: none; - min-height: 24px; - max-height: 120px; - line-height: 1.5; - overflow-y: auto; - box-shadow: none; - transition: all 0.2s ease; -} - -.message-input:focus { - outline: none; - box-shadow: none; - border: none; -} - -.message-input::selection { - background: var(--vscode-editor-selectionBackground); -} - -.message-input::placeholder { - color: var(--vscode-input-placeholderForeground); - font-size: 14px; -} - -.chat-interface--sidebar .message-input { - font-size: 12px; -} - -.chat-interface--sidebar .message-input::placeholder { - font-size: 12px; -} - -/* Input Actions */ -.input-actions { - display: flex; - align-items: center; - gap: 4px; - flex-shrink: 0; -} - -.attach-btn, -.send-btn { - width: 24px; - height: 24px; - border: none; - border-radius: 50%; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s ease; - flex-shrink: 0; - position: relative; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); -} - -.attach-btn { - background: transparent; - color: var(--vscode-descriptionForeground); - box-shadow: none; - opacity: 0.6; - border-radius: 0; -} - -.attach-btn:hover:not(:disabled) { - background: var(--vscode-list-hoverBackground); - color: var(--vscode-foreground); - opacity: 1; - border-radius: 50%; -} - -.send-btn { - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); -} - -.send-btn:hover:not(:disabled) { - background: var(--vscode-button-hoverBackground); - transform: scale(1.05); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); -} - -.send-btn:disabled, -.attach-btn:disabled { - opacity: 0.5; - cursor: not-allowed; - transform: none; - box-shadow: none; -} - -.stop-btn { - background: var(--vscode-input-background) !important; - color: var(--vscode-foreground) !important; - border: 1px solid var(--vscode-input-border) !important; -} - -.stop-btn:hover:not(:disabled) { - background: var(--vscode-list-hoverBackground) !important; - border-color: var(--vscode-focusBorder) !important; - transform: scale(1.05); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); -} - -.attach-btn svg { - width: 12px; - height: 12px; -} - -.send-btn svg { - width: 14px; - height: 14px; -} - -.chat-interface--sidebar .attach-btn, -.chat-interface--sidebar .send-btn { - width: 28px; - height: 28px; - font-size: 12px; -} - -/* Responsive adjustments */ -@media (max-width: 480px) { - .input-controls { - padding: 4px 6px; - gap: 4px; - } - - .selectors-group { - gap: 2px; - flex-shrink: 1; - min-width: 0; - } - - .agent-selector { - min-width: 80px; - font-size: 10px; - padding: 2px 6px 2px 16px; - background-size: 10px; - background-position: right 4px center; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 12 4-4 4 4'/%3E%3C/svg%3E"); - } - - /* Selector icon responsive styles removed - handled by ModelSelector component */ - - .attach-btn, - .send-btn { - width: 20px; - height: 20px; - flex-shrink: 0; - } - - .attach-btn svg { - width: 10px; - height: 10px; - } - - .send-btn svg { - width: 12px; - height: 12px; - } - - .chat-input { - padding: 6px 8px 4px 8px; - } - - .message-input { - font-size: 13px; - } -} - -/* Streaming cursor animation - simplified */ -.streaming-cursor { - color: var(--vscode-descriptionForeground); - font-weight: normal; - animation: blink 1.2s ease-in-out infinite; - margin-left: 1px; -} - -@keyframes blink { - 0%, 50% { opacity: 1; } - 51%, 100% { opacity: 0.3; } -} - -/* Tool message styles - compact */ -.tool-message { - margin-bottom: 4px; - padding: 6px 8px; - border: 1px solid var(--vscode-input-border); - border-radius: 6px; - background: var(--vscode-input-background); - line-height: 1.2; - transition: all 0.2s ease; - position: relative; -} - -.tool-message:hover { - border-color: var(--vscode-focusBorder); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); -} - -.tool-message--panel { - margin-bottom: 6px; - padding: 8px 10px; - border-radius: 8px; - font-size: 12px; -} - -.tool-message--sidebar { - margin-bottom: 3px; - padding: 6px 8px; - border-radius: 6px; - font-size: 11px; -} - -.tool-message--success { - border-left: 3px solid var(--vscode-charts-green); -} - -.tool-message--error { - background: var(--vscode-inputValidation-errorBackground); - border: 1px solid var(--vscode-inputValidation-errorBorder); - border-left: 3px solid var(--vscode-errorForeground); -} - -.tool-message--loading { - border-left: 3px solid var(--vscode-progressBar-background); -} - -.tool-message--complete { - border-left: 3px solid var(--vscode-charts-green); -} - -.tool-message__header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - margin-bottom: 0; - font-weight: 600; - opacity: 0.8; - font-size: 11px; -} - -.chat-interface--sidebar .tool-message__header { - margin-bottom: 0; - font-size: 10px; -} - -/* Only add bottom margin when details are showing */ -.tool-message__header:has(+ .tool-message__details) { - margin-bottom: 8px; -} - -.chat-interface--sidebar .tool-message__header:has(+ .tool-message__details) { - margin-bottom: 6px; -} - -.tool-message__main { - display: flex; - align-items: center; - gap: 6px; - flex: 1; - min-width: 0; -} - -.tool-icon { - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - width: 14px; - height: 14px; - color: var(--vscode-descriptionForeground); -} - -/* Tool icon state-specific colors */ -.tool-message--success .tool-icon { - color: var(--vscode-charts-green); -} - -.tool-message--complete .tool-icon { - color: var(--vscode-charts-green); -} - -.tool-message--error .tool-icon { - color: var(--vscode-errorForeground); -} - -.tool-info { - display: flex; - flex-direction: column; - gap: 0; - flex: 1; - min-width: 0; -} - -.tool-name { - font-weight: 500; - color: var(--vscode-foreground); - flex-shrink: 0; - font-size: 11px; - line-height: 1.2; -} - -.tool-description { - color: var(--vscode-descriptionForeground); - font-size: 10px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - line-height: 1.2; -} - -.tool-actions { - display: flex; - align-items: center; - gap: 2px; - flex-shrink: 0; -} - -.tool-status { - display: flex; - align-items: center; - justify-content: center; - width: 12px; - height: 12px; - flex-shrink: 0; - color: var(--vscode-charts-green); -} - -.tool-status--success { - color: var(--vscode-charts-green); -} - -.tool-status--complete { - color: var(--vscode-charts-green); -} - -.tool-status--error { - color: var(--vscode-errorForeground); -} - -.tool-expand-btn { - background: transparent; - border: none; - color: var(--vscode-descriptionForeground); - cursor: pointer; - padding: 1px; - border-radius: 2px; - transition: all 0.2s ease; - flex-shrink: 0; - width: 14px; - height: 14px; - display: flex; - align-items: center; - justify-content: center; -} - -.tool-expand-btn:hover { - background: var(--vscode-toolbar-hoverBackground); - color: var(--vscode-foreground); -} - -.tool-expand-btn.expanded { - transform: rotate(45deg); - color: var(--vscode-foreground); -} - -.tool-message__details { - margin-top: 6px; - padding-top: 6px; - border-top: 1px solid var(--vscode-input-border); -} - -.tool-detail { - margin-bottom: 8px; -} - -.tool-detail:last-child { - margin-bottom: 0; -} - -.tool-detail__label { - display: block; - font-weight: 600; - color: var(--vscode-descriptionForeground); - font-size: 9px; - margin-bottom: 2px; - text-transform: uppercase; - letter-spacing: 0.3px; - line-height: 1.2; -} - -.tool-detail__value { - display: block; - color: var(--vscode-editor-foreground); - font-size: 10px; - line-height: 1.3; -} - -.tool-detail__value--text { - background: var(--vscode-textCodeBlock-background); - border: 1px solid var(--vscode-widget-border); - border-radius: 3px; - padding: 4px 6px; - white-space: pre-wrap; - word-wrap: break-word; -} - -.tool-detail__value--json { - background: var(--vscode-textCodeBlock-background); - border: 1px solid var(--vscode-widget-border); - border-radius: 3px; - padding: 4px 6px; - font-family: var(--vscode-editor-font-family); - font-size: 9px; - overflow-x: auto; - max-height: 150px; - overflow-y: auto; -} - -/* Details element styling */ -.tool-detail details { - margin: 0; -} - -.tool-detail details summary { - cursor: pointer; - padding: 2px 0; - font-weight: 600; - color: var(--vscode-descriptionForeground); - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.3px; - transition: color 0.2s ease; -} - -.tool-detail details summary:hover { - color: var(--vscode-foreground); -} - -.tool-detail details[open] summary { - margin-bottom: 4px; -} - -.tool-detail__value--result { - background: var(--vscode-textCodeBlock-background); - border: 1px solid var(--vscode-widget-border); - border-radius: 3px; - padding: 0; - overflow: hidden; -} - -.tool-detail__value--error { - border-color: var(--vscode-errorForeground); - background: var(--vscode-inputValidation-errorBackground); -} - -.tool-result-content { - padding: 6px 8px; - margin: 0; - font-family: var(--vscode-editor-font-family); - font-size: 9px; - line-height: 1.3; - white-space: pre-wrap; - word-wrap: break-word; - max-height: 200px; - overflow-y: auto; - background: transparent; - border: none; -} - -.tool-result__show-more { - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); - border: none; - border-top: 1px solid var(--vscode-widget-border); - width: 100%; - padding: 4px 8px; - font-size: 9px; - cursor: pointer; - transition: background-color 0.15s ease; -} - -.tool-result__show-more:hover { - background: var(--vscode-button-hoverBackground); -} - -.tool-result__show-more:active { - background: var(--vscode-button-background); -} - -/* Tool result styles */ -.tool-result { - margin-bottom: 6px; - border: 1px solid var(--vscode-widget-border); - border-radius: 4px; - background: var(--vscode-editor-background); - overflow: hidden; -} - -.tool-result--panel { - margin-bottom: 8px; -} - -.tool-result--sidebar { - margin-bottom: 4px; -} - -.tool-result--success { - border-left: 2px solid var(--vscode-charts-green); -} - -.tool-result--error { - border-left: 2px solid var(--vscode-errorForeground); -} - -.tool-result__header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 4px 6px; - cursor: pointer; - background: transparent; - border-bottom: 1px solid transparent; - transition: background-color 0.15s ease; -} - -.tool-result__header:hover { - background: var(--vscode-list-hoverBackground); -} - -.tool-result__main { - display: flex; - align-items: center; - gap: 4px; - flex: 1; - min-width: 0; -} - -.tool-result-icon { - font-size: 10px; - flex-shrink: 0; -} - -.tool-result-label { - font-weight: 600; - color: var(--vscode-foreground); - flex-shrink: 0; - font-size: 9px; -} - -.tool-result-meta { - color: var(--vscode-descriptionForeground); - font-size: 8px; - background: var(--vscode-badge-background); - color: var(--vscode-badge-foreground); - padding: 1px 3px; - border-radius: 6px; - flex-shrink: 0; -} - -.tool-result__content { - padding: 6px 8px; - border-top: 1px solid var(--vscode-widget-border); - background: var(--vscode-editor-background); -} - -.tool-result__text { - background: var(--vscode-textCodeBlock-background); - border: 1px solid var(--vscode-widget-border); - border-radius: 3px; - padding: 6px 8px; - font-family: var(--vscode-editor-font-family); - font-size: 9px; - line-height: 1.3; - white-space: pre-wrap; - word-wrap: break-word; - margin: 0; - max-height: 250px; - overflow-y: auto; -} - -.tool-result__show-more { - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); - border: none; - border-radius: 3px; - padding: 3px 6px; - font-size: 9px; - cursor: pointer; - margin-top: 4px; - transition: background-color 0.15s ease; -} - -.tool-result__show-more:hover { - background: var(--vscode-button-hoverBackground); -} - -.tool-result__show-more:active { - background: var(--vscode-button-background); -} - -.loading-spinner { - animation: spin 1s linear infinite; -} - -@keyframes spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } -} - -/* Tool group styles - compact */ -.tool-group { - margin-bottom: 8px; - padding: 6px 8px; - border: 1px solid var(--vscode-input-border); - border-radius: 6px; - background: var(--vscode-input-background); - line-height: 1.2; - transition: all 0.2s ease; - position: relative; -} - -.tool-group:hover { - border-color: var(--vscode-focusBorder); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); -} - -.tool-group--panel { - margin-bottom: 10px; - padding: 8px 10px; - border-radius: 8px; - font-size: 12px; -} - -.tool-group--sidebar { - margin-bottom: 6px; - padding: 6px 8px; - border-radius: 6px; - font-size: 11px; -} - -.tool-group--success { - border-left: 3px solid var(--vscode-charts-green); -} - -.tool-group--error { - background: var(--vscode-inputValidation-errorBackground); - border: 1px solid var(--vscode-inputValidation-errorBorder); - border-left: 3px solid var(--vscode-errorForeground); -} - -.tool-group--loading { - border-left: 3px solid var(--vscode-progressBar-background); -} - -.tool-group--complete { - border-left: 3px solid var(--vscode-charts-green); -} - -.tool-group__header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - margin-bottom: 0; - font-weight: 600; - opacity: 0.8; - font-size: 11px; -} - -.chat-interface--sidebar .tool-group__header { - margin-bottom: 0; - font-size: 10px; -} - -/* Only add bottom margin when children are showing */ -.tool-group__header:has(+ .tool-group__children) { - margin-bottom: 8px; -} - -.chat-interface--sidebar .tool-group__header:has(+ .tool-group__children) { - margin-bottom: 6px; -} - -.tool-group__main { - display: flex; - align-items: center; - gap: 6px; - flex: 1; - min-width: 0; -} - -.tool-group-icon { - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - width: 14px; - height: 14px; - color: var(--vscode-descriptionForeground); -} - -/* Tool group icon state-specific colors */ -.tool-group--success .tool-group-icon { - color: var(--vscode-charts-green); -} - -.tool-group--complete .tool-group-icon { - color: var(--vscode-charts-green); -} - -.tool-group--error .tool-group-icon { - color: var(--vscode-errorForeground); -} - -.tool-group-info { - display: flex; - flex-direction: column; - gap: 0; - flex: 1; - min-width: 0; -} - -.tool-group-name { - font-weight: 500; - color: var(--vscode-foreground); - flex-shrink: 0; - font-size: 11px; - line-height: 1.2; -} - -.tool-time-remaining { - display: flex; - align-items: center; - gap: 2px; - font-size: 10px; - color: var(--vscode-descriptionForeground); - font-weight: normal; - line-height: 1.2; -} - -.tool-group-actions { - display: flex; - align-items: center; - gap: 2px; - flex-shrink: 0; -} - -.tool-group-count { - background: var(--vscode-badge-background); - color: var(--vscode-badge-foreground); - font-size: 9px; - padding: 1px 3px; - border-radius: 2px; - font-weight: 500; - flex-shrink: 0; -} - -/* Simple loading icon */ -.loading-icon-simple { - position: relative; - width: 16px; - height: 16px; - display: flex; - align-items: center; - justify-content: center; -} - -.loading-ring { - width: 12px; - height: 12px; - border: 1px solid var(--vscode-list-inactiveSelectionBackground); - border-top: 1px solid var(--vscode-progressBar-background); - border-radius: 50%; - animation: spin-simple 1s linear infinite; -} - -@keyframes spin-simple { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -.tool-group__children { - margin-top: 6px; - padding-top: 6px; - border-top: 1px solid var(--vscode-input-border); -} - -.tool-group__children .tool-message { - margin-bottom: 6px; - margin-left: 8px; - border-left: 2px solid var(--vscode-input-border); - padding-left: 6px; - background: transparent; - border-radius: 4px; - padding: 4px 6px; -} - -.tool-group__children .tool-message:last-child { - margin-bottom: 0; -} - -/* Context Display */ -.context-display { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - background: transparent; - border: 1px solid var(--vscode-input-border); - border-radius: 4px; - font-size: 11px; - margin-left: 6px; - margin-top: 6px; - width: fit-content; -} - -.context-icon { - font-size: 12px; - opacity: 0.8; -} - -.context-text { - color: var(--vscode-descriptionForeground); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 200px; -} - -.context-clear-btn { - background: none; - border: none; - color: var(--vscode-descriptionForeground); - cursor: pointer; - font-size: 14px; - padding: 0; - margin-left: 4px; - opacity: 0.6; - transition: opacity 0.2s; -} - -.context-clear-btn:hover { - opacity: 1; -} - -/* Upload Progress */ -.upload-progress { - margin-left: 6px; - margin-top: 6px; - display: flex; - flex-direction: column; - gap: 4px; -} - -.upload-summary { - font-size: 11px; - color: var(--vscode-button-background); - font-weight: 600; - margin-bottom: 2px; - padding-left: 2px; -} - -.uploading-item { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - background: var(--vscode-editor-hoverHighlightBackground); - border: 1px solid var(--vscode-input-border); - border-radius: 4px; - font-size: 11px; - width: fit-content; - max-width: 300px; -} - -.upload-icon { - font-size: 12px; - opacity: 0.8; -} - -.upload-text { - color: var(--vscode-descriptionForeground); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - flex: 1; -} - -.upload-spinner { - width: 12px; - height: 12px; - border: 1px solid var(--vscode-input-border); - border-top: 1px solid var(--vscode-button-background); - border-radius: 50%; - animation: spin 1s linear infinite; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -/* Loading Tips */ -.tool-loading-tips { - background: var(--vscode-textCodeBlock-background); - border: 1px solid var(--vscode-input-border); - border-radius: 6px; - padding: 12px; - margin-bottom: 12px; -} - -.loading-tip { - display: flex; - align-items: flex-start; - gap: 8px; -} - -.tip-icon { - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - color: var(--vscode-descriptionForeground); - opacity: 0.8; -} - -.tip-text { - font-size: 12px; - line-height: 1.4; - color: var(--vscode-foreground); - font-style: italic; - animation: tipFadeIn 0.5s ease-in; -} - -@keyframes tipFadeIn { - 0% { opacity: 0; transform: translateY(5px); } - 100% { opacity: 1; transform: translateY(0); } -} - -/* Enhanced tool message states */ -.tool-message--loading .tool-icon { - color: var(--vscode-progressBar-background); - animation: pulse-loading 2s ease-in-out infinite; -} - -/* Enhanced tool group states */ -.tool-group--loading .tool-group-icon { - color: var(--vscode-progressBar-background); - animation: pulse-loading 2s ease-in-out infinite; -} - -/* Tool status enhancements - simplified */ -.tool-status--loading { - color: var(--vscode-descriptionForeground); - animation: rotate 2s linear infinite; -} - -@keyframes rotate { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -@keyframes pulse-loading { - 0%, 100% { opacity: 0.5; } - 50% { opacity: 1; } -} - -/* Responsive adjustments for mobile */ -@media (max-width: 768px) { - .tip-text { - font-size: 11px; - } - - .tool-group-info { - gap: 1px; - } - - .tool-time-remaining { - font-size: 10px; - } -} - -/* Sidebar Action Bar - VS Code style action buttons */ -.sidebar-action-bar { - position: absolute; - top: 8px; - right: 12px; - z-index: 100; - display: flex; - gap: 4px; - align-items: center; -} - -.sidebar-action-btn { - background: transparent; - border: none; - color: var(--vscode-icon-foreground); - cursor: pointer; - padding: 2px; - border-radius: 3px; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s ease; - opacity: 0.7; - width: 22px; - height: 22px; -} - -.sidebar-action-btn:hover:not(:disabled) { - background: var(--vscode-toolbar-hoverBackground); - opacity: 1; -} - -.sidebar-action-btn:active { - background: var(--vscode-toolbar-activeBackground); -} - -.sidebar-action-btn:disabled { - opacity: 0.4; - cursor: not-allowed; - background: transparent; -} - -/* Panel Action Bar */ -.chat-action-bar { - display: flex; - align-items: center; - justify-content: space-between; - padding: 6px 12px; - background: var(--vscode-titleBar-activeBackground); - border-bottom: 1px solid var(--vscode-panel-border); - min-height: 32px; - flex-shrink: 0; -} - -.open-canvas-btn { - display: flex; - align-items: center; - gap: 6px; - background: transparent; - border: none; - color: var(--vscode-titleBar-activeForeground); - cursor: pointer; - padding: 4px 8px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; - transition: all 0.2s ease; -} - -.open-canvas-btn:hover { - background: var(--vscode-titleBar-hoverBackground); - color: var(--vscode-titleBar-hoverForeground); -} - -.open-canvas-btn svg { - flex-shrink: 0; -} - -.clear-chat-icon-btn { - background: transparent; - border: none; - color: var(--vscode-titleBar-activeForeground); - cursor: pointer; - padding: 4px; - border-radius: 3px; - display: flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - transition: all 0.2s ease; - opacity: 0.8; -} - -.clear-chat-icon-btn:hover:not(:disabled) { - background: var(--vscode-titleBar-hoverBackground); - color: var(--vscode-titleBar-hoverForeground); - opacity: 1; -} - -.clear-chat-icon-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.chat-interface { -} - -/* Markdown content styles */ -.markdown-content { - line-height: 1.3; - word-wrap: break-word; - overflow-wrap: break-word; -} - -.markdown-content h1, -.markdown-content h2, -.markdown-content h3, -.markdown-content h4, -.markdown-content h5, -.markdown-content h6 { - margin: 16px 0 8px 0; - font-weight: 600; - line-height: 1.2; - color: var(--vscode-editor-foreground); -} - -.markdown-content h1:first-child, -.markdown-content h2:first-child, -.markdown-content h3:first-child, -.markdown-content h4:first-child, -.markdown-content h5:first-child, -.markdown-content h6:first-child { - margin-top: 0; -} - -.markdown-content h1 { - font-size: 1.8em; -} - -.markdown-content h2 { - font-size: 1.5em; -} - -.markdown-content h3 { - font-size: 1.25em; -} - -.markdown-content h4 { - font-size: 1.1em; -} - -.markdown-content p { - margin: 8px 0; - line-height: 1.5; -} - -.markdown-content p:first-child { - margin-top: 0; -} - -.markdown-content p:last-child { - margin-bottom: 0; -} - -.markdown-content ul, -.markdown-content ol { - margin: 8px 0; - padding-left: 24px; -} - -.markdown-content ul:first-child, -.markdown-content ol:first-child { - margin-top: 0; -} - -.markdown-content ul:last-child, -.markdown-content ol:last-child { - margin-bottom: 0; -} - -.markdown-content li { - margin: 2px 0; - line-height: 1.4; -} - -.markdown-content li p { - margin: 2px 0; -} - -/* Nested lists */ -.markdown-content li ul, -.markdown-content li ol { - margin: 4px 0; - padding-left: 20px; -} - -.markdown-content pre { - background: var(--vscode-textCodeBlock-background); - border: 1px solid var(--vscode-panel-border); - border-radius: 6px; - padding: 8px; - margin: 4px 0; - overflow-x: auto; - font-family: var(--vscode-editor-font-family); - font-size: 13px; - line-height: 1.3; -} - -.markdown-content pre code { - background: transparent; - padding: 0; - border: none; - border-radius: 0; - font-size: inherit; -} - -.markdown-content .inline-code { - background: var(--vscode-textCodeBlock-background); - border: 1px solid var(--vscode-panel-border); - border-radius: 3px; - padding: 2px 4px; - font-family: var(--vscode-editor-font-family); - font-size: 0.9em; - color: var(--vscode-textPreformat-foreground); -} - -.markdown-content a { - color: var(--vscode-textLink-foreground); - text-decoration: none; -} - -.markdown-content a:hover { - color: var(--vscode-textLink-activeForeground); - text-decoration: underline; -} - -.markdown-content .table-wrapper { - overflow-x: auto; - margin: 8px 0; -} - -.markdown-content table { - border-collapse: collapse; - width: 100%; - border: 1px solid var(--vscode-panel-border); -} - -.markdown-content th, -.markdown-content td { - border: 1px solid var(--vscode-panel-border); - padding: 6px 10px; - text-align: left; -} - -.markdown-content th { - background: var(--vscode-editorWidget-background); - font-weight: 600; -} - -.markdown-content .markdown-blockquote { - border-left: 4px solid var(--vscode-textLink-foreground); - margin: 8px 0; - padding: 6px 12px; - background: var(--vscode-textBlockQuote-background); - font-style: italic; - color: var(--vscode-textBlockQuote-foreground); -} - -.markdown-content hr { - border: none; - border-top: 1px solid var(--vscode-panel-border); - margin: 12px 0; -} - -.markdown-content strong { - font-weight: 600; - color: var(--vscode-editor-foreground); -} - -.markdown-content em { - font-style: italic; -} - -/* Syntax highlighting styles for VS Code theme compatibility */ -.markdown-content .hljs { - background: transparent; - color: var(--vscode-editor-foreground); -} - -.markdown-content .hljs-keyword, -.markdown-content .hljs-built_in { - color: var(--vscode-symbolIcon-keywordForeground, #569cd6); -} - -.markdown-content .hljs-string, -.markdown-content .hljs-title { - color: var(--vscode-symbolIcon-stringForeground, #ce9178); -} - -.markdown-content .hljs-comment { - color: var(--vscode-symbolIcon-colorForeground, #6a9955); - font-style: italic; -} - -.markdown-content .hljs-number { - color: var(--vscode-symbolIcon-numberForeground, #b5cea8); -} - -.markdown-content .hljs-function { - color: var(--vscode-symbolIcon-functionForeground, #dcdcaa); -} - -.markdown-content .hljs-variable { - color: var(--vscode-symbolIcon-variableForeground, #9cdcfe); -} - -/* Responsive adjustments for sidebar */ -.chat-interface--sidebar .markdown-content h1 { - font-size: 1.4em; - margin: 12px 0 6px 0; -} - -.chat-interface--sidebar .markdown-content h2 { - font-size: 1.2em; - margin: 12px 0 6px 0; -} - -.chat-interface--sidebar .markdown-content h3 { - font-size: 1.1em; - margin: 10px 0 4px 0; -} - -.chat-interface--sidebar .markdown-content h4 { - margin: 8px 0 4px 0; -} - -.chat-interface--sidebar .markdown-content p { - margin: 6px 0; - line-height: 1.4; -} - -.chat-interface--sidebar .markdown-content ul, -.chat-interface--sidebar .markdown-content ol { - margin: 6px 0; - padding-left: 20px; -} - -.chat-interface--sidebar .markdown-content li { - margin: 1px 0; - line-height: 1.3; -} - -.chat-interface--sidebar .markdown-content pre { - padding: 6px; - font-size: 11px; - margin: 6px 0; -} - -.chat-interface--sidebar .markdown-content .inline-code { - font-size: 0.85em; -} - -.chat-interface--sidebar .markdown-content table { - font-size: 11px; -} - -.chat-interface--sidebar .markdown-content th, -.chat-interface--sidebar .markdown-content td { - padding: 3px 5px; -} - -/* Drag & Drop Overlay */ - -/* Error message with action buttons */ -.error-message-content { - margin-bottom: 8px; - line-height: 1.4; - position: relative; - padding-right: 24px; /* Add space for close button in sidebar mode */ - color: var(--vscode-errorForeground); -} - -.error-actions { - display: flex; - gap: 8px; - margin-top: 8px; -} - -.error-action-btn { - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); - border: 1px solid var(--vscode-button-border); - border-radius: 4px; - padding: 6px 12px; - font-size: 12px; - cursor: pointer; - transition: all 0.2s ease; - font-family: inherit; -} - -.error-action-btn:hover { - background: var(--vscode-button-hoverBackground); - border-color: var(--vscode-button-hoverBackground); -} - -.error-action-btn:active { - background: var(--vscode-button-activeBackground); - transform: scale(0.98); -} - -.error-action-btn:focus { - outline: 2px solid var(--vscode-focusBorder); - outline-offset: 2px; -} - -.error-close-btn { - background: none; - border: none; - color: var(--vscode-descriptionForeground); - cursor: pointer; - font-size: 18px; - font-weight: bold; - padding: 0; - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 3px; - transition: all 0.15s ease; - opacity: 0.6; - flex-shrink: 0; -} - -.error-close-btn:hover { - background: var(--vscode-toolbar-hoverBackground); - opacity: 1; - color: var(--vscode-foreground); -} - -.error-close-btn:active { - background: var(--vscode-toolbar-activeBackground); -} - -.error-close-btn--sidebar { - position: absolute; - top: 8px; - right: 8px; - font-size: 16px; - width: 18px; - height: 18px; -} - -/* Panel specific styling */ -.chat-interface--panel .error-actions { - gap: 10px; -} - -.chat-interface--panel .error-action-btn { - padding: 8px 16px; - font-size: 13px; -} - -/* Theme Tool Message Styles - Added for generateTheme tool calls */ -.theme-tool-message { - margin-bottom: 4px; - padding: 0; - border: none; - border-radius: 0; - background: transparent; - transition: all 0.2s ease; - position: relative; -} - -.theme-tool-message--panel { - margin-bottom: 4px; - padding: 0; -} - -.theme-tool-message--sidebar { - margin-bottom: 4px; - padding: 0; -} - -.theme-error-notice { - margin: 0.5rem 0; - padding: 0.75rem; - background-color: var(--vscode-inputValidation-errorBackground); - color: var(--vscode-inputValidation-errorForeground); - border: 1px solid var(--vscode-inputValidation-errorBorder); - border-left: 3px solid var(--vscode-errorForeground); - border-radius: 0.375rem; - font-size: 0.875rem; - font-family: var(--vscode-editor-font-family); -} - -/* Ensure theme preview cards use proper VSCode CSS variables */ -.theme-tool-message .theme-preview-card { - /* Override any hardcoded colors with VSCode theme variables */ - --background: var(--vscode-editor-background); - --foreground: var(--vscode-editor-foreground); - --muted: var(--vscode-input-background); - --muted-foreground: var(--vscode-descriptionForeground); - --border: var(--vscode-input-border); - --card: var(--vscode-editorWidget-background); - --card-foreground: var(--vscode-editorWidget-foreground); - --primary: var(--vscode-textLink-foreground); - --primary-foreground: var(--vscode-button-foreground); - --secondary: var(--vscode-button-secondaryBackground); - --secondary-foreground: var(--vscode-button-secondaryForeground); - --accent: var(--vscode-list-hoverBackground); - --accent-foreground: var(--vscode-list-hoverForeground); - --destructive: var(--vscode-errorForeground); - --destructive-foreground: var(--vscode-button-foreground); - --ring: var(--vscode-focusBorder); - --input: var(--vscode-input-background); - --font-mono: var(--vscode-editor-font-family); -} - -/* Responsive theme tool message styling */ -@media (max-width: 768px) { - .theme-tool-message--panel { - margin-bottom: 16px; - } - - .theme-tool-message--sidebar { - margin-bottom: 10px; - } -} - -/* Tool result content */ - -/* Remove double margins when chat-message contains tool messages */ -.chat-message--tool-container { - margin-bottom: 0 !important; - padding: 0 !important; - background: transparent !important; - border: none !important; -} - -.chat-message--tool-container .chat-message__header { - display: none !important; -} - -.chat-message--tool-container .chat-message__content { - padding: 0 !important; -} - -/* Mixed content messages (text + tool calls) */ -.chat-message--mixed-content { - background: transparent; - border: none; - margin-bottom: 12px; - padding: 0; -} - -.chat-message--mixed-content .chat-message__content { - margin-bottom: 6px; -} - -.chat-message--mixed-content .chat-message__tools { - margin-top: 4px; -} - -.chat-message--mixed-content .chat-message__tools .tool-message { - margin-bottom: 4px; -} - -.chat-message--mixed-content .chat-message__tools .theme-tool-message { - margin-bottom: 4px; -} - -/* Multiple tool calls container */ -.tool-calls-container { - display: flex; - flex-direction: column; - gap: 4px; -} - -.tool-calls-container .tool-message { - margin-bottom: 0; -} - -.tool-calls-container .theme-tool-message { - margin-bottom: 0; -} - -/* Theme copy button styling */ -.theme-copy-btn:hover { - background: var(--vscode-list-hoverBackground) !important; - color: var(--vscode-foreground) !important; -} - - - \ No newline at end of file +import React, { useState, useEffect, useRef } from 'react'; +import { useChat, ChatMessage } from '../../hooks/useChat'; +import { useFirstTimeUser } from '../../hooks/useFirstTimeUser'; +import { WebviewLayout } from '../../../types/context'; +import MarkdownRenderer from '../MarkdownRenderer'; +import { TaskIcon, ClockIcon, CheckIcon, LightBulbIcon, GroupIcon, BrainIcon } from '../Icons'; +import Welcome from '../Welcome'; +import ThemePreviewCard from './ThemePreviewCard'; +import ModelSelector from './ModelSelector'; +import chatStyles from './ChatInterface.css'; +import welcomeStyles from '../Welcome/Welcome.css'; + +interface ChatInterfaceProps { + layout: WebviewLayout; + vscode: any; +} + +const ChatInterface: React.FC = ({ layout, vscode }) => { + const { chatHistory, isLoading, sendMessage, clearHistory, setChatHistory } = useChat(vscode); + const { isFirstTime, isLoading: isCheckingFirstTime, markAsReturningUser, resetFirstTimeUser } = useFirstTimeUser(); + const [inputMessage, setInputMessage] = useState(''); + const [selectedModel, setSelectedModel] = useState('claude-3-5-sonnet-20241022'); + const [expandedTools, setExpandedTools] = useState>({}); + const [showFullContent, setShowFullContent] = useState<{[key: string]: boolean}>({}); + const [currentContext, setCurrentContext] = useState<{fileName: string; type: string} | null>(null); + const [showWelcome, setShowWelcome] = useState(false); + + // Drag and drop state + const [uploadingImages, setUploadingImages] = useState([]); + const [pendingImages, setPendingImages] = useState<{fileName: string; originalName: string; fullPath: string}[]>([]); + const [toolTimers, setToolTimers] = useState>({}); + const timerIntervals = useRef>({}); + + // Helper function to check if we have meaningful conversation messages + const hasConversationMessages = () => { + return chatHistory.some(msg => + msg.role === 'user' || + (msg.role === 'assistant' && typeof msg.content === 'string' && msg.content.trim().length > 0) || + (msg.role === 'assistant' && Array.isArray(msg.content) && msg.content.some(part => part.type === 'text' && (part as any).text?.trim().length > 0)) + ); + }; + + // Request current provider on mount + useEffect(() => { + vscode.postMessage({ + command: 'getCurrentProvider' + }); + + const handleMessage = (event: MessageEvent) => { + const message = event.data; + if (message.command === 'currentProviderResponse') { + let fallbackModel: string; + switch (message.provider) { + case 'openai': + fallbackModel = 'gpt-4o'; + break; + case 'vscodelm': + fallbackModel = 'vscodelm/auto'; + break; + case 'openrouter': + fallbackModel = 'anthropic/claude-3-7-sonnet-20250219'; + break; + case 'anthropic': + default: + fallbackModel = 'claude-3-5-sonnet-20241022'; + break; + } + setSelectedModel(message.model || fallbackModel); + } else if (message.command === 'providerChanged') { + setSelectedModel(message.model); + } + }; + + window.addEventListener('message', handleMessage); + return () => window.removeEventListener('message', handleMessage); + }, []); + + const handleModelChange = (model: string) => { + // Send model change request to extension + vscode.postMessage({ + command: 'changeProvider', + model: model + }); + }; + + useEffect(() => { + // Inject ChatInterface CSS styles + const styleId = 'chat-interface-styles'; + let styleElement = document.getElementById(styleId) as HTMLStyleElement; + + if (!styleElement) { + styleElement = document.createElement('style'); + styleElement.id = styleId; + styleElement.textContent = chatStyles; + document.head.appendChild(styleElement); + } + + // Inject Welcome CSS styles + const welcomeStyleId = 'welcome-styles'; + let welcomeStyleElement = document.getElementById(welcomeStyleId) as HTMLStyleElement; + + if (!welcomeStyleElement) { + welcomeStyleElement = document.createElement('style'); + welcomeStyleElement.id = welcomeStyleId; + welcomeStyleElement.textContent = welcomeStyles; + document.head.appendChild(welcomeStyleElement); + } + + // Auto-open canvas if not already open + const autoOpenCanvas = () => { + // Check if canvas panel is already open by looking for the canvas webview + vscode.postMessage({ + command: 'checkCanvasStatus' + }); + + // Listen for canvas status response and context messages + const handleMessage = (event: MessageEvent) => { + const message = event.data; + if (message.command === 'canvasStatusResponse') { + if (!message.isOpen) { + // Canvas is not open, auto-open it + console.log('🎨 Auto-opening canvas view...'); + vscode.postMessage({ + command: 'autoOpenCanvas' + }); + } + } else if (message.command === 'contextFromCanvas') { + // Handle context from canvas + console.log('πŸ“„ Received context from canvas:', message.data); + console.log('πŸ“„ Current context before setting:', currentContext); + if (message.data.type === 'clear' || !message.data.fileName) { + setCurrentContext(null); + console.log('πŸ“„ Context cleared'); + } else { + setCurrentContext(message.data); + console.log('πŸ“„ Context set to:', message.data); + } + } else if (message.command === 'imageSavedToMoodboard') { + // Handle successful image save with full path + console.log('πŸ“Ž Image saved with full path:', message.data); + setPendingImages(prev => [...prev, { + fileName: message.data.fileName, + originalName: message.data.originalName, + fullPath: message.data.fullPath + }]); + // Remove from uploading state + setUploadingImages(prev => prev.filter(name => name !== message.data.originalName)); + } else if (message.command === 'imageSaveError') { + // Handle image save error + console.error('πŸ“Ž Image save error:', message.data); + setUploadingImages(prev => prev.filter(name => name !== message.data.originalName)); + } else if (message.command === 'clearChat') { + // Handle clear chat command from toolbar + handleNewConversation(); + } else if (message.command === 'resetWelcome') { + // Handle reset welcome command from command palette + resetFirstTimeUser(); + setShowWelcome(true); + console.log('πŸ‘‹ Welcome screen reset and shown'); + } else if (message.command === 'setChatPrompt') { + // Handle prompt from canvas floating buttons + console.log('πŸ“ Received prompt from canvas:', message.data.prompt); + setInputMessage(message.data.prompt); + } + }; + + window.addEventListener('message', handleMessage); + + // Cleanup listener + return () => { + window.removeEventListener('message', handleMessage); + }; + }; + + // Delay the check slightly to ensure chat is fully loaded + const timeoutId = setTimeout(autoOpenCanvas, 500); + + return () => { + clearTimeout(timeoutId); + // Clean up on unmount + const existingStyle = document.getElementById(styleId); + if (existingStyle) { + document.head.removeChild(existingStyle); + } + const existingWelcomeStyle = document.getElementById(welcomeStyleId); + if (existingWelcomeStyle) { + document.head.removeChild(existingWelcomeStyle); + } + }; + }, [vscode]); + + // Handle first-time user welcome display + useEffect(() => { + if (!isCheckingFirstTime && isFirstTime && !hasConversationMessages()) { + setShowWelcome(true); + console.log('πŸ‘‹ Showing welcome for first-time user'); + } + }, [isCheckingFirstTime, isFirstTime, chatHistory]); + + // Auto-collapse tools when new messages arrive + useEffect(() => { + const handleAutoCollapse = () => { + setExpandedTools(prev => { + const newState = { ...prev }; + const toolIndices = chatHistory + .map((msg, index) => ({ msg, index })) + .filter(({ msg }) => msg.role === 'tool') + .map(({ index }) => index); + + // Keep only the last tool/tool-result expanded + if (toolIndices.length > 1) { + const lastToolIndex = toolIndices[toolIndices.length - 1]; + toolIndices.forEach(index => { + if (index !== lastToolIndex) { + newState[index] = false; + } + }); + } + + return newState; + }); + }; + + window.addEventListener('autoCollapseTools', handleAutoCollapse); + return () => window.removeEventListener('autoCollapseTools', handleAutoCollapse); + }, [chatHistory]); + + const handleSendMessage = async () => { + if (inputMessage.trim()) { + let messageContent: any; + + console.log('πŸ“€ Sending message with context:', currentContext); + console.log('πŸ“€ Input message:', inputMessage); + + // Check if we have image context to include + if (currentContext && (currentContext.type === 'image' || currentContext.type === 'images')) { + try { + // Create structured content with text and images + const contentParts: any[] = [ + { + type: 'text', + text: inputMessage + } + ]; + + // Process image context + const imagePaths = currentContext.type === 'images' + ? currentContext.fileName.split(', ') + : [currentContext.fileName]; + + // Convert each image to base64 + for (const imagePath of imagePaths) { + try { + // Request base64 data from the extension + const base64Data = await new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => { + reject(new Error('Timeout waiting for base64 data')); + }, 10000); + + const handler = (event: MessageEvent) => { + const message = event.data; + if (message.command === 'base64ImageResponse' && message.filePath === imagePath) { + clearTimeout(timeoutId); + window.removeEventListener('message', handler); + if (message.error) { + reject(new Error(message.error)); + } else { + resolve(message.base64Data); + } + } + }; + + window.addEventListener('message', handler); + + // Request base64 data from extension + vscode.postMessage({ + command: 'getBase64Image', + filePath: imagePath + }); + }); + + // Extract MIME type from base64 data URL + const mimeMatch = base64Data.match(/^data:([^;]+);base64,/); + const mimeType = mimeMatch ? mimeMatch[1] : 'image/png'; + const base64Content = base64Data.replace(/^data:[^;]+;base64,/, ''); + + contentParts.push({ + type: 'image', + image: base64Content, + mimeType: mimeType + }); + + console.log('πŸ“Ž Added image to message:', imagePath, 'MIME:', mimeType); + } catch (error) { + console.error('Failed to load image:', imagePath, error); + // Add error note to text content instead + contentParts[0].text += `\n\n[Note: Could not load image ${imagePath}: ${error}]`; + } + } + + messageContent = contentParts; + console.log('πŸ“€ Final structured message content:', contentParts.length, 'parts'); + } catch (error) { + console.error('Error processing images:', error); + // Fallback to text-only message with context info + messageContent = currentContext.type === 'images' + ? `Context: Multiple images in moodboard\n\nMessage: ${inputMessage}` + : `Context: ${currentContext.fileName}\n\nMessage: ${inputMessage}`; + } + } else if (currentContext) { + // Non-image context - use simple text format + messageContent = `Context: ${currentContext.fileName}\n\nMessage: ${inputMessage}`; + console.log('πŸ“€ Final message with non-image context:', messageContent); + } else { + // No context - just the message text + messageContent = inputMessage; + console.log('πŸ“€ No context available, sending message as-is'); + } + + sendMessage(messageContent); + setInputMessage(''); + } + }; + + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSendMessage(); + } + }; + + const handleInputChange = (e: React.ChangeEvent) => { + setInputMessage(e.target.value); + resizeTextarea(e.target); + }; + + const resizeTextarea = (textarea: HTMLTextAreaElement) => { + // Auto-resize textarea + textarea.style.height = 'auto'; // Reset height to calculate new height + + // Set height based on scroll height, with max height of 120px (about 6 lines) + const maxHeight = 120; + const newHeight = Math.min(textarea.scrollHeight, maxHeight); + textarea.style.height = `${newHeight}px`; + }; + + // Reset textarea height when input is cleared (e.g., after sending message) + useEffect(() => { + if (!inputMessage.trim()) { + const textarea = document.querySelector('.message-input') as HTMLTextAreaElement; + if (textarea) { + textarea.style.height = 'auto'; + } + } + }, [inputMessage]); + + const handleAddContext = () => { + // TODO: Implement context addition functionality + console.log('Add Context clicked'); + }; + + const handleNewConversation = () => { + clearHistory(); + setInputMessage(''); + setCurrentContext(null); + setUploadingImages([]); + setPendingImages([]); + setToolTimers({}); // Clear all tool timers + + // Clear all timer intervals + Object.values(timerIntervals.current).forEach(timer => clearInterval(timer)); + timerIntervals.current = {}; + + markAsReturningUser(); + }; + + const handleWelcomeGetStarted = () => { + setShowWelcome(false); + markAsReturningUser(); + console.log('πŸ‘‹ User clicked Get Started, welcome dismissed'); + + // Auto-trigger initialize Superdesign command + vscode.postMessage({ + command: 'initializeSuperdesign' + }); + console.log('πŸš€ Auto-triggering Initialize Superdesign command'); + }; + + // Drag and drop handlers + const handleDragEnter = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + + // Check if dragged items contain files + if (e.dataTransfer.types.includes('Files')) { + e.dataTransfer.effectAllowed = 'copy'; + e.dataTransfer.dropEffect = 'copy'; + } + }; + + const handleDragLeave = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + + // Essential: Must prevent default and set dropEffect for drop to work + if (e.dataTransfer.types.includes('Files')) { + e.dataTransfer.dropEffect = 'copy'; + } + }; + + const handleDrop = async (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + + if (isLoading) { + return; + } + + const files = Array.from(e.dataTransfer.files); + const imageFiles = files.filter(file => file.type.startsWith('image/')); + + if (imageFiles.length === 0) { + return; + } + + // Process each image file + for (const file of imageFiles) { + try { + await handleImageUpload(file); + } catch (error) { + console.error('Error processing dropped image:', error); + } + } + }; + + const handleImageUpload = async (file: File): Promise => { + const maxSize = 10 * 1024 * 1024; // 10MB limit + if (file.size > maxSize) { + const displayName = file.name || 'clipboard image'; + console.error('Image too large:', displayName); + vscode.postMessage({ + command: 'showError', + data: `Image "${displayName}" is too large. Maximum size is 10MB.` + }); + return; + } + + // Create a unique filename - handle clipboard images without names + const timestamp = Date.now(); + const originalName = file.name || `clipboard-image-${timestamp}`; + const extension = file.type.split('/')[1] || 'png'; + const safeName = originalName.replace(/[^a-zA-Z0-9.-]/g, '_'); + const fileName = safeName.includes('.') ? `${timestamp}_${safeName}` : `${timestamp}_${safeName}.${extension}`; + + // Add to uploading state + setUploadingImages(prev => [...prev, originalName]); + + // Convert to base64 for sending to extension + const reader = new FileReader(); + reader.onload = () => { + const base64Data = reader.result as string; + + // Send to extension to save in moodboard + vscode.postMessage({ + command: 'saveImageToMoodboard', + data: { + fileName, + originalName, + base64Data, + mimeType: file.type, + size: file.size + } + }); + + console.log('πŸ“Ž Image sent to extension for saving:', fileName); + }; + + reader.onerror = () => { + console.error('Error reading file:', file.name); + setUploadingImages(prev => prev.filter(name => name !== file.name)); + vscode.postMessage({ + command: 'showError', + data: `Failed to read image "${file.name}"` + }); + }; + + reader.readAsDataURL(file); + }; + + // Auto-set context when images finish uploading + useEffect(() => { + if (uploadingImages.length === 0 && pendingImages.length > 0) { + if (pendingImages.length === 1) { + // Single image - set as context with full path + setCurrentContext({ + fileName: pendingImages[0].fullPath, + type: 'image' + }); + } else { + // Multiple images - create a combined context with all full paths + const fullPaths = pendingImages.map(img => img.fullPath).join(', '); + setCurrentContext({ + fileName: fullPaths, + type: 'images' + }); + } + // Clear pending images after setting context + setPendingImages([]); + } + }, [uploadingImages.length, pendingImages.length]); + + // Helper function to check if tool is loading + const isToolLoading = (toolCallPart: any, msgIndex: number) => { + const toolCallId = toolCallPart.toolCallId; + const hasResult = chatHistory.slice(msgIndex + 1).some(laterMsg => + laterMsg.role === 'tool' && + Array.isArray(laterMsg.content) && + laterMsg.content.some(resultPart => + resultPart.type === 'tool-result' && + (resultPart as any).toolCallId === toolCallId + ) + ); + return !hasResult || toolCallPart.metadata?.is_loading || false; + }; + + // Manage countdown timers for tool calls + useEffect(() => { + const activeTimers = new Set(); + + // Process each message to find tool calls + chatHistory.forEach((msg, msgIndex) => { + if (msg.role === 'assistant' && Array.isArray(msg.content)) { + // Find tool call parts and use same indexing as UI + const toolCallParts = msg.content.filter(part => part.type === 'tool-call') as any[]; + + toolCallParts.forEach((toolCallPart, toolCallIndex) => { + const uniqueKey = `${msgIndex}_${toolCallIndex}`; // Use tool call index, not content index + const isLoading = isToolLoading(toolCallPart, msgIndex); + + activeTimers.add(uniqueKey); + + if (isLoading) { + // Initialize timer if doesn't exist + setToolTimers(prev => { + if (!(uniqueKey in prev)) { + const estimatedDuration = toolCallPart.metadata?.estimated_duration || 90; + const elapsedTime = toolCallPart.metadata?.elapsed_time || 0; + const initialRemaining = Math.max(0, estimatedDuration - elapsedTime); + + return { + ...prev, + [uniqueKey]: initialRemaining + }; + } + return prev; + }); + + // Start interval if not already running + if (!timerIntervals.current[uniqueKey]) { + timerIntervals.current[uniqueKey] = setInterval(() => { + setToolTimers(current => { + const newTime = Math.max(0, (current[uniqueKey] || 0) - 1); + return { + ...current, + [uniqueKey]: newTime + }; + }); + }, 1000); + } + } else { + // Tool completed, clean up + if (timerIntervals.current[uniqueKey]) { + clearInterval(timerIntervals.current[uniqueKey]); + delete timerIntervals.current[uniqueKey]; + } + setToolTimers(prev => { + const { [uniqueKey]: removed, ...rest } = prev; + return rest; + }); + } + }); + } + }); + + // Clean up orphaned timers + Object.keys(timerIntervals.current).forEach(key => { + if (!activeTimers.has(key)) { + clearInterval(timerIntervals.current[key]); + delete timerIntervals.current[key]; + } + }); + + // Cleanup on unmount + return () => { + Object.values(timerIntervals.current).forEach(timer => clearInterval(timer)); + }; + }, [chatHistory]); + + // Global drag & drop fallback for VS Code webview + useEffect(() => { + const handleGlobalDragOver = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (e.dataTransfer && e.dataTransfer.types.includes('Files')) { + e.dataTransfer.dropEffect = 'copy'; + } + }; + + const handleGlobalDrop = async (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + console.log('🎯 Global drop detected!', e.dataTransfer?.files.length, 'files'); + + if (!e.dataTransfer?.files) return; + + const files = Array.from(e.dataTransfer.files); + console.log('🎯 Global files from drop:', files.map(f => `${f.name} (${f.type})`)); + + const imageFiles = files.filter(file => file.type.startsWith('image/')); + console.log('🎯 Global image files:', imageFiles.map(f => f.name)); + + if (imageFiles.length > 0 && !isLoading) { + console.log('πŸ“Ž Processing images from global drop:', imageFiles.map(f => f.name)); + + for (const file of imageFiles) { + try { + await handleImageUpload(file); + } catch (error) { + console.error('Error processing dropped image:', error); + } + } + } + }; + + const handleGlobalPaste = async (e: ClipboardEvent) => { + // Only handle paste if we're focused on the chat and not loading + if (isLoading || showWelcome) return; + + const clipboardItems = e.clipboardData?.items; + if (!clipboardItems) return; + + console.log('πŸ“‹ Paste detected, checking for images...'); + + // Look for image items in clipboard + const imageItems = Array.from(clipboardItems).filter(item => + item.type.startsWith('image/') + ); + + if (imageItems.length > 0) { + e.preventDefault(); + console.log('πŸ“‹ Found', imageItems.length, 'image(s) in clipboard'); + + for (const item of imageItems) { + const file = item.getAsFile(); + if (file) { + try { + console.log('πŸ“‹ Processing pasted image:', file.name || 'clipboard-image', file.type); + await handleImageUpload(file); + } catch (error) { + console.error('Error processing pasted image:', error); + vscode.postMessage({ + command: 'showError', + data: `Failed to process pasted image: ${error instanceof Error ? error.message : String(error)}` + }); + } + } + } + } + }; + + // Add global listeners + document.addEventListener('dragover', handleGlobalDragOver); + document.addEventListener('drop', handleGlobalDrop); + document.addEventListener('paste', handleGlobalPaste); + + return () => { + document.removeEventListener('dragover', handleGlobalDragOver); + document.removeEventListener('drop', handleGlobalDrop); + document.removeEventListener('paste', handleGlobalPaste); + }; + }, [isLoading, handleImageUpload, showWelcome]); + + const renderChatMessage = (msg: ChatMessage, index: number) => { + // Helper function to extract text content from CoreMessage + const getMessageText = (msg: ChatMessage): string => { + if (typeof msg.content === 'string') { + return msg.content; + } else if (Array.isArray(msg.content)) { + // Find text parts and concatenate them + return msg.content + .filter(part => part.type === 'text') + .map(part => (part as any).text) + .join('\n'); + } + return ''; + }; + + // Check if message has tool calls + const hasToolCalls = Array.isArray(msg.content) && + msg.content.some(part => part.type === 'tool-call'); + + // Helper function to find tool result for a tool call + const findToolResult = (toolCallId: string) => { + // Look for a tool message with matching toolCallId + for (let i = index + 1; i < chatHistory.length; i++) { + const laterMsg = chatHistory[i]; + if (laterMsg.role === 'tool' && Array.isArray(laterMsg.content)) { + const toolResultPart = laterMsg.content.find( + part => part.type === 'tool-result' && (part as any).toolCallId === toolCallId + ); + if (toolResultPart) { + return toolResultPart as any; + } + } + } + return null; + }; + + // Check if message has tool results + const hasToolResults = Array.isArray(msg.content) && + msg.content.some(part => part.type === 'tool-result'); + + const isLastUserMessage = msg.role === 'user' && index === chatHistory.length - 1 && isLoading; + const isLastStreamingMessage = (msg.role === 'assistant' || hasToolResults) && index === chatHistory.length - 1; + const isStreaming = isLastStreamingMessage && isLoading; + const messageText = getMessageText(msg); + + // Handle tool call messages specially - but for mixed content, we need to show both text AND tools + if (msg.role === 'assistant' && hasToolCalls) { + // Check if there's also text content + const hasTextContent = messageText.trim().length > 0; + + if (hasTextContent) { + // Mixed content: show both text and tool calls + return ( +
+ {layout === 'panel' && ( +
+ Claude + {msg.metadata && ( + + {msg.metadata.duration_ms && ( + {msg.metadata.duration_ms}ms + )} + {msg.metadata.total_cost_usd && ( + ${msg.metadata.total_cost_usd.toFixed(4)} + )} + + )} +
+ )} +
+ + {isStreaming && β–‹} +
+
+ {renderToolCalls(msg, index, findToolResult)} +
+
+ ); + } else { + // Only tool calls, no text content - use original tool-only rendering + return renderToolCalls(msg, index, findToolResult); + } + } + + // Handle error messages with actions specially + if (msg.role === 'assistant' && msg.metadata?.is_error && msg.metadata?.actions) { + return renderErrorMessage(msg, index, setChatHistory); + } + + // Determine message label and styling + let messageLabel = ''; + let messageClass = ''; + + switch (msg.role) { + case 'user': + messageLabel = 'You'; + messageClass = 'user'; + break; + case 'assistant': + messageLabel = 'Claude'; + messageClass = 'assistant'; + break; + case 'system': + messageLabel = 'System'; + messageClass = 'system'; + break; + case 'tool': + messageLabel = 'Tool Result'; + messageClass = 'tool-result'; + break; + } + + const hasToolCall = hasToolCalls || hasToolResults; + + return ( +
+ {layout === 'panel' && ( +
+ + {messageLabel} + + {msg.metadata && ( + + {msg.metadata.duration_ms && ( + {msg.metadata.duration_ms}ms + )} + {msg.metadata.total_cost_usd && ( + ${msg.metadata.total_cost_usd.toFixed(4)} + )} + + )} +
+ )} +
+ {(msg.role === 'assistant') ? ( + + ) : ( + (() => { + // Check if this is a user message with context + if (messageText.startsWith('Context: ') && messageText.includes('\n\nMessage: ')) { + const contextMatch = messageText.match(/^Context: (.+)\n\nMessage: (.+)$/s); + if (contextMatch) { + const contextFile = contextMatch[1]; + const actualMessage = contextMatch[2]; + + // Handle display for multiple images or single image + let displayFileName; + if (contextFile.includes(', ')) { + // Multiple images - show count + const paths = contextFile.split(', '); + displayFileName = `${paths.length} images in moodboard`; + } else { + // Single image - show just filename + displayFileName = contextFile.includes('.superdesign') + ? contextFile.split('.superdesign/')[1] || contextFile.split('/').pop() || contextFile + : contextFile.split('/').pop() || contextFile; + } + + return ( + <> +
+ @ + {displayFileName} +
+
{actualMessage}
+ + ); + } + } + return messageText; + })() + )} + {isStreaming && β–‹} +
+ {isLastUserMessage && ( +
+ Generating +
+ )} +
+ ); + }; + + // New function to handle multiple tool calls in a single message + const renderToolCalls = (msg: ChatMessage, index: number, findToolResult: (toolCallId: string) => any) => { + if (!Array.isArray(msg.content)) { + return
Invalid tool message content
; + } + + // Find ALL tool call parts + const toolCallParts = msg.content.filter(part => part.type === 'tool-call') as any[]; + + if (toolCallParts.length === 0) { + return
No tool calls found
; + } + + // Render each tool call separately + return ( +
+ {toolCallParts.map((toolCallPart, subIndex) => + renderSingleToolCall(toolCallPart, index, subIndex, findToolResult) + )} +
+ ); + }; + + // Updated function to render a single tool call with unique subIndex for state management + const renderSingleToolCall = (toolCallPart: any, messageIndex: number, subIndex: number, findToolResult: (toolCallId: string) => any) => { + try { + const toolName = toolCallPart.toolName || 'Unknown Tool'; + const toolInput = toolCallPart.args || {}; + const uniqueKey = `${messageIndex}_${subIndex}`; + + // Special handling for generateTheme tool calls + if (toolName === 'generateTheme') { + // For generateTheme, check if we have a tool result to determine completion + const toolCallId = toolCallPart.toolCallId; + const toolResultPart = findToolResult(toolCallId); + const hasResult = !!toolResultPart; + const resultIsError = toolResultPart?.isError || false; + + // Tool is loading if we don't have a result yet, or if metadata indicates loading + const isLoading = !hasResult || toolCallPart.metadata?.is_loading || false; + + // Extract theme data from tool input + const themeName = toolInput.theme_name || 'Untitled Theme'; + const reasoning = toolInput.reasoning_reference || ''; + const cssSheet = toolInput.cssSheet || ''; + + // Try to get CSS file path from metadata or result + let cssFilePath = null; + if (hasResult && !resultIsError) { + // Check both input and result for cssFilePath + cssFilePath = toolInput.cssFilePath || toolResultPart?.result?.cssFilePath; + } + + return ( +
+ + {resultIsError && ( +
+ ⚠️ Theme generation encountered an error. The preview above shows the input data. +
+ )} +
+ ); + } + + // Continue with existing generic tool rendering for other tools + const isExpanded = expandedTools[uniqueKey] || false; + const showFullResult = showFullContent[uniqueKey] || false; + const showFullInput = showFullContent[`${uniqueKey}_input`] || false; + const showFullPrompt = showFullContent[`${uniqueKey}_prompt`] || false; + + const description = toolInput.description || ''; + const command = toolInput.command || ''; + const prompt = toolInput.prompt || ''; + + // Tool result data - find from separate tool message + const toolCallId = toolCallPart.toolCallId; + const toolResultPart = findToolResult(toolCallId); + const hasResult = !!toolResultPart; + const resultIsError = toolResultPart?.isError || false; + + // Tool is loading if we don't have a result yet, or if metadata indicates loading + const isLoading = !hasResult || toolCallPart.metadata?.is_loading || false; + + const toolResult = toolResultPart ? + (typeof toolResultPart.result === 'string' ? toolResultPart.result : JSON.stringify(toolResultPart.result, null, 2)) : + ''; + + // Tool is complete when it has finished (regardless of errors) + const toolComplete = hasResult && !isLoading; + + // Get the countdown timer for this specific tool + const timerRemaining = toolTimers[uniqueKey] || 0; + + // Enhanced loading data + const estimatedDuration = toolCallPart.metadata?.estimated_duration || 90; + const elapsedTime = toolCallPart.metadata?.elapsed_time || 0; + const progressPercentage = toolCallPart.metadata?.progress_percentage || 0; + // Use timer state for remaining time, fallback to calculated if timer not started yet + const remainingTime = isLoading ? (timerRemaining > 0 ? timerRemaining : Math.max(0, estimatedDuration - elapsedTime)) : 0; + + // Format time display + const formatTime = (seconds: number): string => { + const mins = Math.floor(seconds / 60); + const secs = Math.floor(seconds % 60); + return `${mins}:${secs.toString().padStart(2, '0')}`; + }; + + // Get friendly tool name for display + const getFriendlyToolName = (name: string): string => { + const friendlyNames: { [key: string]: string } = { + 'mcp_taskmaster-ai_parse_prd': 'Parsing Requirements Document', + 'mcp_taskmaster-ai_analyze_project_complexity': 'Analyzing Project Complexity', + 'mcp_taskmaster-ai_expand_task': 'Expanding Task', + 'mcp_taskmaster-ai_expand_all': 'Expanding All Tasks', + 'mcp_taskmaster-ai_research': 'Researching Information', + 'codebase_search': 'Searching Codebase', + 'read_file': 'Reading File', + 'edit_file': 'Editing File', + 'run_terminal_cmd': 'Running Command' + }; + return friendlyNames[name] || name.replace(/mcp_|_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); + }; + + // Get helpful loading tips based on tool and progress + const getLoadingTip = (toolName: string, progress: number): string => { + const progressStage = progress < 25 ? 'early' : progress < 50 ? 'mid' : progress < 75 ? 'late' : 'final'; + + const tipsByTool: { [key: string]: { [stage: string]: string[] } } = { + 'mcp_taskmaster-ai_parse_prd': { + early: ['Analyzing requirements and identifying key features...', 'Breaking down complex requirements into manageable tasks...'], + mid: ['Structuring tasks based on dependencies and priorities...', 'Defining implementation details for each component...'], + late: ['Finalizing task relationships and estimates...', 'Optimizing task breakdown for efficient development...'], + final: ['Completing task generation and validation...', 'Almost ready with your project roadmap!'] + }, + 'mcp_taskmaster-ai_research': { + early: ['Gathering the latest information from multiple sources...', 'Searching for best practices and recent developments...'], + mid: ['Analyzing findings and filtering relevant information...', 'Cross-referencing multiple sources for accuracy...'], + late: ['Synthesizing research into actionable insights...', 'Preparing comprehensive research summary...'], + final: ['Finalizing research report with recommendations...', 'Almost done with your research!'] + }, + 'mcp_taskmaster-ai_expand_task': { + early: ['Breaking down the task into detailed subtasks...', 'Analyzing task complexity and dependencies...'], + mid: ['Defining implementation steps and requirements...', 'Creating detailed subtask specifications...'], + late: ['Optimizing subtask flow and dependencies...', 'Adding implementation details and strategies...'], + final: ['Finalizing subtask breakdown...', 'Your detailed implementation plan is almost ready!'] + } + }; + + const generalTips = { + early: ['AI is working hard to process your request...', 'Analyzing your requirements in detail...', 'Loading the best approach for your needs...'], + mid: ['Making good progress on your request...', 'Processing complex logic and relationships...', 'Halfway there! Building your solution...'], + late: ['Finalizing details and optimizations...', 'Almost finished with the heavy lifting...', 'Putting the finishing touches on your request...'], + final: ['Just a few more seconds...', 'Completing final validations...', 'Almost ready with your results!'] + }; + + const toolTips = tipsByTool[toolName] || generalTips; + const stageTips = toolTips[progressStage] || generalTips[progressStage]; + const randomIndex = Math.floor((progress / 10)) % stageTips.length; + + return stageTips[randomIndex]; + }; + + const toggleExpanded = () => { + setExpandedTools(prev => ({ + ...prev, + [uniqueKey]: !prev[uniqueKey] + })); + }; + + const toggleShowFullResult = () => { + setShowFullContent(prev => ({ + ...prev, + [uniqueKey]: !prev[uniqueKey] + })); + }; + + const toggleShowFullInput = () => { + setShowFullContent(prev => ({ + ...prev, + [`${uniqueKey}_input`]: !prev[`${uniqueKey}_input`] + })); + }; + + const toggleShowFullPrompt = () => { + setShowFullContent(prev => ({ + ...prev, + [`${uniqueKey}_prompt`]: !prev[`${uniqueKey}_prompt`] + })); + }; + + // Determine if content needs truncation + const MAX_PREVIEW = 300; + + // Result truncation + const resultNeedsTruncation = toolResult.length > MAX_PREVIEW; + const displayResult = resultNeedsTruncation && !showFullResult + ? toolResult.substring(0, MAX_PREVIEW) + '...' + : toolResult; + + // Input truncation + const inputString = JSON.stringify(toolInput, null, 2); + const inputNeedsTruncation = inputString.length > MAX_PREVIEW; + const displayInput = inputNeedsTruncation && !showFullInput + ? inputString.substring(0, MAX_PREVIEW) + '...' + : inputString; + + // Prompt truncation + const promptNeedsTruncation = prompt.length > MAX_PREVIEW; + const displayPrompt = promptNeedsTruncation && !showFullPrompt + ? prompt.substring(0, MAX_PREVIEW) + '...' + : prompt; + + return ( +
+
+
+ + {isLoading ? ( +
+
+
+ ) : ( + + )} +
+
+ {getFriendlyToolName(toolName)} + {description && ( + {description} + )} + {isLoading && ( + + {formatTime(remainingTime)} remaining + + )} +
+
+
+ {toolComplete && ( + + + + )} + +
+
+ {isExpanded && ( +
+ {isLoading && ( +
+
+ + + {getLoadingTip(toolName, Math.floor((estimatedDuration - remainingTime) / estimatedDuration * 100))} + +
+
+ )} + {command && ( +
+ Command: + {command} +
+ )} + {Object.keys(toolInput).length > 0 && ( +
+ Input: +
+
+                                            {displayInput}
+                                        
+ {inputNeedsTruncation && ( + + )} +
+
+ )} + {prompt && ( +
+ Prompt: +
+
+                                            {displayPrompt}
+                                        
+ {promptNeedsTruncation && ( + + )} +
+
+ )} + {hasResult && ( +
+ + {resultIsError ? 'Error Result:' : 'Result:'} + +
+
+                                            {displayResult}
+                                        
+ {resultNeedsTruncation && ( + + )} +
+
+ )} +
+ )} +
+ ); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return ( +
+
+
+ ⚠️ +
+ Error rendering tool: {toolCallPart.toolName || 'Unknown'} + {errorMessage} +
+
+
+
+ ); + } + }; + + const renderErrorMessage = (msg: ChatMessage, index: number, setChatHistory: React.Dispatch>) => { + const handleActionClick = (action: { text: string; command: string; args?: string }) => { + console.log('Action clicked:', action); + vscode.postMessage({ + command: 'executeAction', + actionCommand: action.command, + actionArgs: action.args + }); + }; + + const handleCloseError = () => { + // Remove this error message from chat history + setChatHistory((prev: ChatMessage[]) => prev.filter((_, i: number) => i !== index)); + }; + + return ( +
+ {layout === 'panel' && ( +
+ Error + +
+ )} +
+
+ {typeof msg.content === 'string' ? msg.content : 'Error occurred'} +
+ {msg.metadata?.actions && msg.metadata.actions.length > 0 && ( +
+ {msg.metadata.actions.map((action, actionIndex) => ( + + ))} +
+ )} + {layout === 'sidebar' && ( + + )} +
+
+ ); + }; + + const renderPlaceholder = () => ( +
+
+
+

+ Cursor/Windsurf/Claude Code rules already added, prompt Cursor/Windsurf/Claude Code to design UI like Help me design a calculator UI and preview the UI in Superdesign canvas by Cmd+Shift+P 'Superdesign: Open canvas view' +

+
OR
+

+ You can start with native superdesign agent chat below (We have better UX) +

+
+
+
+ ); + + return ( +
+ + {layout === 'panel' && ( +
+

πŸ’¬ Chat with Claude

+

Ask Claude anything about code, design, or development!

+ +
+ )} + +
+
+ {showWelcome ? ( + + ) : hasConversationMessages() ? ( + <> + {chatHistory + .filter(msg => { + // All messages are now displayed since we use CoreMessage format + return true; + }) + .map(renderChatMessage) + } + + ) : renderPlaceholder()} +
+ + {!showWelcome && ( +
+ {/* Context Display */} + {currentContext ? ( +
+ + {currentContext.type === 'image' ? 'πŸ–ΌοΈ' : currentContext.type === 'images' ? 'πŸ–ΌοΈ' : 'πŸ“„'} + + + {currentContext.type === 'image' ? 'Image: ' : currentContext.type === 'images' ? 'Images: ' : 'Context: '} + {currentContext.type === 'images' ? + `${currentContext.fileName.split(', ').length} images in moodboard` : + (currentContext.fileName.includes('.superdesign') + ? currentContext.fileName.split('.superdesign/')[1] || currentContext.fileName.split('/').pop() || currentContext.fileName + : currentContext.fileName.split('/').pop() || currentContext.fileName + ) + } + + +
+ ) : null} + + {/* Upload Progress */} + {uploadingImages.length > 0 && ( +
+ {uploadingImages.length > 1 && ( +
+ Uploading {uploadingImages.length} images... +
+ )} + {uploadingImages.map((fileName, index) => ( +
+ πŸ“Ž + Uploading {fileName}... +
+
+ ))} +
+ )} + + {/* Add Context Button */} + {!currentContext && uploadingImages.length === 0 && ( + + )} + + {/* Input Area */} +
+