perf: speed up code queue first paint
This commit is contained in:
@@ -14,7 +14,7 @@ const h = React.createElement;
|
||||
const { useEffect, useMemo, useRef } = React;
|
||||
const useState: any = React.useState;
|
||||
const codexTranscriptChunkLimit = 120;
|
||||
const codexInitialTaskLimit = 24;
|
||||
const codexInitialTaskLimit = 12;
|
||||
const codexMoreTaskLimit = 48;
|
||||
const queueErrorPreviewLength = 1200;
|
||||
|
||||
@@ -317,14 +317,18 @@ function queueRunnableTaskId(queueRows: any[], queueId: string, rows: any[]): st
|
||||
async function loadTaskList(apiBaseUrl: string, queueId = allQueuesId, searchQuery = ""): Promise<any> {
|
||||
return requestJson(codexApi(
|
||||
apiBaseUrl,
|
||||
`/api/tasks/overview?limit=${codexInitialTaskLimit}&transcriptLimit=1&compact=1&selected=0&skipTrace=1${taskListQuerySuffix(queueId, searchQuery)}`,
|
||||
`/api/tasks/overview?limit=${codexInitialTaskLimit}&transcriptLimit=1&compact=1&selected=0&includeActive=0&stats=0&skipTrace=1${taskListQuerySuffix(queueId, searchQuery)}`,
|
||||
), codexNoCacheOptions());
|
||||
}
|
||||
|
||||
async function loadTaskOverview(apiBaseUrl: string, preferId: string, afterSeq = 0, queueId = allQueuesId, searchQuery = "", skipTrace = false): Promise<any> {
|
||||
async function loadTaskOverview(apiBaseUrl: string, preferId: string, afterSeq = 0, queueId = allQueuesId, searchQuery = "", skipTrace = false, options: AnyRecord = {}): Promise<any> {
|
||||
const selectedParam = options.selected === false ? "&selected=0" : "";
|
||||
const includeActiveParam = options.includeActive === false ? "&includeActive=0" : "";
|
||||
const statsParam = options.stats === false ? "&stats=0" : "";
|
||||
const limit = Number.isInteger(options.limit) && options.limit > 0 ? Math.min(500, options.limit) : codexInitialTaskLimit;
|
||||
return requestJson(codexApi(
|
||||
apiBaseUrl,
|
||||
`/api/tasks/overview?limit=${codexInitialTaskLimit}&transcriptLimit=3&compact=1&afterSeq=${encodeURIComponent(String(Math.max(0, afterSeq)))}&preferId=${encodeURIComponent(preferId)}${skipTrace ? "&skipTrace=1" : ""}${taskListQuerySuffix(queueId, searchQuery)}`,
|
||||
`/api/tasks/overview?limit=${encodeURIComponent(String(limit))}&transcriptLimit=3&compact=1&afterSeq=${encodeURIComponent(String(Math.max(0, afterSeq)))}&preferId=${encodeURIComponent(preferId)}${selectedParam}${includeActiveParam}${statsParam}${skipTrace ? "&skipTrace=1" : ""}${taskListQuerySuffix(queueId, searchQuery)}`,
|
||||
), codexNoCacheOptions());
|
||||
}
|
||||
|
||||
@@ -2528,7 +2532,10 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
|
||||
const requestedTranscript = Array.isArray(requestedCached?.task?.transcript) ? requestedCached.task.transcript : [];
|
||||
const overviewAfterSeq = transcriptResumeSeq(requestedTranscript);
|
||||
let tasksResult = null;
|
||||
tasksResult = await loadTaskOverview(apiBaseUrl, requestedId, overviewAfterSeq, queueFilterId, "", true);
|
||||
const firstPaintListOnly = trackLoad && requestedId.length === 0;
|
||||
tasksResult = firstPaintListOnly
|
||||
? await loadTaskList(apiBaseUrl, queueFilterId, "")
|
||||
: await loadTaskOverview(apiBaseUrl, requestedId, overviewAfterSeq, queueFilterId, "", true, { stats: false });
|
||||
if (token !== queueLoadTokenRef.current) {
|
||||
if (trackLoad) trackedLoadInFlightRef.current = false;
|
||||
return;
|
||||
@@ -2574,6 +2581,56 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
|
||||
const cached = sessionCacheRef.current.get(nextId);
|
||||
if (cached?.task) sessionCacheRef.current.set(nextId, { ...cached, task: { ...row, ...cached.task, status: row.status, updatedAt: row.updatedAt } });
|
||||
}
|
||||
if (firstPaintListOnly && row) {
|
||||
publishCachedTask(nextId, {
|
||||
...row,
|
||||
_summaryLoaded: false,
|
||||
transcript: [],
|
||||
_detailLoaded: false,
|
||||
_transcriptComplete: false,
|
||||
_transcriptPreview: true,
|
||||
}, detailLoadTokenRef.current);
|
||||
setSelectedDetailLoading(true);
|
||||
setLoadStats({
|
||||
phase: "complete",
|
||||
taskId: nextId,
|
||||
queueMs,
|
||||
detailMs: 0,
|
||||
totalMs: performance.now() - startedAt,
|
||||
chunks: 0,
|
||||
transcriptRows: 0,
|
||||
partial: true,
|
||||
completedAt: new Date(),
|
||||
});
|
||||
setRefreshedAt(new Date());
|
||||
if (trackLoad) trackedLoadInFlightRef.current = false;
|
||||
void ensureTraceSummary(nextId, true)
|
||||
.catch((err) => setError(errorText(err, "加载 Codex Trace Summary 失败")));
|
||||
void loadTaskOverview(apiBaseUrl, nextId, 0, queueFilterId, "", false)
|
||||
.then((full: any) => {
|
||||
if (token !== queueLoadTokenRef.current) return;
|
||||
const fullRows = taskRows(full);
|
||||
const selected = overviewSelectedTask(full);
|
||||
if (fullRows.length > 0) {
|
||||
setTasksData((previous: any) => {
|
||||
const mergedRows = mergeTaskRowsPreferLatest([taskRows(previous), fullRows], activeSortId);
|
||||
return { ...previous, statistics: full?.statistics || previous?.statistics, tasks: applyLocalReadStateToRows(mergedRows) };
|
||||
});
|
||||
}
|
||||
if (selected?.id === selectedIdRef.current) {
|
||||
const transcript = Array.isArray(selected.transcript) ? selected.transcript : [];
|
||||
publishCachedTask(selected.id, {
|
||||
...selected,
|
||||
transcript,
|
||||
_summaryLoaded: true,
|
||||
_detailLoaded: transcript.length > 0,
|
||||
_transcriptPreview: Boolean(full?.selected?.preview),
|
||||
}, detailLoadTokenRef.current);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
return;
|
||||
}
|
||||
if (overviewTask?.id === nextId && overviewTranscript !== null) {
|
||||
const cached = sessionCacheRef.current.get(nextId);
|
||||
const existingTranscript = Array.isArray(cached?.task?.transcript) ? cached.task.transcript : [];
|
||||
|
||||
@@ -457,29 +457,40 @@ async function queueSummaryForResponse(includeDevReady = true, tasks?: QueueTask
|
||||
async function queueSummaryForHealth(includeDevReady = true): Promise<JsonValue> {
|
||||
const summary = queueSummary(includeDevReady, ctx().tasks()) as Record<string, JsonValue>;
|
||||
if (!ctx().databaseReady()) return summary;
|
||||
const [totalRows, statusRows, queueStatusRows, unreadRows] = await Promise.all([
|
||||
ctx().sql<Array<{ total: string | number }>>`SELECT COUNT(*) AS total FROM unidesk_code_queue_tasks`,
|
||||
ctx().sql<Array<{ status: string; count: string | number }>>`
|
||||
SELECT status, COUNT(*) AS count
|
||||
FROM unidesk_code_queue_tasks
|
||||
GROUP BY status
|
||||
`,
|
||||
ctx().sql<Array<{ queue_id: string; status: string; count: string | number }>>`
|
||||
SELECT queue_id, status, COUNT(*) AS count
|
||||
FROM unidesk_code_queue_tasks
|
||||
GROUP BY queue_id, status
|
||||
`,
|
||||
ctx().sql<Array<{ queue_id: string; count: string | number }>>`
|
||||
SELECT queue_id, COUNT(*) AS count
|
||||
FROM unidesk_code_queue_tasks
|
||||
WHERE status IN ('succeeded', 'failed', 'canceled')
|
||||
AND read_at IS NULL
|
||||
GROUP BY queue_id
|
||||
`,
|
||||
]);
|
||||
const aggregateRows = await ctx().sql<QueueSummaryAggregateRow[]>`
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM unidesk_code_queue_tasks) AS total,
|
||||
COALESCE((
|
||||
SELECT jsonb_object_agg(status, count)
|
||||
FROM (
|
||||
SELECT status, COUNT(*) AS count
|
||||
FROM unidesk_code_queue_tasks
|
||||
GROUP BY status
|
||||
) AS status_counts
|
||||
), '{}'::jsonb) AS status_counts,
|
||||
COALESCE((
|
||||
SELECT jsonb_agg(jsonb_build_object('queueId', queue_id, 'status', status, 'count', count))
|
||||
FROM (
|
||||
SELECT queue_id, status, COUNT(*) AS count
|
||||
FROM unidesk_code_queue_tasks
|
||||
GROUP BY queue_id, status
|
||||
) AS queue_status_counts
|
||||
), '[]'::jsonb) AS queue_status_counts,
|
||||
COALESCE((
|
||||
SELECT jsonb_agg(jsonb_build_object('queueId', queue_id, 'count', count))
|
||||
FROM (
|
||||
SELECT queue_id, COUNT(*) AS count
|
||||
FROM unidesk_code_queue_tasks
|
||||
WHERE status IN ('succeeded', 'failed', 'canceled')
|
||||
AND read_at IS NULL
|
||||
GROUP BY queue_id
|
||||
) AS unread_counts
|
||||
), '[]'::jsonb) AS unread_counts
|
||||
`;
|
||||
const aggregate = aggregateRows[0] ?? { total: ctx().tasks().length, status_counts: {}, queue_status_counts: [], unread_counts: [] };
|
||||
const counts: Record<string, number> = {};
|
||||
for (const row of statusRows) counts[row.status] = Number(row.count);
|
||||
const unreadByQueue = new Map(unreadRows.map((row) => [ctx().safeQueueId(row.queue_id), Number(row.count)]));
|
||||
for (const [status, count] of Object.entries(aggregate.status_counts ?? {})) counts[status] = Number(count);
|
||||
const unreadByQueue = new Map((aggregate.unread_counts ?? []).map((row) => [ctx().safeQueueId(String(row.queueId ?? row.queue_id ?? "")), Number(row.count ?? 0)]));
|
||||
const summaries = new Map<string, {
|
||||
name: string;
|
||||
total: number;
|
||||
@@ -502,8 +513,8 @@ async function queueSummaryForHealth(includeDevReady = true): Promise<JsonValue>
|
||||
updatedAt: queue.updatedAt,
|
||||
});
|
||||
}
|
||||
for (const row of queueStatusRows) {
|
||||
const queueId = ctx().safeQueueId(row.queue_id);
|
||||
for (const row of aggregate.queue_status_counts ?? []) {
|
||||
const queueId = ctx().safeQueueId(String(row.queueId ?? row.queue_id ?? ""));
|
||||
let queue = summaries.get(queueId);
|
||||
if (queue === undefined) {
|
||||
queue = {
|
||||
@@ -518,8 +529,9 @@ async function queueSummaryForHealth(includeDevReady = true): Promise<JsonValue>
|
||||
};
|
||||
summaries.set(queueId, queue);
|
||||
}
|
||||
const count = Number(row.count);
|
||||
queue.counts[row.status] = count;
|
||||
const count = Number(row.count ?? 0);
|
||||
const status = String(row.status || "");
|
||||
queue.counts[status] = count;
|
||||
queue.total += count;
|
||||
}
|
||||
for (const [queueId, queue] of summaries) {
|
||||
@@ -540,7 +552,7 @@ async function queueSummaryForHealth(includeDevReady = true): Promise<JsonValue>
|
||||
createdAt: queue.createdAt,
|
||||
updatedAt: queue.updatedAt,
|
||||
})) as unknown as JsonValue[];
|
||||
summary.total = Number(totalRows[0]?.total ?? ctx().tasks().length);
|
||||
summary.total = Number(aggregate.total ?? ctx().tasks().length);
|
||||
summary.counts = counts as unknown as JsonValue;
|
||||
summary.unreadTerminal = Array.from(unreadByQueue.values()).reduce((total, count) => total + count, 0);
|
||||
summary.queues = queues;
|
||||
@@ -657,6 +669,12 @@ function taskPageRows(filteredTasks: QueueTask[], url: URL, limit: number): {
|
||||
|
||||
type TaskIdRow = { id: string; created_at?: Date | string };
|
||||
type CountRow = { count: string | number };
|
||||
type QueueSummaryAggregateRow = {
|
||||
total: string | number;
|
||||
status_counts: Record<string, string | number> | null;
|
||||
queue_status_counts: Array<{ queueId?: string; queue_id?: string; status?: string; count?: string | number }> | null;
|
||||
unread_counts: Array<{ queueId?: string; queue_id?: string; count?: string | number }> | null;
|
||||
};
|
||||
type DailyCountRow = { date: string; count: string | number };
|
||||
type DailyCompletedRow = {
|
||||
date: string;
|
||||
|
||||
Reference in New Issue
Block a user