import * as vscode from 'vscode'; import { ClaudeCodeService } from '../services/claudeCodeService'; import { ChatMessageService } from '../services/chatMessageService'; import { generateWebviewHtml } from '../templates/webviewTemplate'; import { WebviewContext } from '../types/context'; import { AgentService } from '../types/agent'; export class ChatSidebarProvider implements vscode.WebviewViewProvider { public static readonly VIEW_TYPE = 'superdesign.chatView'; private _view?: vscode.WebviewView; private messageHandler: ChatMessageService; private customMessageHandler?: (message: any) => void; constructor( private readonly _extensionUri: vscode.Uri, private readonly agentService: AgentService, private readonly outputChannel: vscode.OutputChannel ) { this.messageHandler = new ChatMessageService(agentService, outputChannel); } public setMessageHandler(handler: (message: any) => void) { this.customMessageHandler = handler; } public sendMessage(message: any) { if (this._view) { this._view.webview.postMessage(message); } } public resolveWebviewView( webviewView: vscode.WebviewView, _context: vscode.WebviewViewResolveContext, _token: vscode.CancellationToken, ) { this._view = webviewView; webviewView.webview.options = { enableScripts: true, localResourceRoots: [ vscode.Uri.joinPath(this._extensionUri, 'dist'), vscode.Uri.joinPath(this._extensionUri, 'src', 'assets') ] }; const webviewContext: WebviewContext = { layout: 'sidebar', extensionUri: this._extensionUri.toString() }; webviewView.webview.html = generateWebviewHtml( webviewView.webview, this._extensionUri, webviewContext ); // Handle messages from the webview webviewView.webview.onDidReceiveMessage( async (message) => { // First try custom message handler for auto-canvas functionality if (this.customMessageHandler) { this.customMessageHandler(message); } // Then handle regular chat messages switch (message.command) { case 'chatMessage': await this.messageHandler.handleChatMessage(message, webviewView.webview); break; case 'stopChat': await this.messageHandler.stopCurrentChat(webviewView.webview); break; case 'executeAction': // Execute command from error action buttons console.log('Executing action:', message.actionCommand, message.actionArgs); if (message.actionArgs) { await vscode.commands.executeCommand(message.actionCommand, message.actionArgs); } else { await vscode.commands.executeCommand(message.actionCommand); } break; case 'getBase64Image': // Forward to extension for image conversion // This will be handled by extension.ts break; case 'getCurrentProvider': await this.handleGetCurrentProvider(webviewView.webview); break; case 'changeProvider': await this.handleChangeProvider(message.model, webviewView.webview); break; } } ); } private async handleGetCurrentProvider(webview: vscode.Webview) { const config = vscode.workspace.getConfiguration('superdesign'); const currentProvider = config.get('aiModelProvider', 'anthropic'); const currentModel = config.get('aiModel'); // If no specific model is set, use defaults let defaultModel: string; switch (currentProvider) { case 'openai': defaultModel = 'gpt-4o'; break; case 'openrouter': defaultModel = 'anthropic/claude-3-7-sonnet-20250219'; break; case 'anthropic': default: defaultModel = 'claude-3-5-sonnet-20241022'; break; } webview.postMessage({ command: 'currentProviderResponse', provider: currentProvider, model: currentModel || defaultModel }); } private async handleChangeProvider(model: string, webview: vscode.Webview) { try { const config = vscode.workspace.getConfiguration('superdesign'); // Determine provider and API key based on model let provider: string; let apiKeyKey: string; let configureCommand: string; let displayName: string; if (model === 'openai-auto') { provider = 'openai'; apiKeyKey = 'openaiApiKey'; configureCommand = 'superdesign.configureOpenAIApiKey'; displayName = 'OpenAI (AUTO)'; } else if (model.includes('/')) { // OpenRouter model (contains slash like "openai/gpt-4o") provider = 'openrouter'; apiKeyKey = 'openrouterApiKey'; configureCommand = 'superdesign.configureOpenRouterApiKey'; displayName = `OpenRouter (${this.getModelDisplayName(model)})`; } else if (model.startsWith('claude-')) { provider = 'anthropic'; apiKeyKey = 'anthropicApiKey'; configureCommand = 'superdesign.configureApiKey'; displayName = `Anthropic (${this.getModelDisplayName(model)})`; } else { provider = 'openai'; apiKeyKey = 'openaiApiKey'; configureCommand = 'superdesign.configureOpenAIApiKey'; displayName = `OpenAI (${this.getModelDisplayName(model)})`; } // Update both provider and specific model 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); } } // Notify webview of successful change webview.postMessage({ command: 'providerChanged', provider: provider, model: model }); } catch (error) { vscode.window.showErrorMessage(`Failed to update AI model: ${error}`); } } private getModelDisplayName(model: string): string { const modelNames: { [key: string]: string } = { // OpenAI models 'gpt-4.1': 'GPT-4.1', 'gpt-4.1-mini': 'GPT-4.1 Mini', 'gpt-4.1-nano': 'GPT-4.1 Nano', 'gpt-4o': 'GPT-4o', 'gpt-4o-mini': 'GPT-4o Mini', // Anthropic models 'claude-4-opus-20250514': 'Claude 4 Opus', 'claude-4-sonnet-20250514': 'Claude 4 Sonnet', 'claude-3-7-sonnet-20250219': 'Claude 3.7 Sonnet', 'claude-3-5-sonnet-20241022': 'Claude 3.5 Sonnet', 'claude-3-opus-20240229': 'Claude 3 Opus', 'claude-3-sonnet-20240229': 'Claude 3 Sonnet', 'claude-3-haiku-20240307': 'Claude 3 Haiku', // OpenRouter - Google models 'google/gemini-2.5-pro': 'Gemini 2.5 Pro', 'google/gemini-2.5-flash': 'Gemini 2.5 Flash', 'google/gemini-2.5-pro-preview-06-05': 'Gemini 2.5 Pro Preview', 'google/gemini-2.5-flash-preview-05-20': 'Gemini 2.5 Flash Preview', 'google/gemini-2.5-pro-preview-03-25': 'Gemini 2.5 Pro Preview (Mar)', 'google/gemini-2.0-flash-001': 'Gemini 2.0 Flash', 'google/gemini-2.0-flash-exp': 'Gemini 2.0 Flash Exp', 'google/gemini-2.0-flash-lite-001': 'Gemini 2.0 Flash Lite', 'google/gemma-3-27b-it': 'Gemma 3 27B', 'google/gemma-3-12b-it': 'Gemma 3 12B', 'google/gemma-3-4b-it': 'Gemma 3 4B', 'google/gemma-2-27b-it': 'Gemma 2 27B', 'google/gemma-2-9b-it': 'Gemma 2 9B', 'google/gemini-flash-1.5': 'Gemini Flash 1.5', 'google/gemini-flash-1.5-8b': 'Gemini Flash 1.5 8B', 'google/gemini-pro-1.5': 'Gemini Pro 1.5', // OpenRouter - Meta models 'meta-llama/llama-4-maverick-17b-128e-instruct': 'Llama 4 Maverick 17B', 'meta-llama/llama-4-scout-17b-16e-instruct': 'Llama 4 Scout 17B', 'meta-llama/llama-3.3-70b-instruct': 'Llama 3.3 70B', 'meta-llama/llama-3.2-90b-vision-instruct': 'Llama 3.2 90B Vision', 'meta-llama/llama-3.2-11b-vision-instruct': 'Llama 3.2 11B Vision', 'meta-llama/llama-3.2-3b-instruct': 'Llama 3.2 3B', 'meta-llama/llama-3.2-1b-instruct': 'Llama 3.2 1B', 'meta-llama/llama-3.1-405b-instruct': 'Llama 3.1 405B', 'meta-llama/llama-3.1-70b-instruct': 'Llama 3.1 70B', 'meta-llama/llama-3.1-8b-instruct': 'Llama 3.1 8B', 'meta-llama/llama-3-70b-instruct': 'Llama 3 70B', 'meta-llama/llama-3-8b-instruct': 'Llama 3 8B', 'meta-llama/llama-guard-4-12b': 'Llama Guard 4 12B', 'meta-llama/llama-guard-3-8b': 'Llama Guard 3 8B', 'meta-llama/llama-guard-2-8b': 'Llama Guard 2 8B', // OpenRouter - DeepSeek models 'deepseek/deepseek-r1': 'DeepSeek R1', 'deepseek/deepseek-r1-0528': 'DeepSeek R1 0528', 'deepseek/deepseek-r1-distill-llama-70b': 'DeepSeek R1 Distill Llama 70B', 'deepseek/deepseek-r1-distill-llama-8b': 'DeepSeek R1 Distill Llama 8B', 'deepseek/deepseek-r1-distill-qwen-32b': 'DeepSeek R1 Distill Qwen 32B', 'deepseek/deepseek-r1-distill-qwen-14b': 'DeepSeek R1 Distill Qwen 14B', 'deepseek/deepseek-r1-distill-qwen-7b': 'DeepSeek R1 Distill Qwen 7B', 'deepseek/deepseek-r1-distill-qwen-1.5b': 'DeepSeek R1 Distill Qwen 1.5B', 'deepseek/deepseek-chat-v3': 'DeepSeek Chat V3', 'deepseek/deepseek-v3-base': 'DeepSeek V3 Base', 'deepseek/deepseek-prover-v2': 'DeepSeek Prover V2', // OpenRouter - Mistral models 'mistralai/mistral-small-3.2-24b-instruct-2506': 'Mistral Small 3.2 24B', 'mistralai/magistral-small-2506': 'Magistral Small', 'mistralai/magistral-medium-2506': 'Magistral Medium', 'mistralai/devstral-small-2505': 'Devstral Small', 'mistralai/mistral-medium-3': 'Mistral Medium 3', 'mistralai/mistral-small-3.1-24b-instruct-2503': 'Mistral Small 3.1 24B', 'mistralai/mistral-saba-2502': 'Mistral Saba', 'mistralai/mistral-small-24b-instruct-2501': 'Mistral Small 24B', 'mistralai/codestral-2501': 'Codestral', 'mistralai/mistral-large-2411': 'Mistral Large 2411', 'mistralai/mistral-large-2407': 'Mistral Large 2407', 'mistralai/pixtral-large-2411': 'Pixtral Large', 'mistralai/pixtral-12b': 'Pixtral 12B', 'mistralai/ministral-8b': 'Ministral 8B', 'mistralai/ministral-3b': 'Ministral 3B', 'mistralai/mistral-nemo': 'Mistral Nemo', 'mistralai/mistral-large': 'Mistral Large', 'mistralai/mixtral-8x22b-instruct': 'Mixtral 8x22B', 'mistralai/mixtral-8x7b-instruct': 'Mixtral 8x7B', 'mistralai/mistral-7b-instruct': 'Mistral 7B', // OpenRouter - xAI models 'x-ai/grok-3': 'Grok 3', 'x-ai/grok-3-mini': 'Grok 3 Mini', 'x-ai/grok-3-beta': 'Grok 3 Beta', 'x-ai/grok-3-mini-beta': 'Grok 3 Mini Beta', 'x-ai/grok-2-vision-1212': 'Grok 2 Vision', 'x-ai/grok-2-1212': 'Grok 2', 'x-ai/grok-vision-beta': 'Grok Vision Beta', // OpenRouter - Qwen models 'qwen/qwen3-235b-a22b-04-28': 'Qwen3 235B', 'qwen/qwen3-32b-04-28': 'Qwen3 32B', 'qwen/qwen3-30b-a3b-04-28': 'Qwen3 30B', 'qwen/qwen3-14b-04-28': 'Qwen3 14B', 'qwen/qwen3-8b-04-28': 'Qwen3 8B', 'qwen/qwen2.5-vl-72b-instruct': 'Qwen2.5 VL 72B', 'qwen/qwen2.5-vl-32b-instruct': 'Qwen2.5 VL 32B', 'qwen/qwen-2.5-coder-32b-instruct': 'Qwen 2.5 Coder 32B', 'qwen/qwen-2.5-72b-instruct': 'Qwen 2.5 72B', 'qwen/qwen-2.5-7b-instruct': 'Qwen 2.5 7B', 'qwen/qwen-2-72b-instruct': 'Qwen 2 72B', 'qwen/qwen-2-vl-7b-instruct': 'Qwen 2 VL 7B', 'qwen/qwq-32b': 'QwQ 32B', 'qwen/qwq-32b-preview': 'QwQ 32B Preview', 'qwen/qwen-vl-max-2025-01-25': 'Qwen VL Max', 'qwen/qwen-vl-plus': 'Qwen VL Plus', 'qwen/qwen-max-2025-01-25': 'Qwen Max', 'qwen/qwen-plus-2025-01-25': 'Qwen Plus', 'qwen/qwen-turbo-2024-11-01': 'Qwen Turbo', // OpenRouter - Perplexity models 'perplexity/sonar-reasoning-pro': 'Sonar Reasoning Pro', 'perplexity/sonar-pro': 'Sonar Pro', 'perplexity/sonar-deep-research': 'Sonar Deep Research', 'perplexity/sonar-reasoning': 'Sonar Reasoning', 'perplexity/sonar': 'Sonar', 'perplexity/r1-1776': 'R1-1776', 'perplexity/llama-3.1-sonar-large-128k-online': 'Llama 3.1 Sonar Large Online', 'perplexity/llama-3.1-sonar-small-128k-online': 'Llama 3.1 Sonar Small Online', // OpenRouter - Microsoft models 'microsoft/phi-4-reasoning-plus-04-30': 'Phi-4 Reasoning Plus', 'microsoft/mai-ds-r1': 'MAI-DS-R1', 'microsoft/phi-4-multimodal-instruct': 'Phi-4 Multimodal', 'microsoft/phi-4': 'Phi-4', 'microsoft/phi-3.5-mini-128k-instruct': 'Phi-3.5 Mini', 'microsoft/phi-3-medium-128k-instruct': 'Phi-3 Medium', 'microsoft/phi-3-mini-128k-instruct': 'Phi-3 Mini', 'microsoft/wizardlm-2-8x22b': 'WizardLM-2 8x22B', // OpenRouter - NVIDIA models 'nvidia/llama-3.3-nemotron-super-49b-v1': 'Llama 3.3 Nemotron Super 49B', 'nvidia/llama-3.1-nemotron-ultra-253b-v1': 'Llama 3.1 Nemotron Ultra 253B', 'nvidia/llama-3.1-nemotron-70b-instruct': 'Llama 3.1 Nemotron 70B', // OpenRouter - Other models 'minimax/minimax-01': 'MiniMax-01', 'minimax/minimax-m1': 'MiniMax-M1', 'liquid/lfm-40b': 'LFM 40B', 'liquid/lfm-7b': 'LFM 7B', 'liquid/lfm-3b': 'LFM 3B', 'cohere/command-a-03-2025': 'Command A', 'cohere/command-r7b-12-2024': 'Command R7B', 'cohere/command-r-plus': 'Command R Plus', 'cohere/command-r': 'Command R', 'amazon/nova-pro-v1': 'Nova Pro', 'amazon/nova-lite-v1': 'Nova Lite', 'amazon/nova-micro-v1': 'Nova Micro', 'ai21/jamba-1.6-large': 'Jamba 1.6 Large', 'ai21/jamba-1.6-mini': 'Jamba 1.6 Mini', '01-ai/yi-large': 'Yi Large', 'inflection/inflection-3-productivity': 'Inflection 3 Productivity', 'inflection/inflection-3-pi': 'Inflection 3 Pi', 'rekaai/reka-flash-3': 'Reka Flash 3', 'openrouter/auto': 'Auto (Best Available)' }; return modelNames[model] || model; } }