- Added new scripts for rendering and previewing videos using Remotion, enhancing multimedia capabilities. - Updated package.json to include Remotion dependencies and commands for rendering videos in different formats. - Modified .gitignore to exclude Remotion output files, ensuring a cleaner repository. - Enhanced README to reflect new features and usage instructions for video rendering. This commit significantly improves the application's multimedia functionality by integrating Remotion, allowing users to create and preview videos seamlessly.
363 lines
11 KiB
TypeScript
363 lines
11 KiB
TypeScript
/**
|
|
* IPC Handlers for Session Operations.
|
|
*
|
|
* Handlers:
|
|
* - get-sessions: List sessions for a project
|
|
* - get-sessions-paginated: List sessions with cursor-based pagination
|
|
* - get-session-detail: Get full session detail with subagents
|
|
* - get-session-groups: Get conversation groups for a session
|
|
* - get-session-metrics: Get metrics for a session
|
|
*/
|
|
|
|
import { createLogger } from '@shared/utils/logger';
|
|
import { type IpcMain, type IpcMainInvokeEvent } from 'electron';
|
|
|
|
import { DataCache } from '../services';
|
|
import {
|
|
type ConversationGroup,
|
|
type PaginatedSessionsResult,
|
|
type Session,
|
|
type SessionDetail,
|
|
type SessionMetrics,
|
|
type SessionsByIdsOptions,
|
|
type SessionsPaginationOptions,
|
|
} from '../types';
|
|
|
|
import { coercePageLimit, validateProjectId, validateSessionId } from './guards';
|
|
|
|
import type { ServiceContextRegistry } from '../services';
|
|
import type { WaterfallData } from '@shared/types';
|
|
|
|
const logger = createLogger('IPC:sessions');
|
|
|
|
// Service registry - set via initialize
|
|
let registry: ServiceContextRegistry;
|
|
|
|
/**
|
|
* Initializes session handlers with service registry.
|
|
*/
|
|
export function initializeSessionHandlers(contextRegistry: ServiceContextRegistry): void {
|
|
registry = contextRegistry;
|
|
}
|
|
|
|
/**
|
|
* Registers all session-related IPC handlers.
|
|
*/
|
|
export function registerSessionHandlers(ipcMain: IpcMain): void {
|
|
ipcMain.handle('get-sessions', handleGetSessions);
|
|
ipcMain.handle('get-sessions-paginated', handleGetSessionsPaginated);
|
|
ipcMain.handle('get-sessions-by-ids', handleGetSessionsByIds);
|
|
ipcMain.handle('get-session-detail', handleGetSessionDetail);
|
|
ipcMain.handle('get-session-groups', handleGetSessionGroups);
|
|
ipcMain.handle('get-session-metrics', handleGetSessionMetrics);
|
|
ipcMain.handle('get-waterfall-data', handleGetWaterfallData);
|
|
|
|
logger.info('Session handlers registered');
|
|
}
|
|
|
|
/**
|
|
* Removes all session IPC handlers.
|
|
*/
|
|
export function removeSessionHandlers(ipcMain: IpcMain): void {
|
|
ipcMain.removeHandler('get-sessions');
|
|
ipcMain.removeHandler('get-sessions-paginated');
|
|
ipcMain.removeHandler('get-sessions-by-ids');
|
|
ipcMain.removeHandler('get-session-detail');
|
|
ipcMain.removeHandler('get-session-groups');
|
|
ipcMain.removeHandler('get-session-metrics');
|
|
ipcMain.removeHandler('get-waterfall-data');
|
|
|
|
logger.info('Session handlers removed');
|
|
}
|
|
|
|
// =============================================================================
|
|
// Handler Implementations
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Handler for 'get-sessions' IPC call.
|
|
* Lists all sessions for a given project.
|
|
*/
|
|
async function handleGetSessions(
|
|
_event: IpcMainInvokeEvent,
|
|
projectId: string
|
|
): Promise<Session[]> {
|
|
try {
|
|
const validatedProject = validateProjectId(projectId);
|
|
if (!validatedProject.valid) {
|
|
logger.error(`get-sessions rejected: ${validatedProject.error ?? 'Invalid projectId'}`);
|
|
return [];
|
|
}
|
|
|
|
const { projectScanner } = registry.getActive();
|
|
const sessions = await projectScanner.listSessions(validatedProject.value!);
|
|
return sessions;
|
|
} catch (error) {
|
|
logger.error(`Error in get-sessions for project ${projectId}:`, error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handler for 'get-sessions-paginated' IPC call.
|
|
* Lists sessions for a project with cursor-based pagination.
|
|
*/
|
|
async function handleGetSessionsPaginated(
|
|
_event: IpcMainInvokeEvent,
|
|
projectId: string,
|
|
cursor: string | null,
|
|
limit?: number,
|
|
options?: SessionsPaginationOptions
|
|
): Promise<PaginatedSessionsResult> {
|
|
try {
|
|
const validatedProject = validateProjectId(projectId);
|
|
if (!validatedProject.valid) {
|
|
logger.error(
|
|
`get-sessions-paginated rejected: ${validatedProject.error ?? 'Invalid projectId'}`
|
|
);
|
|
return { sessions: [], nextCursor: null, hasMore: false, totalCount: 0 };
|
|
}
|
|
|
|
const { projectScanner } = registry.getActive();
|
|
const safeLimit = coercePageLimit(limit, 20);
|
|
const result = await projectScanner.listSessionsPaginated(
|
|
validatedProject.value!,
|
|
cursor,
|
|
safeLimit,
|
|
options
|
|
);
|
|
return result;
|
|
} catch (error) {
|
|
logger.error(`Error in get-sessions-paginated for project ${projectId}:`, error);
|
|
return { sessions: [], nextCursor: null, hasMore: false, totalCount: 0 };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handler for 'get-sessions-by-ids' IPC call.
|
|
* Fetches lightweight session metadata for specific session IDs.
|
|
* Used to load pinned sessions that may not be in the paginated list.
|
|
*/
|
|
async function handleGetSessionsByIds(
|
|
_event: IpcMainInvokeEvent,
|
|
projectId: string,
|
|
sessionIds: string[],
|
|
options?: SessionsByIdsOptions
|
|
): Promise<Session[]> {
|
|
try {
|
|
const validatedProject = validateProjectId(projectId);
|
|
if (!validatedProject.valid) {
|
|
logger.error(
|
|
`get-sessions-by-ids rejected: ${validatedProject.error ?? 'Invalid projectId'}`
|
|
);
|
|
return [];
|
|
}
|
|
|
|
if (!Array.isArray(sessionIds)) {
|
|
logger.error('get-sessions-by-ids rejected: sessionIds must be an array');
|
|
return [];
|
|
}
|
|
|
|
// Cap at 50 IDs
|
|
const capped = sessionIds.slice(0, 50);
|
|
|
|
// Validate each session ID
|
|
const validIds: string[] = [];
|
|
for (const id of capped) {
|
|
const validated = validateSessionId(id);
|
|
if (validated.valid) {
|
|
validIds.push(validated.value!);
|
|
}
|
|
}
|
|
|
|
if (validIds.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
const { projectScanner } = registry.getActive();
|
|
const fsType = projectScanner.getFileSystemProvider().type;
|
|
const metadataLevel = options?.metadataLevel ?? (fsType === 'ssh' ? 'light' : 'deep');
|
|
const results = await Promise.all(
|
|
validIds.map((id) =>
|
|
projectScanner.getSessionWithOptions(validatedProject.value!, id, { metadataLevel })
|
|
)
|
|
);
|
|
|
|
return results.filter((s): s is Session => s !== null);
|
|
} catch (error) {
|
|
logger.error(`Error in get-sessions-by-ids for project ${projectId}:`, error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handler for 'get-session-detail' IPC call.
|
|
* Gets full session detail including parsed chunks and subagents.
|
|
* Uses cache to avoid re-parsing large files.
|
|
*/
|
|
async function handleGetSessionDetail(
|
|
_event: IpcMainInvokeEvent,
|
|
projectId: string,
|
|
sessionId: string
|
|
): Promise<SessionDetail | null> {
|
|
try {
|
|
const validatedProject = validateProjectId(projectId);
|
|
const validatedSession = validateSessionId(sessionId);
|
|
if (!validatedProject.valid || !validatedSession.valid) {
|
|
logger.error(
|
|
`get-session-detail rejected: ${validatedProject.error ?? validatedSession.error ?? 'Invalid parameters'}`
|
|
);
|
|
return null;
|
|
}
|
|
|
|
const { projectScanner, sessionParser, subagentResolver, chunkBuilder, dataCache } =
|
|
registry.getActive();
|
|
|
|
const safeProjectId = validatedProject.value!;
|
|
const safeSessionId = validatedSession.value!;
|
|
const cacheKey = DataCache.buildKey(safeProjectId, safeSessionId);
|
|
|
|
// Check cache first
|
|
let sessionDetail = dataCache.get(cacheKey);
|
|
|
|
if (sessionDetail) {
|
|
return sessionDetail;
|
|
}
|
|
|
|
const fsType = projectScanner.getFileSystemProvider().type;
|
|
// In SSH mode, avoid an extra deep metadata scan before full parse.
|
|
const session = await projectScanner.getSessionWithOptions(safeProjectId, safeSessionId, {
|
|
metadataLevel: fsType === 'ssh' ? 'light' : 'deep',
|
|
});
|
|
if (!session) {
|
|
logger.error(`Session not found: ${sessionId}`);
|
|
return null;
|
|
}
|
|
|
|
// Parse session messages
|
|
const parsedSession = await sessionParser.parseSession(safeProjectId, safeSessionId);
|
|
|
|
// Resolve subagents
|
|
const subagents = await subagentResolver.resolveSubagents(
|
|
safeProjectId,
|
|
safeSessionId,
|
|
parsedSession.taskCalls,
|
|
parsedSession.messages
|
|
);
|
|
session.hasSubagents = subagents.length > 0;
|
|
|
|
// Build session detail with chunks
|
|
sessionDetail = chunkBuilder.buildSessionDetail(session, parsedSession.messages, subagents);
|
|
|
|
// Cache the result
|
|
dataCache.set(cacheKey, sessionDetail);
|
|
|
|
return sessionDetail;
|
|
} catch (error) {
|
|
logger.error(`Error in get-session-detail for ${projectId}/${sessionId}:`, error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handler for 'get-session-groups' IPC call.
|
|
* Gets conversation groups for a session using the new buildGroups API.
|
|
* This is an alternative to chunks that provides a simpler, more natural grouping.
|
|
*/
|
|
async function handleGetSessionGroups(
|
|
_event: IpcMainInvokeEvent,
|
|
projectId: string,
|
|
sessionId: string
|
|
): Promise<ConversationGroup[]> {
|
|
try {
|
|
const validatedProject = validateProjectId(projectId);
|
|
const validatedSession = validateSessionId(sessionId);
|
|
if (!validatedProject.valid || !validatedSession.valid) {
|
|
logger.error(
|
|
`get-session-groups rejected: ${validatedProject.error ?? validatedSession.error ?? 'Invalid parameters'}`
|
|
);
|
|
return [];
|
|
}
|
|
const { sessionParser, subagentResolver, chunkBuilder } = registry.getActive();
|
|
const safeProjectId = validatedProject.value!;
|
|
const safeSessionId = validatedSession.value!;
|
|
|
|
// Parse session messages
|
|
const parsedSession = await sessionParser.parseSession(safeProjectId, safeSessionId);
|
|
|
|
// Resolve subagents
|
|
const subagents = await subagentResolver.resolveSubagents(
|
|
safeProjectId,
|
|
safeSessionId,
|
|
parsedSession.taskCalls,
|
|
parsedSession.messages
|
|
);
|
|
|
|
// Build conversation groups using the new API
|
|
const groups = chunkBuilder.buildGroups(parsedSession.messages, subagents);
|
|
|
|
return groups;
|
|
} catch (error) {
|
|
logger.error(`Error in get-session-groups for ${projectId}/${sessionId}:`, error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handler for 'get-session-metrics' IPC call.
|
|
* Gets metrics for a session without full detail.
|
|
*/
|
|
async function handleGetSessionMetrics(
|
|
_event: IpcMainInvokeEvent,
|
|
projectId: string,
|
|
sessionId: string
|
|
): Promise<SessionMetrics | null> {
|
|
try {
|
|
const validatedProject = validateProjectId(projectId);
|
|
const validatedSession = validateSessionId(sessionId);
|
|
if (!validatedProject.valid || !validatedSession.valid) {
|
|
return null;
|
|
}
|
|
const { sessionParser, dataCache } = registry.getActive();
|
|
const safeProjectId = validatedProject.value!;
|
|
const safeSessionId = validatedSession.value!;
|
|
|
|
// Try to get from cache first
|
|
const cacheKey = DataCache.buildKey(safeProjectId, safeSessionId);
|
|
const cached = dataCache.get(cacheKey);
|
|
|
|
if (cached) {
|
|
return cached.metrics;
|
|
}
|
|
|
|
// Parse session to get metrics
|
|
const parsedSession = await sessionParser.parseSession(safeProjectId, safeSessionId);
|
|
return parsedSession.metrics;
|
|
} catch (error) {
|
|
logger.error(`Error in get-session-metrics for ${projectId}/${sessionId}:`, error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handler for 'get-waterfall-data' IPC call.
|
|
* Builds waterfall chart data for a session.
|
|
*/
|
|
async function handleGetWaterfallData(
|
|
_event: IpcMainInvokeEvent,
|
|
projectId: string,
|
|
sessionId: string
|
|
): Promise<WaterfallData | null> {
|
|
try {
|
|
const detail = await handleGetSessionDetail(_event, projectId, sessionId);
|
|
if (!detail) {
|
|
return null;
|
|
}
|
|
|
|
const { chunkBuilder } = registry.getActive();
|
|
return chunkBuilder.buildWaterfallData(detail.chunks, detail.processes);
|
|
} catch (error) {
|
|
logger.error(`Error in get-waterfall-data for ${projectId}/${sessionId}:`, error);
|
|
return null;
|
|
}
|
|
}
|