feat: add web UI dashboard SPA with dashboard, chat, sessions, and settings pages
- Add SPA shell with hash-based router, sidebar navigation, and WebSocket RPC client - Add dashboard page with system health cards, channel status, and auto-refresh - Add chat page with session selector, streaming tool events, and markdown rendering - Add sessions page with list, history viewer, and delete functionality - Add settings page with hook pattern editor, tool list, and config viewer - Add backend handlers: sessions.delete, sessions.switch, system.channels, system.usage - Wire channelRegistry into gateway server for channel status reporting - Extend static file server with .mjs, .png, .ico, .woff2 content types
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import type { GatewayRequest, OutboundMessage } from '../protocol.js';
|
||||
import { makeResponse, makeError, ErrorCode } from '../protocol.js';
|
||||
import type { SessionManager } from '../../session/manager.js';
|
||||
import type { SessionBridge } from '../session-bridge.js';
|
||||
|
||||
export interface SessionHandlerDeps {
|
||||
sessionManager: SessionManager;
|
||||
sessionBridge?: SessionBridge;
|
||||
}
|
||||
|
||||
export function createSessionHandlers(deps: SessionHandlerDeps) {
|
||||
@@ -55,5 +57,44 @@ export function createSessionHandlers(deps: SessionHandlerDeps) {
|
||||
|
||||
return makeResponse(request.id, { sessionId });
|
||||
},
|
||||
|
||||
'sessions.delete': async (request: GatewayRequest): Promise<OutboundMessage> => {
|
||||
const params = request.params as { sessionId?: string } | undefined;
|
||||
if (!params?.sessionId) {
|
||||
return makeError(request.id, ErrorCode.InvalidRequest, 'sessionId is required');
|
||||
}
|
||||
|
||||
const { sessionId } = params;
|
||||
const parts = sessionId.split(':');
|
||||
const frontend = parts[0];
|
||||
const userId = parts.slice(1).join(':');
|
||||
const session = deps.sessionManager.getSession(frontend, userId);
|
||||
session.clear();
|
||||
|
||||
return makeResponse(request.id, { deleted: true, sessionId });
|
||||
},
|
||||
|
||||
'sessions.switch': async (request: GatewayRequest): Promise<OutboundMessage> => {
|
||||
const params = request.params as { sessionId?: string; connectionId?: string } | undefined;
|
||||
if (!params?.sessionId) {
|
||||
return makeError(request.id, ErrorCode.InvalidRequest, 'sessionId is required');
|
||||
}
|
||||
if (!deps.sessionBridge) {
|
||||
return makeError(request.id, ErrorCode.InternalError, 'Session switching not available');
|
||||
}
|
||||
|
||||
const connectionId = params.connectionId as string;
|
||||
if (!connectionId) {
|
||||
return makeError(request.id, ErrorCode.InvalidRequest, 'connectionId is required');
|
||||
}
|
||||
|
||||
try {
|
||||
deps.sessionBridge.switchSession(connectionId, params.sessionId);
|
||||
return makeResponse(request.id, { switched: true, sessionId: params.sessionId });
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Failed to switch session';
|
||||
return makeError(request.id, ErrorCode.InternalError, message);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ export interface SystemHandlerDeps {
|
||||
getConnectionCount: () => number;
|
||||
/** Optional callback to trigger a graceful restart. If not provided, system.restart returns an error. */
|
||||
restart?: () => Promise<void>;
|
||||
getChannels?: () => Array<{ name: string; status: string }>;
|
||||
getUsage?: () => { totalSessions: number; activeConnections: number };
|
||||
}
|
||||
|
||||
export function createSystemHandlers(deps: SystemHandlerDeps) {
|
||||
@@ -41,5 +43,22 @@ export function createSystemHandlers(deps: SystemHandlerDeps) {
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
'system.channels': async (request: GatewayRequest): Promise<OutboundMessage> => {
|
||||
if (!deps.getChannels) {
|
||||
return makeResponse(request.id, { channels: [] });
|
||||
}
|
||||
return makeResponse(request.id, { channels: deps.getChannels() });
|
||||
},
|
||||
|
||||
'system.usage': async (request: GatewayRequest): Promise<OutboundMessage> => {
|
||||
const uptime = Math.floor((Date.now() - deps.startTime) / 1000);
|
||||
const usage = deps.getUsage?.() ?? { totalSessions: 0, activeConnections: 0 };
|
||||
return makeResponse(request.id, {
|
||||
uptime,
|
||||
...usage,
|
||||
tools: deps.getToolCount(),
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user