Add dashboard room activity timeline

This commit is contained in:
Eyejoker
2026-04-27 11:46:35 +09:00
committed by GitHub
parent d7fe6fa13a
commit d28068f646
8 changed files with 1031 additions and 2 deletions

View File

@@ -10,7 +10,12 @@ import {
deleteTask,
getAllOpenPairedTasks,
getAllTasks,
getLatestPairedTaskForChat,
getPairedTaskById,
getPairedTurnAttempts,
getPairedTurnOutputs,
getPairedTurnsForTask,
getRecentChatMessages,
getTaskById,
hasMessage,
storeChatMetadata,
@@ -34,6 +39,7 @@ import type {
} from './types.js';
import { isWatchCiTask } from './task-watch-status.js';
import {
buildWebDashboardRoomActivity,
buildWebDashboardOverview,
sanitizeScheduledTask,
} from './web-dashboard-data.js';
@@ -106,6 +112,11 @@ export interface WebDashboardHandlerOptions {
}) => void;
deleteTask?: (id: string) => void;
getPairedTasks?: () => PairedTask[];
getLatestPairedTaskForChat?: (chatJid: string) => PairedTask | undefined;
getPairedTurnsForTask?: typeof getPairedTurnsForTask;
getPairedTurnAttempts?: typeof getPairedTurnAttempts;
getPairedTurnOutputs?: typeof getPairedTurnOutputs;
getRecentChatMessages?: typeof getRecentChatMessages;
getPairedTaskById?: (id: string) => PairedTask | undefined;
updatePairedTaskIfUnchanged?: (
id: string,
@@ -265,6 +276,16 @@ function parseRoomMessagePath(pathname: string): string | null {
}
}
function parseRoomTimelinePath(pathname: string): string | null {
const match = pathname.match(/^\/api\/rooms\/([^/]+)\/timeline$/);
if (!match) return null;
try {
return decodeURIComponent(match[1]);
} catch {
return null;
}
}
function parseServiceActionPath(pathname: string): string | null {
const match = pathname.match(/^\/api\/services\/([^/]+)\/actions$/);
if (!match) return null;
@@ -648,6 +669,16 @@ export function createWebDashboardHandler(
const loadPairedTaskById = opts.getPairedTaskById ?? getPairedTaskById;
const mutatePairedTaskIfUnchanged =
opts.updatePairedTaskIfUnchanged ?? updatePairedTaskIfUnchanged;
const loadLatestPairedTaskForChat =
opts.getLatestPairedTaskForChat ?? getLatestPairedTaskForChat;
const loadPairedTurnsForTask =
opts.getPairedTurnsForTask ?? getPairedTurnsForTask;
const loadPairedTurnOutputs =
opts.getPairedTurnOutputs ?? getPairedTurnOutputs;
const loadPairedTurnAttempts =
opts.getPairedTurnAttempts ?? getPairedTurnAttempts;
const loadRecentChatMessages =
opts.getRecentChatMessages ?? getRecentChatMessages;
const schedulePairedFollowUp =
opts.schedulePairedFollowUp ?? schedulePairedFollowUpIntent;
const loadRoomBindings = opts.getRoomBindings;
@@ -699,6 +730,7 @@ export function createWebDashboardHandler(
const taskId = parseTaskPath(url.pathname);
const actionInboxId = parseInboxActionPath(url.pathname);
const messageRoomJid = parseRoomMessagePath(url.pathname);
const timelineRoomJid = parseRoomTimelinePath(url.pathname);
const actionServiceId = parseServiceActionPath(url.pathname);
if (actionTaskId) {
@@ -949,6 +981,47 @@ export function createWebDashboardHandler(
return jsonResponse({ ok: true, id, queued: true });
}
if (timelineRoomJid) {
if (request.method !== 'GET' && request.method !== 'HEAD') {
return jsonResponse({ error: 'Method not allowed' }, { status: 405 });
}
const snapshots = readSnapshots(statusMaxAgeMs);
const matched = snapshots
.flatMap((snapshot) =>
snapshot.entries
.filter((entry) => entry.jid === timelineRoomJid)
.map((entry) => ({ snapshot, entry })),
)
.sort((a, b) =>
b.snapshot.updatedAt.localeCompare(a.snapshot.updatedAt),
)
.at(0);
if (!matched) {
return jsonResponse(
{ error: 'Room timeline not found' },
{ status: 404 },
);
}
const pairedTask = loadLatestPairedTaskForChat(timelineRoomJid) ?? null;
const turns = pairedTask ? loadPairedTurnsForTask(pairedTask.id) : [];
const attempts = turns.flatMap((turn) =>
loadPairedTurnAttempts(turn.turn_id),
);
return jsonResponse(
buildWebDashboardRoomActivity({
serviceId: matched.snapshot.serviceId,
entry: matched.entry,
pairedTask,
turns,
attempts,
outputs: pairedTask ? loadPairedTurnOutputs(pairedTask.id) : [],
messages: loadRecentChatMessages(timelineRoomJid, 8),
}),
);
}
if (actionServiceId) {
if (request.method !== 'POST') {
return jsonResponse({ error: 'Method not allowed' }, { status: 405 });