mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
refactor(vscode-ide-companion): update message handling and configuration
This commit is contained in:
@@ -378,7 +378,29 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[SessionMessageHandler] Error sending message:', error);
|
console.error('[SessionMessageHandler] Error sending message:', error);
|
||||||
|
|
||||||
|
const err = error as unknown as Error;
|
||||||
const errorMsg = String(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
|
// Check for session not found error and handle it appropriately
|
||||||
if (
|
if (
|
||||||
errorMsg.includes('Session not found') ||
|
errorMsg.includes('Session not found') ||
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ export const useMessageHandling = () => {
|
|||||||
const endStreaming = useCallback(() => {
|
const endStreaming = useCallback(() => {
|
||||||
// Finalize streaming; content already lives in the placeholder message
|
// Finalize streaming; content already lives in the placeholder message
|
||||||
setIsStreaming(false);
|
setIsStreaming(false);
|
||||||
setIsWaitingForResponse(false);
|
|
||||||
streamingMessageIndexRef.current = null;
|
streamingMessageIndexRef.current = null;
|
||||||
// Remove the thinking message if it exists (collapse thoughts)
|
// Remove the thinking message if it exists (collapse thoughts)
|
||||||
setMessages((prev) => {
|
setMessages((prev) => {
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ interface UseMessageSubmitProps {
|
|||||||
setInputText: (text: string) => void;
|
setInputText: (text: string) => void;
|
||||||
inputFieldRef: React.RefObject<HTMLDivElement>;
|
inputFieldRef: React.RefObject<HTMLDivElement>;
|
||||||
isStreaming: boolean;
|
isStreaming: boolean;
|
||||||
|
// When true, do NOT auto-attach the active editor file/selection to context
|
||||||
|
skipAutoActiveContext?: boolean;
|
||||||
|
|
||||||
fileContext: {
|
fileContext: {
|
||||||
getFileReference: (fileName: string) => string | undefined;
|
getFileReference: (fileName: string) => string | undefined;
|
||||||
@@ -38,6 +40,7 @@ export const useMessageSubmit = ({
|
|||||||
setInputText,
|
setInputText,
|
||||||
inputFieldRef,
|
inputFieldRef,
|
||||||
isStreaming,
|
isStreaming,
|
||||||
|
skipAutoActiveContext = false,
|
||||||
fileContext,
|
fileContext,
|
||||||
messageHandling,
|
messageHandling,
|
||||||
}: UseMessageSubmitProps) => {
|
}: UseMessageSubmitProps) => {
|
||||||
@@ -94,8 +97,8 @@ export const useMessageSubmit = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add active file selection context if present
|
// Add active file selection context if present and not skipped
|
||||||
if (fileContext.activeFilePath) {
|
if (fileContext.activeFilePath && !skipAutoActiveContext) {
|
||||||
const fileName = fileContext.activeFileName || 'current file';
|
const fileName = fileContext.activeFileName || 'current file';
|
||||||
context.push({
|
context.push({
|
||||||
type: 'file',
|
type: 'file',
|
||||||
@@ -115,7 +118,11 @@ export const useMessageSubmit = ({
|
|||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
if (fileContext.activeFilePath && fileContext.activeFileName) {
|
if (
|
||||||
|
fileContext.activeFilePath &&
|
||||||
|
fileContext.activeFileName &&
|
||||||
|
!skipAutoActiveContext
|
||||||
|
) {
|
||||||
fileContextForMessage = {
|
fileContextForMessage = {
|
||||||
fileName: fileContext.activeFileName,
|
fileName: fileContext.activeFileName,
|
||||||
filePath: fileContext.activeFilePath,
|
filePath: fileContext.activeFilePath,
|
||||||
@@ -146,6 +153,7 @@ export const useMessageSubmit = ({
|
|||||||
inputFieldRef,
|
inputFieldRef,
|
||||||
vscode,
|
vscode,
|
||||||
fileContext,
|
fileContext,
|
||||||
|
skipAutoActiveContext,
|
||||||
messageHandling,
|
messageHandling,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -112,6 +112,9 @@ export const useWebViewMessages = ({
|
|||||||
}: UseWebViewMessagesProps) => {
|
}: UseWebViewMessagesProps) => {
|
||||||
// VS Code API for posting messages back to the extension host
|
// VS Code API for posting messages back to the extension host
|
||||||
const vscode = useVSCode();
|
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
|
// Use ref to store callbacks to avoid useEffect dependency issues
|
||||||
const handlersRef = useRef({
|
const handlersRef = useRef({
|
||||||
sessionManagement,
|
sessionManagement,
|
||||||
@@ -260,14 +263,19 @@ export const useWebViewMessages = ({
|
|||||||
// no-op: stream might not have been started
|
// no-op: stream might not have been started
|
||||||
console.warn('[PanelManager] Failed to end streaming:', err);
|
console.warn('[PanelManager] Failed to end streaming:', err);
|
||||||
}
|
}
|
||||||
try {
|
// Important: Do NOT blindly clear the waiting message if there are
|
||||||
handlers.messageHandling.clearWaitingForResponse();
|
// still active tool calls running. We keep the waiting indicator
|
||||||
} catch (err) {
|
// tied to tool-call lifecycle instead.
|
||||||
// no-op: already cleared
|
if (activeExecToolCallsRef.current.size === 0) {
|
||||||
console.warn(
|
try {
|
||||||
'[PanelManager] Failed to clear waiting for response:',
|
handlers.messageHandling.clearWaitingForResponse();
|
||||||
err,
|
} catch (err) {
|
||||||
);
|
// no-op: already cleared
|
||||||
|
console.warn(
|
||||||
|
'[PanelManager] Failed to clear waiting for response:',
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -293,6 +301,11 @@ export const useWebViewMessages = ({
|
|||||||
case 'streamEnd':
|
case 'streamEnd':
|
||||||
handlers.messageHandling.endStreaming();
|
handlers.messageHandling.endStreaming();
|
||||||
handlers.messageHandling.clearThinking();
|
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;
|
break;
|
||||||
|
|
||||||
case 'error':
|
case 'error':
|
||||||
@@ -439,20 +452,33 @@ export const useWebViewMessages = ({
|
|||||||
const kind = (toolCallData.kind || '').toString().toLowerCase();
|
const kind = (toolCallData.kind || '').toString().toLowerCase();
|
||||||
const isExec =
|
const isExec =
|
||||||
kind === 'execute' || kind === 'bash' || kind === 'command';
|
kind === 'execute' || kind === 'bash' || kind === 'command';
|
||||||
if (isExec && (status === 'pending' || status === 'in_progress')) {
|
|
||||||
const rawInput = toolCallData.rawInput;
|
if (isExec) {
|
||||||
let cmd = '';
|
const id = (toolCallData.toolCallId || '').toString();
|
||||||
if (typeof rawInput === 'string') {
|
|
||||||
cmd = rawInput;
|
// Maintain the active set by status
|
||||||
} else if (rawInput && typeof rawInput === 'object') {
|
if (status === 'pending' || status === 'in_progress') {
|
||||||
const maybe = rawInput as { command?: string };
|
activeExecToolCallsRef.current.add(id);
|
||||||
cmd = maybe.command || '';
|
|
||||||
|
// Build a helpful hint from rawInput
|
||||||
|
const rawInput = toolCallData.rawInput;
|
||||||
|
let cmd = '';
|
||||||
|
if (typeof rawInput === 'string') {
|
||||||
|
cmd = rawInput;
|
||||||
|
} else if (rawInput && typeof rawInput === 'object') {
|
||||||
|
const maybe = rawInput as { command?: string };
|
||||||
|
cmd = maybe.command || '';
|
||||||
|
}
|
||||||
|
const hint = cmd ? `Running: ${cmd}` : 'Running command...';
|
||||||
|
handlers.messageHandling.setWaitingForResponse(hint);
|
||||||
|
} else if (status === 'completed' || status === 'failed') {
|
||||||
|
activeExecToolCallsRef.current.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no active exec tool remains, clear the waiting message.
|
||||||
|
if (activeExecToolCallsRef.current.size === 0) {
|
||||||
|
handlers.messageHandling.clearWaitingForResponse();
|
||||||
}
|
}
|
||||||
const hint = cmd ? `Running: ${cmd}` : 'Running command...';
|
|
||||||
handlers.messageHandling.setWaitingForResponse(hint);
|
|
||||||
}
|
|
||||||
if (status === 'completed' || status === 'failed') {
|
|
||||||
handlers.messageHandling.clearWaitingForResponse();
|
|
||||||
}
|
}
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
// Best-effort UI hint; ignore errors
|
// Best-effort UI hint; ignore errors
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
|
|
||||||
export function isDevelopmentMode(): boolean {
|
export function isDevelopmentMode(): boolean {
|
||||||
// TODO: 调试用
|
// TODO: 调试用
|
||||||
return false;
|
// return false;
|
||||||
// return (
|
return (
|
||||||
// process.env.NODE_ENV === 'development' ||
|
process.env.NODE_ENV === 'development' ||
|
||||||
// process.env.DEBUG === 'true' ||
|
process.env.DEBUG === 'true' ||
|
||||||
// process.env.NODE_ENV !== 'production'
|
process.env.NODE_ENV !== 'production'
|
||||||
// );
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,10 +34,16 @@ export default {
|
|||||||
'0%, 100%': { opacity: '1' },
|
'0%, 100%': { opacity: '1' },
|
||||||
'50%': { opacity: '0.5' },
|
'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: {
|
animation: {
|
||||||
'completion-menu-enter': 'completion-menu-enter 150ms ease-out both',
|
'completion-menu-enter': 'completion-menu-enter 150ms ease-out both',
|
||||||
'pulse-slow': 'pulse-slow 1.5s ease-in-out infinite',
|
'pulse-slow': 'pulse-slow 1.5s ease-in-out infinite',
|
||||||
|
'slide-up': 'slide-up 200ms ease-out both',
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
qwen: {
|
qwen: {
|
||||||
|
|||||||
Reference in New Issue
Block a user