@@ -178,6 +178,7 @@ export class ChatPanel {
178178 <div id="input-container">
179179 <textarea id="input" placeholder="Type your message here... (Ctrl+Enter to send)"></textarea>
180180 <button id="send">Send</button>
181+ <button id="stop" style="display: none;">Stop</button>
181182 <button id="new-session" style="position: absolute; top: 10px; right: 10px;">New Session</button>
182183 </div>
183184 <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
@@ -186,59 +187,90 @@ export class ChatPanel {
186187 <script defer src="https://cdn.jsdelivr.net/npm/[email protected] /dist/contrib/auto-render.min.js"></script> 187188 <script>
188189 const vscode = acquireVsCodeApi();
190+
189191 const chat = document.getElementById('chat');
190192 const input = document.getElementById('input');
193+ const sendButton = document.getElementById('send');
194+ const stopButton = document.getElementById('stop');
191195
192196 // 初始化代码高亮
193197 hljs.configure({ ignoreUnescapedHTML: true });
194198
195199 // 消息处理
196200 window.addEventListener('message', (event) => {
197- const { role, content } = event.data;
198- const lastChild = chat.lastElementChild;
199-
200- let targetDiv;
201- if (lastChild && lastChild.classList.contains(role)) {
202- targetDiv = lastChild;
203- targetDiv.dataset.markdownContent += content;
204- } else {
205- targetDiv = document.createElement('div');
206- targetDiv.className = role;
207- targetDiv.dataset.markdownContent = content;
208- chat.appendChild(targetDiv);
209- }
201+ const data = event.data;
202+
203+ // 处理 role 和 content 的消息
204+ if (data.role && data.content) {
205+ const { role, content } = data;
206+ const lastChild = chat.lastElementChild;
207+
208+ let targetDiv;
209+ if (lastChild && lastChild.classList.contains(role)) {
210+ targetDiv = lastChild;
211+ targetDiv.dataset.markdownContent += content;
212+ } else {
213+ targetDiv = document.createElement('div');
214+ targetDiv.className = role;
215+ targetDiv.dataset.markdownContent = content;
216+ chat.appendChild(targetDiv);
217+ }
218+
219+ if (role === 'model') {
220+ // 解析 Markdown
221+ targetDiv.innerHTML = marked.parse(targetDiv.dataset.markdownContent, {
222+ breaks: true,
223+ mangle: false,
224+ headerIds: false,
225+ highlight: (code, lang) => {
226+ const validLang = hljs.getLanguage(lang) ? lang : 'plaintext';
227+ return hljs.highlight(code, { language: validLang }).value;
228+ }
229+ });
230+
231+ // 渲染数学公式
232+ renderMathInElement(targetDiv, {
233+ delimiters: [
234+ { left: '$$', right: '$$', display: true },
235+ { left: '$', right: '$', display: false },
236+ { left: '\\[', right: '\\]', display: true },
237+ { left: '\\(', right: '\\)', display: false }
238+ ],
239+ throwOnError: false
240+ });
241+
242+ // 重新高亮代码块
243+ hljs.highlightAll();
244+ } else {
245+ // 用户消息保持纯文本
246+ targetDiv.textContent = targetDiv.dataset.markdownContent;
247+ }
210248
211- if (role === 'model') {
212- // 解析Markdown
213- targetDiv.innerHTML = marked.parse(targetDiv.dataset.markdownContent, {
214- breaks: true,
215- mangle: false,
216- headerIds: false,
217- highlight: (code, lang) => {
218- const validLang = hljs.getLanguage(lang) ? lang : 'plaintext';
219- return hljs.highlight(code, { language: validLang }).value;
220- }
221- });
222-
223- // 渲染数学公式
224- renderMathInElement(targetDiv, {
225- delimiters: [
226- { left: '$$', right: '$$', display: true },
227- { left: '$', right: '$', display: false },
228- { left: '\\[', right: '\\]', display: true },
229- { left: '\\(', right: '\\)', display: false }
230- ],
231- throwOnError: false
232- });
233-
234- // 重新高亮代码块
235- hljs.highlightAll();
236- } else {
237- // 用户消息保持纯文本
238- targetDiv.textContent = targetDiv.dataset.markdownContent;
249+ chat.scrollTop = chat.scrollHeight;
239250 }
251+
252+ // 处理 command 类型的消息
253+ if (data.command) {
254+ const { command } = data;
240255
241- chat.scrollTop = chat.scrollHeight;
256+ switch (command) {
257+ case 'disableSendButton':
258+ sendButton.disabled = true;
259+ break;
260+ case 'enableSendButton':
261+ sendButton.disabled = false;
262+ break;
263+ case 'showStopButton':
264+ stopButton.style.display = 'inline-block';
265+ break;
266+ case 'hideStopButton':
267+ stopButton.style.display = 'none';
268+ break;
269+ default:
270+ // 处理其他 command 消息
271+ break;
272+ }
273+ }
242274 });
243275
244276 // 发送消息逻辑
@@ -250,6 +282,12 @@ export class ChatPanel {
250282 document.getElementById('new-session').addEventListener('click', () => {
251283 vscode.postMessage({ command: 'newSession' });
252284 chat.innerHTML = '';
285+ sendButton.disabled = false;
286+ stopButton.style.display = 'none';
287+ });
288+
289+ stopButton.addEventListener('click', () => {
290+ vscode.postMessage({ command: 'stop' });
253291 });
254292
255293 input.addEventListener('keydown', (e) => {
@@ -266,13 +304,17 @@ export class ChatPanel {
266304
267305 private async _handleMessage ( message : any ) {
268306 const webviewOutputChannel = new WebviewOutputChannel ( this . _panel . webview , 'DeepSeek API Output' ) ;
269-
307+
270308 switch ( message . command ) {
271309 case 'sendMessage' :
272310 this . _conversation . push ( { role : 'user' , content : message . text } ) ;
273311 this . _panel . webview . postMessage ( { role : 'user' , content : message . text } ) ;
274312
275313 try {
314+ // 发送消息到 Webview,禁用发送按钮并显示停止按钮
315+ this . _panel . webview . postMessage ( { command : 'disableSendButton' } ) ;
316+ this . _panel . webview . postMessage ( { command : 'showStopButton' } ) ;
317+
276318 const response = await callDeepSeekApi (
277319 message . text ,
278320 'You are a helpful assistant. Always format answers with Markdown.' ,
@@ -281,9 +323,17 @@ export class ChatPanel {
281323 undefined ,
282324 getCurrentOperationController ( ) . signal
283325 ) ;
284-
326+
327+ // 发送消息到 Webview,启用发送按钮并隐藏停止按钮
328+ this . _panel . webview . postMessage ( { command : 'enableSendButton' } ) ;
329+ this . _panel . webview . postMessage ( { command : 'hideStopButton' } ) ;
330+
285331 this . _conversation . push ( { role : 'model' , content : response || '' } ) ;
286332 } catch ( error ) {
333+ // 发送消息到 Webview,启用发送按钮并隐藏停止按钮
334+ this . _panel . webview . postMessage ( { command : 'enableSendButton' } ) ;
335+ this . _panel . webview . postMessage ( { command : 'hideStopButton' } ) ;
336+
287337 this . _panel . webview . postMessage ( {
288338 role : 'model' ,
289339 content : `**Error**: ${ error instanceof Error ? error . message : 'Unknown error' } `
@@ -295,6 +345,10 @@ export class ChatPanel {
295345 this . _conversation = [ ] ;
296346 resetCurrentOperationController ( ) ;
297347 break ;
348+ case 'stop' :
349+ resetCurrentOperationController ( ) ;
350+ this . _panel . webview . postMessage ( { role : 'model' , content : '\n\n**Operation stopped by user**' } ) ;
351+ break ;
298352 }
299353 }
300354
0 commit comments