refactor(vscode-ide-companion): update message handling and configuration

This commit is contained in:
yiliang114
2025-12-05 18:03:46 +08:00
parent ac9cb3a6d3
commit be44e7af56
6 changed files with 92 additions and 31 deletions

View File

@@ -378,7 +378,29 @@ export class SessionMessageHandler extends BaseMessageHandler {
} catch (error) {
console.error('[SessionMessageHandler] Error sending message:', error);
const err = error as unknown as Error;
const errorMsg = String(error);
const lower = errorMsg.toLowerCase();
// Suppress user-cancelled/aborted errors (ESC/Stop button)
const isAbortLike =
(err && (err as Error).name === 'AbortError') ||
lower.includes('abort') ||
lower.includes('aborted') ||
lower.includes('request was aborted') ||
lower.includes('canceled') ||
lower.includes('cancelled') ||
lower.includes('user_cancelled');
if (isAbortLike) {
// Do not show VS Code error popup for intentional cancellations.
// Ensure the webview knows the stream ended due to user action.
this.sendToWebView({
type: 'streamEnd',
data: { timestamp: Date.now(), reason: 'user_cancelled' },
});
return;
}
// Check for session not found error and handle it appropriately
if (
errorMsg.includes('Session not found') ||

View File

@@ -105,7 +105,6 @@ export const useMessageHandling = () => {
const endStreaming = useCallback(() => {
// Finalize streaming; content already lives in the placeholder message
setIsStreaming(false);
setIsWaitingForResponse(false);
streamingMessageIndexRef.current = null;
// Remove the thinking message if it exists (collapse thoughts)
setMessages((prev) => {

View File

@@ -14,6 +14,8 @@ interface UseMessageSubmitProps {
setInputText: (text: string) => void;
inputFieldRef: React.RefObject<HTMLDivElement>;
isStreaming: boolean;
// When true, do NOT auto-attach the active editor file/selection to context
skipAutoActiveContext?: boolean;
fileContext: {
getFileReference: (fileName: string) => string | undefined;
@@ -38,6 +40,7 @@ export const useMessageSubmit = ({
setInputText,
inputFieldRef,
isStreaming,
skipAutoActiveContext = false,
fileContext,
messageHandling,
}: UseMessageSubmitProps) => {
@@ -94,8 +97,8 @@ export const useMessageSubmit = ({
}
}
// Add active file selection context if present
if (fileContext.activeFilePath) {
// Add active file selection context if present and not skipped
if (fileContext.activeFilePath && !skipAutoActiveContext) {
const fileName = fileContext.activeFileName || 'current file';
context.push({
type: 'file',
@@ -115,7 +118,11 @@ export const useMessageSubmit = ({
}
| undefined;
if (fileContext.activeFilePath && fileContext.activeFileName) {
if (
fileContext.activeFilePath &&
fileContext.activeFileName &&
!skipAutoActiveContext
) {
fileContextForMessage = {
fileName: fileContext.activeFileName,
filePath: fileContext.activeFilePath,
@@ -146,6 +153,7 @@ export const useMessageSubmit = ({
inputFieldRef,
vscode,
fileContext,
skipAutoActiveContext,
messageHandling,
],
);

View File

@@ -112,6 +112,9 @@ export const useWebViewMessages = ({
}: UseWebViewMessagesProps) => {
// VS Code API for posting messages back to the extension host
const vscode = useVSCode();
// Track active long-running tool calls (execute/bash/command) so we can
// keep the bottom "waiting" message visible until all of them complete.
const activeExecToolCallsRef = useRef<Set<string>>(new Set());
// Use ref to store callbacks to avoid useEffect dependency issues
const handlersRef = useRef({
sessionManagement,
@@ -260,6 +263,10 @@ export const useWebViewMessages = ({
// no-op: stream might not have been started
console.warn('[PanelManager] Failed to end streaming:', err);
}
// Important: Do NOT blindly clear the waiting message if there are
// still active tool calls running. We keep the waiting indicator
// tied to tool-call lifecycle instead.
if (activeExecToolCallsRef.current.size === 0) {
try {
handlers.messageHandling.clearWaitingForResponse();
} catch (err) {
@@ -270,6 +277,7 @@ export const useWebViewMessages = ({
);
}
}
}
break;
}
@@ -293,6 +301,11 @@ export const useWebViewMessages = ({
case 'streamEnd':
handlers.messageHandling.endStreaming();
handlers.messageHandling.clearThinking();
// Clear the generic waiting indicator only if there are no active
// long-running tool calls. Otherwise, keep it visible.
if (activeExecToolCallsRef.current.size === 0) {
handlers.messageHandling.clearWaitingForResponse();
}
break;
case 'error':
@@ -439,7 +452,15 @@ export const useWebViewMessages = ({
const kind = (toolCallData.kind || '').toString().toLowerCase();
const isExec =
kind === 'execute' || kind === 'bash' || kind === 'command';
if (isExec && (status === 'pending' || status === 'in_progress')) {
if (isExec) {
const id = (toolCallData.toolCallId || '').toString();
// Maintain the active set by status
if (status === 'pending' || status === 'in_progress') {
activeExecToolCallsRef.current.add(id);
// Build a helpful hint from rawInput
const rawInput = toolCallData.rawInput;
let cmd = '';
if (typeof rawInput === 'string') {
@@ -450,10 +471,15 @@ export const useWebViewMessages = ({
}
const hint = cmd ? `Running: ${cmd}` : 'Running command...';
handlers.messageHandling.setWaitingForResponse(hint);
} else if (status === 'completed' || status === 'failed') {
activeExecToolCallsRef.current.delete(id);
}
if (status === 'completed' || status === 'failed') {
// If no active exec tool remains, clear the waiting message.
if (activeExecToolCallsRef.current.size === 0) {
handlers.messageHandling.clearWaitingForResponse();
}
}
} catch (_err) {
// Best-effort UI hint; ignore errors
}

View File

@@ -6,10 +6,10 @@
export function isDevelopmentMode(): boolean {
// TODO: 调试用
return false;
// return (
// process.env.NODE_ENV === 'development' ||
// process.env.DEBUG === 'true' ||
// process.env.NODE_ENV !== 'production'
// );
// return false;
return (
process.env.NODE_ENV === 'development' ||
process.env.DEBUG === 'true' ||
process.env.NODE_ENV !== 'production'
);
}

View File

@@ -34,10 +34,16 @@ export default {
'0%, 100%': { opacity: '1' },
'50%': { opacity: '0.5' },
},
// PermissionDrawer enter animation: slide up from bottom
'slide-up': {
'0%': { transform: 'translateY(100%)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
},
},
animation: {
'completion-menu-enter': 'completion-menu-enter 150ms ease-out both',
'pulse-slow': 'pulse-slow 1.5s ease-in-out infinite',
'slide-up': 'slide-up 200ms ease-out both',
},
colors: {
qwen: {