mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
feat(vscode-ide-companion): enhance session management with pagination support
Implement cursor-based pagination for session listing and improve session handling - Add pagination state management in useSessionManagement hook - Implement handleLoadMoreSessions for infinite scrolling - Update SessionMessageHandler to support paged session requests - Add ChatHeader component for improved UI layout - Fix session title duplication issue - Improve error handling in session operations
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
interface ChatHeaderProps {
|
||||
currentSessionTitle: string;
|
||||
onLoadSessions: () => void;
|
||||
onSaveSession: () => void;
|
||||
onNewSession: () => void;
|
||||
}
|
||||
|
||||
export const ChatHeader: React.FC<ChatHeaderProps> = ({
|
||||
currentSessionTitle,
|
||||
onLoadSessions,
|
||||
onSaveSession: _onSaveSession,
|
||||
onNewSession,
|
||||
}) => (
|
||||
<div
|
||||
className="flex gap-1 select-none py-1.5 px-2.5"
|
||||
style={{
|
||||
borderBottom: '1px solid var(--app-primary-border-color)',
|
||||
backgroundColor: 'var(--app-header-background)',
|
||||
}}
|
||||
>
|
||||
{/* Past Conversations Button */}
|
||||
<button
|
||||
className="flex-none py-1 px-2 bg-transparent border border-transparent rounded cursor-pointer flex items-center justify-center outline-none font-medium transition-colors duration-200 hover:bg-[var(--app-ghost-button-hover-background)] focus:bg-[var(--app-ghost-button-hover-background)]"
|
||||
style={{
|
||||
borderRadius: 'var(--corner-radius-small)',
|
||||
color: 'var(--app-primary-foreground)',
|
||||
fontSize: 'var(--vscode-chat-font-size, 13px)',
|
||||
}}
|
||||
onClick={onLoadSessions}
|
||||
title="Past conversations"
|
||||
>
|
||||
<span className="flex items-center gap-1">
|
||||
<span style={{ fontSize: 'var(--vscode-chat-font-size, 13px)' }}>
|
||||
{currentSessionTitle}
|
||||
</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
className="w-3.5 h-3.5"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{/* Spacer */}
|
||||
<div className="flex-1"></div>
|
||||
|
||||
{/* Save Session Button */}
|
||||
{/* <button
|
||||
className="flex-none p-0 bg-transparent border border-transparent rounded cursor-pointer flex items-center justify-center outline-none w-6 h-6 hover:bg-[var(--app-ghost-button-hover-background)] focus:bg-[var(--app-ghost-button-hover-background)]"
|
||||
style={{
|
||||
color: 'var(--app-primary-foreground)',
|
||||
}}
|
||||
onClick={onSaveSession}
|
||||
title="Save Conversation"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
data-slot="icon"
|
||||
className="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M4.25 2A2.25 2.25 0 0 0 2 4.25v11.5A2.25 2.25 0 0 0 4.25 18h11.5A2.25 2.25 0 0 0 18 15.75V8.25a.75.75 0 0 1 .217-.517l.083-.083a.75.75 0 0 1 1.061 0l2.239 2.239A.75.75 0 0 1 22 10.5v5.25a4.75 4.75 0 0 1-4.75 4.75H4.75A4.75 4.75 0 0 1 0 15.75V4.25A4.75 4.75 0 0 1 4.75 0h5a.75.75 0 0 1 0 1.5h-5ZM9.017 6.5a1.5 1.5 0 0 1 2.072.58l.43.862a1 1 0 0 0 .895.558h3.272a1.5 1.5 0 0 1 1.5 1.5v6.75a1.5 1.5 0 0 1-1.5 1.5h-7.5a1.5 1.5 0 0 1-1.5-1.5v-6.75a1.5 1.5 0 0 1 1.5-1.5h1.25a1 1 0 0 0 .895-.558l.43-.862a1.5 1.5 0 0 1 .511-.732ZM11.78 8.47a.75.75 0 0 0-1.06-1.06L8.75 9.379 7.78 8.41a.75.75 0 0 0-1.06 1.06l1.5 1.5a.75.75 0 0 0 1.06 0l2.5-2.5Z"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button> */}
|
||||
|
||||
{/* New Session Button */}
|
||||
<button
|
||||
className="flex-none p-0 bg-transparent border border-transparent rounded cursor-pointer flex items-center justify-center outline-none w-6 h-6 hover:bg-[var(--app-ghost-button-hover-background)] focus:bg-[var(--app-ghost-button-hover-background)]"
|
||||
style={{
|
||||
color: 'var(--app-primary-foreground)',
|
||||
}}
|
||||
onClick={onNewSession}
|
||||
title="New Session"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
data-slot="icon"
|
||||
className="w-4 h-4"
|
||||
>
|
||||
<path d="M10.75 4.75a.75.75 0 0 0-1.5 0v4.5h-4.5a.75.75 0 0 0 0 1.5h4.5v4.5a.75.75 0 0 0 1.5 0v-4.5h4.5a.75.75 0 0 0 0-1.5h-4.5v-4.5Z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@@ -17,6 +17,9 @@ interface SessionSelectorProps {
|
||||
onSearchChange: (query: string) => void;
|
||||
onSelectSession: (sessionId: string) => void;
|
||||
onClose: () => void;
|
||||
hasMore?: boolean;
|
||||
isLoading?: boolean;
|
||||
onLoadMore?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,6 +34,9 @@ export const SessionSelector: React.FC<SessionSelectorProps> = ({
|
||||
onSearchChange,
|
||||
onSelectSession,
|
||||
onClose,
|
||||
hasMore = false,
|
||||
isLoading = false,
|
||||
onLoadMore,
|
||||
}) => {
|
||||
if (!visible) {
|
||||
return null;
|
||||
@@ -66,7 +72,17 @@ export const SessionSelector: React.FC<SessionSelectorProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Session List with Grouping */}
|
||||
<div className="session-list-content overflow-y-auto flex-1 select-none p-2">
|
||||
<div
|
||||
className="session-list-content overflow-y-auto flex-1 select-none p-2"
|
||||
onScroll={(e) => {
|
||||
const el = e.currentTarget;
|
||||
const distanceToBottom =
|
||||
el.scrollHeight - (el.scrollTop + el.clientHeight);
|
||||
if (distanceToBottom < 48 && hasMore && !isLoading) {
|
||||
onLoadMore?.();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{hasNoSessions ? (
|
||||
<div
|
||||
className="p-5 text-center text-[var(--app-secondary-foreground)]"
|
||||
@@ -126,6 +142,11 @@ export const SessionSelector: React.FC<SessionSelectorProps> = ({
|
||||
</React.Fragment>
|
||||
))
|
||||
)}
|
||||
{hasMore && (
|
||||
<div className="p-2 text-center opacity-60 text-[0.9em]">
|
||||
{isLoading ? 'Loading…' : ''}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -16,6 +16,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
private currentStreamContent = '';
|
||||
private isSavingCheckpoint = false;
|
||||
private loginHandler: (() => Promise<void>) | null = null;
|
||||
private isTitleSet = false; // Flag to track if title has been set
|
||||
|
||||
canHandle(messageType: string): boolean {
|
||||
return [
|
||||
@@ -74,7 +75,10 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
break;
|
||||
|
||||
case 'getQwenSessions':
|
||||
await this.handleGetQwenSessions();
|
||||
await this.handleGetQwenSessions(
|
||||
(data?.cursor as number | undefined) ?? undefined,
|
||||
(data?.size as number | undefined) ?? undefined,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'saveSession':
|
||||
@@ -231,8 +235,8 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
);
|
||||
}
|
||||
|
||||
// Generate title for first message
|
||||
if (isFirstMessage) {
|
||||
// Generate title for first message, but only if it hasn't been set yet
|
||||
if (isFirstMessage && !this.isTitleSet) {
|
||||
const title = text.substring(0, 50) + (text.length > 50 ? '...' : '');
|
||||
this.sendToWebView({
|
||||
type: 'sessionTitleUpdated',
|
||||
@@ -241,6 +245,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
title,
|
||||
},
|
||||
});
|
||||
this.isTitleSet = true; // Mark title as set
|
||||
}
|
||||
|
||||
// Save user message
|
||||
@@ -280,33 +285,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
vscode.window.showInformationMessage(
|
||||
'Please wait while we connect to Qwen Code...',
|
||||
);
|
||||
await vscode.commands.executeCommand('qwenCode.login');
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate current session before sending message
|
||||
const isSessionValid = await this.agentManager.checkSessionValidity();
|
||||
if (!isSessionValid) {
|
||||
console.warn('[SessionMessageHandler] Current session is not valid');
|
||||
|
||||
// Show non-modal notification with Login button
|
||||
const result = await vscode.window.showWarningMessage(
|
||||
'Your session has expired. Please login again to continue using Qwen Code.',
|
||||
'Login Now',
|
||||
);
|
||||
|
||||
if (result === 'Login Now') {
|
||||
// Use login handler directly
|
||||
if (this.loginHandler) {
|
||||
await this.loginHandler();
|
||||
} else {
|
||||
// Fallback to command
|
||||
vscode.window.showInformationMessage(
|
||||
'Please wait while we connect to Qwen Code...',
|
||||
);
|
||||
await vscode.commands.executeCommand('qwenCode.login');
|
||||
await vscode.commands.executeCommand('qwen-code.login');
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -379,7 +358,8 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
console.error('[SessionMessageHandler] Error sending message:', error);
|
||||
|
||||
const err = error as unknown as Error;
|
||||
const errorMsg = String(error);
|
||||
// Safely convert error to string
|
||||
const errorMsg = error ? String(error) : 'Unknown error';
|
||||
const lower = errorMsg.toLowerCase();
|
||||
|
||||
// Suppress user-cancelled/aborted errors (ESC/Stop button)
|
||||
@@ -420,7 +400,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
if (this.loginHandler) {
|
||||
await this.loginHandler();
|
||||
} else {
|
||||
await vscode.commands.executeCommand('qwenCode.login');
|
||||
await vscode.commands.executeCommand('qwen-code.login');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,7 +436,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
if (this.loginHandler) {
|
||||
await this.loginHandler();
|
||||
} else {
|
||||
await vscode.commands.executeCommand('qwenCode.login');
|
||||
await vscode.commands.executeCommand('qwen-code.login');
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
@@ -489,13 +469,17 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
type: 'conversationCleared',
|
||||
data: {},
|
||||
});
|
||||
|
||||
// Reset title flag when creating a new session
|
||||
this.isTitleSet = false;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'[SessionMessageHandler] Failed to create new session:',
|
||||
error,
|
||||
);
|
||||
|
||||
const errorMsg = String(error);
|
||||
// Safely convert error to string
|
||||
const errorMsg = error ? String(error) : 'Unknown error';
|
||||
// Check for authentication/session expiration errors
|
||||
if (
|
||||
errorMsg.includes('Authentication required') ||
|
||||
@@ -514,7 +498,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
if (this.loginHandler) {
|
||||
await this.loginHandler();
|
||||
} else {
|
||||
await vscode.commands.executeCommand('qwenCode.login');
|
||||
await vscode.commands.executeCommand('qwen-code.login');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,7 +535,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
if (this.loginHandler) {
|
||||
await this.loginHandler();
|
||||
} else {
|
||||
await vscode.commands.executeCommand('qwenCode.login');
|
||||
await vscode.commands.executeCommand('qwen-code.login');
|
||||
}
|
||||
} else if (selection === 'View Offline') {
|
||||
// Show messages from local cache only
|
||||
@@ -593,8 +577,8 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// Get session details
|
||||
let sessionDetails = null;
|
||||
// Get session details (includes cwd and filePath when using ACP)
|
||||
let sessionDetails: Record<string, unknown> | null = null;
|
||||
try {
|
||||
const allSessions = await this.agentManager.getSessionList();
|
||||
sessionDetails = allSessions.find(
|
||||
@@ -613,8 +597,10 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
|
||||
// Try to load session via ACP (now we should be connected)
|
||||
try {
|
||||
const loadResponse =
|
||||
await this.agentManager.loadSessionViaAcp(sessionId);
|
||||
const loadResponse = await this.agentManager.loadSessionViaAcp(
|
||||
sessionId,
|
||||
(sessionDetails?.cwd as string | undefined) || undefined,
|
||||
);
|
||||
console.log(
|
||||
'[SessionMessageHandler] session/load succeeded:',
|
||||
loadResponse,
|
||||
@@ -628,13 +614,21 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
type: 'qwenSessionSwitched',
|
||||
data: { sessionId, messages, session: sessionDetails },
|
||||
});
|
||||
|
||||
// Reset title flag when switching sessions
|
||||
this.isTitleSet = false;
|
||||
|
||||
// Successfully loaded session, return early to avoid fallback logic
|
||||
return;
|
||||
} catch (loadError) {
|
||||
console.warn(
|
||||
'[SessionMessageHandler] session/load failed, using fallback:',
|
||||
loadError,
|
||||
);
|
||||
|
||||
const errorMsg = String(loadError);
|
||||
// Safely convert error to string
|
||||
const errorMsg = loadError ? String(loadError) : 'Unknown error';
|
||||
|
||||
// Check for authentication/session expiration errors
|
||||
if (
|
||||
errorMsg.includes('Authentication required') ||
|
||||
@@ -653,7 +647,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
if (this.loginHandler) {
|
||||
await this.loginHandler();
|
||||
} else {
|
||||
await vscode.commands.executeCommand('qwenCode.login');
|
||||
await vscode.commands.executeCommand('qwen-code.login');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,16 +675,29 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
data: { sessionId, messages, session: sessionDetails },
|
||||
});
|
||||
|
||||
// Only show the cache warning if we actually fell back to local cache
|
||||
// and didn't successfully load via ACP
|
||||
// Check if we truly fell back by checking if loadError is not null/undefined
|
||||
// and if it's not a successful response that looks like an error
|
||||
if (
|
||||
loadError &&
|
||||
typeof loadError === 'object' &&
|
||||
!('result' in loadError)
|
||||
) {
|
||||
vscode.window.showWarningMessage(
|
||||
'Session restored from local cache. Some context may be incomplete.',
|
||||
);
|
||||
}
|
||||
} catch (createError) {
|
||||
console.error(
|
||||
'[SessionMessageHandler] Failed to create session:',
|
||||
createError,
|
||||
);
|
||||
|
||||
const createErrorMsg = String(createError);
|
||||
// Safely convert error to string
|
||||
const createErrorMsg = createError
|
||||
? String(createError)
|
||||
: 'Unknown error';
|
||||
// Check for authentication/session expiration errors in session creation
|
||||
if (
|
||||
createErrorMsg.includes('Authentication required') ||
|
||||
@@ -709,7 +716,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
if (this.loginHandler) {
|
||||
await this.loginHandler();
|
||||
} else {
|
||||
await vscode.commands.executeCommand('qwenCode.login');
|
||||
await vscode.commands.executeCommand('qwen-code.login');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -738,7 +745,8 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
} catch (error) {
|
||||
console.error('[SessionMessageHandler] Failed to switch session:', error);
|
||||
|
||||
const errorMsg = String(error);
|
||||
// Safely convert error to string
|
||||
const errorMsg = error ? String(error) : 'Unknown error';
|
||||
// Check for authentication/session expiration errors
|
||||
if (
|
||||
errorMsg.includes('Authentication required') ||
|
||||
@@ -757,7 +765,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
if (this.loginHandler) {
|
||||
await this.loginHandler();
|
||||
} else {
|
||||
await vscode.commands.executeCommand('qwenCode.login');
|
||||
await vscode.commands.executeCommand('qwen-code.login');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -778,17 +786,31 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
/**
|
||||
* Handle get Qwen sessions request
|
||||
*/
|
||||
private async handleGetQwenSessions(): Promise<void> {
|
||||
private async handleGetQwenSessions(
|
||||
cursor?: number,
|
||||
size?: number,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const sessions = await this.agentManager.getSessionList();
|
||||
// Paged when possible; falls back to full list if ACP not supported
|
||||
const page = await this.agentManager.getSessionListPaged({
|
||||
cursor,
|
||||
size,
|
||||
});
|
||||
const append = typeof cursor === 'number';
|
||||
this.sendToWebView({
|
||||
type: 'qwenSessionList',
|
||||
data: { sessions },
|
||||
data: {
|
||||
sessions: page.sessions,
|
||||
nextCursor: page.nextCursor,
|
||||
hasMore: page.hasMore,
|
||||
append,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[SessionMessageHandler] Failed to get sessions:', error);
|
||||
|
||||
const errorMsg = String(error);
|
||||
// Safely convert error to string
|
||||
const errorMsg = error ? String(error) : 'Unknown error';
|
||||
// Check for authentication/session expiration errors
|
||||
if (
|
||||
errorMsg.includes('Authentication required') ||
|
||||
@@ -807,7 +829,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
if (this.loginHandler) {
|
||||
await this.loginHandler();
|
||||
} else {
|
||||
await vscode.commands.executeCommand('qwenCode.login');
|
||||
await vscode.commands.executeCommand('qwen-code.login');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -851,7 +873,8 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
data: response,
|
||||
});
|
||||
} catch (acpError) {
|
||||
const errorMsg = String(acpError);
|
||||
// Safely convert error to string
|
||||
const errorMsg = acpError ? String(acpError) : 'Unknown error';
|
||||
// Check for authentication/session expiration errors
|
||||
if (
|
||||
errorMsg.includes('Authentication required') ||
|
||||
@@ -870,7 +893,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
if (this.loginHandler) {
|
||||
await this.loginHandler();
|
||||
} else {
|
||||
await vscode.commands.executeCommand('qwenCode.login');
|
||||
await vscode.commands.executeCommand('qwen-code.login');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -898,7 +921,8 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
} catch (error) {
|
||||
console.error('[SessionMessageHandler] Failed to save session:', error);
|
||||
|
||||
const errorMsg = String(error);
|
||||
// Safely convert error to string
|
||||
const errorMsg = error ? String(error) : 'Unknown error';
|
||||
// Check for authentication/session expiration errors
|
||||
if (
|
||||
errorMsg.includes('Authentication required') ||
|
||||
@@ -917,7 +941,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
if (this.loginHandler) {
|
||||
await this.loginHandler();
|
||||
} else {
|
||||
await vscode.commands.executeCommand('qwenCode.login');
|
||||
await vscode.commands.executeCommand('qwen-code.login');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -983,7 +1007,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
if (this.loginHandler) {
|
||||
await this.loginHandler();
|
||||
} else {
|
||||
await vscode.commands.executeCommand('qwenCode.login');
|
||||
await vscode.commands.executeCommand('qwen-code.login');
|
||||
}
|
||||
} else if (selection === 'View Offline') {
|
||||
const messages =
|
||||
@@ -1014,8 +1038,16 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
type: 'qwenSessionSwitched',
|
||||
data: { sessionId, messages },
|
||||
});
|
||||
|
||||
// Reset title flag when resuming sessions
|
||||
this.isTitleSet = false;
|
||||
|
||||
// Successfully loaded session, return early to avoid fallback logic
|
||||
await this.handleGetQwenSessions();
|
||||
return;
|
||||
} catch (acpError) {
|
||||
const errorMsg = String(acpError);
|
||||
// Safely convert error to string
|
||||
const errorMsg = acpError ? String(acpError) : 'Unknown error';
|
||||
// Check for authentication/session expiration errors
|
||||
if (
|
||||
errorMsg.includes('Authentication required') ||
|
||||
@@ -1034,7 +1066,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
if (this.loginHandler) {
|
||||
await this.loginHandler();
|
||||
} else {
|
||||
await vscode.commands.executeCommand('qwenCode.login');
|
||||
await vscode.commands.executeCommand('qwen-code.login');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1065,7 +1097,8 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
} catch (error) {
|
||||
console.error('[SessionMessageHandler] Failed to resume session:', error);
|
||||
|
||||
const errorMsg = String(error);
|
||||
// Safely convert error to string
|
||||
const errorMsg = error ? String(error) : 'Unknown error';
|
||||
// Check for authentication/session expiration errors
|
||||
if (
|
||||
errorMsg.includes('Authentication required') ||
|
||||
@@ -1084,7 +1117,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
if (this.loginHandler) {
|
||||
await this.loginHandler();
|
||||
} else {
|
||||
await vscode.commands.executeCommand('qwenCode.login');
|
||||
await vscode.commands.executeCommand('qwen-code.login');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,11 @@ export const useSessionManagement = (vscode: VSCodeAPI) => {
|
||||
const [showSessionSelector, setShowSessionSelector] = useState(false);
|
||||
const [sessionSearchQuery, setSessionSearchQuery] = useState('');
|
||||
const [savedSessionTags, setSavedSessionTags] = useState<string[]>([]);
|
||||
const [nextCursor, setNextCursor] = useState<number | undefined>(undefined);
|
||||
const [hasMore, setHasMore] = useState<boolean>(true);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
/**
|
||||
* Filter session list
|
||||
@@ -44,10 +49,24 @@ export const useSessionManagement = (vscode: VSCodeAPI) => {
|
||||
* Load session list
|
||||
*/
|
||||
const handleLoadQwenSessions = useCallback(() => {
|
||||
vscode.postMessage({ type: 'getQwenSessions', data: {} });
|
||||
// Reset pagination state and load first page
|
||||
setQwenSessions([]);
|
||||
setNextCursor(undefined);
|
||||
setHasMore(true);
|
||||
setIsLoading(true);
|
||||
vscode.postMessage({ type: 'getQwenSessions', data: { size: PAGE_SIZE } });
|
||||
setShowSessionSelector(true);
|
||||
}, [vscode]);
|
||||
|
||||
const handleLoadMoreSessions = useCallback(() => {
|
||||
if (!hasMore || isLoading || nextCursor === undefined) return;
|
||||
setIsLoading(true);
|
||||
vscode.postMessage({
|
||||
type: 'getQwenSessions',
|
||||
data: { cursor: nextCursor, size: PAGE_SIZE },
|
||||
});
|
||||
}, [hasMore, isLoading, nextCursor, vscode]);
|
||||
|
||||
/**
|
||||
* Create new session
|
||||
*/
|
||||
@@ -117,6 +136,9 @@ export const useSessionManagement = (vscode: VSCodeAPI) => {
|
||||
sessionSearchQuery,
|
||||
filteredSessions,
|
||||
savedSessionTags,
|
||||
nextCursor,
|
||||
hasMore,
|
||||
isLoading,
|
||||
|
||||
// State setters
|
||||
setQwenSessions,
|
||||
@@ -125,6 +147,9 @@ export const useSessionManagement = (vscode: VSCodeAPI) => {
|
||||
setShowSessionSelector,
|
||||
setSessionSearchQuery,
|
||||
setSavedSessionTags,
|
||||
setNextCursor,
|
||||
setHasMore,
|
||||
setIsLoading,
|
||||
|
||||
// Operations
|
||||
handleLoadQwenSessions,
|
||||
@@ -132,5 +157,6 @@ export const useSessionManagement = (vscode: VSCodeAPI) => {
|
||||
handleSwitchSession,
|
||||
handleSaveSession,
|
||||
handleSaveSessionResponse,
|
||||
handleLoadMoreSessions,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -10,18 +10,27 @@ import type { Conversation } from '../../storage/conversationStore.js';
|
||||
import type {
|
||||
PermissionOption,
|
||||
ToolCall as PermissionToolCall,
|
||||
} from '../components/PermissionRequest.js';
|
||||
import type { PlanEntry } from '../components/PlanDisplay.js';
|
||||
} from '../components/PermissionDrawer/PermissionRequest.js';
|
||||
import type { ToolCallUpdate } from '../types/toolCall.js';
|
||||
import type { PlanEntry } from '../../agents/qwenTypes.js';
|
||||
|
||||
interface UseWebViewMessagesProps {
|
||||
// Session management
|
||||
sessionManagement: {
|
||||
currentSessionId: string | null;
|
||||
setQwenSessions: (sessions: Array<Record<string, unknown>>) => void;
|
||||
setQwenSessions: (
|
||||
sessions:
|
||||
| Array<Record<string, unknown>>
|
||||
| ((
|
||||
prev: Array<Record<string, unknown>>,
|
||||
) => Array<Record<string, unknown>>),
|
||||
) => void;
|
||||
setCurrentSessionId: (id: string | null) => void;
|
||||
setCurrentSessionTitle: (title: string) => void;
|
||||
setShowSessionSelector: (show: boolean) => void;
|
||||
setNextCursor: (cursor: number | undefined) => void;
|
||||
setHasMore: (hasMore: boolean) => void;
|
||||
setIsLoading: (loading: boolean) => void;
|
||||
handleSaveSessionResponse: (response: {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
@@ -487,8 +496,19 @@ export const useWebViewMessages = ({
|
||||
}
|
||||
|
||||
case 'qwenSessionList': {
|
||||
const sessions = message.data.sessions || [];
|
||||
handlers.sessionManagement.setQwenSessions(sessions);
|
||||
const sessions =
|
||||
(message.data.sessions as Array<Record<string, unknown>>) || [];
|
||||
const append = Boolean(message.data.append);
|
||||
const nextCursor = message.data.nextCursor as number | undefined;
|
||||
const hasMore = Boolean(message.data.hasMore);
|
||||
|
||||
handlers.sessionManagement.setQwenSessions(
|
||||
(prev: Array<Record<string, unknown>>) =>
|
||||
append ? [...prev, ...sessions] : sessions,
|
||||
);
|
||||
handlers.sessionManagement.setNextCursor(nextCursor);
|
||||
handlers.sessionManagement.setHasMore(hasMore);
|
||||
handlers.sessionManagement.setIsLoading(false);
|
||||
if (
|
||||
handlers.sessionManagement.currentSessionId &&
|
||||
sessions.length > 0
|
||||
@@ -533,8 +553,26 @@ export const useWebViewMessages = ({
|
||||
} else {
|
||||
handlers.messageHandling.clearMessages();
|
||||
}
|
||||
|
||||
// Clear and restore tool calls if provided in session data
|
||||
handlers.clearToolCalls();
|
||||
if (message.data.toolCalls && Array.isArray(message.data.toolCalls)) {
|
||||
message.data.toolCalls.forEach((toolCall: unknown) => {
|
||||
if (toolCall && typeof toolCall === 'object') {
|
||||
handlers.handleToolCallUpdate(toolCall as ToolCallUpdate);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Restore plan entries if provided
|
||||
if (
|
||||
message.data.planEntries &&
|
||||
Array.isArray(message.data.planEntries)
|
||||
) {
|
||||
handlers.setPlanEntries(message.data.planEntries);
|
||||
} else {
|
||||
handlers.setPlanEntries([]);
|
||||
}
|
||||
lastPlanSnapshotRef.current = null;
|
||||
break;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user