feat(sessions): add API endpoints for hiding and unhiding sessions

- Implemented new POST endpoints for hiding and unhiding individual and bulk sessions in the configuration.
- Added error handling and validation for project and session IDs in the new API routes.
- Enhanced the existing session management functionality in the ConfigManager to support these operations.
This commit is contained in:
matt 2026-02-16 23:33:56 +09:00
parent da1a8998fc
commit fb66b14d62
3 changed files with 118 additions and 22 deletions

View file

@ -382,6 +382,94 @@ export function registerConfigRoutes(app: FastifyInstance): void {
} }
); );
// Hide session
app.post<{ Body: { projectId: string; sessionId: string } }>(
'/api/config/hide-session',
async (request) => {
try {
const { projectId, sessionId } = request.body;
if (!projectId || typeof projectId !== 'string') {
return { success: false, error: 'Project ID is required and must be a string' };
}
if (!sessionId || typeof sessionId !== 'string') {
return { success: false, error: 'Session ID is required and must be a string' };
}
configManager.hideSession(projectId, sessionId);
return { success: true };
} catch (error) {
logger.error('Error in POST /api/config/hide-session:', error);
return { success: false, error: getErrorMessage(error) };
}
}
);
// Unhide session
app.post<{ Body: { projectId: string; sessionId: string } }>(
'/api/config/unhide-session',
async (request) => {
try {
const { projectId, sessionId } = request.body;
if (!projectId || typeof projectId !== 'string') {
return { success: false, error: 'Project ID is required and must be a string' };
}
if (!sessionId || typeof sessionId !== 'string') {
return { success: false, error: 'Session ID is required and must be a string' };
}
configManager.unhideSession(projectId, sessionId);
return { success: true };
} catch (error) {
logger.error('Error in POST /api/config/unhide-session:', error);
return { success: false, error: getErrorMessage(error) };
}
}
);
// Bulk hide sessions
app.post<{ Body: { projectId: string; sessionIds: string[] } }>(
'/api/config/hide-sessions',
async (request) => {
try {
const { projectId, sessionIds } = request.body;
if (!projectId || typeof projectId !== 'string') {
return { success: false, error: 'Project ID is required and must be a string' };
}
if (!Array.isArray(sessionIds) || sessionIds.some((id) => typeof id !== 'string')) {
return { success: false, error: 'Session IDs must be an array of strings' };
}
configManager.hideSessions(projectId, sessionIds);
return { success: true };
} catch (error) {
logger.error('Error in POST /api/config/hide-sessions:', error);
return { success: false, error: getErrorMessage(error) };
}
}
);
// Bulk unhide sessions
app.post<{ Body: { projectId: string; sessionIds: string[] } }>(
'/api/config/unhide-sessions',
async (request) => {
try {
const { projectId, sessionIds } = request.body;
if (!projectId || typeof projectId !== 'string') {
return { success: false, error: 'Project ID is required and must be a string' };
}
if (!Array.isArray(sessionIds) || sessionIds.some((id) => typeof id !== 'string')) {
return { success: false, error: 'Session IDs must be an array of strings' };
}
configManager.unhideSessions(projectId, sessionIds);
return { success: true };
} catch (error) {
logger.error('Error in POST /api/config/unhide-sessions:', error);
return { success: false, error: getErrorMessage(error) };
}
}
);
// Select folders - no-op in browser mode // Select folders - no-op in browser mode
app.post('/api/config/select-folders', async (): Promise<ConfigResult<string[]>> => { app.post('/api/config/select-folders', async (): Promise<ConfigResult<string[]>> => {
return { success: true, data: [] }; return { success: true, data: [] };

View file

@ -562,14 +562,18 @@ export async function analyzeSessionFileMetadata(
} }
// Last phase: final tokens - last post-compaction // Last phase: final tokens - last post-compaction
// Guard: if the last compaction had no subsequent assistant message, post is 0.
// In that case, skip the final phase to avoid double-counting.
const lastPhase = compactionPhases[compactionPhases.length - 1]; const lastPhase = compactionPhases[compactionPhases.length - 1];
const lastContribution = lastMainAssistantInputTokens - lastPhase.post; if (lastPhase.post > 0) {
total += lastContribution; const lastContribution = lastMainAssistantInputTokens - lastPhase.post;
phaseBreakdown.push({ total += lastContribution;
phaseNumber: compactionPhases.length + 1, phaseBreakdown.push({
contribution: lastContribution, phaseNumber: compactionPhases.length + 1,
peakTokens: lastMainAssistantInputTokens, contribution: lastContribution,
}); peakTokens: lastMainAssistantInputTokens,
});
}
contextConsumption = total; contextConsumption = total;
} }

View file

@ -413,23 +413,27 @@ export const ChatHistory = ({ tabId }: ChatHistoryProps): JSX.Element => {
const prevItem = aiItemIndex > 0 ? conversation.items[aiItemIndex - 1] : null; const prevItem = aiItemIndex > 0 ? conversation.items[aiItemIndex - 1] : null;
if (prevItem?.type !== 'user') return; if (prevItem?.type !== 'user') return;
const groupId = prevItem.group.id; const run = async (): Promise<void> => {
const element = chatItemRefs.current.get(groupId); const groupId = prevItem.group.id;
if (!element) return; await ensureGroupVisible(groupId);
const element = chatItemRefs.current.get(groupId);
if (!element) return;
element.scrollIntoView({ behavior: 'smooth', block: 'center' }); element.scrollIntoView({ behavior: 'smooth', block: 'center' });
setHighlightedGroupId(groupId); setHighlightedGroupId(groupId);
setIsNavigationHighlight(true); setIsNavigationHighlight(true);
if (navigationHighlightTimerRef.current) { if (navigationHighlightTimerRef.current) {
clearTimeout(navigationHighlightTimerRef.current); clearTimeout(navigationHighlightTimerRef.current);
} }
navigationHighlightTimerRef.current = setTimeout(() => { navigationHighlightTimerRef.current = setTimeout(() => {
setHighlightedGroupId(null); setHighlightedGroupId(null);
setIsNavigationHighlight(false); setIsNavigationHighlight(false);
navigationHighlightTimerRef.current = null; navigationHighlightTimerRef.current = null;
}, 2000); }, 2000);
};
void run();
}, },
[conversation, setHighlightedGroupId] [conversation, ensureGroupVisible, setHighlightedGroupId]
); );
// Handler to navigate to a specific tool within a turn from context panel // Handler to navigate to a specific tool within a turn from context panel