33 */
44
55import * as vscode from 'vscode' ;
6+ import * as path from 'path' ;
67import { LLMService , ModelConfig } from '../bridge/mpp-core' ;
78
89/**
@@ -27,10 +28,12 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
2728
2829 webviewView . webview . options = {
2930 enableScripts : true ,
30- localResourceRoots : [ this . context . extensionUri ]
31+ localResourceRoots : [
32+ vscode . Uri . joinPath ( this . context . extensionUri , 'dist' , 'webview' )
33+ ]
3134 } ;
3235
33- webviewView . webview . html = this . getHtmlContent ( ) ;
36+ webviewView . webview . html = this . getHtmlContent ( webviewView . webview ) ;
3437
3538 // Handle messages from webview
3639 webviewView . webview . onDidReceiveMessage ( async ( message ) => {
@@ -133,131 +136,99 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
133136 this . postMessage ( { type : 'historyCleared' } ) ;
134137 }
135138
136- private getHtmlContent ( ) : string {
139+ private getHtmlContent ( webview : vscode . Webview ) : string {
140+ // Check if React build exists
141+ const webviewPath = vscode . Uri . joinPath ( this . context . extensionUri , 'dist' , 'webview' ) ;
142+ const scriptUri = webview . asWebviewUri ( vscode . Uri . joinPath ( webviewPath , 'assets' , 'index.js' ) ) ;
143+ const styleUri = webview . asWebviewUri ( vscode . Uri . joinPath ( webviewPath , 'assets' , 'index.css' ) ) ;
144+
145+ // Use nonce for security
146+ const nonce = this . getNonce ( ) ;
147+
148+ // Try to use React build, fallback to inline HTML
137149 return `<!DOCTYPE html>
138150<html lang="en">
139151<head>
140152 <meta charset="UTF-8">
141153 <meta name="viewport" content="width=device-width, initial-scale=1.0">
154+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${ webview . cspSource } 'unsafe-inline'; script-src 'nonce-${ nonce } ';">
155+ <link rel="stylesheet" href="${ styleUri } ">
142156 <title>AutoDev Chat</title>
143- <style>
144- * { box-sizing: border-box; margin: 0; padding: 0; }
145- body {
146- font-family: var(--vscode-font-family);
147- font-size: var(--vscode-font-size);
148- color: var(--vscode-foreground);
149- background: var(--vscode-editor-background);
150- height: 100vh;
151- display: flex;
152- flex-direction: column;
153- }
154- #messages {
155- flex: 1;
156- overflow-y: auto;
157- padding: 12px;
158- }
159- .message {
160- margin-bottom: 12px;
161- padding: 8px 12px;
162- border-radius: 6px;
163- max-width: 90%;
164- }
165- .user { background: var(--vscode-button-background); margin-left: auto; }
166- .assistant { background: var(--vscode-editor-inactiveSelectionBackground); }
167- .error { background: var(--vscode-inputValidation-errorBackground); color: var(--vscode-inputValidation-errorForeground); }
168- #input-container {
169- padding: 12px;
170- border-top: 1px solid var(--vscode-panel-border);
171- display: flex;
172- gap: 8px;
173- }
174- #input {
175- flex: 1;
176- padding: 8px;
177- border: 1px solid var(--vscode-input-border);
178- background: var(--vscode-input-background);
179- color: var(--vscode-input-foreground);
180- border-radius: 4px;
181- resize: none;
182- }
183- button {
184- padding: 8px 16px;
185- background: var(--vscode-button-background);
186- color: var(--vscode-button-foreground);
187- border: none;
188- border-radius: 4px;
189- cursor: pointer;
190- }
191- button:hover { background: var(--vscode-button-hoverBackground); }
192- .typing { opacity: 0.7; font-style: italic; }
193- </style>
194157</head>
195158<body>
196- <div id="messages"></div>
197- <div id="input-container">
198- <textarea id="input" rows="2" placeholder="Ask AutoDev..."></textarea>
199- <button id="send">Send</button>
200- </div>
201- <script>
202- const vscode = acquireVsCodeApi();
203- const messagesEl = document.getElementById('messages');
204- const inputEl = document.getElementById('input');
205- const sendBtn = document.getElementById('send');
206- let currentResponse = null;
207-
208- function addMessage(role, content) {
209- const div = document.createElement('div');
210- div.className = 'message ' + role;
211- div.textContent = content;
212- messagesEl.appendChild(div);
213- messagesEl.scrollTop = messagesEl.scrollHeight;
214- return div;
215- }
216-
217- sendBtn.onclick = () => {
218- const content = inputEl.value.trim();
219- if (content) {
220- vscode.postMessage({ type: 'sendMessage', content });
221- inputEl.value = '';
222- }
223- };
224-
225- inputEl.onkeydown = (e) => {
226- if (e.key === 'Enter' && !e.shiftKey) {
227- e.preventDefault();
228- sendBtn.click();
159+ <div id="root"></div>
160+ <script nonce="${ nonce } " src="${ scriptUri } "></script>
161+ <script nonce="${ nonce } ">
162+ // Fallback if React bundle not loaded
163+ if (!document.getElementById('root').hasChildNodes()) {
164+ document.getElementById('root').innerHTML = \`
165+ <div style="height: 100vh; display: flex; flex-direction: column; font-family: var(--vscode-font-family); color: var(--vscode-foreground); background: var(--vscode-editor-background);">
166+ <div id="messages" style="flex: 1; overflow-y: auto; padding: 12px;"></div>
167+ <div style="padding: 12px; border-top: 1px solid var(--vscode-panel-border); display: flex; gap: 8px;">
168+ <textarea id="input" rows="2" placeholder="Ask AutoDev..." style="flex: 1; padding: 8px; border: 1px solid var(--vscode-input-border); background: var(--vscode-input-background); color: var(--vscode-input-foreground); border-radius: 4px; resize: none;"></textarea>
169+ <button id="send" style="padding: 8px 16px; background: var(--vscode-button-background); color: var(--vscode-button-foreground); border: none; border-radius: 4px; cursor: pointer;">Send</button>
170+ </div>
171+ </div>
172+ \`;
173+
174+ const vscode = acquireVsCodeApi();
175+ const messagesEl = document.getElementById('messages');
176+ const inputEl = document.getElementById('input');
177+ const sendBtn = document.getElementById('send');
178+ let currentResponse = null;
179+
180+ function addMessage(role, content) {
181+ const div = document.createElement('div');
182+ div.className = 'message ' + role;
183+ div.style.cssText = 'margin-bottom: 12px; padding: 8px 12px; border-radius: 6px; max-width: 90%;';
184+ if (role === 'user') div.style.cssText += 'background: var(--vscode-button-background); margin-left: auto;';
185+ else if (role === 'assistant') div.style.cssText += 'background: var(--vscode-editor-inactiveSelectionBackground);';
186+ else div.style.cssText += 'background: var(--vscode-inputValidation-errorBackground); color: var(--vscode-inputValidation-errorForeground);';
187+ div.textContent = content;
188+ messagesEl.appendChild(div);
189+ messagesEl.scrollTop = messagesEl.scrollHeight;
190+ return div;
229191 }
230- };
231192
232- window.addEventListener('message', (e) => {
233- const msg = e.data;
234- switch (msg.type) {
235- case 'userMessage':
236- addMessage('user', msg.content);
237- break;
238- case 'startResponse':
239- currentResponse = addMessage('assistant', '');
240- currentResponse.classList.add('typing');
241- break;
242- case 'responseChunk':
243- if (currentResponse) currentResponse.textContent += msg.content;
244- messagesEl.scrollTop = messagesEl.scrollHeight;
245- break;
246- case 'endResponse':
247- if (currentResponse) currentResponse.classList.remove('typing');
248- currentResponse = null;
249- break;
250- case 'error':
251- addMessage('error', msg.content);
252- break;
253- case 'historyCleared':
254- messagesEl.innerHTML = '';
255- break;
256- }
257- });
193+ sendBtn.onclick = () => {
194+ const content = inputEl.value.trim();
195+ if (content) {
196+ vscode.postMessage({ type: 'sendMessage', content });
197+ inputEl.value = '';
198+ }
199+ };
200+
201+ inputEl.onkeydown = (e) => {
202+ if (e.key === 'Enter' && !e.shiftKey) {
203+ e.preventDefault();
204+ sendBtn.click();
205+ }
206+ };
207+
208+ window.addEventListener('message', (e) => {
209+ const msg = e.data;
210+ switch (msg.type) {
211+ case 'userMessage': addMessage('user', msg.content); break;
212+ case 'startResponse': currentResponse = addMessage('assistant', ''); break;
213+ case 'responseChunk': if (currentResponse) { currentResponse.textContent += msg.content; messagesEl.scrollTop = messagesEl.scrollHeight; } break;
214+ case 'endResponse': currentResponse = null; break;
215+ case 'error': addMessage('error', msg.content); break;
216+ case 'historyCleared': messagesEl.innerHTML = ''; break;
217+ }
218+ });
219+ }
258220 </script>
259221</body>
260222</html>` ;
261223 }
224+
225+ private getNonce ( ) : string {
226+ let text = '' ;
227+ const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' ;
228+ for ( let i = 0 ; i < 32 ; i ++ ) {
229+ text += possible . charAt ( Math . floor ( Math . random ( ) * possible . length ) ) ;
230+ }
231+ return text ;
232+ }
262233}
263234
0 commit comments