import * as vscode from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; export class ChatViewProvider implements vscode.WebviewViewProvider { public static readonly viewType = 'aiChatView'; private _view?: vscode.WebviewView; private _logger: (message: string) => void; constructor( private readonly _extensionUri: vscode.Uri, logger?: (message: string) => void ) { this._logger = logger || ((message: string) => console.log(message)); } public resolveWebviewView( webviewView: vscode.WebviewView, _context: vscode.WebviewViewResolveContext, _token: vscode.CancellationToken, ) { this._view = webviewView; this._logger(`WebView视图已创建: ${ChatViewProvider.viewType}`); webviewView.webview.options = { // 启用JavaScript enableScripts: true, localResourceRoots: [ this._extensionUri ] }; webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); this._logger('WebView HTML内容已设置'); // 处理来自Webview的消息 webviewView.webview.onDidReceiveMessage(message => { this._logger(`收到WebView消息: ${message.type}`); switch (message.type) { case 'getSettings': this._logger(`处理getSettings请求`); this._sendSettings(); break; case 'saveSettings': this._logger(`保存设置: ${JSON.stringify(message.settings, (key, value) => key === 'apiKey' ? '***' : value)}`); this._saveSettings(message.settings); break; case 'log': // 为SettingsModal相关日志添加特殊标记 if (message.message && message.message.includes('SettingsModal')) { this._logger(`【SettingsModal日志】: ${message.message}`); } // 为WebSocket连接相关日志添加更详细的输出 else if (message.message && ( message.message.includes('WebSocket') || message.message.includes('ChatStore') || message.message.includes('initWebSocketService') )) { this._logger(`WebSocket日志: ${message.message}`); } // 为重要日志添加特殊标记 else if (message.message && message.message.includes('【重要】')) { this._logger(`⚠️ ${message.message}`); } else { this._logger(`WebView日志: ${message.message}`); } break; case 'error': this._logger(`WebView错误: ${message.message}`); if (message.stack) { this._logger(`错误堆栈: ${message.stack}`); } break; } }); // 初始化时发送主题颜色 this._sendThemeColors(); // 监听主题变化 vscode.window.onDidChangeActiveColorTheme(() => { this._sendThemeColors(); }); } // 发送主题颜色 private _sendThemeColors() { if (!this._view) return; // 获取当前主题颜色 const theme = vscode.window.activeColorTheme; const isDark = theme.kind === vscode.ColorThemeKind.Dark; const isLight = theme.kind === vscode.ColorThemeKind.Light; // 获取常用颜色 const colors = { // 基础颜色 foreground: this._getColor('foreground'), background: this._getColor('editor.background'), // 文本颜色 descriptionForeground: this._getColor('descriptionForeground'), errorForeground: this._getColor('errorForeground'), // 按钮颜色 buttonBackground: this._getColor('button.background'), buttonForeground: this._getColor('button.foreground'), buttonHoverBackground: this._getColor('button.hoverBackground'), // 输入框颜色 inputBackground: this._getColor('input.background'), inputForeground: this._getColor('input.foreground'), inputPlaceholderForeground: this._getColor('input.placeholderForeground'), inputBorder: this._getColor('input.border'), // 链接颜色 linkForeground: this._getColor('textLink.foreground'), linkActiveForeground: this._getColor('textLink.activeForeground'), // 其他界面元素 panelBorder: this._getColor('panel.border'), // 主题类型 isDark, isLight }; this._view.webview.postMessage({ type: 'themeColors', colors }); } // 获取主题颜色 private _getColor(colorId: string): string | undefined { // 提供一组默认颜色值 const defaultColors: Record = { 'foreground': '#cccccc', 'editor.background': '#1e1e1e', 'descriptionForeground': '#999999', 'errorForeground': '#f48771', 'button.background': '#0e639c', 'button.foreground': '#ffffff', 'button.hoverBackground': '#1177bb', 'input.background': '#3c3c3c', 'input.foreground': '#cccccc', 'input.placeholderForeground': '#a6a6a6', 'input.border': '#3c3c3c', 'textLink.foreground': '#3794ff', 'textLink.activeForeground': '#3794ff', 'panel.border': '#80808059' }; try { // 尝试从配置中获取颜色 const themeOverride = vscode.workspace.getConfiguration('workbench') .get>('colorCustomizations', {}); // 首先尝试从用户配置的覆盖颜色中获取 if (themeOverride[colorId]) { return themeOverride[colorId]; } // 然后从默认颜色中获取 return defaultColors[colorId] || '#cccccc'; } catch (error) { this._logger(`获取颜色失败 ${colorId}: ${error}`); // 回退到默认颜色 return defaultColors[colorId] || '#cccccc'; } } // 发送设置 private _sendSettings() { if (!this._view) return; const config = vscode.workspace.getConfiguration('aiChat'); const settings = { apiHost: config.get('apiHost', ''), apiKey: config.get('apiKey', '') }; this._view.webview.postMessage({ type: 'settings', settings }); } // 保存设置 private _saveSettings(settings: { apiHost: string; apiKey: string }) { if (!settings) return; this._logger(`准备保存设置: apiHost=${settings.apiHost}, apiKey=***`); const config = vscode.workspace.getConfiguration('aiChat'); let hasChanges = false; // 保存成功标志 let success = true; let errorMessage = ''; // 检查并更新API主机 const updateHost = async () => { if (settings.apiHost === config.get('apiHost')) return; hasChanges = true; this._logger('更新API主机设置'); try { await config.update('apiHost', settings.apiHost, vscode.ConfigurationTarget.Global); this._logger('已更新API主机设置'); } catch (error) { success = false; errorMessage = error instanceof Error ? error.message : '更新API主机失败'; this._logger(`更新API主机设置失败: ${errorMessage}`); } }; // 检查并更新API密钥 const updateKey = async () => { if (settings.apiKey === config.get('apiKey')) return; hasChanges = true; this._logger('更新API密钥设置'); try { await config.update('apiKey', settings.apiKey, vscode.ConfigurationTarget.Global); this._logger('已更新API密钥设置'); } catch (error) { success = false; errorMessage = error instanceof Error ? error.message : '更新API密钥失败'; this._logger(`更新API密钥设置失败: ${errorMessage}`); } }; // 执行所有更新操作 const updateAll = async () => { await updateHost(); await updateKey(); // 通知前端设置已保存 if (this._view) { if (success) { this._logger('所有设置保存成功'); this._view.webview.postMessage({ type: 'settingsSaved', success: true }); } else { this._logger(`保存设置失败: ${errorMessage}`); this._view.webview.postMessage({ type: 'settingsSaved', success: false, error: `保存设置失败: ${errorMessage}` }); } } }; // 启动更新过程 updateAll().catch(error => { this._logger(`保存设置过程中发生未捕获异常: ${error}`); // 确保前端收到失败消息 if (this._view) { this._view.webview.postMessage({ type: 'settingsSaved', success: false, error: '保存设置过程中发生错误' }); } }); } // 生成Webview HTML内容 private _getHtmlForWebview(webview: vscode.Webview) { // 在生产环境中,使用打包后的Webview文件 const distPath = path.join(this._extensionUri.fsPath, 'webview', 'dist'); // 生成随机nonce以增强安全性 const nonce = this._getNonce(); // 检查dist目录是否存在 if (fs.existsSync(distPath)) { try { // 读取生产环境的index.html let indexHtml = fs.readFileSync( path.join(distPath, 'index.html'), 'utf-8' ); // 添加CSP策略和nonce if (!indexHtml.includes('content-security-policy')) { indexHtml = indexHtml.replace( '', ` ` ); } // 确保有VSCode API脚本 if (!indexHtml.includes('acquireVsCodeApi')) { indexHtml = indexHtml.replace( '', ` ` ); } // 将资源路径替换为Webview可访问的URI indexHtml = indexHtml.replace( /(href|src)="([^"]*)"/g, (_, attribute, url) => { // 跳过外部URL或数据URI if (url.startsWith('http') || url.startsWith('data:')) { return `${attribute}="${url}"`; } // 转换为Webview URI const resourceUri = webview.asWebviewUri( vscode.Uri.joinPath(this._extensionUri, 'webview', 'dist', url) ); // 为脚本添加nonce if (attribute === 'src' && url.endsWith('.js')) { return `${attribute}="${resourceUri}" nonce="${nonce}"`; } return `${attribute}="${resourceUri}"`; } ); this._logger('WebView HTML内容已生成,包含VSCode API初始化脚本'); return indexHtml; } catch (error) { this._logger(`生成WebView HTML时出错: ${error}`); return this._getFallbackHtml(webview, nonce); } } else { return this._getFallbackHtml(webview, nonce); } } // 获取回退HTML内容 private _getFallbackHtml(webview: vscode.Webview, nonce: string) { // 开发环境下使用占位符HTML return ` AI Chat

AI Chat

开发模式:请运行 "npm run webview:build" 来构建Webview前端,或在开发时使用 "npm run dev" 进行热重载开发。

`; } // 生成随机nonce private _getNonce() { let text = ''; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for (let i = 0; i < 32; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; } }