+
{label}
diff --git a/packages/vscode-ide-companion/src/webview/components/messages/SimpleTimeline.css b/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/MergedSimpleTimeline.css
similarity index 75%
rename from packages/vscode-ide-companion/src/webview/components/messages/SimpleTimeline.css
rename to packages/vscode-ide-companion/src/webview/components/toolcalls/shared/MergedSimpleTimeline.css
index 97893592..ae3829b2 100644
--- a/packages/vscode-ide-companion/src/webview/components/messages/SimpleTimeline.css
+++ b/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/MergedSimpleTimeline.css
@@ -3,10 +3,12 @@
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*
- * Simplified timeline styles for testing
+ * Simplified timeline styles for tool calls and messages
+ * Merged version of both SimpleTimeline.css files
*/
/* Basic timeline container */
+.simple-toolcall-container,
.simple-timeline-container {
position: relative;
padding-left: 30px;
@@ -15,6 +17,7 @@
}
/* Timeline connector - simple version */
+.simple-toolcall-container::after,
.simple-timeline-container::after {
content: '';
position: absolute;
@@ -26,11 +29,13 @@
}
/* First item connector starts lower */
+.simple-toolcall-container:first-child::after,
.simple-timeline-container:first-child::after {
top: 24px;
}
/* Last item connector ends higher */
+.simple-toolcall-container:last-child::after,
.simple-timeline-container:last-child::after {
height: calc(100% - 24px);
top: 0;
@@ -38,6 +43,7 @@
}
/* Bullet point */
+.simple-toolcall-container::before,
.simple-timeline-container::before {
content: '\25cf';
position: absolute;
@@ -46,4 +52,4 @@
font-size: 10px;
color: var(--app-secondary-foreground);
z-index: 2;
-}
+}
\ No newline at end of file
diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/SimpleTimeline.css b/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/SimpleTimeline.css
deleted file mode 100644
index 1aafdb93..00000000
--- a/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/SimpleTimeline.css
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * @license
- * Copyright 2025 Qwen Team
- * SPDX-License-Identifier: Apache-2.0
- *
- * Simplified timeline styles for tool calls
- */
-
-/* Basic timeline container */
-.simple-toolcall-container {
- position: relative;
- padding-left: 30px;
- padding-top: 8px;
- padding-bottom: 8px;
-}
-
-/* Timeline connector - simple version */
-.simple-toolcall-container::after {
- content: '';
- position: absolute;
- left: 12px;
- top: 0;
- bottom: 0;
- width: 1px;
- background-color: var(--app-primary-border-color);
-}
-
-/* First item connector starts lower */
-.simple-toolcall-container:first-child::after {
- top: 24px;
-}
-
-/* Last item connector ends higher */
-.simple-toolcall-container:last-child::after {
- height: calc(100% - 24px);
- top: 0;
- bottom: auto;
-}
-
-/* Bullet point */
-.simple-toolcall-container::before {
- content: '\25cf';
- position: absolute;
- left: 8px;
- padding-top: 2px;
- font-size: 10px;
- color: var(--app-secondary-foreground);
- z-index: 2;
-}
diff --git a/packages/vscode-ide-companion/src/webview/handlers/FileMessageHandler.ts b/packages/vscode-ide-companion/src/webview/handlers/FileMessageHandler.ts
index 097d8eba..080d5744 100644
--- a/packages/vscode-ide-companion/src/webview/handlers/FileMessageHandler.ts
+++ b/packages/vscode-ide-companion/src/webview/handlers/FileMessageHandler.ts
@@ -5,6 +5,9 @@
*/
import * as vscode from 'vscode';
+import * as fs from 'fs';
+import * as os from 'os';
+import * as path from 'path';
import { BaseMessageHandler } from './BaseMessageHandler.js';
import { getFileName } from '../utils/webviewUtils.js';
@@ -20,6 +23,7 @@ export class FileMessageHandler extends BaseMessageHandler {
'getWorkspaceFiles',
'openFile',
'openDiff',
+ 'createAndOpenTempFile',
].includes(messageType);
}
@@ -47,6 +51,10 @@ export class FileMessageHandler extends BaseMessageHandler {
await this.handleOpenDiff(data);
break;
+ case 'createAndOpenTempFile':
+ await this.handleCreateAndOpenTempFile(data);
+ break;
+
default:
console.warn(
'[FileMessageHandler] Unknown message type:',
@@ -347,4 +355,52 @@ export class FileMessageHandler extends BaseMessageHandler {
vscode.window.showErrorMessage(`Failed to open diff: ${error}`);
}
}
+
+ /**
+ * Create and open temporary file
+ */
+ private async handleCreateAndOpenTempFile(
+ data: Record | undefined,
+ ): Promise {
+ if (!data) {
+ console.warn(
+ '[FileMessageHandler] No data provided for createAndOpenTempFile',
+ );
+ return;
+ }
+
+ try {
+ const content = (data.content as string) || '';
+ const fileName = (data.fileName as string) || 'temp';
+ const fileExtension = (data.fileExtension as string) || '.txt';
+
+ // Create temporary file path
+ const tempDir = os.tmpdir();
+ const tempFileName = `${fileName}-${Date.now()}${fileExtension}`;
+ const tempFilePath = path.join(tempDir, tempFileName);
+
+ // Write content to temporary file
+ await fs.promises.writeFile(tempFilePath, content, 'utf8');
+
+ // Open the temporary file in VS Code
+ const uri = vscode.Uri.file(tempFilePath);
+ await vscode.window.showTextDocument(uri, {
+ preview: false,
+ preserveFocus: false,
+ });
+
+ console.log(
+ '[FileMessageHandler] Created and opened temporary file:',
+ tempFilePath,
+ );
+ } catch (error) {
+ console.error(
+ '[FileMessageHandler] Failed to create and open temporary file:',
+ error,
+ );
+ vscode.window.showErrorMessage(
+ `Failed to create and open temporary file: ${error}`,
+ );
+ }
+ }
}
diff --git a/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts b/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts
index 30364e88..c468aeec 100644
--- a/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts
+++ b/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts
@@ -25,6 +25,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
'getQwenSessions',
'saveSession',
'resumeSession',
+ 'cancelStreaming',
// UI action: open a new chat tab (new WebviewPanel)
'openNewChatTab',
].includes(messageType);
@@ -102,6 +103,11 @@ export class SessionMessageHandler extends BaseMessageHandler {
}
break;
+ case 'cancelStreaming':
+ // Handle cancel streaming request from webview
+ await this.handleCancelStreaming();
+ break;
+
default:
console.warn(
'[SessionMessageHandler] Unknown message type:',
@@ -910,6 +916,34 @@ export class SessionMessageHandler extends BaseMessageHandler {
}
}
+ /**
+ * Handle cancel streaming request
+ */
+ private async handleCancelStreaming(): Promise {
+ try {
+ console.log('[SessionMessageHandler] Canceling streaming...');
+
+ // Cancel the current streaming operation in the agent manager
+ await this.agentManager.cancelCurrentPrompt();
+
+ // Send streamEnd message to WebView to update UI
+ this.sendToWebView({
+ type: 'streamEnd',
+ data: { timestamp: Date.now(), reason: 'user_cancelled' },
+ });
+
+ console.log('[SessionMessageHandler] Streaming cancelled successfully');
+ } catch (_error) {
+ console.log('[SessionMessageHandler] Streaming cancelled (interrupted)');
+
+ // Always send streamEnd to update UI, regardless of errors
+ this.sendToWebView({
+ type: 'streamEnd',
+ data: { timestamp: Date.now(), reason: 'user_cancelled' },
+ });
+ }
+ }
+
/**
* Handle resume session request
*/
diff --git a/packages/vscode-ide-companion/src/webview/hooks/useWebViewMessages.ts b/packages/vscode-ide-companion/src/webview/hooks/useWebViewMessages.ts
index a67c1a86..96a8a492 100644
--- a/packages/vscode-ide-companion/src/webview/hooks/useWebViewMessages.ts
+++ b/packages/vscode-ide-companion/src/webview/hooks/useWebViewMessages.ts
@@ -5,6 +5,7 @@
*/
import { useEffect, useRef, useCallback } from 'react';
+import { useVSCode } from './useVSCode.js';
import type { Conversation } from '../../storage/conversationStore.js';
import type {
PermissionOption,
@@ -108,6 +109,8 @@ export const useWebViewMessages = ({
inputFieldRef,
setInputText,
}: UseWebViewMessagesProps) => {
+ // VS Code API for posting messages back to the extension host
+ const vscode = useVSCode();
// Use ref to store callbacks to avoid useEffect dependency issues
const handlersRef = useRef({
sessionManagement,
@@ -469,6 +472,8 @@ export const useWebViewMessages = ({
(session.name as string) ||
'Past Conversations';
handlers.sessionManagement.setCurrentSessionTitle(title);
+ // Update the VS Code webview tab title as well
+ vscode.postMessage({ type: 'updatePanelTitle', data: { title } });
}
if (message.data.messages) {
handlers.messageHandling.setMessages(message.data.messages);
@@ -487,6 +492,11 @@ export const useWebViewMessages = ({
handlers.sessionManagement.setCurrentSessionTitle(
'Past Conversations',
);
+ // Reset the VS Code tab title to default label
+ vscode.postMessage({
+ type: 'updatePanelTitle',
+ data: { title: 'Qwen Code' },
+ });
lastPlanSnapshotRef.current = null;
break;
@@ -496,6 +506,8 @@ export const useWebViewMessages = ({
if (sessionId && title) {
handlers.sessionManagement.setCurrentSessionId(sessionId);
handlers.sessionManagement.setCurrentSessionTitle(title);
+ // Ask extension host to reflect this title in the tab label
+ vscode.postMessage({ type: 'updatePanelTitle', data: { title } });
}
break;
}
@@ -563,11 +575,23 @@ export const useWebViewMessages = ({
break;
}
+ case 'cancelStreaming':
+ // Handle cancel streaming request from webview
+ handlers.messageHandling.endStreaming();
+ handlers.messageHandling.clearWaitingForResponse();
+ // Add interrupted message
+ handlers.messageHandling.addMessage({
+ role: 'assistant',
+ content: 'Interrupted',
+ timestamp: Date.now(),
+ });
+ break;
+
default:
break;
}
},
- [inputFieldRef, setInputText],
+ [inputFieldRef, setInputText, vscode],
);
useEffect(() => {
diff --git a/packages/vscode-ide-companion/src/webview/styles/ClaudeCodeStyles.css b/packages/vscode-ide-companion/src/webview/styles/ClaudeCodeStyles.css
index b9eb5283..429263ce 100644
--- a/packages/vscode-ide-companion/src/webview/styles/ClaudeCodeStyles.css
+++ b/packages/vscode-ide-companion/src/webview/styles/ClaudeCodeStyles.css
@@ -10,8 +10,8 @@
/* Import component styles */
@import '../components/toolcalls/shared/DiffDisplay.css';
@import '../components/messages/AssistantMessage.css';
-@import '../components/messages/SimpleTimeline.css';
-@import '../components/toolcalls/shared/SimpleTimeline.css';
+@import '../components/toolcalls/shared/MergedSimpleTimeline.css';
+@import '../components/messages/QwenMessageTimeline.css';
/* ===========================
diff --git a/packages/vscode-ide-companion/src/webview/utils/tempFileManager.ts b/packages/vscode-ide-companion/src/webview/utils/tempFileManager.ts
new file mode 100644
index 00000000..8ab17e30
--- /dev/null
+++ b/packages/vscode-ide-companion/src/webview/utils/tempFileManager.ts
@@ -0,0 +1,33 @@
+/**
+ * @license
+ * Copyright 2025 Qwen Team
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Temporary file manager for creating and opening temporary files in webview
+ */
+
+/**
+ * Creates a temporary file with the given content and opens it in VS Code
+ * @param content The content to write to the temporary file
+ * @param fileName Optional file name (without extension)
+ * @param fileExtension Optional file extension (defaults to .txt)
+ */
+export async function createAndOpenTempFile(
+ postMessage: (message: {
+ type: string;
+ data: Record;
+ }) => void,
+ content: string,
+ fileName: string = 'temp',
+ fileExtension: string = '.txt',
+): Promise {
+ // Send message to VS Code extension to create and open temp file
+ postMessage({
+ type: 'createAndOpenTempFile',
+ data: {
+ content,
+ fileName,
+ fileExtension,
+ },
+ });
+}