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:
parent
da1a8998fc
commit
fb66b14d62
3 changed files with 118 additions and 22 deletions
|
|
@ -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: [] };
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue