learning_ai_notes/web/src/lib/review-client.ts

111 lines
3.9 KiB
TypeScript

import { createNotesApiClient } from "@/lib/api-helpers";
import { RETRY_WHEN_ONLINE_MESSAGE, withMutationRetry } from "@/lib/mutation-retry";
import type { AgentTimelineItem, ApprovalQueueItem, NoteAgentActionDoc, NoteAgentActionListResponse } from "@/lib/types";
function toSeverity(actionType: NoteAgentActionDoc["actionType"]): ApprovalQueueItem["severity"] {
if (actionType === "update" || actionType === "extract_tasks") {
return "medium";
}
if (actionType === "attach_citation") {
return "low";
}
return "high";
}
function toTimelineItem(action: NoteAgentActionDoc): AgentTimelineItem {
return {
id: action.id,
actor: action.actorId,
action: `${action.actorType} ${action.actionType.replaceAll("_", " ")}`,
timestamp: action.updatedAt,
status: action.state,
summary: action.afterSummary ?? action.reason ?? action.toolName ?? action.actionType,
};
}
function toApprovalQueueItem(action: NoteAgentActionDoc): ApprovalQueueItem {
return {
id: action.id,
title: action.afterSummary ?? action.reason ?? `${action.actionType} proposal`,
owner: action.actorId,
severity: toSeverity(action.actionType),
status: action.state,
noteId: action.noteId,
workspaceId: action.workspaceId,
before: action.beforeSummary,
after: action.afterSummary,
};
}
async function updateAgentActionState(
id: string,
workspaceId: string,
state: "approved" | "rejected",
reviewNote?: string,
): Promise<NoteAgentActionDoc> {
const api = createNotesApiClient();
const path = `/note-agent-actions/${encodeURIComponent(id)}?workspaceId=${encodeURIComponent(workspaceId)}`;
return withMutationRetry({
run: () => api.fetch<NoteAgentActionDoc>(path, {
method: "PATCH",
body: JSON.stringify({
state,
reviewedAt: new Date().toISOString(),
...(reviewNote ? { reviewNote } : {}),
}),
}),
offlineMessage: RETRY_WHEN_ONLINE_MESSAGE,
});
}
export async function listApprovalQueue(): Promise<ApprovalQueueItem[]> {
const api = createNotesApiClient();
const response = await api.fetch<NoteAgentActionListResponse>("/note-agent-actions/pending");
return response.items
.sort((a: NoteAgentActionDoc, b: NoteAgentActionDoc) => b.updatedAt.localeCompare(a.updatedAt))
.map(toApprovalQueueItem);
}
export async function listAgentTimeline(noteId?: string): Promise<AgentTimelineItem[]> {
const api = createNotesApiClient();
const response = await api.fetch<NoteAgentActionListResponse>("/note-agent-actions/pending");
return response.items
.filter((action: NoteAgentActionDoc) => (noteId ? action.noteId === noteId : true))
.sort((a: NoteAgentActionDoc, b: NoteAgentActionDoc) => b.updatedAt.localeCompare(a.updatedAt))
.map(toTimelineItem);
}
export async function approveReviewItem(item: ApprovalQueueItem, reviewNote?: string): Promise<ApprovalQueueItem> {
const updated = await updateAgentActionState(item.id, item.workspaceId, "approved", reviewNote);
return toApprovalQueueItem(updated);
}
export async function rejectReviewItem(item: ApprovalQueueItem, reviewNote?: string): Promise<ApprovalQueueItem> {
const updated = await updateAgentActionState(item.id, item.workspaceId, "rejected", reviewNote);
return toApprovalQueueItem(updated);
}
export async function batchReviewItems(
items: ApprovalQueueItem[],
state: "approved" | "rejected",
reviewNote?: string,
): Promise<{ updated: number; total: number }> {
const api = createNotesApiClient();
const result = await withMutationRetry({
run: () => api.fetch<{ updated: number; total: number }>("/note-agent-actions/batch-review", {
method: "POST",
body: JSON.stringify({
ids: items.map((item) => ({ id: item.id, workspaceId: item.workspaceId })),
state,
...(reviewNote ? { reviewNote } : {}),
}),
}),
offlineMessage: RETRY_WHEN_ONLINE_MESSAGE,
});
return result;
}